[
  {
    "path": ".gitignore",
    "content": "# Xcode\n.DS_Store\nxcuserdata/\n*.xcodeproj/*\n!*.xcodeproj/project.pbxproj\n!*.xcodeproj/xcshareddata/\n!*.xcodeproj/project.xcworkspace/\n!*.xcworkspace/contents.xcworkspacedata\n/*.gcno\n**/xcshareddata/WorkspaceSettings.xcsettings\n\n# Swift Package Manager\n.build/\nPackages/\nPackage.pins\nPackage.resolved\n*.xcodeproj\n\n# CocoaPods\nPods/\n\n# Carthage\nCarthage/Build/\n\n# fastlane\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots/**/*.png\nfastlane/test_output\n\n# Code Injection\ninjectionforxcode/\n\n# User-specific files\n*.swp\n*~\n.vscode/\n*.moved-aside\n\n# macOS specific\n.AppleDouble\n.LSOverride\nIcon\n._*\n.Spotlight-V100\n.Trashes\n\n# Build products\nbuild/\nDerivedData/\n*.hmap\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n# Playgrounds\ntimeline.xctimeline\nplayground.xcworkspace\n\n# Swift Package Manager\n.swiftpm\n\n# App packaging\n*.app\n*.pkg\n*.dmg\n\n# Crash logs\n*.crash\n*.ips\n\n# Other\n*.log\n*.sqlite\n"
  },
  {
    "path": "LICENSE",
    "content": "### MIT License\n\n```\nMIT License\n\nCopyright (c) 2024 Dena Sohrabi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\n1. The above copyright notice and this permission notice shall be included\n   in all copies or substantial portions of the Software.\n\n2. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n```\n"
  },
  {
    "path": "There/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"icon-166.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"icon-1662.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"icon-3222222.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"icon-322e22ee2.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"e1e11221ee21.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"icon-256feefef.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"ddwdwdwdw 2.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"dwqqdwdwqdqwdqw.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"dwqqdwdwqdqwdqw 1.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"dqdwqdwqdwq.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/Earth.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"Earch&Sun.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/Logo.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"ThereIcon.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/Night.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Night.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/appIcon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"iconTemplate.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"iconTemplate@2x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"iconTemplate@3x.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"template\"\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/early-afternoon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Early afternoon.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/early-evening.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Early Eavning.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/early-morning.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Early Morning.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/evening.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Eavning.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/late-afternoon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Late Afternoon.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/late-morning.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Late Morning.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/telegram-logo.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Logo.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/Assets.xcassets/twitter.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"twitter.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/ContentView.swift",
    "content": "import SwiftUI\n\nstruct ContentView: View {\n    @EnvironmentObject var router: Router\n    var body: some View {\n        switch router.activeRoute {\n        case .mainView:\n            MainView()\n        case .addTimezone:\n            AddTimezone()\n                .transition(.asymmetric(insertion: .push(from: .trailing), removal: .push(from: .leading)))\n        case let .editTimeZone(entryId):\n            EditTimeZoneView(entryId: entryId)\n                .transition(.asymmetric(insertion: .push(from: .trailing), removal: .push(from: .leading)))\n        }\n    }\n}\n\n#Preview {\n    ContentView()\n        .frame(width: 300, height: 400)\n}\n"
  },
  {
    "path": "There/Data/Database.swift",
    "content": "import Foundation\nimport GRDB\nimport os.log\n\n///\n/// You create an `AppDatabase` with a connection to an SQLite database\n/// (see <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>).\n///\n/// Create those connections with a configuration returned from\n/// `AppDatabase/makeConfiguration(_:)`.\n///\n/// For example:\n///\n/// ```swift\n/// // Create an in-memory AppDatabase\n/// let config = AppDatabase.makeConfiguration()\n/// let dbQueue = try DatabaseQueue(configuration: config)\n/// let appDatabase = try AppDatabase(dbQueue)\n/// ```\nstruct AppDatabase {\n    /// Creates an `AppDatabase`, and makes sure the database schema\n    /// is ready.\n    ///\n    /// - important: Create the `DatabaseWriter` with a configuration\n    ///   returned by ``makeConfiguration(_:)``.\n    init(_ dbWriter: any DatabaseWriter) throws {\n        self.dbWriter = dbWriter\n        try migrator.migrate(dbWriter)\n    }\n\n    /// Provides access to the database.\n    ///\n    /// Application can use a `DatabasePool`, while SwiftUI previews and tests\n    /// can use a fast in-memory `DatabaseQueue`.\n    ///\n    /// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>\n    let dbWriter: any DatabaseWriter\n}\n\n// MARK: - Database Configuration\n\nextension AppDatabase {\n    private static let sqlLogger = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: \"SQL\")\n\n    /// Returns a database configuration suited for `PlayerRepository`.\n    ///\n    /// SQL statements are logged if the `SQL_TRACE` environment variable\n    /// is set.\n    ///\n    /// - parameter base: A base configuration.\n    public static func makeConfiguration(_ base: Configuration = Configuration()) -> Configuration {\n        var config = base\n\n        // An opportunity to add required custom SQL functions or\n        // collations, if needed:\n        // config.prepareDatabase { db in\n        //     db.add(function: ...)\n        // }\n\n        // Log SQL statements if the `SQL_TRACE` environment variable is set.\n        // See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/database/trace(options:_:)>\n        if ProcessInfo.processInfo.environment[\"SQL_TRACE\"] != nil {\n            config.prepareDatabase { db in\n                db.trace {\n                    // It's ok to log statements publicly. Sensitive\n                    // information (statement arguments) are not logged\n                    // unless config.publicStatementArguments is set\n                    // (see below).\n                    os_log(\"%{public}@\", log: sqlLogger, type: .debug, String(describing: $0))\n                }\n            }\n        }\n\n        #if DEBUG\n            // Protect sensitive information by enabling verbose debugging in\n            // DEBUG builds only.\n            // See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/configuration/publicstatementarguments>\n            config.publicStatementArguments = true\n        #endif\n\n        return config\n    }\n}\n\n// MARK: - Database Migrations\n\nextension AppDatabase {\n    /// The DatabaseMigrator that defines the database schema.\n    ///\n    /// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>\n    private var migrator: DatabaseMigrator {\n        var migrator = DatabaseMigrator()\n\n        #if DEBUG\n            // Speed up development by nuking the database when migrations change\n            // See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>\n            migrator.eraseDatabaseOnSchemaChange = true\n        #endif\n\n        // Migrations for future application versions will be inserted here:\n        migrator.registerMigration(\"add entry\") { db in\n            try db.create(table: Entry.databaseTableName) { t in\n                t.primaryKey(\"id\", .integer).notNull()\n                t.column(Entry.Columns.type.rawValue, .text).notNull()\n                t.column(Entry.Columns.name.rawValue, .text).notNull()\n                t.column(Entry.Columns.city.rawValue, .text).notNull()\n                t.column(Entry.Columns.timezoneIdentifier.rawValue, .text).notNull()\n                t.column(Entry.Columns.flag.rawValue, .text)\n                t.column(Entry.Columns.photoData.rawValue, .text)\n            }\n        }\n\n        return migrator\n    }\n}\n\n// MARK: - Database Access: Writes\n\n// The write methods execute invariant-preserving database transactions.\n\nextension AppDatabase {\n    /// A validation error that prevents some players from being saved into\n    /// the database.\n    enum ValidationError: LocalizedError {\n        case missingName\n\n        var errorDescription: String? {\n            switch self {\n            case .missingName:\n                return \"Please provide a name\"\n            }\n        }\n    }\n}\n\n// MARK: - Database Access: Reads\n\n// This demo app does not provide any specific reading method, and instead\n// gives an unrestricted read-only access to the rest of the application.\n// In your app, you are free to choose another path, and define focused\n// reading methods.\nextension AppDatabase {\n    /// Provides a read-only access to the database\n    var reader: DatabaseReader {\n        dbWriter\n    }\n}\n"
  },
  {
    "path": "There/Data/Entry.swift",
    "content": "import Foundation\nimport GRDB\nimport SwiftUI\n\nenum EntryType: String, Codable {\n    case place\n    case person\n}\n\nenum DayPeriod: String, CaseIterable {\n    case earlyMorning = \"early-morning\"\n    case lateMorning = \"late-morning\"\n    case earlyAfternoon = \"early-afternoon\"\n    case lateAfternoon = \"late-afternoon\"\n    case earlyEvening = \"early-evening\"\n    case evening\n    case night\n}\n\nstruct Entry: Codable, Equatable, Identifiable, FetchableRecord, PersistableRecord {\n    let id: Int64\n    var type: EntryType\n    var name: String\n    var city: String\n    var timezoneIdentifier: String\n    var flag: String?\n    var photoData: String?\n\n    enum Columns: String, ColumnExpression {\n        case id, type, name, city, country, timezoneIdentifier, flag, photoData\n    }\n\n    init(id: Int64 = Int64.random(in: 1 ... 99999), type: EntryType, name: String, city: String, timezoneIdentifier: String, flag: String? = nil, photoData: String? = nil) {\n        self.id = id\n        self.type = type\n        self.name = name\n        self.city = city\n        self.timezoneIdentifier = timezoneIdentifier\n        self.flag = flag\n        self.photoData = photoData\n    }\n\n    // MARK: - Codable\n\n    enum CodingKeys: String, CodingKey {\n        case id, type, name, city, timezoneIdentifier, flag, photoData\n    }\n\n    var timeDifference: (hours: Int, minutes: Int, dayPeriod: DayPeriod) {\n        let currentDate = Date()\n        let calendar = Calendar.current\n\n        // Get the current user's time zone\n        let localTimeZone = TimeZone.current\n\n        // Get the entry's time zone\n        guard let entryTimeZone = TimeZone(identifier: timezoneIdentifier) else {\n            return (0, 0, .night)\n        }\n\n        // Calculate the time difference\n        let differenceInSeconds = entryTimeZone.secondsFromGMT(for: currentDate) - localTimeZone.secondsFromGMT(for: currentDate)\n\n        // Convert seconds to hours and minutes\n        let hours = differenceInSeconds / 3600\n        let minutes = (differenceInSeconds % 3600) / 60\n\n        // Calculate the time in the entry's time zone\n        var entryDateComponents = calendar.dateComponents(in: entryTimeZone, from: currentDate)\n        let entryHour = entryDateComponents.hour ?? 0\n\n        let dayPeriod: DayPeriod\n        switch entryHour {\n        case 5 ..< 8:\n            dayPeriod = .earlyMorning\n        case 8 ..< 12:\n            dayPeriod = .lateMorning\n        case 12 ..< 15:\n            dayPeriod = .earlyAfternoon\n        case 15 ..< 17:\n            dayPeriod = .lateAfternoon\n        case 17 ..< 19:\n            dayPeriod = .earlyEvening\n        case 19 ..< 22:\n            dayPeriod = .evening\n        default:\n            dayPeriod = .night\n        }\n\n        return (hours, minutes, dayPeriod)\n    }\n\n    var timeIcon: String {\n        return timeDifference.dayPeriod.rawValue\n    }\n}\n"
  },
  {
    "path": "There/Data/Fetcher.swift",
    "content": "import Combine\nimport Foundation\nimport GRDB\n\nenum SortOrder {\n    case timeAscending\n    case timeDescending\n}\n\nclass Fetcher: ObservableObject {\n    @Published var entries: [Entry] = []\n    @Published var getEntries: AnyCancellable?\n    var database: AppDatabase = .shared\n\n    init() {\n        getEntries = ValueObservation.tracking { db in\n            try Entry.fetchAll(db)\n        }\n        .publisher(in: database.dbWriter, scheduling: .immediate)\n        .sink(\n            receiveCompletion: { _ in /* ignore error */ },\n            receiveValue: { [weak self] entries in\n                self?.entries = entries\n            }\n        )\n    }\n\n    func sortEntries(by order: SortOrder) {\n        switch order {\n        case .timeAscending:\n            entries.sort { $0.timeDifference.hours < $1.timeDifference.hours }\n        case .timeDescending:\n            entries.sort { $0.timeDifference.hours > $1.timeDifference.hours }\n        }\n    }\n}\n"
  },
  {
    "path": "There/Data/LaunchAgent.swift",
    "content": "import Foundation\nimport ServiceManagement\n\nfunc installLaunchAgent() {\n    let fileManager = FileManager.default\n\n    guard let libraryDirectory = fileManager.urls(for: .libraryDirectory, in: .userDomainMask).first else {\n        print(\"Unable to find user's Library directory\")\n        return\n    }\n\n    let launchAgentsDirectory = libraryDirectory.appendingPathComponent(\"LaunchAgents\")\n    let plistName = \"pm.there.There.LaunchAgent.plist\"\n    let plistPath = launchAgentsDirectory.appendingPathComponent(plistName)\n\n    // Create LaunchAgents directory if it doesn't exist\n    if !fileManager.fileExists(atPath: launchAgentsDirectory.path) {\n        do {\n            try fileManager.createDirectory(at: launchAgentsDirectory, withIntermediateDirectories: true, attributes: nil)\n        } catch {\n            print(\"Error creating LaunchAgents directory: \\(error)\")\n            return\n        }\n    }\n\n    // Create the plist content\n    let plistContent = \"\"\"\n    <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n    <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n    <plist version=\"1.0\">\n    <dict>\n        <key>Label</key>\n        <string>pm.there.There.LaunchAgent</string>\n        <key>ProgramArguments</key>\n        <array>\n            <string>/Applications/There.app/Contents/MacOS/There</string>\n        </array>\n        <key>RunAtLoad</key>\n        <true/>\n        <key>KeepAlive</key>\n        <true/>\n        <key>LimitLoadToSessionType</key>\n        <string>Aqua</string>\n    </dict>\n    </plist>\n    \"\"\"\n\n    do {\n        // Write the plist content to the file\n        try plistContent.write(to: plistPath, atomically: true, encoding: .utf8)\n        print(\"Launch Agent plist created successfully\")\n\n        // Set the correct permissions\n        try fileManager.setAttributes([.posixPermissions: 0o644], ofItemAtPath: plistPath.path)\n\n        // Load the launch agent\n        try Process.run(URL(fileURLWithPath: \"/bin/launchctl\"), arguments: [\"load\", plistPath.path])\n        print(\"Launch Agent loaded successfully\")\n    } catch {\n        print(\"Error installing or loading Launch Agent: \\(error)\")\n    }\n\n    // For macOS 13 and later, also register using SMAppService\n    if #available(macOS 13.0, *) {\n        do {\n            try SMAppService.mainApp.register()\n            print(\"App registered as login item using SMAppService\")\n        } catch {\n            print(\"Failed to register app as login item using SMAppService: \\(error)\")\n        }\n    }\n}\n\nfunc uninstallLaunchAgent() {\n    let fileManager = FileManager.default\n    guard let libraryDirectory = fileManager.urls(for: .libraryDirectory, in: .userDomainMask).first else {\n        print(\"Unable to find user's Library directory\")\n        return\n    }\n\n    let launchAgentsDirectory = libraryDirectory.appendingPathComponent(\"LaunchAgents\")\n    let plistName = \"pm.there.There.LaunchAgent.plist\"\n    let plistPath = launchAgentsDirectory.appendingPathComponent(plistName)\n\n    do {\n        // Unload the launch agent\n        try Process.run(URL(fileURLWithPath: \"/bin/launchctl\"), arguments: [\"unload\", plistPath.path])\n        print(\"Launch Agent unloaded successfully\")\n\n        // Remove the plist file\n        try fileManager.removeItem(at: plistPath)\n        print(\"Launch Agent plist removed successfully\")\n    } catch {\n        print(\"Error uninstalling Launch Agent: \\(error)\")\n    }\n\n    // For macOS 13 and later, also unregister using SMAppService\n    if #available(macOS 13.0, *) {\n        do {\n            try SMAppService.mainApp.unregister()\n            print(\"App unregistered as login item using SMAppService\")\n        } catch {\n            print(\"Failed to unregister app as login item using SMAppService: \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "There/Data/Persistence.swift",
    "content": "import Foundation\nimport GRDB\n\nextension AppDatabase {\n    /// The database for the application\n    static let shared = makeShared()\n\n    private static func makeShared() -> AppDatabase {\n        do {\n            // Apply recommendations from\n            // <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>\n            //\n            // Create the \"Application Support/Database\" directory if needed\n            let fileManager = FileManager.default\n            let appSupportURL = try fileManager.url(\n                for: .applicationSupportDirectory, in: .userDomainMask,\n                appropriateFor: nil, create: true)\n            let directoryURL = appSupportURL.appendingPathComponent(\"Database\", isDirectory: true)\n\n            // Support for tests: delete the database if requested\n            if CommandLine.arguments.contains(\"-reset\") {\n                try? fileManager.removeItem(at: directoryURL)\n            }\n\n            // Create the database folder if needed\n            try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)\n\n            // Open or create the database\n            let databaseURL = directoryURL.appendingPathComponent(\"db.sqlite\")\n            NSLog(\"Database stored at \\(databaseURL.path)\")\n            let dbPool = try DatabasePool(\n                path: databaseURL.path,\n                // Use default AppDatabase configuration\n                configuration: AppDatabase.makeConfiguration())\n\n            // Create the AppDatabase\n            let appDatabase = try AppDatabase(dbPool)\n\n            return appDatabase\n        } catch {\n            // Replace this implementation with code to handle the error appropriately.\n            // fatalError() causes the application to generate a crash log and terminate.\n            //\n            // Typical reasons for an error here include:\n            // * The parent directory cannot be created, or disallows writing.\n            // * The database is not accessible, due to permissions or data protection when the device is locked.\n            // * The device is out of space.\n            // * The database could not be migrated to its latest schema version.\n            // Check the error message to determine what the actual problem was.\n            fatalError(\"Unresolved error \\(error)\")\n        }\n    }\n\n    /// Creates an empty database for SwiftUI previews\n    static func empty() -> AppDatabase {\n        // Connect to an in-memory database\n        // See https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections\n        let dbQueue = try! DatabaseQueue(configuration: AppDatabase.makeConfiguration())\n        return try! AppDatabase(dbQueue)\n    }\n\n    /// Creates a database full of random players for SwiftUI previews\n    static func random() -> AppDatabase {\n        let appDatabase = empty()\n        return appDatabase\n    }\n}\n"
  },
  {
    "path": "There/Data/Router.swift",
    "content": "import SwiftUI\n\nenum Route {\n    case addTimezone\n    case mainView\n    case editTimeZone(entryId: Int64?)\n}\n\nclass Router: ObservableObject {\n    @Published var activeRoute: Route = .mainView\n\n    func setActiveRoute(to route: Route) {\n        withAnimation(.default) {\n            activeRoute = route\n        }\n    }\n\n    func cleanActiveRoute() {\n        withAnimation(.default) {\n            activeRoute = .mainView\n        }\n    }\n}\n"
  },
  {
    "path": "There/Data/SearchCompleter.swift",
    "content": "import Foundation\nimport MapKit\n\nclass TimeZoneSearchCompiler: NSObject {\n    private var commonAbbreviations: [String: (fullName: String, identifier: String)]\n    private var utcOffsets: [String: String]\n    private var currentCompletion: (([TimeZoneSearchResult]) -> Void)?\n\n    override init() {\n        // Initialize common abbreviations\n        commonAbbreviations = [\n            // North America\n            \"EST\": (\"Eastern Standard Time\", \"America/New_York\"),\n            \"EDT\": (\"Eastern Daylight Time\", \"America/New_York\"),\n            \"CST\": (\"Central Standard Time\", \"America/Chicago\"),\n            \"CDT\": (\"Central Daylight Time\", \"America/Chicago\"),\n            \"MST\": (\"Mountain Standard Time\", \"America/Denver\"),\n            \"MDT\": (\"Mountain Daylight Time\", \"America/Denver\"),\n            \"PST\": (\"Pacific Standard Time\", \"America/Los_Angeles\"),\n            \"PDT\": (\"Pacific Daylight Time\", \"America/Los_Angeles\"),\n            \"AKST\": (\"Alaska Standard Time\", \"America/Anchorage\"),\n            \"AKDT\": (\"Alaska Daylight Time\", \"America/Anchorage\"),\n            \"HST\": (\"Hawaii Standard Time\", \"Pacific/Honolulu\"),\n\n            // Europe\n            \"GMT\": (\"Greenwich Mean Time\", \"Etc/GMT\"),\n            \"BST\": (\"British Summer Time\", \"Europe/London\"),\n            \"CET\": (\"Central European Time\", \"Europe/Paris\"),\n            \"CEST\": (\"Central European Summer Time\", \"Europe/Paris\"),\n\n            // Asia\n            \"IST\": (\"India Standard Time\", \"Asia/Kolkata\"),\n            \"JST\": (\"Japan Standard Time\", \"Asia/Tokyo\"),\n\n            // Australia\n            \"AEST\": (\"Australian Eastern Standard Time\", \"Australia/Sydney\"),\n            \"AEDT\": (\"Australian Eastern Daylight Time\", \"Australia/Sydney\"),\n\n            // Coordinated Universal Time\n            \"UTC\": (\"Coordinated Universal Time\", \"Etc/UTC\"),\n        ]\n\n        // Initialize UTC offsets\n        utcOffsets = [\n            \"UTC+0\": \"Etc/GMT\",\n            \"UTC-1\": \"Etc/GMT+1\",\n            \"UTC-2\": \"Etc/GMT+2\",\n            \"UTC-3\": \"Etc/GMT+3\",\n            \"UTC-4\": \"Etc/GMT+4\",\n            \"UTC-5\": \"Etc/GMT+5\",\n            \"UTC-6\": \"Etc/GMT+6\",\n            \"UTC-7\": \"Etc/GMT+7\",\n            \"UTC-8\": \"Etc/GMT+8\",\n            \"UTC-9\": \"Etc/GMT+9\",\n            \"UTC-10\": \"Etc/GMT+10\",\n            \"UTC-11\": \"Etc/GMT+11\",\n            \"UTC-12\": \"Etc/GMT+12\",\n            \"UTC+1\": \"Etc/GMT-1\",\n            \"UTC+2\": \"Etc/GMT-2\",\n            \"UTC+3\": \"Etc/GMT-3\",\n            \"UTC+4\": \"Etc/GMT-4\",\n            \"UTC+5\": \"Etc/GMT-5\",\n            \"UTC+5:30\": \"Asia/Kolkata\",\n            \"UTC+6\": \"Etc/GMT-6\",\n            \"UTC+7\": \"Etc/GMT-7\",\n            \"UTC+8\": \"Etc/GMT-8\",\n            \"UTC+9\": \"Etc/GMT-9\",\n            \"UTC+10\": \"Etc/GMT-10\",\n            \"UTC+11\": \"Etc/GMT-11\",\n            \"UTC+12\": \"Etc/GMT-12\",\n            \"UTC+13\": \"Pacific/Apia\",\n            \"UTC+14\": \"Pacific/Kiritimati\",\n        ]\n\n        super.init()\n    }\n\n    func search(query: String, completion: @escaping ([TimeZoneSearchResult]) -> Void) {\n        currentCompletion = completion\n\n        let results = searchAbbreviations(query: query) + searchUTCOffsets(query: query)\n\n        if !results.isEmpty {\n            completion(results)\n            return\n        }\n\n        let request = MKLocalSearch.Request()\n        request.naturalLanguageQuery = query\n        request.resultTypes = .address\n        \n\n        let search = MKLocalSearch(request: request)\n        \n        search.start { [weak self] response, _ in\n            guard let self = self, let response = response else {\n                completion([])\n                return\n            }\n\n            let results = self.processResults(response.mapItems)\n            DispatchQueue.main.async {\n                completion(results)\n            }\n        }\n    }\n\n    private func processResults(_ mapItems: [MKMapItem]) -> [TimeZoneSearchResult] {\n        return mapItems.compactMap { item in\n//            guard let placemark = item.placemark else { return nil }\n            let placemark = item.placemark\n            \n\n            let city = placemark.locality ?? placemark.name ?? \"\"\n            let country = placemark.country ?? \"\"\n            // placemark.administrativeArea,\n            let subtitle = [country].compactMap { $0 }.joined(separator: \", \")\n\n            print(\"placemark.timeZone \\(city) \\(placemark)\")\n            \n            return TimeZoneSearchResult(\n                title: city,\n                subtitle: subtitle,\n                identifier: placemark.timeZone?.identifier,\n                type: .city,\n                region: placemark.region,\n                coordinate: placemark.location\n            )\n        }\n    }\n\n    private func searchAbbreviations(query: String) -> [TimeZoneSearchResult] {\n        return commonAbbreviations\n            .filter { $0.key.lowercased().contains(query.lowercased()) }\n            .map { TimeZoneSearchResult(title: $0.key, subtitle: $0.value.fullName, identifier: $0.value.identifier, type: .abbreviation, region: nil, coordinate: nil) }\n    }\n\n    private func searchUTCOffsets(query: String) -> [TimeZoneSearchResult] {\n        return utcOffsets\n            .filter { $0.key.lowercased().contains(query.lowercased()) }\n            .map { TimeZoneSearchResult(title: $0.key, subtitle: \"Coordinated Universal Time Offset\", identifier: $0.value, type: .utcOffset, region: nil, coordinate: nil) }\n    }\n}\n\nstruct TimeZoneSearchResult: Identifiable, Equatable {\n    static func ==(lhs: TimeZoneSearchResult, rhs: TimeZoneSearchResult) -> Bool {\n        lhs.id == rhs.id && lhs.title == rhs.title && lhs.subtitle == rhs.subtitle && lhs.identifier == rhs.identifier && lhs.type == rhs.type && lhs.region == rhs.region\n    }\n    \n    let id = UUID()\n    let title: String\n    let subtitle: String\n    let identifier: String?\n    let type: TimeZoneSearchResultType\n    let region: CLRegion?\n    let coordinate: CLLocation?\n\n    func getTimeZone() async  -> TimeZone? {\n        switch type {\n        case .city:\n            print(\"title \\(title) coordinate \\(coordinate) region \\(region)\")\n            if let coordinate = coordinate {\n                \n                return await TimeZone.timeZone(for: coordinate)\n            }\n            if let region = region as? CLCircularRegion {\n                let location = CLLocation(latitude: region.center.latitude, longitude: region.center.longitude)\n                return await TimeZone.timeZone(for: location)\n            }\n            return nil\n        case .abbreviation, .utcOffset:\n            return identifier.flatMap { TimeZone(identifier: $0) }\n        }\n    }\n}\n\nenum TimeZoneSearchResultType {\n    case city\n    case abbreviation\n    case utcOffset\n}\n\nextension TimeZone {\n    static func timeZone(for location: CLLocation) async -> TimeZone? {\n           let geocoder = CLGeocoder()\n           do {\n               let placemarks = try await geocoder.reverseGeocodeLocation(location)\n               print(\"\\(placemarks)\")\n               return placemarks.first?.timeZone\n           } catch {\n               print(\"Geocoding error: \\(error.localizedDescription)\")\n               return nil\n           }\n       }\n}\n\nclass SearchCompleter: ObservableObject {\n    @Published var results: [TimeZoneSearchResult] = []\n    @Published var queryFragment: String = \"\" {\n        didSet {\n            if queryFragment.isEmpty {\n                results = defaultResults\n            } else {\n                updateResults(for: queryFragment)\n            }\n        }\n    }\n\n    private let timeZoneSearchCompiler: TimeZoneSearchCompiler\n    private var defaultResults: [TimeZoneSearchResult] = []\n\n    init() {\n        timeZoneSearchCompiler = TimeZoneSearchCompiler()\n        setupDefaultResults()\n    }\n\n    private func setupDefaultResults() {\n        // All timezones and locations\n        for identifier in TimeZone.knownTimeZoneIdentifiers {\n            let components = identifier.split(separator: \"/\")\n            if components.count >= 2 {\n                let location = String(components.last!).replacingOccurrences(of: \"_\", with: \" \")\n                let region = String(components.first!)\n                defaultResults.append(TimeZoneSearchResult(title: location, subtitle: region, identifier: identifier, type: .city, region: nil, coordinate: nil))\n            }\n        }\n\n        // Add UTC offsets\n        for offset in -12 ... 14 {\n            let sign = offset >= 0 ? \"+\" : \"\"\n            let title = \"UTC\\(sign)\\(offset)\"\n            let identifier = \"Etc/GMT\\(offset == 0 ? \"\" : (offset > 0 ? \"-\" : \"+\") + \"\\(abs(offset))\")\"\n            defaultResults.append(TimeZoneSearchResult(title: title, subtitle: \"Coordinated Universal Time Offset\", identifier: identifier, type: .utcOffset, region: nil, coordinate: nil))\n        }\n\n        results = defaultResults\n    }\n\n    private var latestSearch = \"\"\n\n    private func updateResults(for query: String) {\n        latestSearch = query\n\n        if query.isEmpty {\n            results = defaultResults\n        } else {\n            timeZoneSearchCompiler.search(query: query) { [weak self] searchResults in\n                DispatchQueue.main.async {\n                    if self?.latestSearch == query {\n                        self?.results = searchResults\n                    } else {\n                        // discard\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "There/Data/Utilities.swift",
    "content": "import Foundation\nimport SwiftUI\n\nclass Utils {\n    public static var shared = Utils()\n    func selectPhoto() -> NSImage? {\n        let openPanel = NSOpenPanel()\n        openPanel.allowedContentTypes = [.jpeg, .png]\n        openPanel.allowsMultipleSelection = false\n        openPanel.prompt = \"Select Image\"\n        if openPanel.runModal() == .OK, let url = openPanel.url {\n            return NSImage(contentsOf: url)\n        } else {\n            return nil\n        }\n    }\n\n    /// Converts a two-letter country code to its corresponding emoji flag.\n    ///\n    /// - Parameter countryCode: ISO 3166-1 alpha-2 country code.\n    /// - Returns: Emoji representation of the country's flag.\n    func getCountryEmoji(for countryCode: String) -> String {\n        // Unicode offset for Regional Indicator Symbols\n        let base: UInt32 = 127397\n\n        // Convert each letter to its corresponding Regional Indicator Symbol\n        return countryCode.uppercased().unicodeScalars.map {\n            String(UnicodeScalar(base + $0.value)!)\n        }.joined()\n    }\n}\n"
  },
  {
    "path": "There/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "There/There.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.developer.maps</key>\n\t<true/>\n\t<key>com.apple.security.app-sandbox</key>\n\t<true/>\n\t<key>com.apple.security.application-groups</key>\n\t<array>\n\t\t<string>group.pm.there.There</string>\n\t</array>\n\t<key>com.apple.security.files.user-selected.read-only</key>\n\t<true/>\n\t<key>com.apple.security.network.client</key>\n\t<true/>\n\t<key>com.apple.security.network.server</key>\n\t<true/>\n\t<key>com.apple.security.personal-information.location</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "There/ThereApp.swift",
    "content": "//\n//  ThereApp.swift\n//  There\n//\n//  Created by Dena Sohrabi on 9/2/24.\n//\n\nimport AppKit\nimport MenuBarExtraAccess\nimport PostHog\nimport SwiftUI\nimport UserNotifications\n\n@main\nstruct ThereApp: App {\n    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate\n    @Environment(\\.openWindow) var openWindow\n    @ObservedObject var appState = AppState.shared\n    @StateObject var router: Router = Router()\n    var body: some Scene {\n        MenuBarExtra {\n            ContentView()\n                .environment(\\.database, .shared)\n                .frame(width: 320)\n                .frame(minHeight: 300)\n                .frame(maxHeight: 600)\n                .background(Color(NSColor.windowBackgroundColor).opacity(0.78).ignoresSafeArea())\n                .environmentObject(appState)\n                .environmentObject(router)\n        } label: {\n            let image: NSImage = {\n                let ratio = $0.size.height / $0.size.width\n                $0.size.height = 20\n                $0.size.width = 20 / ratio\n                return $0\n            }(NSImage(named: \"appIcon\")!)\n\n            Image(nsImage: image)\n                .onAppear {\n                    if UserDefaults.standard.bool(forKey: \"hasCompletedInitialSetup\") == false {\n                        openWindow(id: \"init\")\n                    }\n                }\n                .foregroundColor(.primary)\n        }\n        .menuBarExtraStyle(.window)\n        .menuBarExtraAccess(isPresented: $appState.menuBarViewIsPresented)\n        .windowResizability(.contentSize)\n\n        WindowGroup(\"init\", id: \"init\") {\n            InitialView()\n                .environment(\\.database, .shared)\n                .fixedSize()\n                .frame(width: 600, height: 400)\n                .environmentObject(appState)\n        }\n        .windowStyle(.hiddenTitleBar)\n        .defaultSize(width: 600, height: 400)\n        .defaultPosition(.center)\n        .windowResizability(.contentSize)\n        #if MAC_OS_VERSION_15_0\n            .windowBackgroundDragBehavior(.enabled)\n        #endif\n\n        Settings {\n            Text(\"Coming soon...\")\n        }\n        #if MAC_OS_VERSION_15_0\n            .windowStyle(.plain)\n        #endif\n            .defaultSize(width: 600, height: 400)\n            .windowResizability(.automatic)\n    }\n}\n\nextension EnvironmentValues {\n    @Entry var database: AppDatabase = .shared\n}\n\nclass AppState: ObservableObject {\n    static let shared = AppState()\n    @Published var menuBarViewIsPresented: Bool = false\n    func presentMenu() {\n        menuBarViewIsPresented = true\n    }\n\n    func hideMenu() {\n        menuBarViewIsPresented = true\n    }\n}\n\nclass AppDelegate: NSObject, NSApplicationDelegate {\n    func applicationDidFinishLaunching(_: Notification) {\n        let POSTHOG_API_KEY = \"phc_XZFRnJFd8RVNegex9sLKplgz8KCFxGyLZwxh5usmoig\"\n        let POSTHOG_HOST = \"https://eu.i.posthog.com\"\n\n        let config = PostHogConfig(apiKey: POSTHOG_API_KEY, host: POSTHOG_HOST)\n\n        PostHogSDK.shared.setup(config)\n    }\n}\n"
  },
  {
    "path": "There/UI/Button.swift",
    "content": "import SwiftUI\n\n// PrimaryButton\nstruct PrimaryButton: View {\n    var title: String\n    var action: () -> Void\n\n    var body: some View {\n        Button(action: action) {\n            Text(title)\n        }\n        .buttonStyle(PrimaryButtonStyle())\n    }\n}\n\nstruct PrimaryButtonStyle: ButtonStyle {\n    let lightBlue = Color(red: 0.24, green: 0.67, blue: 0.91) // #3DAAE8\n    let darkBlue = Color(red: 0.22, green: 0.60, blue: 0.82) // #3799D1\n\n    @State private var hovered: Bool = false\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .foregroundColor(.white)\n            .fontWeight(.medium)\n            .frame(width: 200, height: 32)\n            .background(\n                ZStack {\n                    RoundedRectangle(cornerRadius: 8, style: .continuous)\n                        .fill(hovered ? lightBlue.opacity(0.85) : lightBlue)\n                    RoundedRectangle(cornerRadius: 8, style: .continuous)\n                        .stroke(hovered ? darkBlue.opacity(0.6) : darkBlue, lineWidth: 3)\n                }\n            )\n            .cornerRadius(8)\n            .shadow(color: .primary.opacity(0.08), radius: 0.5, x: 0, y: configuration.isPressed ? 0 : (hovered ? 2 : 1))\n            .scaleEffect(configuration.isPressed ? 0.98 : 1.0)\n            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)\n            .onHover { hovering in\n                withAnimation {\n                    self.hovered = hovering\n                }\n            }\n    }\n}\n\n// SecondaryButton\nstruct SecondaryButton: View {\n    var title: String\n    var action: () -> Void\n\n    var body: some View {\n        Button(action: action) {\n            Text(title)\n                .foregroundColor(.primary)\n        }\n        .buttonStyle(SecondaryButtonStyle())\n    }\n}\n\nstruct SecondaryButtonStyle: ButtonStyle {\n    @Environment(\\.colorScheme) var scheme\n\n    var white: Color {\n        if scheme == .dark {\n            return Color(.gray).opacity(0.2)\n        } else {\n            return Color(red: 1.0, green: 1.0, blue: 1.0)\n        }\n    }\n\n    var lightGray: Color {\n        if scheme == .dark {\n            return Color(NSColor.systemGray).opacity(0.2)\n        } else {\n            return Color(red: 0.86, green: 0.86, blue: 0.86) // #DCDCDC\n        }\n    }\n\n    @State private var hovered: Bool = false\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .lineLimit(1)\n            .padding(.horizontal, 6)\n            .foregroundColor(.primary.opacity(0.8))\n            .fontWeight(.medium)\n            .frame(width: 200, height: 32)\n            .background(\n                ZStack {\n                    RoundedRectangle(cornerRadius: 8, style: .continuous)\n                        .fill(hovered ? white.opacity(0.85) : white)\n                    RoundedRectangle(cornerRadius: 8, style: .continuous)\n                        .stroke(hovered ? lightGray.opacity(0.6) : lightGray, lineWidth: 3)\n                }\n            )\n            .cornerRadius(8)\n            .shadow(color: .primary.opacity(0.08), radius: 0.5, x: 0, y: configuration.isPressed ? 0 : (hovered ? 2 : 1))\n            .scaleEffect(configuration.isPressed ? 0.98 : 1.0)\n            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)\n            .onHover { hovering in\n                withAnimation {\n                    self.hovered = hovering\n                }\n            }\n    }\n}\n\n// CompactButton\nstruct CompactButton: View {\n    var title: String\n    var action: () -> Void\n\n    var body: some View {\n        Button(action: action) {\n            Text(title)\n        }\n        .buttonStyle(CompactButtonStyle())\n    }\n}\n\nstruct CompactButtonStyle: ButtonStyle {\n    @Environment(\\.colorScheme) var scheme\n\n    var lightGray: Color {\n        if scheme == .dark {\n            return Color(NSColor.systemGray).opacity(0.2)\n        } else {\n            return Color(red: 0.86, green: 0.86, blue: 0.86) // #DCDCDC\n        }\n    }\n\n    @State private var hovered: Bool = false\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .padding(.horizontal, 8)\n            .foregroundColor(.primary)\n            .fontWeight(.medium)\n            .frame(height: 28)\n            .background(hovered ? (scheme == .dark ? Color(.gray).opacity(0.2) : .white) : (scheme == .dark ? Color(.gray).opacity(0.3) : .white.opacity(0.8)))\n            .cornerRadius(8)\n            .shadow(color: .primary.opacity(0.04), radius: 0.5, x: 0, y: configuration.isPressed ? 0 : (hovered ? 2 : 1))\n            .scaleEffect(configuration.isPressed ? 0.98 : 1.0)\n            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)\n            .onHover { hovering in\n                withAnimation {\n                    self.hovered = hovering\n                }\n            }\n    }\n}\n\n// CompactPrimaryButton\nstruct CompactPrimaryButton: View {\n    var title: String\n    var action: () -> Void\n    var width: CGFloat = 232\n\n    var body: some View {\n        Button(action: action) {\n            Text(title)\n        }\n        .buttonStyle(CompactPrimaryButtonStyle(width: width))\n    }\n}\n\nstruct CompactPrimaryButtonStyle: ButtonStyle {\n    let lightBlue = Color(red: 0.24, green: 0.67, blue: 0.91) // #3DAAE8\n    let darkBlue = Color(red: 0.22, green: 0.60, blue: 0.82) // #3799D1\n\n    @State private var hovered: Bool = false\n    var width: CGFloat\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .foregroundColor(.white)\n            .fontWeight(.medium)\n            .frame(width: width, height: 32)\n            .padding(.horizontal, 6)\n            .background(\n                ZStack {\n                    RoundedRectangle(cornerRadius: 8, style: .continuous)\n                        .fill(hovered ? lightBlue.opacity(0.85) : lightBlue)\n                    RoundedRectangle(cornerRadius: 8, style: .continuous)\n                        .stroke(hovered ? darkBlue.opacity(0.6) : darkBlue, lineWidth: 3)\n                }\n            )\n            .cornerRadius(8)\n            .shadow(color: .primary.opacity(0.08), radius: 0.5, x: 0, y: configuration.isPressed ? 0 : (hovered ? 2 : 1))\n            .scaleEffect(configuration.isPressed ? 0.98 : 1.0)\n            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)\n            .onHover { hovering in\n                withAnimation {\n                    self.hovered = hovering\n                }\n            }\n    }\n}\n\n// Preview\nstruct ButtonPreviews: PreviewProvider {\n    static var previews: some View {\n        VStack(spacing: 20) {\n            PrimaryButton(title: \"Primary Button\", action: {})\n            SecondaryButton(title: \"Secondary Button\", action: {})\n            CompactButton(title: \"Compact Button\", action: {})\n            CompactPrimaryButton(title: \"Compact Primary Button\", action: {})\n        }\n        .padding()\n    }\n}\n"
  },
  {
    "path": "There/UI/Heading.swift",
    "content": "import SwiftUI\n\nstruct Heading: View {\n    let title: String\n\n    var body: some View {\n        Text(title)\n            .font(.title2)\n            .fontWeight(.medium)\n    }\n}\n\n#Preview {\n    Heading(title: \"Hello, World!\")\n}\n"
  },
  {
    "path": "There/UI/Input.swift",
    "content": "import SwiftUI\n\nstruct AdaptiveColors {\n    static let textFieldBackground = Color(.textBackgroundColor)\n    static let textFieldBorder = Color.secondary\n    static let textColor = Color.primary\n}\n\nstruct Input: View {\n    @Binding var text: String\n    var placeholder: String\n    @FocusState private var isFocused: Bool\n\n    var body: some View {\n        CustomTextInput(text: $text, placeholder: placeholder, isFocused: _isFocused)\n            .frame(width: 200, height: 32)\n    }\n}\n\nstruct CompactInput: View {\n    @Binding var text: String\n    var placeholder: String\n    @FocusState private var isFocused: Bool\n\n    var body: some View {\n        CustomTextInput(text: $text, placeholder: placeholder, isFocused: _isFocused)\n            .frame(height: 32)\n            .padding(.bottom)\n            .scaledToFill()\n    }\n}\n\nstruct AutocompleteInput: View {\n    @Binding var text: String\n    let placeholder: String\n    let suggestions: [String]\n    let onCommit: () -> Void\n\n    @State private var isEditing = false\n    @State private var showSuggestions = false\n    @FocusState private var isFocused: Bool\n\n    var body: some View {\n        VStack(alignment: .leading) {\n            CustomTextInput(text: $text, placeholder: placeholder, isFocused: _isFocused)\n                .frame(height: 32)\n                .onChange(of: isFocused) { focused in\n                    isEditing = focused\n                    showSuggestions = focused && !suggestions.isEmpty\n                }\n                .onChange(of: text) { _ in\n                    showSuggestions = isEditing && !suggestions.isEmpty\n                }\n\n            if showSuggestions {\n                ScrollView {\n                    LazyVStack(alignment: .leading) {\n                        ForEach(suggestions.prefix(5), id: \\.self) { suggestion in\n                            Text(suggestion)\n                                .padding(.vertical, 2)\n                                .onTapGesture {\n                                    text = suggestion\n                                    showSuggestions = false\n                                    onCommit()\n                                }\n                        }\n                    }\n                }\n                .frame(maxHeight: 150)\n                .background(AdaptiveColors.textFieldBackground)\n                .cornerRadius(5)\n                .shadow(color: Color.primary.opacity(0.2), radius: 5)\n            }\n        }\n    }\n}\n\nstruct CustomTextInput: View {\n    @Binding var text: String\n    var placeholder: String\n    @FocusState var isFocused: Bool\n\n    var body: some View {\n        ZStack(alignment: .leading) {\n            RoundedRectangle(cornerRadius: 8)\n                .fill(AdaptiveColors.textFieldBackground)\n\n            RoundedRectangle(cornerRadius: 8)\n                .stroke(isFocused ? .blue : AdaptiveColors.textFieldBorder.opacity(0.5), lineWidth: 1)\n\n            TextField(placeholder, text: $text)\n                .textFieldStyle(.plain)\n                .padding(.horizontal, 6)\n                .foregroundColor(AdaptiveColors.textColor)\n                .focused($isFocused)\n        }\n        .onTapGesture {\n            isFocused = true\n        }\n    }\n}\n"
  },
  {
    "path": "There/UI/Label.swift",
    "content": "import SwiftUI\n\nstruct StyledLabel: View {\n    let title: String\n    \n    var body: some View {\n        Text(title)\n            .font(.caption)\n            .fontWeight(.semibold)\n            .foregroundColor(.secondary)\n    }\n}\n\n#Preview {\n    StyledLabel(title: \"Name\")\n}\n"
  },
  {
    "path": "There/UI/LocalImageView.swift",
    "content": "import SwiftUI\n\nstruct LocalImageView: View {\n    let imageURL: URL\n\n    @State private var image: NSImage?\n    @State private var isLoading = true\n    @State private var errorMessage: String?\n\n    var body: some View {\n        Group {\n            if let image = image {\n                Image(nsImage: image)\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n            } else if isLoading {\n                ProgressView()\n            } else if let errorMessage = errorMessage {\n                Text(errorMessage)\n                    .foregroundColor(.red)\n            }\n        }\n        .onAppear(perform: loadImage)\n    }\n\n    private func loadImage() {\n        isLoading = true\n        DispatchQueue.global(qos: .userInitiated).async {\n            if let imageData = try? Data(contentsOf: imageURL),\n               let loadedImage = NSImage(data: imageData) {\n                DispatchQueue.main.async {\n                    self.image = loadedImage\n                    self.isLoading = false\n                }\n            } else {\n                DispatchQueue.main.async {\n                    self.errorMessage = \"Failed to load image\"\n                    self.isLoading = false\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "There/UI/Titlebar.swift",
    "content": "import SwiftUI\n\nstruct Titlebar: View {\n    @EnvironmentObject var router: Router\n    @State var hovered: Bool = false\n    var body: some View {\n        HStack(alignment: .center) {\n            Button {\n                router.cleanActiveRoute()\n            } label: {\n                Image(systemName: \"chevron.left\")\n                    .foregroundColor(.secondary)\n                    .font(.body)\n                    .padding(4)\n                    .background(hovered ? Color(NSColor.separatorColor).opacity(0.8) : .clear)\n                    .cornerRadius(6)\n                    .onHover { hovered in\n                        withAnimation {\n                            self.hovered = hovered\n                        }\n                    }\n            }\n            .buttonStyle(.plain)\n\n            switch router.activeRoute {\n            case .addTimezone:\n                Text(\"🗺️\")\n                    .font(.callout)\n                Text(\"Add Time Zone\")\n                    .font(.body)\n                    .fontWeight(.medium)\n            case .editTimeZone:\n                Text(\"✍️\")\n                    .font(.callout)\n                Text(\"Edit Time Zone\")\n                    .font(.body)\n                    .fontWeight(.medium)\n            case .mainView:\n                EmptyView()\n            }\n        }\n    }\n}\n\n#Preview {\n    Titlebar()\n        .padding()\n}\n"
  },
  {
    "path": "There/UI/TransparentBackgroundView.swift",
    "content": "import SwiftUI\n\nstruct TransparentBackgroundView: NSViewRepresentable {\n    func makeNSView(context: Context) -> NSVisualEffectView {\n        let view = NSVisualEffectView()\n        view.blendingMode = .withinWindow\n        view.state = .active\n        view.material = .popover\n        return view\n    }\n\n    func updateNSView(_ nsView: NSVisualEffectView, context: Context) {}\n}\n\n#Preview {\n    TransparentBackgroundView()\n}\n"
  },
  {
    "path": "There/Views/ButtomBar/AddButton.swift",
    "content": "import SwiftUI\n\nstruct AddButton: View {\n    @State private var addHovered: Bool = false\n    @EnvironmentObject var appState: AppState\n    @EnvironmentObject var router: Router\n\n    var body: some View {\n        CompactButton(title: \"Add\") {\n            router.setActiveRoute(to: .addTimezone)\n        }\n    }\n}\n\n#Preview {\n    AddButton()\n        .padding()\n}\n"
  },
  {
    "path": "There/Views/ButtomBar/BottomBarView.swift",
    "content": "import SwiftUI\n\nstruct BottomBarView: View {\n    @Binding var isAtBottom: Bool\n    @Binding var sortOrder: SortOrder\n    @Binding var showSlider: Bool\n\n    var body: some View {\n        HStack(spacing: 2) {\n            AddButton()\n            Spacer()\n\n            Button {\n                withAnimation {\n                    showSlider.toggle()\n                }\n            } label: {\n                Image(systemName: \"clock.arrow.2.circlepath\")\n                    .font(.body)\n            }\n            .buttonStyle(SettingsButtonStyle())\n            .help(\"Time Slider\")\n\n            SettingsButton(sortOrder: $sortOrder)\n        }\n        .padding(.horizontal, 8)\n        .frame(maxWidth: .infinity)\n        .frame(height: 45)\n        .overlay(alignment: .top) {\n            if isAtBottom {\n                Divider()\n                    .padding(.top, -2)\n            }\n        }\n        .animation(.default, value: isAtBottom)\n    }\n}\n\n#Preview {\n    BottomBarView(isAtBottom: .constant(true), sortOrder: .constant(.timeDescending), showSlider: .constant(false))\n}\n"
  },
  {
    "path": "There/Views/ButtomBar/SettingsButton.swift",
    "content": "import SwiftUI\n\nstruct SettingsButton: View {\n    @State private var settingsHovered: Bool = false\n    @Environment(\\.openWindow) var openWindow\n    @Environment(\\.openURL) var openURL\n    @EnvironmentObject var appState: AppState\n    @Environment(\\.database) var database: AppDatabase\n    @Environment(\\.colorScheme) var scheme\n    @AppStorage(\"launchAtLogin\") private var launchAtLogin = false\n    @Binding var sortOrder: SortOrder\n    var backgroundColor: Color {\n        if scheme == .dark {\n            return Color(.gray).opacity(0.2)\n        } else {\n            return .white\n        }\n    }\n\n    var body: some View {\n        Menu {\n            Toggle(\"Launch at Login\", isOn: $launchAtLogin)\n                .onChange(of: launchAtLogin) { newValue in\n                    if newValue {\n                        installLaunchAgent()\n                    } else {\n                        uninstallLaunchAgent()\n                    }\n                }\n            Toggle(\"Ascending order\", isOn: Binding(\n                get: { sortOrder == .timeAscending },\n                set: { newValue in\n                    sortOrder = newValue ? .timeAscending : .timeDescending\n                }\n            ))\n\n            Section(\"Support\") {\n                Button(\"DM on X\") {\n                    openURL(URL(string: \"https://twitter.com/messages/compose?recipient_id=1434101346110689282\")!)\n                }\n                Button(\"Email Us\") {\n                    openAppleMailComposer(to: \"support@there.pm\",\n                                          subject: \"Support Request\",\n                                          body: \"Hello, I need assistance with...\")\n                }\n            }\n            Section {\n                Button(\"Open Website\") {\n                    openURL(URL(string: \"https://there.pm\")!)\n                }\n                Button(\"Follow on X\") {\n                    openURL(URL(string: \"https://x.com/ThereHQ\")!)\n                }\n            }\n\n            #if targetEnvironment(simulator) || DEBUG\n                Section(\"Dev & Debug\") {\n                    Button(\"Clear Cache & Data\") {\n                        UserDefaults.standard.removeObject(forKey: \"hasCompletedInitialSetup\")\n                        do {\n                            _ = try database.dbWriter.write { db in\n                                try Entry.deleteAll(db)\n                            }\n                        } catch {\n                            print(\"Can't clear DB \\(error)\")\n                        }\n                    }\n                }\n            #endif\n\n            Divider()\n\n            Button(\"Quit\") {\n                NSApplication.shared.terminate(nil)\n            }\n            .keyboardShortcut(\"q\", modifiers: .command)\n        } label: {\n            Image(systemName: \"gearshape.fill\")\n                .font(.body)\n        }\n        .buttonStyle(SettingsButtonStyle())\n    }\n\n    func openAppleMailComposer(to recipient: String, subject: String? = nil, body: String? = nil) {\n        let appleMailBundleIdentifier = \"com.apple.mail\"\n\n        guard let appleMailURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: appleMailBundleIdentifier) else {\n            print(\"Apple Mail not found\")\n            return\n        }\n\n        var components = URLComponents()\n        components.scheme = \"mailto\"\n        components.path = recipient\n\n        var queryItems = [URLQueryItem]()\n        if let subject = subject {\n            queryItems.append(URLQueryItem(name: \"subject\", value: subject))\n        }\n        if let body = body {\n            queryItems.append(URLQueryItem(name: \"body\", value: body))\n        }\n\n        if !queryItems.isEmpty {\n            components.queryItems = queryItems\n        }\n\n        guard let emailURL = components.url else {\n            print(\"Invalid email URL\")\n            return\n        }\n\n        let configuration = NSWorkspace.OpenConfiguration()\n        configuration.activates = true\n\n        NSWorkspace.shared.open([emailURL], withApplicationAt: appleMailURL, configuration: configuration) { _, error in\n            if let error = error {\n                print(\"Failed to open Apple Mail: \\(error.localizedDescription)\")\n            }\n        }\n    }\n}\n\n#Preview {\n    SettingsButton(sortOrder: .constant(.timeAscending))\n}\n\nstruct SettingsButtonStyle: ButtonStyle {\n    @Environment(\\.colorScheme) var scheme\n    @State private var isHovered = false\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .foregroundColor(isHovered ? .primary : .secondary)\n            .frame(height: 28)\n            .padding(.horizontal, 8)\n            .background(isHovered ? backgroundColor : .clear)\n            .cornerRadius(8)\n            .onHover { hovering in\n                withAnimation {\n                    isHovered = hovering\n                }\n            }\n    }\n\n    private var backgroundColor: Color {\n        scheme == .dark ? Color(.gray).opacity(0.2) : .white\n    }\n}\n"
  },
  {
    "path": "There/Views/CLAuthorizationStatus+Description.swift ",
    "content": "import CoreLocation\n\nextension CLAuthorizationStatus: CustomStringConvertible {\n    public var description: String {\n        switch self {\n        case .notDetermined: return \"Not Determined\"\n        case .restricted: return \"Restricted\"\n        case .denied: return \"Denied\"\n        case .authorizedAlways: return \"Authorized Always\"\n        @unknown default: return \"Unknown\"\n        }\n    }\n}\n"
  },
  {
    "path": "There/Views/EmptyTimezoneView.swift",
    "content": "import SwiftUI\n\nstruct EmptyTimezoneView: View {\n    var body: some View {\n        VStack {\n            Image(\"Earth\")\n                .resizable()\n                .frame(width: 158, height: 140)\n                .padding(.bottom, 8)\n                .padding(.leading, -43)\n            Text(\"Hey There!\")\n                .font(.title)\n                .fontWeight(.medium)\n\n            Text(\"Add your first timezone\")\n                .foregroundColor(.secondary)\n        }\n    }\n}\n\n#Preview {\n    EmptyTimezoneView()\n        .frame(width: 320, height: 320)\n}\n"
  },
  {
    "path": "There/Views/EntryUI/EntryIcon.swift",
    "content": "import CoreLocation\nimport SwiftUI\n\nstruct EntryIcon: View {\n    let entry: Entry\n    @Environment(\\.colorScheme) var scheme\n    @Environment(\\.database) var database\n    @State private var useClockIcon: Bool = false\n\n    var backgroundColor: Color {\n        if scheme == .dark {\n            return Color(NSColor.darkGray)\n        } else {\n            return Color.white.opacity(0.6)\n        }\n    }\n\n    var body: some View {\n        Group {\n            if let data = entry.photoData, !data.isEmpty {\n                photoIcon(data: data)\n            } else if let flag = entry.flag {\n                placeIcon\n            } else {\n                defaultIcon\n            }\n        }\n        .overlay(alignment: .bottomTrailing) {\n            timeIcon\n        }\n        .onAppear {\n            if entry.flag == nil || entry.flag!.isEmpty {\n                searchForFlag()\n            }\n        }\n    }\n\n    private var placeIcon: some View {\n        Circle()\n            .fill(backgroundColor)\n            .frame(width: 45)\n            .overlay {\n                if let flag = entry.flag {\n                    Text(flag)\n                        .font(.largeTitle)\n                } else {\n                    Image(systemName: \"clock\")\n                        .font(.largeTitle)\n                        .foregroundColor(.secondary)\n                }\n            }\n    }\n\n    private func photoIcon(data: String) -> some View {\n        Group {\n            if let url = URL(string: data) {\n                AsyncImage(url: url) { phase in\n                    switch phase {\n                    case let .success(image):\n                        image\n                            .resizable()\n                            .aspectRatio(contentMode: .fill)\n                            .frame(width: 45, height: 45)\n                            .clipShape(Circle())\n                    case .failure:\n                        defaultIcon\n                    case .empty:\n                        ProgressView()\n                    @unknown default:\n                        defaultIcon\n                    }\n                }\n            } else {\n                defaultIcon\n            }\n        }\n    }\n\n    private var defaultIcon: some View {\n        Circle()\n            .fill(backgroundColor)\n            .frame(width: 45)\n            .overlay {\n                if let flag = entry.flag, !flag.isEmpty {\n                    Text(flag)\n                        .font(.largeTitle)\n                } else {\n                    Image(systemName: \"clock\")\n                        .font(.largeTitle)\n                        .foregroundColor(.secondary)\n                }\n            }\n    }\n\n    private var timeIcon: some View {\n        Image(entry.timeIcon)\n            .resizable()\n            .aspectRatio(contentMode: .fit)\n            .frame(width: 14, height: 14)\n            .background(\n                TransparentBackgroundView()\n                    .frame(width: 18, height: 18)\n                    .cornerRadius(50)\n            )\n            .padding(.bottom, 4)\n            .padding(.trailing, -3)\n    }\n\n    func searchForFlag() {\n        let geocoder = CLGeocoder()\n        geocoder.geocodeAddressString(entry.city) { placemarks, _ in\n            if let placemark = placemarks?.first, let timezone = placemark.timeZone {\n                Task {\n                    do {\n                        try await database.dbWriter.write { db in\n                            var entry = try Entry.fetchOne(db, id: entry.id)\n                            let countryEmoji = Utils.shared.getCountryEmoji(for: placemark.isoCountryCode ?? \"\")\n                            entry?.flag = countryEmoji.isEmpty ? \"🌍\" : countryEmoji\n                            try entry?.update(db)\n                        }\n                    } catch {\n                        print(\"Can't find flag \\(error)\")\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "There/Views/EntryUI/EntryRow.swift",
    "content": "import Combine\nimport SwiftUI\n\nstruct EntryRow: View {\n    let entry: Entry\n    @State private var isHovered: Bool = false\n    @Environment(\\.colorScheme) var scheme\n    @EnvironmentObject var router: Router\n    @Environment(\\.scenePhase) private var scenePhase\n    @State private var currentDate = Date()\n    @State private var timer: Publishers.Autoconnect<Timer.TimerPublisher>?\n    @Binding var timeOffset: Double\n\n    var body: some View {\n        HStack {\n            EntryIcon(entry: entry)\n            VStack(alignment: .leading) {\n                Text(entry.name.isEmpty ? entry.city : entry.name)\n                    .font(.title3)\n                    .fontWeight(.medium)\n                    .lineLimit(1)\n\n                Text(entry.city)\n                    .font(.body)\n                    .foregroundColor(.secondary)\n                    .lineLimit(1)\n            }\n            .padding(.leading, 6)\n            Spacer()\n            VStack(alignment: .trailing) {\n                HStack(spacing: 0) {\n                    Text(formattedTime(timeZoneIdentifier: entry.timezoneIdentifier))\n                        .monospaced()\n                        .font(.body)\n                        .contentTransition(.numericText())\n                }\n                Text(formatTimeDifference())\n                    .monospaced()\n                    .font(.callout)\n                    .foregroundColor(.gray)\n            }\n        }\n        .frame(maxWidth: .infinity, alignment: .leading)\n        .padding(.horizontal, 8)\n        .padding(.vertical, 6)\n        .background(isHovered ? scheme == .dark ? Color.white.opacity(0.1) : Color.white.opacity(0.6) : Color.clear)\n        .cornerRadius(8)\n        .onHover { isHovered in\n            withAnimation(.easeInOut(duration: 0.1)) {\n                self.isHovered = isHovered\n            }\n        }\n        .onReceive(timer ?? Timer.publish(every: 1, on: .main, in: .common).autoconnect()) { _ in\n            currentDate = Date()\n        }\n        .onTapGesture {\n            router.setActiveRoute(to: .editTimeZone(entryId: entry.id))\n        }\n        .onChange(of: scenePhase) { newPhase in\n            updateTimer(for: newPhase)\n        }\n        .onAppear {\n            updateTimer(for: scenePhase)\n        }\n    }\n\n    private func formattedTime(timeZoneIdentifier: String) -> String {\n        let formatter = DateFormatter()\n        formatter.timeZone = TimeZone(identifier: timeZoneIdentifier)\n\n        // Get the system's locale\n        let locale = Locale.current\n\n        // Create a template that includes both 24-hour and 12-hour formats\n        let template = \"j:mm\"\n\n        // Generate the best format for the current locale\n        if let formatString = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: locale) {\n            formatter.dateFormat = formatString\n        } else {\n            // Fallback to a default format if generation fails\n            formatter.timeStyle = .short\n        }\n        let offsetDate = Date().addingTimeInterval(timeOffset * 3600)\n\n        return formatter.string(from: offsetDate)\n    }\n\n    private func formatTimeDifference() -> String {\n        let userTimeZone = TimeZone.current\n        let entryTimeZone = TimeZone(identifier: entry.timezoneIdentifier) ?? .current\n\n        let userDate = currentDate.addingTimeInterval(TimeInterval(userTimeZone.secondsFromGMT()))\n        let entryDate = currentDate.addingTimeInterval(TimeInterval(entryTimeZone.secondsFromGMT()))\n\n        let difference = Calendar.current.dateComponents([.hour, .minute], from: userDate, to: entryDate)\n\n        let hours = difference.hour ?? 0\n        let minutes = difference.minute ?? 0\n\n        if hours == 0 && minutes == 0 {\n            return \"same time\"\n        }\n\n        let totalHours = Double(hours) + Double(minutes) / 60.0\n\n        return String(format: \"%+.1f hrs\", totalHours)\n    }\n\n    private func updateTimer(for phase: ScenePhase) {\n        timer?.upstream.connect().cancel()\n\n        switch phase {\n        case .active:\n            timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()\n        case .inactive, .background:\n            timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()\n        @unknown default:\n            timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()\n        }\n    }\n}\n"
  },
  {
    "path": "There/Views/MainView.swift",
    "content": "import AppKit\nimport GRDB\nimport PostHog\nimport SwiftUI\n\nstruct MainView: View {\n    @StateObject private var fetcher = Fetcher()\n    @State private var sortOrder: SortOrder = .timeAscending\n    @State private var sortedEntries: [Entry] = []\n    @Environment(\\.database) var database: AppDatabase\n    @EnvironmentObject var appState: AppState\n    @EnvironmentObject var router: Router\n    @State private var currentDate = Date()\n    @State private var isAtBottom: Bool = false\n    @State private var showSlider: Bool = false\n    @State var timeOffset: Double = 0\n\n    var body: some View {\n        VStack(alignment: sortedEntries.isEmpty ? .center : .leading, spacing: 2) {\n            if sortedEntries.isEmpty {\n                Spacer()\n                EmptyTimezoneView()\n                Spacer()\n            } else {\n                ScrollView(.vertical) {\n                    LazyVStack(spacing: 0) {\n                        ForEach(sortedEntries) { entry in\n                            EntryRow(entry: entry, timeOffset: $timeOffset)\n                                .contextMenu {\n                                    Button(\"Edit\") {\n                                        router.setActiveRoute(to: .editTimeZone(entryId: entry.id))\n                                    }\n                                    Button(\"Delete\", role: .destructive) {\n                                        deleteEntry(entry)\n                                    }\n                                }\n\n                                .id(entry.id)\n                        }\n                        .animation(.easeInOut(duration: 0.1), value: sortedEntries.count)\n                        Color.clear\n                            .frame(height: 2)\n                            .onAppear {\n                                isAtBottom = false\n                            }\n                            .onDisappear {\n                                isAtBottom = true\n                            }\n                    }\n                    .padding(.horizontal, 6)\n                }\n                .scrollIndicators(.hidden)\n            }\n            if showSlider {\n                EntryTimeSlider(timeOffset: $timeOffset)\n                    .onDisappear {\n                        withAnimation {\n                            timeOffset = 0.0\n                        }\n                    }\n            }\n            BottomBarView(isAtBottom: $isAtBottom, sortOrder: $sortOrder, showSlider: $showSlider)\n        }\n        .frame(maxHeight: .infinity)\n        .padding(.top, 6)\n        .onAppear {\n            sortEntries()\n        }\n        .onChange(of: sortOrder) { _ in\n            sortEntries()\n        }\n        .onChange(of: fetcher.entries) { _ in\n            sortEntries()\n        }\n        .task {\n            let id = PostHogSDK.shared.getAnonymousId()\n            PostHogSDK.shared.identify(id, userProperties: [\"email\": UserDefaults.standard.string(forKey: \"userEmail\") ?? \"\"])\n        }\n\n    }\n\n    private func sortEntries() {\n        switch sortOrder {\n        case .timeAscending:\n            sortedEntries = fetcher.entries.sorted { $0.timeDifference.hours < $1.timeDifference.hours }\n        case .timeDescending:\n            sortedEntries = fetcher.entries.sorted { $0.timeDifference.hours > $1.timeDifference.hours }\n        }\n    }\n\n    private func deleteEntry(_ entry: Entry) {\n        Task {\n            do {\n                try await database.dbWriter.write { db in\n                    let fetchedEntry = try Entry.fetchOne(db, id: entry.id)\n                    try fetchedEntry?.delete(db)\n                }\n            } catch {\n                print(\"Can't delete entry \\(error)\")\n            }\n        }\n    }\n}\n\n#Preview {\n    MainView()\n        .frame(width: 400, height: 400)\n}\n\nstruct EntryTimeSlider: View {\n    @Binding var timeOffset: Double\n    @State private var previousValue: Double = 0\n    @State var currentHour: Double = Date().hour\n\n    var offset: String {\n        if timeOffset == 1 || timeOffset == -1 {\n            return \"\\(timeOffset > 0 ? \"+\" : \"\") \\(timeOffset) hr\"\n        } else {\n            return \"\\(timeOffset > 0 ? \"+\" : \"\")\\(timeOffset) hrs\"\n        }\n    }\n\n    var body: some View {\n        VStack(spacing: 8) {\n            HStack {\n                Text(\"\\(offset)\")\n                    .monospaced()\n                    .font(.callout)\n                    .foregroundColor(.gray)\n                    .contentTransition(.numericText())\n                Spacer()\n                Text(formattedTime())\n                    .monospaced()\n                    .font(.body)\n                    .fontWeight(.semibold)\n                    .contentTransition(.numericText())\n            }\n            Slider(value: $currentHour, in: 0 ... 23.5, step: 0.5)\n                .onChange(of: currentHour) { newValue in\n                    withAnimation {\n                        timeOffset = newValue - Date().hour\n                        if Int(newValue) != Int(previousValue) {\n                            performHapticFeedback()\n                        }\n                        previousValue = newValue\n                    }\n                }\n        }\n        .padding(.vertical, 8)\n        .padding(.horizontal, 16)\n        .background(Color(NSColor.windowBackgroundColor))\n    }\n\n    private func formattedTime() -> String {\n        let calendar = Calendar.current\n        let now = Date()\n\n        let formatter = DateFormatter()\n\n        // Get the system's locale\n        let locale = Locale.current\n\n        // Create a template that includes both 24-hour and 12-hour formats\n        let template = \"j:mm\"\n\n        // Generate the best format for the current locale\n        if let formatString = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: locale) {\n            formatter.dateFormat = formatString\n        } else {\n            // Fallback to a default format if generation fails\n            formatter.timeStyle = .short\n        }\n\n        formatter.locale = locale\n        let offsetDate = Date().addingTimeInterval(timeOffset * 3600)\n\n        return formatter.string(from: offsetDate)\n    }\n\n    private func performHapticFeedback() {\n        NSHapticFeedbackManager.defaultPerformer.perform(.levelChange, performanceTime: .default)\n    }\n}\n\nextension Date {\n    var hour: Double {\n        Double(Calendar.current.component(.hour, from: self)) + (Calendar.current.component(.minute, from: self) >= 30 ? 0.5 : 0.0)\n    }\n}\n"
  },
  {
    "path": "There/Views/Onboarding/InitialView.swift",
    "content": "// InitialView.swift\n\nimport PostHog\nimport SwiftUI\n\nstruct InitialView: View {\n    @Environment(\\.database) var database\n    @State private var email: String = \"\"\n    @EnvironmentObject var appState: AppState\n\n    var body: some View {\n        HStack(spacing: 80) {\n            LeftPanel()\n            RightPanel(email: $email, saveEmail: saveEmail)\n        }\n        .padding()\n    }\n\n    func signupForThere(email: String, completion: @escaping (Bool) -> Void) {\n        var hostname: String {\n            #if DEBUG\n                return \"http://localhost:8000\"\n            #else\n                return \"https://headline.inline.chat\"\n            #endif\n        }\n        let url = URL(string: \"\\(hostname)/api/there/signup\")!\n        var request = URLRequest(url: url)\n        request.httpMethod = \"POST\"\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n\n        let timeZone = TimeZone.current.identifier\n        let body: [String: Any] = [\"email\": email, \"timeZone\": timeZone]\n\n        request.httpBody = try? JSONSerialization.data(withJSONObject: body)\n\n        URLSession.shared.dataTask(with: request) { _, response, _ in\n            let success = (response as? HTTPURLResponse)?.statusCode == 200\n            DispatchQueue.main.async {\n                completion(success)\n            }\n        }.resume()\n    }\n\n    func saveEmail() {\n        signupForThere(email: email) { success in\n            if success {\n                PostHogSDK.shared.capture(\"user_signed_up\",\n                                          userProperties: [\"email\": email],\n                                          userPropertiesSetOnce: [\"date_of_sign_up\": Date.now])\n                print(\"Signup successful\")\n                UserDefaults.standard.set(email, forKey: \"userEmail\")\n                UserDefaults.standard.set(true, forKey: \"hasCompletedInitialSetup\")\n\n            } else {\n                print(\"Signup failed\")\n                UserDefaults.standard.set(true, forKey: \"hasCompletedInitialSetup\")\n            }\n        }\n    }\n}\n\n#Preview {\n    InitialView()\n        .frame(width: 600, height: 400)\n}\n"
  },
  {
    "path": "There/Views/Onboarding/LeftPanel.swift",
    "content": "// LeftPanel.swift\n\nimport SwiftUI\n\nstruct LeftPanel: View {\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            Image(\"Logo\")\n            Text(\"Hey There!\")\n                .font(.largeTitle)\n                .fontWeight(.bold)\n            DateTimeInfo()\n        }\n    }\n}\n\nstruct DateTimeInfo: View {\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HStack {\n                DateTimeLabel(date: Date.now, format: .dateTime.timeZone() , color: .green)\n                DateTimeLabel(date: Date.now, format: .dateTime.hour().minute(), color: .cyan)\n            }\n            HStack {\n                DateTimeLabel(text: TimeZone.current.identifier, color: .yellow)\n                DateTimeLabel(date: Date.now, format: .dateTime.weekday(), color: .pink)\n            }\n        }\n    }\n}\n\nstruct DateTimeLabel: View {\n    var date: Date?\n    var text: String?\n    var format: Date.FormatStyle?\n    var color: Color\n\n    init(date: Date? = nil, text: String? = nil, format: Date.FormatStyle? = nil, color: Color = .green) {\n        self.date = date\n        self.text = text\n        self.format = format\n        self.color = color\n    }\n\n    var body: some View {\n        Group {\n            if let date = date, let format = format {\n                Text(date, format: format)\n            } else if let text = text {\n                Text(text)\n            } else {\n                Text(\"Invalid input\")\n            }\n        }\n        .monospaced()\n        .padding(4)\n        .background(color.opacity(0.2))\n        .cornerRadius(8)\n    }\n}\n"
  },
  {
    "path": "There/Views/Onboarding/RightPanel.swift",
    "content": "// RightPanel.swift\n\nimport PostHog\nimport SwiftUI\n\nstruct RightPanel: View {\n    @Binding var email: String\n    let saveEmail: () -> Void\n    @EnvironmentObject var appState: AppState\n    @Environment(\\.presentationMode) var presentationMode\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            Text(\"Please enter your email\")\n                .font(.callout)\n                .fontWeight(.medium)\n                .padding(.bottom, 2)\n\n            Input(text: $email, placeholder: \"dena@example.com\")\n\n            PrimaryButton(title: \"Continue\", action: {\n                saveEmail()\n                appState.presentMenu()\n                presentationMode.wrappedValue.dismiss()\n\n            })\n\n            SecondaryButton(title: \"Skip\", action: {\n                PostHogSDK.shared.capture(\"sign_up_skipped\", properties: [\"date\": Date.now])\n                UserDefaults.standard.setValue(true, forKey: \"hasCompletedInitialSetup\")\n                appState.presentMenu()\n                presentationMode.wrappedValue.dismiss()\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "There/Views/Timezone/AddTimezone + Components.swift",
    "content": "import AppKit\nimport MapKit\nimport SwiftUI\n\n// MARK: - IconView\n\nstruct IconView: View {\n    @Binding var image: NSImage?\n    @Binding var countryEmoji: String\n\n    @State private var showPopover = false\n    @State private var isDropTargeted = false\n    @State private var showingXAccountInput = false\n    @State private var showingTGAccountInput = false\n    @State private var username = \"\"\n    @State private var debounceTask: Task<Void, Never>?\n\n    var body: some View {\n        iconContent\n            .frame(width: 70, height: 70)\n            .onTapGesture { showPopover = true }\n            .popover(isPresented: $showPopover) { popoverContent }\n    }\n\n    private var iconContent: some View {\n        Group {\n            if let image = image {\n                Image(nsImage: image)\n                    .resizable()\n                    .scaledToFill()\n                    .clipShape(Circle())\n            } else if !countryEmoji.isEmpty {\n                flagView\n            } else {\n                placeholderView\n            }\n        }\n    }\n\n    private var flagView: some View {\n        Circle()\n            .fill(.white)\n            .overlay(Text(countryEmoji).font(.largeTitle))\n    }\n\n    private var placeholderView: some View {\n        Circle()\n            .fill(.gray.opacity(0.1))\n            .overlay(\n                Image(systemName: \"photo.badge.plus\")\n                    .font(.title)\n                    .foregroundColor(.gray.opacity(0.8))\n            )\n    }\n\n    private var popoverContent: some View {\n        VStack {\n            importButtons\n\n            if showingXAccountInput {\n                SocialMediaInput(platform: \"X\", username: $username, image: $image, debounceTask: $debounceTask)\n                    .onSubmit {\n                        showPopover = false\n                    }\n            } else if showingTGAccountInput {\n                SocialMediaInput(platform: \"Telegram\", username: $username, image: $image, debounceTask: $debounceTask)\n                    .onSubmit {\n                        showPopover = false\n                    }\n            }\n        }\n        .padding()\n    }\n\n    private var importButtons: some View {\n        HStack(alignment: .center, spacing: 0) {\n            Text(\"Set from\").foregroundColor(.secondary)\n            Button(\"Telegram\") {\n                showingXAccountInput = false\n                showingTGAccountInput = true\n            }\n            .buttonStyle(.link)\n            .padding(.horizontal, 2)\n            Text(\"/\").foregroundColor(.secondary).padding(.horizontal, 2)\n            Button(\"X\") {\n                showingTGAccountInput = false\n                showingXAccountInput = true\n            }\n            .buttonStyle(.link)\n            .padding(.horizontal, 2)\n            Text(\"/\").foregroundColor(.secondary).padding(.horizontal, 2)\n            Button(\"Finder\") {\n                let selectedImage = Utils.shared.selectPhoto()\n                DispatchQueue.main.async {\n                    self.image = selectedImage\n                }\n\n            }.buttonStyle(.link).padding(.horizontal, 2)\n        }\n    }\n\n    private func handleDrop(providers: [NSItemProvider]) -> Bool {\n        guard let provider = providers.first else { return false }\n\n        provider.loadObject(ofClass: NSImage.self) { object, _ in\n            if let image = object as? NSImage {\n                DispatchQueue.main.async {\n                    self.image = image\n                }\n            }\n        }\n        return true\n    }\n}\n\nstruct CitySearchResultRow: View, Equatable {\n    let result: TimeZoneSearchResult\n\n    var body: some View {\n        HStack {\n            VStack(alignment: .leading) {\n                Text(result.title)\n\n                Text(result.subtitle ?? \"\")\n                    .font(.caption)\n                    .foregroundColor(.secondary)\n            }\n            Spacer()\n        }\n        .frame(maxWidth: .infinity)\n        .contentShape(Rectangle())\n    }\n\n    static func == (lhs: CitySearchResultRow, rhs: CitySearchResultRow) -> Bool {\n        lhs.result == rhs.result\n    }\n}\n\n// MARK: - CitySearchResults\n\nstruct CitySearchResults: View {\n    @ObservedObject var searchCompleter: SearchCompleter\n    @Binding var isShowingPopover: Bool\n    @Binding var selectedCity: String\n    @Binding var selectedTimezone: TimeZone?\n    @Binding var countryEmoji: String\n    @FocusState private var isFocused: Bool\n    @State private var selectedIndex: Int = -1\n\n    var body: some View {\n        VStack(spacing: 0) {\n            CustomTextField(text: $searchCompleter.queryFragment, placeholder: \"Search for a city or timezone\", onKeyDown: handleKeyEvent)\n                .textFieldStyle(.roundedBorder)\n                .padding(.horizontal, 6)\n                .frame(width: 280, height: 32)\n                .background(AdaptiveColors.textFieldBackground)\n                .cornerRadius(8)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 8)\n                        .stroke(isFocused ? .blue : AdaptiveColors.textFieldBorder.opacity(0.5), lineWidth: 1)\n                )\n                .focused($isFocused)\n                .foregroundColor(AdaptiveColors.textColor)\n                .padding(.vertical)\n\n            ScrollViewReader { proxy in\n                List(searchCompleter.results.indices, id: \\.self) { index in\n                    let result = searchCompleter.results[index]\n\n                    Button(action: {\n                        self.selectCity(result)\n                    }) {\n                        CitySearchResultRow(result: result)\n                    }\n                    .buttonStyle(.plain)\n                    .listRowBackground(selectedIndex == index ? Color.accentColor.opacity(0.1) : Color.clear)\n                    .id(index)\n                }\n                .listStyle(PlainListStyle())\n                .onChange(of: selectedIndex) { newValue in\n                    if newValue >= 0 {\n                        // This handles all scrolling scenarios, including scrolling to the bottom\n                        // when the last item is selected:\n                        // 1. It triggers whenever selectedIndex changes.\n                        // 2. It scrolls to the newly selected item if it's in the list (index >= 0).\n                        // 3. The .center anchor attempts to center the item in the visible area.\n                        // 4. For the last item, this effectively scrolls to the bottom of the list.\n                        // 5. It also ensures that the selected item is always visible, even if it's\n                        //    not the last item.\n                        proxy.scrollTo(newValue, anchor: .center)\n                    }\n                }\n            }\n        }\n        .frame(width: 300, height: 400)\n    }\n\n    private func handleKeyEvent(_ event: NSEvent) -> Bool {\n        switch event.keyCode {\n        case 126: // Up arrow\n            moveSelection(direction: .up)\n            return true\n        case 125: // Down arrow\n            moveSelection(direction: .down)\n            return true\n        case 36: // Return key\n            if selectedIndex >= 0 && selectedIndex < searchCompleter.results.count {\n                selectCity(searchCompleter.results[selectedIndex])\n            }\n            return true\n        default:\n            return false\n        }\n    }\n\n    private func moveSelection(direction: KeyboardNavigationDirection) {\n        let itemCount = searchCompleter.results.count\n        switch direction {\n        case .up:\n            if selectedIndex > 0 {\n                // If not at the top of the list, move up one item\n                selectedIndex -= 1\n            } else if selectedIndex == 0 {\n                // If at the top of the list, move focus to the search field\n                selectedIndex = -1\n            } else if selectedIndex == -1 {\n                // If focus is on the search field, move to the bottom of the list\n                selectedIndex = itemCount - 1\n            }\n        case .down:\n            if selectedIndex == -1 {\n                // If focus is on the search field and there are items, select the first item\n                if itemCount > 0 {\n                    selectedIndex = 0\n                }\n            } else if selectedIndex < itemCount - 1 {\n                // If not at the bottom of the list, move down one item\n                selectedIndex += 1\n            } else if selectedIndex == itemCount - 1 {\n                // If at the bottom of the list, move focus back to the search field\n                selectedIndex = -1\n            }\n        case .enter:\n            // Enter key handling is done elsewhere\n            break\n        }\n    }\n\n    private func selectCity(_ result: TimeZoneSearchResult) {\n        switch result.type {\n        case .city:\n            selectedCity = \"\\(result.title), \\(result.subtitle)\"\n\n            Task {\n                if let timezone = await result.getTimeZone() {\n                    await MainActor.run {\n                        selectedTimezone = timezone\n                        let geocoder = CLGeocoder()\n                        geocoder.geocodeAddressString(result.title) { [self] placemarks, _ in\n                            if let placemark = placemarks?.first, let timezone = selectedTimezone {\n                                countryEmoji = Utils.shared.getCountryEmoji(for: placemark.isoCountryCode ?? \"\")\n                            }\n                        }\n                    }\n\n                  \n                } else {\n                    await MainActor.run {\n                        fallbackToGeocoding(for: selectedCity)\n                    }\n                }\n            }\n\n        case .abbreviation:\n            selectedCity = result.title\n            selectedTimezone = result.identifier.flatMap { TimeZone(identifier: $0) }\n            countryEmoji = \"\"\n\n        case .utcOffset:\n            selectedCity = result.title\n            selectedTimezone = result.identifier.flatMap { TimeZone(identifier: $0) }\n            countryEmoji = \"\"\n        }\n\n        isShowingPopover = false\n    }\n\n    private func fallbackToGeocoding(for address: String) {\n        let geocoder = CLGeocoder()\n        geocoder.geocodeAddressString(address) { [self] placemarks, _ in\n            if let placemark = placemarks?.first, let timezone = placemark.timeZone {\n                selectedTimezone = timezone\n                countryEmoji = Utils.shared.getCountryEmoji(for: placemark.isoCountryCode ?? \"\")\n            }\n        }\n    }\n\n//    private func selectCity(_ result: TimeZoneSearchResult) {\n//\n//        selectedCity = \"\\(result.title), \\(result.subtitle)\"\n//        selectedTimezone = result.getTimeZone()\n//\n//        if result.type == .city {\n//            let geocoder = CLGeocoder()\n//            geocoder.geocodeAddressString(selectedCity) { placemarks, _ in\n//                if let placemark = placemarks?.first {\n//                    countryEmoji = Utils.shared.getCountryEmoji(for: placemark.isoCountryCode ?? \"\")\n//                }\n//            }\n//        } else {\n//            countryEmoji = \"\"\n//        }\n//\n//        isShowingPopover = false\n//    }\n}\n\nstruct CustomTextField: NSViewRepresentable {\n    @Binding var text: String\n    var placeholder: String\n    var onKeyDown: (NSEvent) -> Bool\n\n    func makeNSView(context: Context) -> NSTextField {\n        let textField = NSTextField()\n        textField.placeholderString = placeholder\n        textField.delegate = context.coordinator\n        textField.focusRingType = .none\n        textField.drawsBackground = true\n        textField.backgroundColor = .white\n        textField.isBordered = false\n        textField.textColor = .black\n        return textField\n    }\n\n    func updateNSView(_ nsView: NSTextField, context: Context) {\n        nsView.stringValue = text\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(self)\n    }\n\n    class Coordinator: NSObject, NSTextFieldDelegate {\n        var parent: CustomTextField\n\n        init(_ parent: CustomTextField) {\n            self.parent = parent\n        }\n\n        func controlTextDidChange(_ obj: Notification) {\n            if let textField = obj.object as? NSTextField {\n                parent.text = textField.stringValue\n            }\n        }\n\n        func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {\n            if commandSelector == #selector(NSResponder.moveUp(_:)) {\n                return parent.onKeyDown(NSEvent.keyEvent(with: .keyDown, location: .zero, modifierFlags: [], timestamp: 0, windowNumber: 0, context: nil, characters: \"\", charactersIgnoringModifiers: \"\", isARepeat: false, keyCode: 126)!)\n            } else if commandSelector == #selector(NSResponder.moveDown(_:)) {\n                return parent.onKeyDown(NSEvent.keyEvent(with: .keyDown, location: .zero, modifierFlags: [], timestamp: 0, windowNumber: 0, context: nil, characters: \"\", charactersIgnoringModifiers: \"\", isARepeat: false, keyCode: 125)!)\n            } else if commandSelector == #selector(NSResponder.insertNewline(_:)) {\n                return parent.onKeyDown(NSEvent.keyEvent(with: .keyDown, location: .zero, modifierFlags: [], timestamp: 0, windowNumber: 0, context: nil, characters: \"\\r\", charactersIgnoringModifiers: \"\\r\", isARepeat: false, keyCode: 36)!)\n            }\n            return false\n        }\n    }\n}\n\nenum KeyboardNavigationDirection {\n    case up, down, enter\n}\n"
  },
  {
    "path": "There/Views/Timezone/AddTimezone + Functions.swift",
    "content": "import AppKit\nimport CoreLocation\nimport Foundation\nimport MapKit\nimport SwiftUI\n\nextension AddTimezone {\n    func searchPlace(_ completion: MKLocalSearchCompletion) {\n        let searchRequest = MKLocalSearch.Request(completion: completion)\n        let search = MKLocalSearch(request: searchRequest)\n        search.start { response, _ in\n            guard let coordinate = response?.mapItems.first?.placemark.coordinate else { return }\n\n            let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)\n            CLGeocoder().reverseGeocodeLocation(location) { placemarks, _ in\n                DispatchQueue.main.async {\n                    if let placemark = placemarks?.first {\n                        if let timeZone = placemark.timeZone {\n                            self.selectedTimeZone = timeZone\n                        }\n                        self.countryEmoji = Utils.shared.getCountryEmoji(for: placemark.isoCountryCode ?? \"\")\n                    }\n                }\n            }\n        }\n    }\n\n    func saveEntry() {\n        let fileName = UUID().uuidString + \".png\"\n        let fileURL = getApplicationSupportDirectory().appendingPathComponent(fileName)\n\n        if let tiffData = image?.tiffRepresentation,\n           let bitmapImage = NSBitmapImageRep(data: tiffData),\n           let pngData = bitmapImage.representation(using: .png, properties: [:]) {\n            do {\n                try pngData.write(to: fileURL)\n            } catch {\n                print(\"Failed to save image: \\(error)\")\n            }\n        }\n\n        do {\n            try database.dbWriter.write { db in\n                let entry = Entry(\n                    id: Int64.random(in: 1 ... 99999),\n                    type: !countryEmoji.isEmpty && image == nil ? .place : .person,\n                    name: name,\n                    city: city,\n                    timezoneIdentifier: selectedTimeZone?.identifier ?? \"\",\n                    flag: image == nil ? countryEmoji : \"\",\n                    photoData: image != nil ? fileURL.absoluteString : nil\n                )\n\n                try entry.save(db)\n            }\n        } catch {\n            print(\"Failed to save entry \\(error)\")\n        }\n\n        router.cleanActiveRoute()\n        resetForm()\n    }\n\n    private func resetForm() {\n        image = nil\n        name = \"\"\n        city = \"\"\n        showingXAccountInput = false\n        showingTGAccountInput = false\n        selectedTimeZone = TimeZone.current\n        isShowingPopover = false\n        countryEmoji = \"\"\n        selectedTimeZone = nil\n    }\n\n    private func getApplicationSupportDirectory() -> URL {\n        FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]\n    }\n}\n"
  },
  {
    "path": "There/Views/Timezone/AddTimezone.swift",
    "content": "import CoreLocation\nimport MapKit\nimport SwiftUI\n\nstruct AddTimezone: View {\n    @Environment(\\.database) var database\n    @StateObject private var searchCompleter = SearchCompleter()\n\n    @EnvironmentObject var router: Router\n\n    @State var image: NSImage?\n    @State var name = \"\"\n    @State var city = \"\"\n    @State var selectedTimeZone: TimeZone? = nil\n    @State var isShowingPopover = false\n    @State var countryEmoji = \"\"\n\n    @State var showingXAccountInput = false\n    @State var showingTGAccountInput = false\n\n    var body: some View {\n        VStack(alignment: .center, spacing: 0) {\n            IconSection(\n                image: $image,\n                countryEmoji: $countryEmoji,\n                showingXAccountInput: $showingXAccountInput,\n                showingTGAccountInput: $showingTGAccountInput\n            )\n\n            FormSection(\n                name: $name,\n                city: $city,\n                selectedTimeZone: $selectedTimeZone,\n                isShowingPopover: $isShowingPopover,\n                searchCompleter: searchCompleter,\n                countryEmoji: $countryEmoji,\n                image: $image,\n                showingTGAccountInput: $showingTGAccountInput,\n                showingXAccountInput: $showingXAccountInput,\n                saveEntry: saveEntry\n            )\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n\n        .overlay(alignment: .topLeading) {\n            Titlebar()\n                .padding(6)\n        }\n    }\n}\n\n#Preview {\n    AddTimezone()\n        .frame(width: 300, height: 400)\n}\n"
  },
  {
    "path": "There/Views/Timezone/EditTimeZone + Functions.swift",
    "content": "import AppKit\nimport CoreLocation\nimport Foundation\nimport MapKit\nimport SwiftUI\n\nextension EditTimeZoneView {\n    func saveEntry() {\n        let fileName = UUID().uuidString + \".png\"\n        let fileURL = getApplicationSupportDirectory().appendingPathComponent(fileName)\n\n        if let tiffData = image?.tiffRepresentation,\n           let bitmapImage = NSBitmapImageRep(data: tiffData),\n           let pngData = bitmapImage.representation(using: .png, properties: [:]) {\n            do {\n                try pngData.write(to: fileURL)\n            } catch {\n                print(\"Failed to save image: \\(error)\")\n            }\n        }\n\n        do {\n            try database.dbWriter.write { db in\n                if let entry = entry {\n                    let entry = Entry(\n                        id: entry.id,\n                        type: !countryEmoji.isEmpty && image == nil ? .place : .person,\n                        name: name,\n                        city: city,\n                        timezoneIdentifier: selectedTimeZone?.identifier ?? \"\",\n                        flag: image == nil ? countryEmoji : \"\",\n                        photoData: fileURL.absoluteString\n                    )\n                    try entry.save(db)\n                }\n            }\n        } catch {\n            print(\"Failed to save entry \\(error)\")\n        }\n\n        router.cleanActiveRoute()\n    }\n\n    private func getApplicationSupportDirectory() -> URL {\n        FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]\n    }\n}\n"
  },
  {
    "path": "There/Views/Timezone/EditTimeZoneView.swift",
    "content": "import CoreLocation\nimport MapKit\nimport SwiftUI\n\nstruct EditTimeZoneView: View {\n    var entryId: Int64?\n    @Environment(\\.database) var database\n    @StateObject private var searchCompleter = SearchCompleter()\n    @EnvironmentObject var router: Router\n\n    @State var entry: Entry?\n    @State var image: NSImage?\n    @State var name = \"\"\n    @State var city = \"\"\n    @State var selectedTimeZone: TimeZone?\n    @State var isShowingPopover = false\n    @State var countryEmoji = \"\"\n\n    @State var showingXAccountInput = false\n    @State var showingTGAccountInput = false\n\n    @State var isLoading = true\n    @State var errorMessage: String?\n\n    var body: some View {\n        VStack(alignment: .center, spacing: 0) {\n            if isLoading {\n                ProgressView()\n            } else if let entry = entry {\n                IconSection(\n                    image: $image,\n                    countryEmoji: $countryEmoji,\n                    showingXAccountInput: $showingXAccountInput,\n                    showingTGAccountInput: $showingTGAccountInput\n                )\n\n                FormSection(\n                    name: $name,\n                    city: $city,\n                    selectedTimeZone: $selectedTimeZone,\n                    isShowingPopover: $isShowingPopover,\n                    searchCompleter: searchCompleter,\n                    countryEmoji: $countryEmoji,\n                    image: $image,\n                    showingTGAccountInput: $showingTGAccountInput,\n                    showingXAccountInput: $showingXAccountInput,\n                    isEditing: true,\n                    saveEntry: saveEntry\n                )\n            } else {\n                NotFoundView()\n            }\n\n            if let errorMessage = errorMessage {\n                Text(errorMessage)\n                    .foregroundColor(.red)\n            }\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .overlay(alignment: .topLeading) {\n            Titlebar()\n                .padding(6)\n        }\n        .task {\n            if entry == nil {\n                await loadEntry()\n            }\n        }\n    }\n\n    private func loadEntry() async {\n        isLoading = true\n        do {\n            try await database.reader.read { db in\n                if let id = entryId {\n                    let fetchedEntry = try Entry.fetchOne(db, id: id)\n                    self.entry = fetchedEntry\n                    if let entry = fetchedEntry {\n                        self.name = entry.name\n                        self.city = entry.city\n                        self.selectedTimeZone = TimeZone(identifier: entry.timezoneIdentifier)\n                        self.countryEmoji = entry.flag ?? \"\"\n                        if entry.photoData != nil, let imageURL = URL(string: entry.photoData!) {\n                            if let imageData = try? Data(contentsOf: imageURL) {\n                                self.image = NSImage(data: imageData)\n                            } else {\n                                print(\"Failed to load image data from URL: \\(imageURL)\")\n                            }\n                        } else {\n                            self.image = nil\n                        }\n                    }\n                }\n            }\n        } catch {\n            errorMessage = \"Failed to load entry: \\(error.localizedDescription)\"\n        }\n        isLoading = false\n    }\n}\n\n#Preview {\n    EditTimeZoneView(entryId: 1712)\n        .frame(width: 300, height: 400)\n        .environment(\\.database, .shared)\n}\n"
  },
  {
    "path": "There/Views/Timezone/FormSection.swift",
    "content": "import PostHog\nimport SwiftUI\nimport UserNotifications\n\nstruct FormSection: View {\n    @Binding var name: String\n    @Binding var city: String\n    @Binding var selectedTimeZone: TimeZone?\n    @Binding var isShowingPopover: Bool\n    @StateObject var searchCompleter: SearchCompleter\n    @Binding var countryEmoji: String\n    @Binding var image: NSImage?\n    @Binding var showingTGAccountInput: Bool\n    @Binding var showingXAccountInput: Bool\n    @State var showError: Bool = false\n    @State private var username = \"\"\n    @State private var debounceTask: Task<Void, Never>?\n    var isEditing: Bool = false\n    let saveEntry: () -> Void\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 2) {\n            StyledLabel(title: \"Name\")\n            Input(text: $name, placeholder: \"eg. Dena or London Office\")\n                .padding(.bottom, 6)\n                .onSubmit {\n                    if !city.isEmpty {\n                        PostHogSDK.shared.capture(\"timezone_added\")\n                        saveEntry()\n                    } else {\n                        withAnimation(.easeIn(duration: 0.1)) {\n                            showError = true\n                        }\n                    }\n                }\n            if !city.isEmpty {\n                SecondaryButton(title: city) {\n                    withAnimation(.easeOut(duration: 0.1)) {\n                        showError = false\n                    }\n                    isShowingPopover = true\n                }\n                .popover(isPresented: $isShowingPopover) {\n                    CitySearchResults(\n                        searchCompleter: searchCompleter,\n                        isShowingPopover: $isShowingPopover,\n                        selectedCity: $city,\n                        selectedTimezone: $selectedTimeZone,\n                        countryEmoji: $countryEmoji\n                    )\n                }\n            } else {\n                SecondaryButton(title: \"Set location / timezone\") {\n                    withAnimation(.easeOut(duration: 0.1)) {\n                        showError = false\n                    }\n                    isShowingPopover = true\n                }\n                .popover(isPresented: $isShowingPopover) {\n                    CitySearchResults(\n                        searchCompleter: searchCompleter,\n                        isShowingPopover: $isShowingPopover,\n                        selectedCity: $city,\n                        selectedTimezone: $selectedTimeZone,\n                        countryEmoji: $countryEmoji\n                    )\n                }\n            }\n\n            if showError {\n                Text(\"please select a Location\")\n                    .font(.caption)\n                    .foregroundColor(.red)\n                    .fontWeight(.medium)\n                    .transition(.opacity)\n            }\n\n            PrimaryButton(title: isEditing ? \"Update\" : \"Add\", action: {\n                if !city.isEmpty {\n                    saveEntry()\n                    PostHogSDK.shared.capture(\"timezone_added\")\n                } else {\n                    withAnimation(.easeIn(duration: 0.1)) {\n                        showError = true\n                    }\n                }\n\n            })\n            .disabled(city.isEmpty)\n            .opacity(city.isEmpty ? 0.6 : 1)\n            .padding(.top, 8)\n        }\n    }\n}\n"
  },
  {
    "path": "There/Views/Timezone/IconSection.swift",
    "content": "import SwiftUI\n\nstruct IconSection: View {\n    @Binding var image: NSImage?\n    @Binding var countryEmoji: String\n    @Binding var showingXAccountInput: Bool\n    @Binding var showingTGAccountInput: Bool\n\n    @State private var xHovered = false\n    @State private var tgHovered = false\n\n    var body: some View {\n        VStack {\n            IconView(\n                image: $image,\n                countryEmoji: $countryEmoji\n            )\n            .padding(.bottom, 6)\n        }\n    }\n}\n\nstruct SocialMediaButton: View {\n    let imageName: String\n    @Binding var isHovered: Bool\n    let action: () -> Void\n\n    var body: some View {\n        Button(action: action) {\n            Image(imageName)\n                .resizable()\n                .frame(width: 18, height: 18)\n                .clipShape(Circle())\n        }\n        .buttonStyle(PlainButtonStyle())\n        .scaleEffect(isHovered ? 1.1 : 1)\n        .shadow(color: isHovered ? .black.opacity(0.2) : .clear, radius: 4, x: 0, y: 4)\n        .onHover { hovering in\n            withAnimation {\n                isHovered = hovering\n            }\n        }\n    }\n}\n\nstruct SocialMediaInput: View {\n    let platform: String\n    @Binding var username: String\n    @Binding var image: NSImage?\n    @Binding var debounceTask: Task<Void, Never>?\n\n    var body: some View {\n        VStack(alignment: .leading) {\n            StyledLabel(title: platform == \"X\" ? \"Enter an \\(platform) username\" : \"Enter a \\(platform) username\")\n                .padding(.top, 8)\n            Input(text: $username, placeholder: \"eg. dena_sohrabi\")\n                .onChange(of: username) { value in\n                    debounceTask?.cancel()\n\n                    if !value.isEmpty {\n                        debounceTask = Task {\n                            try? await Task.sleep(for: .milliseconds(800))\n\n                            if !Task.isCancelled {\n                                do {\n                                    let imageUrl = \"https://unavatar.io/\\(platform.lowercased())/\\(value.lowercased())\"\n                                    let fetchedImage = try await simpleImageFetch(from: imageUrl)\n                                    await MainActor.run {\n                                        self.image = NSImage(data: fetchedImage)\n                                    }\n                                } catch {\n                                    print(\"Got error \\(error)\")\n                                }\n                            }\n                        }\n                    } else {\n                        image = nil\n                    }\n                }\n        }\n    }\n\n    func simpleImageFetch(from urlString: String) async throws -> Data {\n        guard let url = URL(string: urlString) else {\n            throw URLError(.badURL)\n        }\n\n        let (data, response) = try await URLSession.shared.data(from: url)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw URLError(.badServerResponse)\n        }\n\n        image = NSImage(data: data)\n        return data\n    }\n}\n"
  },
  {
    "path": "There/Views/Timezone/NotFoundView.swift",
    "content": "import SwiftUI\n\nstruct NotFoundView: View {\n    var body: some View {\n        VStack(spacing: 2) {\n            Text(\"😕\")\n                .font(.largeTitle)\n            Text(\"Entry not found\")\n                .font(.title)\n                .fontWeight(.medium)\n        }\n    }\n}\n\n#Preview {\n    NotFoundView()\n        .frame(width: 320, height: 320)\n}\n"
  },
  {
    "path": "There/pm.there.There.LaunchAgent.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>Label</key>\n    <string>pm.there.There.LaunchAgent</string>\n    <key>ProgramArguments</key>\n    <array>\n        <string>/Applications/There.app/Contents/MacOS/There</string>\n    </array>\n    <key>RunAtLoad</key>\n    <true/>\n    <key>KeepAlive</key>\n    <true/>\n    <key>LimitLoadToSessionType</key>\n    <string>Aqua</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "ThereTests/ThereTests.swift",
    "content": "//\n//  ThereTests.swift\n//  ThereTests\n//\n//  Created by Dena Sohrabi on 9/2/24.\n//\n\nimport Testing\n\nstruct ThereTests {\n\n    @Test func example() async throws {\n        // Write your test here and use APIs like `#expect(...)` to check expected conditions.\n    }\n\n}\n\n"
  },
  {
    "path": "ThereUITests/ThereUITests.swift",
    "content": "//  ThereUITests.swift\n//  ThereUITests\n//\n//  Created by Dena Sohrabi on 9/2/24.\n//\n\nimport XCTest\n\nfinal class ThereUITests: XCTestCase {\n    let app = XCUIApplication()\n\n    override func setUpWithError() throws {\n        continueAfterFailure = false\n        app.launch()\n    }\n\n    override func tearDownWithError() throws {\n        // Terminate the app after each test\n        app.terminate()\n    }\n\n    func printAccessibleElements() {\n        let allElements = app.descendants(matching: .any)\n        for element in allElements.allElementsBoundByIndex {\n            print(\"Element: \\(element.debugDescription)\")\n        }\n    }\n\n    @MainActor\n    func testUIElementsExistence() throws {\n        // Print all accessible elements\n        print(\"Printing all accessible elements:\")\n        printAccessibleElements()\n\n        // Check for specific element types\n        print(\"\\nSearching for specific element types:\")\n        let searchFields = app.searchFields.allElementsBoundByIndex\n        print(\"Search Fields: \\(searchFields.map { $0.debugDescription })\")\n\n        let textFields = app.textFields.allElementsBoundByIndex\n        print(\"Text Fields: \\(textFields.map { $0.debugDescription })\")\n\n        let buttons = app.buttons.allElementsBoundByIndex\n        print(\"Buttons: \\(buttons.map { $0.debugDescription })\")\n\n        let tables = app.tables.allElementsBoundByIndex\n        print(\"Tables: \\(tables.map { $0.debugDescription })\")\n\n        // Try to find any input field\n        let possibleInputFields = app.descendants(matching: .any).matching(NSPredicate(format: \"type == 'XCUIElementTypeTextField' OR type == 'XCUIElementTypeSearchField'\"))\n        print(\"\\nPossible Input Fields:\")\n        for field in possibleInputFields.allElementsBoundByIndex {\n            print(field.debugDescription)\n        }\n\n        // Assert that we found at least one possible input field\n        XCTAssertTrue(possibleInputFields.count > 0, \"No input fields found in the app\")\n    }\n\n    @MainActor\n    func testLaunchPerformance() throws {\n        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {\n            measure(metrics: [XCTApplicationLaunchMetric()]) {\n                XCUIApplication().launch()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ThereUITests/ThereUITestsLaunchTests.swift",
    "content": "//\n//  ThereUITestsLaunchTests.swift\n//  ThereUITests\n//\n//  Created by Dena Sohrabi on 9/2/24.\n//\n\nimport XCTest\n\nfinal class ThereUITestsLaunchTests: XCTestCase {\n\n    override class var runsForEachTargetApplicationUIConfiguration: Bool {\n        true\n    }\n\n    override func setUpWithError() throws {\n        continueAfterFailure = false\n    }\n\n    @MainActor\n    func testLaunch() throws {\n        let app = XCUIApplication()\n        app.launch()\n\n        // Insert steps here to perform after app launch but before taking a screenshot,\n        // such as logging into a test account or navigating somewhere in the app\n\n        let attachment = XCTAttachment(screenshot: app.screenshot())\n        attachment.name = \"Launch Screen\"\n        attachment.lifetime = .keepAlways\n        add(attachment)\n    }\n}\n"
  },
  {
    "path": "readme.md",
    "content": "# [There](https://there.pm)\n\n[![Swift](https://img.shields.io/badge/Swift-F54A2A?logo=swift&logoColor=white)](#) [![macOS](https://img.shields.io/badge/macOS-000000?logo=apple&logoColor=F0F0F0)](#)\n\nA native menubar app to track friends, teammates or city time zones on macOS.\n\n```\nbrew install --cask there\n```\n\n![Screen-shot of the app](https://there.pm/app@2x.jpg)\n\n- Add photos for people from X (Twitter), Telegram or pick locally\n- Add any city without knowing the time zone\n- Supports raw UTC offsets\n- 0-1% idle CPU usage\n- Ultra-low memory\n- Written in Swift UI\n- macOS 13+\n\n## Roadmap\n\nWe want to keep the app as simple as possible. But here are the things we're considering for future versions:\n\n- [ ] Widgets\n- [ ] Time slider to view future/past times\n- [ ] Auto-update\n- [ ] Apple Script API for third-party integrations i.e. Raycast\n\n## Contributions\n\nContributions are welcome! For simple fixes or improvements feel free to open a PR with the smallest change.\n\nFor features or improvements that add scope to the app or have user facing changes, please open an issue first to discuss your proposal.\n\nIf you want to tackle any of the items in the roadmap section, please also open an issue.\n\n## License\n\nThis project is licensed under the [MIT License](LICENSE)\n"
  }
]