Showing preview only (325K chars total). Download the full file or copy to clipboard to get everything.
Repository: inderdhir/DatWeatherDoe
Branch: main
Commit: 983df24d0f69
Files: 91
Total size: 296.0 KB
Directory structure:
gitextract_t2ij2trf/
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── lint.yml
├── .gitignore
├── .swiftformat
├── .swiftlint.yml
├── CONTRIBUTING.md
├── DatWeatherDoe/
│ ├── API/
│ │ ├── NetworkClient.swift
│ │ ├── Response/
│ │ │ ├── AirQuality.swift
│ │ │ ├── ForecastData.swift
│ │ │ ├── ForecastTemperatureData.swift
│ │ │ ├── SunriseSunsetData.swift
│ │ │ ├── TemperatureData.swift
│ │ │ ├── WeatherAPIResponse.swift
│ │ │ ├── WeatherAPIResponseParser.swift
│ │ │ └── WindData.swift
│ │ ├── WeatherData.swift
│ │ └── WeatherError.swift
│ ├── Config/
│ │ ├── APIKeyParser.swift
│ │ ├── ConfigManager.swift
│ │ └── ConfigOptions.swift
│ ├── DatWeatherDoeApp.swift
│ ├── Localization/
│ │ └── en.xcloc/
│ │ ├── Localized Contents/
│ │ │ └── en.xliff
│ │ ├── Source Contents/
│ │ │ └── DatWeatherDoe/
│ │ │ ├── Resources/
│ │ │ │ └── en.lproj/
│ │ │ │ └── InfoPlist.strings
│ │ │ └── UI/
│ │ │ └── Base.lproj/
│ │ │ └── MainMenu.xib
│ │ └── contents.json
│ ├── Reachability/
│ │ └── WeatherReachability.swift
│ ├── Resources/
│ │ ├── Assets.xcassets/
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── DatWeatherDoe.entitlements
│ │ ├── DevelopmentAssets/
│ │ │ └── TestData.swift
│ │ ├── Info.plist
│ │ └── Localization/
│ │ └── Localizable.xcstrings
│ ├── UI/
│ │ ├── Configure/
│ │ │ ├── ConfigureOptionsView.swift
│ │ │ ├── ConfigureUnitOptionsView.swift
│ │ │ ├── ConfigureValueSeparatorOptionsView.swift
│ │ │ ├── ConfigureView.swift
│ │ │ ├── ConfigureViewModel.swift
│ │ │ ├── ConfigureWeatherOptionsView.swift
│ │ │ └── Options/
│ │ │ ├── MeasurementUnit.swift
│ │ │ ├── RefreshInterval.swift
│ │ │ ├── TemperatureUnit.swift
│ │ │ ├── WeatherConditionPosition.swift
│ │ │ └── WeatherSource.swift
│ │ ├── Decorator/
│ │ │ ├── Condition/
│ │ │ │ ├── WeatherCondition.swift
│ │ │ │ ├── WeatherConditionBuilder.swift
│ │ │ │ └── WeatherConditionTextMapper.swift
│ │ │ ├── Text/
│ │ │ │ ├── HumidityTextBuilder.swift
│ │ │ │ ├── SunriseAndSunsetTextBuilder.swift
│ │ │ │ ├── Temperature/
│ │ │ │ │ ├── TemperatureForecastTextBuilder.swift
│ │ │ │ │ ├── TemperatureFormatter.swift
│ │ │ │ │ ├── TemperatureTextBuilder.swift
│ │ │ │ │ └── TemperatureWithDegreesCreator.swift
│ │ │ │ ├── UVIndexTextBuilder.swift
│ │ │ │ └── WeatherTextBuilder.swift
│ │ │ └── WeatherDataBuilder.swift
│ │ ├── Forecaster/
│ │ │ └── WeatherForecaster.swift
│ │ ├── Menu Bar/
│ │ │ ├── CustomButton.swift
│ │ │ ├── DropdownIcon.swift
│ │ │ ├── MenuOptionsView.swift
│ │ │ ├── MenuView.swift
│ │ │ ├── NonInteractiveMenuOptionView.swift
│ │ │ └── WindSpeedFormatter.swift
│ │ └── Status Bar/
│ │ └── StatusBarView.swift
│ └── ViewModel/
│ ├── Parser/
│ │ ├── CityWeatherResultParser.swift
│ │ └── ZipCodeWeatherResultParser.swift
│ ├── Repository/
│ │ ├── Coordinates/
│ │ │ ├── LocationCoordinatesWeatherRepository.swift
│ │ │ ├── LocationParser.swift
│ │ │ └── LocationValidator.swift
│ │ ├── System/
│ │ │ ├── SystemLocationFetcher.swift
│ │ │ ├── SystemLocationWeatherRepository.swift
│ │ │ └── Task+Retry.swift
│ │ ├── WeatherRepositoryFactory.swift
│ │ ├── WeatherRepositoryType.swift
│ │ ├── WeatherURLBuilder.swift
│ │ └── WeatherValidatorType.swift
│ ├── WeatherDataFormatter.swift
│ ├── WeatherViewModel.swift
│ └── WeatherViewModelType.swift
├── DatWeatherDoe.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm/
│ │ └── Package.resolved
│ └── xcshareddata/
│ └── xcschemes/
│ └── DatWeatherDoe.xcscheme
├── DatWeatherDoeTests/
│ ├── API/
│ │ └── Repository/
│ │ ├── Location/
│ │ │ └── Coordinates/
│ │ │ └── LocationValidatorTests.swift
│ │ └── WeatherURLBuilderTests.swift
│ ├── DatWeatherDoe.xctestplan
│ └── UI/
│ └── Configure/
│ └── Options/
│ ├── RefreshIntervalTests.swift
│ ├── TemperatureUnitTests.swift
│ └── WeatherSourceTests.swift
├── LICENSE
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: inderdhir
================================================
FILE: .github/workflows/lint.yml
================================================
name: SwiftLint
on:
pull_request:
paths:
- '.github/workflows/swiftlint.yml'
- '.swiftlint.yml'
- '**/*.swift'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: GitHub Action for SwiftLint (Different working directory)
uses: norio-nomura/action-swiftlint@3.2.1
env:
WORKING_DIRECTORY: Source
================================================
FILE: .gitignore
================================================
# Xcode
.DS_Store
# Backup files
*~
# JetBrains
.idea/
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
The above adds CocoaPods,
Pods/
Podfile.lock
# Keys
DatWeatherDoe/Resources/Keys.plist
# Secrets
Config.xcconfig
================================================
FILE: .swiftformat
================================================
--disable trailingCommas
================================================
FILE: .swiftlint.yml
================================================
disabled_rules: # rule identifiers to exclude from running
- colon
- comma
- control_statement
- trailing_whitespace
- vertical_parameter_alignment
- opening_brace
opt_in_rules: # some rules are only opt-in
- empty_count
# Find all the available rules by running:
# swiftlint rules
included: # paths to include during linting. `--path` is ignored if present.
- DatWeatherDoe
excluded: # paths to ignore during linting. Takes precedence over `included`.
- Pods
# configurable rules can be customized from this configuration file
# configurable rules can be customized from this configuration file
# binary rules can set their severity level
cyclomatic_complexity:
ignores_case_statements: true
force_cast: warning # implicitly
force_try:
severity: warning # explicitly
# rules that have both warning and error levels, can set just the warning level
# implicitly
line_length: 110
# they can set both implicitly with an array
type_body_length:
- 300 # warning
- 400 # error
# or they can set both explicitly
file_length:
warning: 500
error: 1200
# naming rules can set warnings/errors for min_length and max_length
# additionally they can set excluded names
type_name:
min_length: 4 # only warning
max_length: # warning and error
warning: 40
error: 50
excluded: iPhone # excluded via string
identifier_name:
min_length: # only min_length
error: 2 # only error
excluded: # excluded via string array
- id
- URL
- GlobalAPIKey
function_body_length:
warning: 50
error: 100
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji)
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Contributions / pull requests are welcome!
Please note that the goal of this project to provide a lightweight menu bar app for weather at a glance on macOS (similar to weather app indicators on Ubuntu).
**MacOS 11 provides a weather widget that can be used in conjunction with this app so a macOS widget is NOT on the roadmap.**
================================================
FILE: DatWeatherDoe/API/NetworkClient.swift
================================================
//
// NetworkClient.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/14/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Foundation
protocol NetworkClientType {
func performRequest(url: URL) async throws -> Data
}
final class NetworkClient: NetworkClientType {
func performRequest(url: URL) async throws -> Data {
do {
return try await URLSession.shared.data(from: url).0
} catch {
throw WeatherError.networkError
}
}
}
================================================
FILE: DatWeatherDoe/API/Response/AirQuality.swift
================================================
//
// AirQuality.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 7/1/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import Foundation
// US - EPA standard
enum AirQualityIndex: Int, Decodable {
case good = 1
case moderate = 2
case unhealthyForSensitive = 3
case unhealthy = 4
case veryUnhealthy = 5
case hazardous = 6
var description: String {
switch self {
case .good:
String(localized: "Good")
case .moderate:
String(localized: "Moderate")
case .unhealthyForSensitive:
String(localized: "Unhealthy for sensitive groups")
case .unhealthy:
String(localized: "Unhealthy")
case .veryUnhealthy:
String(localized: "Very unhealthy")
case .hazardous:
String(localized: "Hazardous")
}
}
}
================================================
FILE: DatWeatherDoe/API/Response/ForecastData.swift
================================================
//
// ForecastData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/23/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import Foundation
struct Forecast: Decodable {
let dayDataArr: [ForecastDayData]
private enum CodingKeys: String, CodingKey {
case dayDataArr = "forecastday"
}
}
struct ForecastDayData: Decodable {
let temperatureData: ForecastTemperatureData
let astro: SunriseSunsetData
let hour: [HourlyUVIndex]
private enum CodingKeys: String, CodingKey {
case temperatureData = "day"
case astro
case hour
}
}
struct HourlyUVIndex: Decodable {
// swiftlint:disable:next identifier_name
let uv: Double
}
================================================
FILE: DatWeatherDoe/API/Response/ForecastTemperatureData.swift
================================================
//
// ForecastTemperatureData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/23/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import Foundation
struct ForecastTemperatureData: Decodable {
let maxTempC: Double
let maxTempF: Double
let minTempC: Double
let minTempF: Double
private enum CodingKeys: String, CodingKey {
case maxTempC = "maxtemp_c"
case maxTempF = "maxtemp_f"
case minTempC = "mintemp_c"
case minTempF = "mintemp_f"
}
}
================================================
FILE: DatWeatherDoe/API/Response/SunriseSunsetData.swift
================================================
//
// SunriseSunsetData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/22/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import Foundation
struct SunriseSunsetData: Decodable {
let sunrise: String
let sunset: String
}
================================================
FILE: DatWeatherDoe/API/Response/TemperatureData.swift
================================================
//
// TemperatureData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/23/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import Foundation
struct TemperatureData: Decodable {
let tempCelsius: Double
let feelsLikeTempCelsius: Double
let tempFahrenheit: Double
let feelsLikeTempFahrenheit: Double
private enum CodingKeys: String, CodingKey {
case tempCelsius = "temp_c"
case feelsLikeTempCelsius = "feelslike_c"
case tempFahrenheit = "temp_f"
case feelsLikeTempFahrenheit = "feelslike_f"
}
}
================================================
FILE: DatWeatherDoe/API/Response/WeatherAPIResponse.swift
================================================
//
// WeatherAPIResponse.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 2/3/18.
// Copyright © 2018 Inder Dhir. All rights reserved.
//
import Foundation
struct WeatherAPIResponse: Decodable {
let locationName: String
let temperatureData: TemperatureData
let isDay: Bool
let weatherConditionCode: Int
let humidity: Int
let windData: WindData
let uvIndex: Double
let forecastDayData: ForecastDayData
let airQualityIndex: AirQualityIndex
private enum RootKeys: String, CodingKey {
case location, current, forecast
}
private enum LocationKeys: String, CodingKey {
case name
}
private enum CurrentKeys: String, CodingKey {
case isDay = "is_day"
case condition, humidity
case airQuality = "air_quality"
case uvIndex = "uv"
}
private enum WeatherConditionKeys: String, CodingKey {
case code
}
private enum ForecastKeys: String, CodingKey {
case forecastDay = "forecastday"
}
private enum ForecastDayKeys: String, CodingKey {
case day, astro
}
private enum AirQualityKeys: String, CodingKey {
case usEpaIndex = "us-epa-index"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKeys.self)
let locationContainer = try container.nestedContainer(keyedBy: LocationKeys.self, forKey: .location)
locationName = try locationContainer.decode(String.self, forKey: .name)
temperatureData = try container.decode(TemperatureData.self, forKey: .current)
let currentContainer = try container.nestedContainer(keyedBy: CurrentKeys.self, forKey: .current)
let isDayInt = try currentContainer.decode(Int.self, forKey: .isDay)
isDay = isDayInt > 0
let weatherConditionContainer = try currentContainer.nestedContainer(
keyedBy: WeatherConditionKeys.self,
forKey: .condition
)
weatherConditionCode = try weatherConditionContainer.decode(Int.self, forKey: .code)
humidity = try currentContainer.decode(Int.self, forKey: .humidity)
windData = try container.decode(WindData.self, forKey: .current)
uvIndex = try currentContainer.decode(Double.self, forKey: .uvIndex)
let forecast = try container.decode(Forecast.self, forKey: .forecast)
if let dayData = forecast.dayDataArr.first {
forecastDayData = dayData
} else {
throw DecodingError.dataCorruptedError(
forKey: .forecast,
in: container,
debugDescription: "Missing forecast day data"
)
}
let airQualityContainer =
try currentContainer.nestedContainer(keyedBy: AirQualityKeys.self, forKey: .airQuality)
airQualityIndex = try airQualityContainer.decode(AirQualityIndex.self, forKey: .usEpaIndex)
}
init(
locationName: String,
temperatureData: TemperatureData,
isDay: Bool,
weatherConditionCode: Int,
humidity: Int,
windData: WindData,
uvIndex: Double,
forecastDayData: ForecastDayData,
airQualityIndex: AirQualityIndex
) {
self.locationName = locationName
self.temperatureData = temperatureData
self.isDay = isDay
self.weatherConditionCode = weatherConditionCode
self.humidity = humidity
self.windData = windData
self.uvIndex = uvIndex
self.forecastDayData = forecastDayData
self.airQualityIndex = airQualityIndex
}
// hour = [0-23]
func getHourlyUVIndex(hour: Int) -> Double {
forecastDayData.hour[safe: hour]?.uv ?? uvIndex
}
}
private extension Array {
subscript(safe index: Index) -> Element? { indices ~= index ? self[index] : nil }
}
================================================
FILE: DatWeatherDoe/API/Response/WeatherAPIResponseParser.swift
================================================
//
// WeatherAPIResponseParser.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/10/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Foundation
protocol WeatherAPIResponseParserType {
func parse(_ data: Data) throws -> WeatherAPIResponse
}
final class WeatherAPIResponseParser: WeatherAPIResponseParserType {
func parse(_ data: Data) throws -> WeatherAPIResponse {
try JSONDecoder().decode(WeatherAPIResponse.self, from: data)
}
}
================================================
FILE: DatWeatherDoe/API/Response/WindData.swift
================================================
//
// WindData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/23/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import Foundation
struct WindData: Decodable {
let speedMph: Double
let degrees: Int
let direction: String
private enum CodingKeys: String, CodingKey {
case speedMph = "wind_mph"
case degrees = "wind_degree"
case direction = "wind_dir"
}
}
================================================
FILE: DatWeatherDoe/API/WeatherData.swift
================================================
//
// WeatherData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 5/22/21.
// Copyright © 2021 Inder Dhir. All rights reserved.
//
import Foundation
struct WeatherData {
let showWeatherIcon: Bool
let textualRepresentation: String?
let weatherCondition: WeatherCondition
let response: WeatherAPIResponse
}
================================================
FILE: DatWeatherDoe/API/WeatherError.swift
================================================
//
// WeatherError.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 5/22/21.
// Copyright © 2021 Inder Dhir. All rights reserved.
//
import Foundation
enum WeatherError: LocalizedError {
case unableToConstructUrl
case locationError
case latLongIncorrect
case networkError
var errorDescription: String? {
switch self {
case .unableToConstructUrl:
"Unable to construct URL"
case .locationError:
String(localized: "❗️Location")
case .latLongIncorrect:
String(localized: "❗️Lat/Long")
case .networkError:
"🖧"
}
}
}
================================================
FILE: DatWeatherDoe/Config/APIKeyParser.swift
================================================
//
// APIKeyParser.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/11/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Foundation
final class APIKeyParser {
func parse() -> String {
guard let apiKey = Bundle.main.infoDictionary?["WEATHER_API_KEY"] as? String else {
fatalError("Unable to find OPENWEATHERMAP_APP_ID in `Config.xcconfig`")
}
return apiKey
}
}
================================================
FILE: DatWeatherDoe/Config/ConfigManager.swift
================================================
//
// ConfigManager.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/29/16.
// Copyright © 2016 Inder Dhir. All rights reserved.
//
import Foundation
import SwiftUI
protocol ConfigManagerType: AnyObject {
var measurementUnit: String { get set }
var weatherSource: String { get set }
var weatherSourceText: String { get set }
var refreshInterval: TimeInterval { get set }
var isShowingWeatherIcon: Bool { get set }
var isShowingHumidity: Bool { get set }
var isShowingUVIndex: Bool { get set }
var isRoundingOffData: Bool { get set }
var isUnitLetterOff: Bool { get set }
var isUnitSymbolOff: Bool { get set }
var valueSeparator: String { get set }
var isWeatherConditionAsTextEnabled: Bool { get set }
var weatherConditionPosition: String { get set }
func updateWeatherSource(_ source: WeatherSource, sourceText: String)
func setConfigOptions(_ options: ConfigOptions)
var parsedMeasurementUnit: MeasurementUnit { get }
}
final class ConfigManager: ConfigManagerType {
@AppStorage("measurementUnit")
public var measurementUnit = MeasurementUnit.imperial.rawValue
@AppStorage("weatherSource")
public var weatherSource = WeatherSource.location.rawValue
@AppStorage("weatherSourceText")
public var weatherSourceText = ""
@AppStorage("refreshInterval")
public var refreshInterval = RefreshInterval.fifteenMinutes.rawValue
@AppStorage("isShowingWeatherIcon")
public var isShowingWeatherIcon = true
@AppStorage("isShowingHumidity")
public var isShowingHumidity = false
@AppStorage("isShowingUVIndex")
public var isShowingUVIndex = false
@AppStorage("isRoundingOffData")
public var isRoundingOffData = false
@AppStorage("isUnitLetterOff")
public var isUnitLetterOff = false
@AppStorage("isUnitSymbolOff")
public var isUnitSymbolOff = false
@AppStorage("valueSeparator")
public var valueSeparator = "\u{007C}"
@AppStorage("isWeatherConditionAsTextEnabled")
public var isWeatherConditionAsTextEnabled = false
@AppStorage("weatherConditionPosition")
public var weatherConditionPosition = WeatherConditionPosition.beforeTemperature.rawValue
func updateWeatherSource(_ source: WeatherSource, sourceText: String) {
weatherSource = source.rawValue
weatherSourceText = source == .location ? "" : sourceText
}
func setConfigOptions(_ options: ConfigOptions) {
refreshInterval = options.refreshInterval.rawValue
isShowingHumidity = options.isShowingHumidity
isShowingUVIndex = options.isShowingUVIndex
isRoundingOffData = options.isRoundingOffData
isUnitLetterOff = options.isUnitLetterOff
isUnitSymbolOff = options.isUnitSymbolOff
valueSeparator = options.valueSeparator
isWeatherConditionAsTextEnabled = options.isWeatherConditionAsTextEnabled
}
var parsedMeasurementUnit: MeasurementUnit {
MeasurementUnit(rawValue: measurementUnit) ?? .imperial
}
}
================================================
FILE: DatWeatherDoe/Config/ConfigOptions.swift
================================================
//
// ConfigOptions.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 8/3/23.
// Copyright © 2023 Inder Dhir. All rights reserved.
//
import Foundation
struct ConfigOptions {
let refreshInterval: RefreshInterval
let isShowingHumidity: Bool
let isShowingUVIndex: Bool
let isRoundingOffData: Bool
let isUnitLetterOff: Bool
let isUnitSymbolOff: Bool
let valueSeparator: String
let isWeatherConditionAsTextEnabled: Bool
}
================================================
FILE: DatWeatherDoe/DatWeatherDoeApp.swift
================================================
//
// DatWeatherDoeApp.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/17/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import MenuBarExtraAccess
import OSLog
import SwiftUI
@main
struct DatWeatherDoeApp: App {
@State private var configManager: ConfigManager
@ObservedObject private var configureViewModel = ConfigureViewModel(configManager: ConfigManager())
@ObservedObject private var viewModel: WeatherViewModel
@State private var isMenuPresented = false
@State private var statusItem: NSStatusItem?
init() {
configManager = ConfigManager()
let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "bundleID", category: "main")
viewModel = WeatherViewModel(
locationFetcher: SystemLocationFetcher(logger: logger),
weatherFactory: WeatherRepositoryFactory(
appId: APIKeyParser().parse(),
networkClient: NetworkClient(),
logger: logger
),
configManager: ConfigManager(),
logger: logger
)
viewModel.setup(with: WeatherDataFormatter(configManager: configManager))
}
var body: some Scene {
MenuBarExtra(
content: {
MenuView(
viewModel: viewModel,
configureViewModel: configureViewModel,
onSeeWeather: {
viewModel.seeForecastForCurrentCity()
closePopover()
},
onRefresh: {
viewModel.getUpdatedWeatherAfterRefresh()
closePopover()
},
onSave: {
closePopover()
}
)
},
label: {
StatusBarView(weatherResult: viewModel.weatherResult)
.onAppear {
viewModel.getUpdatedWeatherAfterRefresh()
}
}
)
.menuBarExtraAccess(isPresented: $isMenuPresented) { statusItem in
self.statusItem = statusItem
}
.onChange(of: isMenuPresented) { newValue in
if !newValue {
configureViewModel.saveConfig()
viewModel.getUpdatedWeatherAfterRefresh()
}
}
.windowStyle(.hiddenTitleBar)
.menuBarExtraStyle(.window)
}
private func closePopover() {
statusItem?.togglePresented()
}
}
================================================
FILE: DatWeatherDoe/Localization/en.xcloc/Localized Contents/en.xliff
================================================
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="DatWeatherDoe/Resources/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="12.5" build-num="12E262"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>DatWeatherDoe</source>
<target>DatWeatherDoe</target>
<note>Bundle name</note>
</trans-unit>
<trans-unit id="NSHumanReadableCopyright" xml:space="preserve">
<source>Copyright © 2016 Inder Dhir. All rights reserved.</source>
<target>Copyright © 2016 Inder Dhir. All rights reserved.</target>
<note>Copyright (human-readable)</note>
</trans-unit>
<trans-unit id="NSLocationWhenInUseUsageDescription" xml:space="preserve">
<source>DatWeatherDoe optionally uses your current location to get the weather</source>
<target>DatWeatherDoe optionally uses your current location to get the weather</target>
<note>Privacy - Location When In Use Usage Description</note>
</trans-unit>
</body>
</file>
<file original="DatWeatherDoe/UI/Base.lproj/MainMenu.xib" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="12.5" build-num="12E262"/>
</header>
<body>
<trans-unit id="1UK-8n-QPP.title" xml:space="preserve">
<source>Customize Toolbar…</source>
<target>Customize Toolbar…</target>
<note>Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP";</note>
</trans-unit>
<trans-unit id="1Xt-HY-uBw.title" xml:space="preserve">
<source>DatWeatherDoe</source>
<target>DatWeatherDoe</target>
<note>Class = "NSMenuItem"; title = "DatWeatherDoe"; ObjectID = "1Xt-HY-uBw";</note>
</trans-unit>
<trans-unit id="1b7-l0-nxx.title" xml:space="preserve">
<source>Find</source>
<target>Find</target>
<note>Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx";</note>
</trans-unit>
<trans-unit id="1tx-W0-xDw.title" xml:space="preserve">
<source>Lower</source>
<target>Lower</target>
<note>Class = "NSMenuItem"; title = "Lower"; ObjectID = "1tx-W0-xDw";</note>
</trans-unit>
<trans-unit id="2h7-ER-AoG.title" xml:space="preserve">
<source>Raise</source>
<target>Raise</target>
<note>Class = "NSMenuItem"; title = "Raise"; ObjectID = "2h7-ER-AoG";</note>
</trans-unit>
<trans-unit id="2oI-Rn-ZJC.title" xml:space="preserve">
<source>Transformations</source>
<target>Transformations</target>
<note>Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC";</note>
</trans-unit>
<trans-unit id="3IN-sU-3Bg.title" xml:space="preserve">
<source>Spelling</source>
<target>Spelling</target>
<note>Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg";</note>
</trans-unit>
<trans-unit id="3Om-Ey-2VK.title" xml:space="preserve">
<source>Use Default</source>
<target>Use Default</target>
<note>Class = "NSMenuItem"; title = "Use Default"; ObjectID = "3Om-Ey-2VK";</note>
</trans-unit>
<trans-unit id="3rS-ZA-NoH.title" xml:space="preserve">
<source>Speech</source>
<target>Speech</target>
<note>Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH";</note>
</trans-unit>
<trans-unit id="4EN-yA-p0u.title" xml:space="preserve">
<source>Find</source>
<target>Find</target>
<note>Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u";</note>
</trans-unit>
<trans-unit id="4sb-4s-VLi.title" xml:space="preserve">
<source>Quit DatWeatherDoe</source>
<target>Quit DatWeatherDoe</target>
<note>Class = "NSMenuItem"; title = "Quit DatWeatherDoe"; ObjectID = "4sb-4s-VLi";</note>
</trans-unit>
<trans-unit id="5QF-Oa-p0T.title" xml:space="preserve">
<source>Edit</source>
<target>Edit</target>
<note>Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T";</note>
</trans-unit>
<trans-unit id="5Vv-lz-BsD.title" xml:space="preserve">
<source>Copy Style</source>
<target>Copy Style</target>
<note>Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "5Vv-lz-BsD";</note>
</trans-unit>
<trans-unit id="5kV-Vb-QxS.title" xml:space="preserve">
<source>About DatWeatherDoe</source>
<target>About DatWeatherDoe</target>
<note>Class = "NSMenuItem"; title = "About DatWeatherDoe"; ObjectID = "5kV-Vb-QxS";</note>
</trans-unit>
<trans-unit id="6dh-zS-Vam.title" xml:space="preserve">
<source>Redo</source>
<target>Redo</target>
<note>Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam";</note>
</trans-unit>
<trans-unit id="8mr-sm-Yjd.title" xml:space="preserve">
<source>Writing Direction</source>
<target>Writing Direction</target>
<note>Class = "NSMenu"; title = "Writing Direction"; ObjectID = "8mr-sm-Yjd";</note>
</trans-unit>
<trans-unit id="9ic-FL-obx.title" xml:space="preserve">
<source>Substitutions</source>
<target>Substitutions</target>
<note>Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx";</note>
</trans-unit>
<trans-unit id="9yt-4B-nSM.title" xml:space="preserve">
<source>Smart Copy/Paste</source>
<target>Smart Copy/Paste</target>
<note>Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM";</note>
</trans-unit>
<trans-unit id="46P-cB-AYj.title" xml:space="preserve">
<source>Tighten</source>
<target>Tighten</target>
<note>Class = "NSMenuItem"; title = "Tighten"; ObjectID = "46P-cB-AYj";</note>
</trans-unit>
<trans-unit id="78Y-hA-62v.title" xml:space="preserve">
<source>Correct Spelling Automatically</source>
<target>Correct Spelling Automatically</target>
<note>Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v";</note>
</trans-unit>
<trans-unit id="AYu-sK-qS6.title" xml:space="preserve">
<source>Main Menu</source>
<target>Main Menu</target>
<note>Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6";</note>
</trans-unit>
<trans-unit id="BOF-NM-1cW.title" xml:space="preserve">
<source>Preferences…</source>
<target>Preferences…</target>
<note>Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW";</note>
</trans-unit>
<trans-unit id="BgM-ve-c93.title" xml:space="preserve">
<source> Left to Right</source>
<target> Left to Right</target>
<note>Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "BgM-ve-c93";</note>
</trans-unit>
<trans-unit id="Bw7-FT-i3A.title" xml:space="preserve">
<source>Save As…</source>
<target>Save As…</target>
<note>Class = "NSMenuItem"; title = "Save As…"; ObjectID = "Bw7-FT-i3A";</note>
</trans-unit>
<trans-unit id="DVo-aG-piG.title" xml:space="preserve">
<source>Close</source>
<target>Close</target>
<note>Class = "NSMenuItem"; title = "Close"; ObjectID = "DVo-aG-piG";</note>
</trans-unit>
<trans-unit id="Dv1-io-Yv7.title" xml:space="preserve">
<source>Spelling and Grammar</source>
<target>Spelling and Grammar</target>
<note>Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7";</note>
</trans-unit>
<trans-unit id="F2S-fz-NVQ.title" xml:space="preserve">
<source>Help</source>
<target>Help</target>
<note>Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ";</note>
</trans-unit>
<trans-unit id="FKE-Sm-Kum.title" xml:space="preserve">
<source>DatWeatherDoe Help</source>
<target>DatWeatherDoe Help</target>
<note>Class = "NSMenuItem"; title = "DatWeatherDoe Help"; ObjectID = "FKE-Sm-Kum";</note>
</trans-unit>
<trans-unit id="Fal-I4-PZk.title" xml:space="preserve">
<source>Text</source>
<target>Text</target>
<note>Class = "NSMenuItem"; title = "Text"; ObjectID = "Fal-I4-PZk";</note>
</trans-unit>
<trans-unit id="FeM-D8-WVr.title" xml:space="preserve">
<source>Substitutions</source>
<target>Substitutions</target>
<note>Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr";</note>
</trans-unit>
<trans-unit id="GB9-OM-e27.title" xml:space="preserve">
<source>Bold</source>
<target>Bold</target>
<note>Class = "NSMenuItem"; title = "Bold"; ObjectID = "GB9-OM-e27";</note>
</trans-unit>
<trans-unit id="GEO-Iw-cKr.title" xml:space="preserve">
<source>Format</source>
<target>Format</target>
<note>Class = "NSMenu"; title = "Format"; ObjectID = "GEO-Iw-cKr";</note>
</trans-unit>
<trans-unit id="GUa-eO-cwY.title" xml:space="preserve">
<source>Use Default</source>
<target>Use Default</target>
<note>Class = "NSMenuItem"; title = "Use Default"; ObjectID = "GUa-eO-cwY";</note>
</trans-unit>
<trans-unit id="Gi5-1S-RQB.title" xml:space="preserve">
<source>Font</source>
<target>Font</target>
<note>Class = "NSMenuItem"; title = "Font"; ObjectID = "Gi5-1S-RQB";</note>
</trans-unit>
<trans-unit id="H1b-Si-o9J.title" xml:space="preserve">
<source>Writing Direction</source>
<target>Writing Direction</target>
<note>Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "H1b-Si-o9J";</note>
</trans-unit>
<trans-unit id="H8h-7b-M4v.title" xml:space="preserve">
<source>View</source>
<target>View</target>
<note>Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v";</note>
</trans-unit>
<trans-unit id="HFQ-gK-NFA.title" xml:space="preserve">
<source>Text Replacement</source>
<target>Text Replacement</target>
<note>Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA";</note>
</trans-unit>
<trans-unit id="HFo-cy-zxI.title" xml:space="preserve">
<source>Show Spelling and Grammar</source>
<target>Show Spelling and Grammar</target>
<note>Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI";</note>
</trans-unit>
<trans-unit id="HyV-fh-RgO.title" xml:space="preserve">
<source>View</source>
<target>View</target>
<note>Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO";</note>
</trans-unit>
<trans-unit id="I0S-gh-46l.title" xml:space="preserve">
<source>Subscript</source>
<target>Subscript</target>
<note>Class = "NSMenuItem"; title = "Subscript"; ObjectID = "I0S-gh-46l";</note>
</trans-unit>
<trans-unit id="IAo-SY-fd9.title" xml:space="preserve">
<source>Open…</source>
<target>Open…</target>
<note>Class = "NSMenuItem"; title = "Open…"; ObjectID = "IAo-SY-fd9";</note>
</trans-unit>
<trans-unit id="J5U-5w-g23.title" xml:space="preserve">
<source>Justify</source>
<target>Justify</target>
<note>Class = "NSMenuItem"; title = "Justify"; ObjectID = "J5U-5w-g23";</note>
</trans-unit>
<trans-unit id="J7y-lM-qPV.title" xml:space="preserve">
<source>Use None</source>
<target>Use None</target>
<note>Class = "NSMenuItem"; title = "Use None"; ObjectID = "J7y-lM-qPV";</note>
</trans-unit>
<trans-unit id="KaW-ft-85H.title" xml:space="preserve">
<source>Revert to Saved</source>
<target>Revert to Saved</target>
<note>Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "KaW-ft-85H";</note>
</trans-unit>
<trans-unit id="Kd2-mp-pUS.title" xml:space="preserve">
<source>Show All</source>
<target>Show All</target>
<note>Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS";</note>
</trans-unit>
<trans-unit id="LE2-aR-0XJ.title" xml:space="preserve">
<source>Bring All to Front</source>
<target>Bring All to Front</target>
<note>Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ";</note>
</trans-unit>
<trans-unit id="LVM-kO-fVI.title" xml:space="preserve">
<source>Paste Ruler</source>
<target>Paste Ruler</target>
<note>Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "LVM-kO-fVI";</note>
</trans-unit>
<trans-unit id="Lbh-J2-qVU.title" xml:space="preserve">
<source> Left to Right</source>
<target> Left to Right</target>
<note>Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "Lbh-J2-qVU";</note>
</trans-unit>
<trans-unit id="MkV-Pr-PK5.title" xml:space="preserve">
<source>Copy Ruler</source>
<target>Copy Ruler</target>
<note>Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "MkV-Pr-PK5";</note>
</trans-unit>
<trans-unit id="NMo-om-nkz.title" xml:space="preserve">
<source>Services</source>
<target>Services</target>
<note>Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz";</note>
</trans-unit>
<trans-unit id="Nop-cj-93Q.title" xml:space="preserve">
<source> Default</source>
<target> Default</target>
<note>Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "Nop-cj-93Q";</note>
</trans-unit>
<trans-unit id="OY7-WF-poV.title" xml:space="preserve">
<source>Minimize</source>
<target>Minimize</target>
<note>Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV";</note>
</trans-unit>
<trans-unit id="OaQ-X3-Vso.title" xml:space="preserve">
<source>Baseline</source>
<target>Baseline</target>
<note>Class = "NSMenuItem"; title = "Baseline"; ObjectID = "OaQ-X3-Vso";</note>
</trans-unit>
<trans-unit id="Olw-nP-bQN.title" xml:space="preserve">
<source>Hide DatWeatherDoe</source>
<target>Hide DatWeatherDoe</target>
<note>Class = "NSMenuItem"; title = "Hide DatWeatherDoe"; ObjectID = "Olw-nP-bQN";</note>
</trans-unit>
<trans-unit id="OwM-mh-QMV.title" xml:space="preserve">
<source>Find Previous</source>
<target>Find Previous</target>
<note>Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV";</note>
</trans-unit>
<trans-unit id="Oyz-dy-DGm.title" xml:space="preserve">
<source>Stop Speaking</source>
<target>Stop Speaking</target>
<note>Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm";</note>
</trans-unit>
<trans-unit id="Ptp-SP-VEL.title" xml:space="preserve">
<source>Bigger</source>
<target>Bigger</target>
<note>Class = "NSMenuItem"; title = "Bigger"; ObjectID = "Ptp-SP-VEL";</note>
</trans-unit>
<trans-unit id="Q5e-8K-NDq.title" xml:space="preserve">
<source>Show Fonts</source>
<target>Show Fonts</target>
<note>Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "Q5e-8K-NDq";</note>
</trans-unit>
<trans-unit id="QvC-M9-y7g.title" xml:space="preserve">
<source>DatWeatherDoe</source>
<target>DatWeatherDoe</target>
<note>Class = "NSWindow"; title = "DatWeatherDoe"; ObjectID = "QvC-M9-y7g";</note>
</trans-unit>
<trans-unit id="R4o-n2-Eq4.title" xml:space="preserve">
<source>Zoom</source>
<target>Zoom</target>
<note>Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4";</note>
</trans-unit>
<trans-unit id="RB4-Sm-HuC.title" xml:space="preserve">
<source> Right to Left</source>
<target> Right to Left</target>
<note>Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "RB4-Sm-HuC";</note>
</trans-unit>
<trans-unit id="Rqc-34-cIF.title" xml:space="preserve">
<source>Superscript</source>
<target>Superscript</target>
<note>Class = "NSMenuItem"; title = "Superscript"; ObjectID = "Rqc-34-cIF";</note>
</trans-unit>
<trans-unit id="Ruw-6m-B2m.title" xml:space="preserve">
<source>Select All</source>
<target>Select All</target>
<note>Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m";</note>
</trans-unit>
<trans-unit id="S0p-oC-mLd.title" xml:space="preserve">
<source>Jump to Selection</source>
<target>Jump to Selection</target>
<note>Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd";</note>
</trans-unit>
<trans-unit id="Td7-aD-5lo.title" xml:space="preserve">
<source>Window</source>
<target>Window</target>
<note>Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo";</note>
</trans-unit>
<trans-unit id="UEZ-Bs-lqG.title" xml:space="preserve">
<source>Capitalize</source>
<target>Capitalize</target>
<note>Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG";</note>
</trans-unit>
<trans-unit id="VIY-Ag-zcb.title" xml:space="preserve">
<source>Center</source>
<target>Center</target>
<note>Class = "NSMenuItem"; title = "Center"; ObjectID = "VIY-Ag-zcb";</note>
</trans-unit>
<trans-unit id="Vdr-fp-XzO.title" xml:space="preserve">
<source>Hide Others</source>
<target>Hide Others</target>
<note>Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO";</note>
</trans-unit>
<trans-unit id="Vjx-xi-njq.title" xml:space="preserve">
<source>Italic</source>
<target>Italic</target>
<note>Class = "NSMenuItem"; title = "Italic"; ObjectID = "Vjx-xi-njq";</note>
</trans-unit>
<trans-unit id="W48-6f-4Dl.title" xml:space="preserve">
<source>Edit</source>
<target>Edit</target>
<note>Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl";</note>
</trans-unit>
<trans-unit id="WRG-CD-K1S.title" xml:space="preserve">
<source>Underline</source>
<target>Underline</target>
<note>Class = "NSMenuItem"; title = "Underline"; ObjectID = "WRG-CD-K1S";</note>
</trans-unit>
<trans-unit id="Was-JA-tGl.title" xml:space="preserve">
<source>New</source>
<target>New</target>
<note>Class = "NSMenuItem"; title = "New"; ObjectID = "Was-JA-tGl";</note>
</trans-unit>
<trans-unit id="WeT-3V-zwk.title" xml:space="preserve">
<source>Paste and Match Style</source>
<target>Paste and Match Style</target>
<note>Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk";</note>
</trans-unit>
<trans-unit id="Xz5-n4-O0W.title" xml:space="preserve">
<source>Find…</source>
<target>Find…</target>
<note>Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W";</note>
</trans-unit>
<trans-unit id="YEy-JH-Tfz.title" xml:space="preserve">
<source>Find and Replace…</source>
<target>Find and Replace…</target>
<note>Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "YEy-JH-Tfz";</note>
</trans-unit>
<trans-unit id="YGs-j5-SAR.title" xml:space="preserve">
<source> Default</source>
<target> Default</target>
<note>Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "YGs-j5-SAR";</note>
</trans-unit>
<trans-unit id="Ynk-f8-cLZ.title" xml:space="preserve">
<source>Start Speaking</source>
<target>Start Speaking</target>
<note>Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ";</note>
</trans-unit>
<trans-unit id="ZM1-6Q-yy1.title" xml:space="preserve">
<source>Align Left</source>
<target>Align Left</target>
<note>Class = "NSMenuItem"; title = "Align Left"; ObjectID = "ZM1-6Q-yy1";</note>
</trans-unit>
<trans-unit id="ZvO-Gk-QUH.title" xml:space="preserve">
<source>Paragraph</source>
<target>Paragraph</target>
<note>Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "ZvO-Gk-QUH";</note>
</trans-unit>
<trans-unit id="aTl-1u-JFS.title" xml:space="preserve">
<source>Print…</source>
<target>Print…</target>
<note>Class = "NSMenuItem"; title = "Print…"; ObjectID = "aTl-1u-JFS";</note>
</trans-unit>
<trans-unit id="aUF-d1-5bR.title" xml:space="preserve">
<source>Window</source>
<target>Window</target>
<note>Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR";</note>
</trans-unit>
<trans-unit id="aXa-aM-Jaq.title" xml:space="preserve">
<source>Font</source>
<target>Font</target>
<note>Class = "NSMenu"; title = "Font"; ObjectID = "aXa-aM-Jaq";</note>
</trans-unit>
<trans-unit id="agt-UL-0e3.title" xml:space="preserve">
<source>Use Default</source>
<target>Use Default</target>
<note>Class = "NSMenuItem"; title = "Use Default"; ObjectID = "agt-UL-0e3";</note>
</trans-unit>
<trans-unit id="bgn-CT-cEk.title" xml:space="preserve">
<source>Show Colors</source>
<target>Show Colors</target>
<note>Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "bgn-CT-cEk";</note>
</trans-unit>
<trans-unit id="bib-Uj-vzu.title" xml:space="preserve">
<source>File</source>
<target>File</target>
<note>Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu";</note>
</trans-unit>
<trans-unit id="buJ-ug-pKt.title" xml:space="preserve">
<source>Use Selection for Find</source>
<target>Use Selection for Find</target>
<note>Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt";</note>
</trans-unit>
<trans-unit id="c8a-y6-VQd.title" xml:space="preserve">
<source>Transformations</source>
<target>Transformations</target>
<note>Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd";</note>
</trans-unit>
<trans-unit id="cDB-IK-hbR.title" xml:space="preserve">
<source>Use None</source>
<target>Use None</target>
<note>Class = "NSMenuItem"; title = "Use None"; ObjectID = "cDB-IK-hbR";</note>
</trans-unit>
<trans-unit id="cqv-fj-IhA.title" xml:space="preserve">
<source>Selection</source>
<target>Selection</target>
<note>Class = "NSMenuItem"; title = "Selection"; ObjectID = "cqv-fj-IhA";</note>
</trans-unit>
<trans-unit id="cwL-P1-jid.title" xml:space="preserve">
<source>Smart Links</source>
<target>Smart Links</target>
<note>Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid";</note>
</trans-unit>
<trans-unit id="d9M-CD-aMd.title" xml:space="preserve">
<source>Make Lower Case</source>
<target>Make Lower Case</target>
<note>Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd";</note>
</trans-unit>
<trans-unit id="d9c-me-L2H.title" xml:space="preserve">
<source>Text</source>
<target>Text</target>
<note>Class = "NSMenu"; title = "Text"; ObjectID = "d9c-me-L2H";</note>
</trans-unit>
<trans-unit id="dMs-cI-mzQ.title" xml:space="preserve">
<source>File</source>
<target>File</target>
<note>Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ";</note>
</trans-unit>
<trans-unit id="dRJ-4n-Yzg.title" xml:space="preserve">
<source>Undo</source>
<target>Undo</target>
<note>Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg";</note>
</trans-unit>
<trans-unit id="gVA-U4-sdL.title" xml:space="preserve">
<source>Paste</source>
<target>Paste</target>
<note>Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL";</note>
</trans-unit>
<trans-unit id="hQb-2v-fYv.title" xml:space="preserve">
<source>Smart Quotes</source>
<target>Smart Quotes</target>
<note>Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv";</note>
</trans-unit>
<trans-unit id="hz2-CU-CR7.title" xml:space="preserve">
<source>Check Document Now</source>
<target>Check Document Now</target>
<note>Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7";</note>
</trans-unit>
<trans-unit id="hz9-B4-Xy5.title" xml:space="preserve">
<source>Services</source>
<target>Services</target>
<note>Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5";</note>
</trans-unit>
<trans-unit id="i1d-Er-qST.title" xml:space="preserve">
<source>Smaller</source>
<target>Smaller</target>
<note>Class = "NSMenuItem"; title = "Smaller"; ObjectID = "i1d-Er-qST";</note>
</trans-unit>
<trans-unit id="ijk-EB-dga.title" xml:space="preserve">
<source>Baseline</source>
<target>Baseline</target>
<note>Class = "NSMenu"; title = "Baseline"; ObjectID = "ijk-EB-dga";</note>
</trans-unit>
<trans-unit id="jBQ-r6-VK2.title" xml:space="preserve">
<source>Kern</source>
<target>Kern</target>
<note>Class = "NSMenuItem"; title = "Kern"; ObjectID = "jBQ-r6-VK2";</note>
</trans-unit>
<trans-unit id="jFq-tB-4Kx.title" xml:space="preserve">
<source> Right to Left</source>
<target> Right to Left</target>
<note>Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "jFq-tB-4Kx";</note>
</trans-unit>
<trans-unit id="jxT-CU-nIS.title" xml:space="preserve">
<source>Format</source>
<target>Format</target>
<note>Class = "NSMenuItem"; title = "Format"; ObjectID = "jxT-CU-nIS";</note>
</trans-unit>
<trans-unit id="mK6-2p-4JG.title" xml:space="preserve">
<source>Check Grammar With Spelling</source>
<target>Check Grammar With Spelling</target>
<note>Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG";</note>
</trans-unit>
<trans-unit id="o6e-r0-MWq.title" xml:space="preserve">
<source>Ligatures</source>
<target>Ligatures</target>
<note>Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "o6e-r0-MWq";</note>
</trans-unit>
<trans-unit id="oas-Oc-fiZ.title" xml:space="preserve">
<source>Open Recent</source>
<target>Open Recent</target>
<note>Class = "NSMenu"; title = "Open Recent"; ObjectID = "oas-Oc-fiZ";</note>
</trans-unit>
<trans-unit id="ogc-rX-tC1.title" xml:space="preserve">
<source>Loosen</source>
<target>Loosen</target>
<note>Class = "NSMenuItem"; title = "Loosen"; ObjectID = "ogc-rX-tC1";</note>
</trans-unit>
<trans-unit id="pa3-QI-u2k.title" xml:space="preserve">
<source>Delete</source>
<target>Delete</target>
<note>Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k";</note>
</trans-unit>
<trans-unit id="pxx-59-PXV.title" xml:space="preserve">
<source>Save…</source>
<target>Save…</target>
<note>Class = "NSMenuItem"; title = "Save…"; ObjectID = "pxx-59-PXV";</note>
</trans-unit>
<trans-unit id="q09-fT-Sye.title" xml:space="preserve">
<source>Find Next</source>
<target>Find Next</target>
<note>Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye";</note>
</trans-unit>
<trans-unit id="qIS-W8-SiK.title" xml:space="preserve">
<source>Page Setup…</source>
<target>Page Setup…</target>
<note>Class = "NSMenuItem"; title = "Page Setup…"; ObjectID = "qIS-W8-SiK";</note>
</trans-unit>
<trans-unit id="rbD-Rh-wIN.title" xml:space="preserve">
<source>Check Spelling While Typing</source>
<target>Check Spelling While Typing</target>
<note>Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN";</note>
</trans-unit>
<trans-unit id="rgM-f4-ycn.title" xml:space="preserve">
<source>Smart Dashes</source>
<target>Smart Dashes</target>
<note>Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn";</note>
</trans-unit>
<trans-unit id="snW-S8-Cw5.title" xml:space="preserve">
<source>Show Toolbar</source>
<target>Show Toolbar</target>
<note>Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5";</note>
</trans-unit>
<trans-unit id="tRr-pd-1PS.title" xml:space="preserve">
<source>Data Detectors</source>
<target>Data Detectors</target>
<note>Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS";</note>
</trans-unit>
<trans-unit id="tXI-mr-wws.title" xml:space="preserve">
<source>Open Recent</source>
<target>Open Recent</target>
<note>Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "tXI-mr-wws";</note>
</trans-unit>
<trans-unit id="tlD-Oa-oAM.title" xml:space="preserve">
<source>Kern</source>
<target>Kern</target>
<note>Class = "NSMenu"; title = "Kern"; ObjectID = "tlD-Oa-oAM";</note>
</trans-unit>
<trans-unit id="uQy-DD-JDr.title" xml:space="preserve">
<source>DatWeatherDoe</source>
<target>DatWeatherDoe</target>
<note>Class = "NSMenu"; title = "DatWeatherDoe"; ObjectID = "uQy-DD-JDr";</note>
</trans-unit>
<trans-unit id="uRl-iY-unG.title" xml:space="preserve">
<source>Cut</source>
<target>Cut</target>
<note>Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG";</note>
</trans-unit>
<trans-unit id="vKC-jM-MkH.title" xml:space="preserve">
<source>Paste Style</source>
<target>Paste Style</target>
<note>Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "vKC-jM-MkH";</note>
</trans-unit>
<trans-unit id="vLm-3I-IUL.title" xml:space="preserve">
<source>Show Ruler</source>
<target>Show Ruler</target>
<note>Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "vLm-3I-IUL";</note>
</trans-unit>
<trans-unit id="vNY-rz-j42.title" xml:space="preserve">
<source>Clear Menu</source>
<target>Clear Menu</target>
<note>Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "vNY-rz-j42";</note>
</trans-unit>
<trans-unit id="vmV-6d-7jI.title" xml:space="preserve">
<source>Make Upper Case</source>
<target>Make Upper Case</target>
<note>Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI";</note>
</trans-unit>
<trans-unit id="w0m-vy-SC9.title" xml:space="preserve">
<source>Ligatures</source>
<target>Ligatures</target>
<note>Class = "NSMenu"; title = "Ligatures"; ObjectID = "w0m-vy-SC9";</note>
</trans-unit>
<trans-unit id="wb2-vD-lq4.title" xml:space="preserve">
<source>Align Right</source>
<target>Align Right</target>
<note>Class = "NSMenuItem"; title = "Align Right"; ObjectID = "wb2-vD-lq4";</note>
</trans-unit>
<trans-unit id="wpr-3q-Mcd.title" xml:space="preserve">
<source>Help</source>
<target>Help</target>
<note>Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd";</note>
</trans-unit>
<trans-unit id="x3v-GG-iWU.title" xml:space="preserve">
<source>Copy</source>
<target>Copy</target>
<note>Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU";</note>
</trans-unit>
<trans-unit id="xQD-1f-W4t.title" xml:space="preserve">
<source>Use All</source>
<target>Use All</target>
<note>Class = "NSMenuItem"; title = "Use All"; ObjectID = "xQD-1f-W4t";</note>
</trans-unit>
<trans-unit id="xrE-MZ-jX0.title" xml:space="preserve">
<source>Speech</source>
<target>Speech</target>
<note>Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0";</note>
</trans-unit>
<trans-unit id="z6F-FW-3nz.title" xml:space="preserve">
<source>Show Substitutions</source>
<target>Show Substitutions</target>
<note>Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz";</note>
</trans-unit>
</body>
</file>
</xliff>
================================================
FILE: DatWeatherDoe/Localization/en.xcloc/Source Contents/DatWeatherDoe/Resources/en.lproj/InfoPlist.strings
================================================
/* Bundle name */
"CFBundleName" = "DatWeatherDoe";
/* Copyright (human-readable) */
"NSHumanReadableCopyright" = "Copyright © 2016 Inder Dhir. All rights reserved.";
/* Privacy - Location When In Use Usage Description */
"NSLocationWhenInUseUsageDescription" = "DatWeatherDoe optionally uses your current location to get the weather";
================================================
FILE: DatWeatherDoe/Localization/en.xcloc/Source Contents/DatWeatherDoe/UI/Base.lproj/MainMenu.xib
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="DatWeatherDoe" customModuleProvider="target">
<connections>
<outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="DatWeatherDoe" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="DatWeatherDoe" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About DatWeatherDoe" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide DatWeatherDoe" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit DatWeatherDoe" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
<connections>
<action selector="newDocument:" target="-1" id="4Si-XN-c54"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="tXI-mr-wws">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
<items>
<menuItem title="Clear Menu" id="vNY-rz-j42">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" id="KaW-ft-85H">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="print:" target="-1" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="jxT-CU-nIS">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
<items>
<menuItem title="Font" id="Gi5-1S-RQB">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
<connections>
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
</connections>
</menuItem>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
</connections>
</menuItem>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
</connections>
</menuItem>
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
<connections>
<action selector="underline:" target="-1" id="FYS-2b-JAY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
</connections>
</menuItem>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
<menuItem title="Kern" id="jBQ-r6-VK2">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
<items>
<menuItem title="Use Default" id="GUa-eO-cwY">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="-1" id="6dk-9l-Ckg"/>
</connections>
</menuItem>
<menuItem title="Use None" id="cDB-IK-hbR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="-1" id="U8a-gz-Maa"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="46P-cB-AYj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="-1" id="hr7-Nz-8ro"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="ogc-rX-tC1">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="-1" id="8i4-f9-FKE"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligatures" id="o6e-r0-MWq">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
<items>
<menuItem title="Use Default" id="agt-UL-0e3">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="-1" id="7uR-wd-Dx6"/>
</connections>
</menuItem>
<menuItem title="Use None" id="J7y-lM-qPV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="-1" id="iX2-gA-Ilz"/>
</connections>
</menuItem>
<menuItem title="Use All" id="xQD-1f-W4t">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="-1" id="KcB-kA-TuK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="OaQ-X3-Vso">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
<items>
<menuItem title="Use Default" id="3Om-Ey-2VK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="-1" id="0vZ-95-Ywn"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="Rqc-34-cIF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="-1" id="3qV-fo-wpU"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="I0S-gh-46l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="-1" id="Q6W-4W-IGz"/>
</connections>
</menuItem>
<menuItem title="Raise" id="2h7-ER-AoG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="-1" id="4sk-31-7Q9"/>
</connections>
</menuItem>
<menuItem title="Lower" id="1tx-W0-xDw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="-1" id="OF1-bc-KW4"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
<connections>
<action selector="orderFrontColorPanel:" target="-1" id="mSX-Xz-DV3"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="-1" id="GJO-xA-L4q"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="-1" id="JfD-CL-leO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="Fal-I4-PZk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="d9c-me-L2H">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
<connections>
<action selector="alignLeft:" target="-1" id="zUv-R1-uAa"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
<connections>
<action selector="alignCenter:" target="-1" id="spX-mk-kcS"/>
</connections>
</menuItem>
<menuItem title="Justify" id="J5U-5w-g23">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="-1" id="ljL-7U-jND"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
<connections>
<action selector="alignRight:" target="-1" id="r48-bG-YeY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
<menuItem title="Writing Direction" id="H1b-Si-o9J">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
<items>
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="YGs-j5-SAR">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionNatural:" target="-1" id="qtV-5e-UBP"/>
</connections>
</menuItem>
<menuItem id="Lbh-J2-qVU">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionLeftToRight:" target="-1" id="S0X-9S-QSf"/>
</connections>
</menuItem>
<menuItem id="jFq-tB-4Kx">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionRightToLeft:" target="-1" id="5fk-qB-AqJ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="Nop-cj-93Q">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionNatural:" target="-1" id="lPI-Se-ZHp"/>
</connections>
</menuItem>
<menuItem id="BgM-ve-c93">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionLeftToRight:" target="-1" id="caW-Bv-w94"/>
</connections>
</menuItem>
<menuItem id="RB4-Sm-HuC">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionRightToLeft:" target="-1" id="EXD-6r-ZUu"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
<menuItem title="Show Ruler" id="vLm-3I-IUL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="-1" id="FOx-HJ-KwY"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="-1" id="71i-fW-3W2"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="-1" id="cSh-wd-qM2"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="-1" id="BXY-wc-z0C"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="runToolbarCustomizationPalette:" target="-1" id="pQI-g3-MTW"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="DatWeatherDoe Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<window title="DatWeatherDoe" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="335" y="390" width="480" height="360"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<view key="contentView" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</window>
</objects>
</document>
================================================
FILE: DatWeatherDoe/Localization/en.xcloc/contents.json
================================================
{
"developmentRegion" : "en",
"project" : "DatWeatherDoe.xcodeproj",
"targetLocale" : "en",
"toolInfo" : {
"toolBuildNumber" : "12E262",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "12.5"
},
"version" : "1.0"
}
================================================
FILE: DatWeatherDoe/Reachability/WeatherReachability.swift
================================================
//
// WeatherReachability.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/11/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import OSLog
import Reachability
final class NetworkReachability {
private let logger: Logger
private var reachability: Reachability?
private var retryWhenReachable = false
init(
logger: Logger,
onBecomingReachable: @escaping () -> Void
) {
self.logger = logger
setup(callback: onBecomingReachable)
}
private func setup(callback: @escaping () -> Void) {
do {
reachability = try Reachability()
try reachability?.startNotifier()
updateReachabilityWhenReachable(callback: callback)
updateReachabilityWhenUnreachable()
} catch {
logger.error("Reachability error!")
}
}
private func updateReachabilityWhenReachable(callback: @escaping () -> Void) {
reachability?.whenReachable = { [weak self] _ in
self?.logger.debug("Reachability status: Reachable")
if self?.retryWhenReachable == true {
self?.retryWhenReachable = false
callback()
}
}
}
private func updateReachabilityWhenUnreachable() {
reachability?.whenUnreachable = { [weak self] _ in
self?.logger.debug("Reachability status: Unreachable")
self?.retryWhenReachable = true
}
}
}
================================================
FILE: DatWeatherDoe/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"filename" : "16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "32-1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "256-1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "512-1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: DatWeatherDoe/Resources/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: DatWeatherDoe/Resources/DatWeatherDoe.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
================================================
FILE: DatWeatherDoe/Resources/DevelopmentAssets/TestData.swift
================================================
//
// TestData.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/25/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import Foundation
let uvIndicesFor24Hours: [HourlyUVIndex] = {
var arr: [HourlyUVIndex] = []
// swiftlint:disable:next identifier_name
for i in 1 ... 24 {
arr.append(HourlyUVIndex(uv: 0))
}
return arr
}()
let response = WeatherAPIResponse(
locationName: "New York City",
temperatureData: .init(
tempCelsius: 31.1,
feelsLikeTempCelsius: 29.1,
tempFahrenheit: 88.0,
feelsLikeTempFahrenheit: 84.4
),
isDay: true,
weatherConditionCode: 1000,
humidity: 45,
windData: .init(speedMph: 12.3, degrees: 305, direction: "NW"),
uvIndex: 7.0,
forecastDayData: .init(
temperatureData: .init(
maxTempC: 32.8, maxTempF: 91.0, minTempC: 20.6, minTempF: 69.2
),
astro: .init(sunrise: "05:26 AM", sunset: "08:31 PM"),
hour: uvIndicesFor24Hours
),
airQualityIndex: .good
)
================================================
FILE: DatWeatherDoe/Resources/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>WEATHER_API_KEY</key>
<string>${WEATHER_API_KEY}</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.weather</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Inder Dhir. All rights reserved.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>DatWeatherDoe optionally uses your current location to get the weather</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
================================================
FILE: DatWeatherDoe/Resources/Localization/Localizable.xcstrings
================================================
{
"sourceLanguage" : "en",
"strings" : {
"" : {
},
"[latitude],[longitude]" : {
"comment" : "Placeholder hint for entering Lat/Long",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "[Breite],[Länge]"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "[latitude],[longitude]"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "[latitudine],[longitudine]"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "[緯度],[経度]"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "[纬度]、[经度]"
}
}
}
},
"❗️City" : {
"comment" : "City error when fetching weather",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "❗️City"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "❗️Città"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "市のエラー"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "城市"
}
}
}
},
"❗️Lat/Long" : {
"comment" : "Lat/Long error when fetching weather",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fehler mit Breite/Länge"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Erreur de Lat/Lon"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "❗️ Lat/Long"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "緯度/経度のエラー"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "经纬度"
}
}
}
},
"❗️Location" : {
"comment" : "Location error when fetching weather",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Positionsfehler"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Erreur d’emplacement"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "❗️Posizione"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "所在地のエラー"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "位置"
}
}
}
},
"5 min" : {
"comment" : "5 min refresh interval",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "5 Min"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "5 min"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "5 min"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "5分"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "5分钟"
}
}
}
},
"15 min" : {
"comment" : "15 min refresh interval",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "15 Min"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "15 min"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "15 min"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "15分"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "15分钟"
}
}
}
},
"30 min" : {
"comment" : "30 min refresh interval",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "30 Min"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "30 min"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "30 min"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "30分"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "30分钟"
}
}
}
},
"60 min" : {
"comment" : "60 min refresh interval",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "60 Min"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "60 min"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "60 min"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "60分"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "60分钟"
}
}
}
},
"After Temperature" : {
"comment" : "Weather condition after temperature",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nach Temperatur"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Après température"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dopo la temperatura"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "温度後"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "在温度后"
}
}
}
},
"All" : {
"comment" : "Show all temperature units",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Alle"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Tous"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Entrambe"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "全て"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "全部"
}
}
}
},
"AQI" : {
"comment" : "Air Quality Index",
"extractionState" : "manual",
"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "空気質指数"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "空气质量"
}
}
},
"shouldTranslate" : false
},
"Before Temperature" : {
"comment" : "Weather condition before temperature",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vor der Temperatur"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Avant la température"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Prima della temperatura"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "温度前"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "在温度前"
}
}
}
},
"CFBundleName" : {
"comment" : "Bundle name",
"extractionState" : "manual",
"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "DatWeatherDoe"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "DatWeatherDoe"
}
}
},
"shouldTranslate" : false
},
"Clear" : {
"comment" : "Clear at night weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Klar"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Temps clair"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sereno"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "クリア"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "晴朗"
}
}
}
},
"Cloudy" : {
"comment" : "Cloudy weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bedeckt"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nuageux"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nuvoloso"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "曇り"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "多云"
}
}
}
},
"Configure" : {
"comment" : "Configure app",
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Konfigurieren"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configure"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configurer"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configura"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "設定する"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "配置"
}
}
}
},
"Done" : {
"comment" : "Finish configuring app",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fertig"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Terminé"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fatto"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "完了"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "完成"
}
}
}
},
"Fog" : {
"comment" : "Fog weather condition",
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nebbia"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "霧"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "雾"
}
}
}
},
"Freezing rain" : {
"comment" : "Freezing rain weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gefrierender Regen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pluie verglaçante"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nevischio"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "雨氷"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "冻雨"
}
}
}
},
"Good" : {
"comment" : "Air Quality Index: Good",
"extractionState" : "manual",
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Buona"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "良好"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "优"
}
}
}
},
"Hazardous" : {
"comment" : "Air Quality Index: Hazardous",
"extractionState" : "manual",
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pericolosa"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "危険"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "良"
}
}
}
},
"Heavy rain" : {
"comment" : "Heavy rain weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Starkregen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Forte pluie"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pioggia abbondante"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "豪雨"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "大雨"
}
}
}
},
"Hide unit ° symbol" : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nascondi simbolo °"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "°単位記号を非表示にする"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "隐藏单位符号 °"
}
}
}
},
"Hide unit letter" : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nascondi unità C"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "単位文字を非表示にする"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "隐藏单位字母 C"
}
}
}
},
"Imperial" : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Imperiale"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "帝国単位"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "英制"
}
}
}
},
"Lat/Long" : {
"comment" : "Weather based on Lat/Long",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Breite/Länge"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Lat/Long"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Lat/Long"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "緯度/経度"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "纬度/经度"
}
}
}
},
"Launch at Login" : {
"comment" : "Launch app at login",
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Beim Einloggen starten"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Launch at Login"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Lancer à la connexion"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Avvia al Login"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "ログイン時に起動"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "自动启动"
}
}
}
},
"Light rain" : {
"comment" : "Light rain weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Leichter Regen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pluie légère"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Lieve pioggia"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "小雨"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "小雨"
}
}
}
},
"Location" : {
"comment" : "Weather based on location",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Position"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Emplacement"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Posizione corrente"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "所在地"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "定位"
}
}
}
},
"Metric" : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Metrico"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "メートル法"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "公制"
}
}
}
},
"Mist" : {
"comment" : "Mist weather condition",
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Foschia"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "靄"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "薄雾"
}
}
}
},
"Moderate" : {
"comment" : "Air Quality Index: Moderate",
"extractionState" : "manual",
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Moderata"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "中等度"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "轻度污染"
}
}
}
},
"NSHumanReadableCopyright" : {
"comment" : "Copyright (human-readable)",
"extractionState" : "manual",
"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "Copyright © 2016 Inder Dhir. All rights reserved."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "Copyright © 2016 Inder Dhir. Alle Rechte vorbehalten."
}
}
},
"shouldTranslate" : false
},
"NSLocationWhenInUseUsageDescription" : {
"comment" : "Privacy - Location When In Use Usage Description",
"extractionState" : "manual",
"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "DatWeatherDoeは、リクエストに応じて地理的位置を使用して天気を判断します。"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "DatWeatherDoe根据请求使用您的地理位置来确定天气."
}
}
},
"shouldTranslate" : false
},
"Partly cloudy" : {
"comment" : "Partly cloudy weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Wolkig"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Partiellement couvert"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Parzialmente nuvoloso"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "晴れ時々曇り"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "局部多云"
}
}
}
},
"Partly cloudy with rain" : {
"comment" : "Partly cloudy with rain weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bewölkt mit Regen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Partiellement couvert avec pluie"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pioggia, parzialmente nuvoloso"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "部分的に曇り雨"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "多云转阴有雨"
}
}
}
},
"Quit" : {
"comment" : "Quit app",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Beenden"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Quitter"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Esci"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "終了する"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "退出"
}
}
}
},
"Refresh" : {
"comment" : "Refresh weather",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aktualisieren"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Rafraîchir"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aggiorna"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "再読み込みする"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "刷新"
}
}
}
},
"Refresh Interval" : {
"comment" : "Weather refresh interval",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aktualisierungsintervall"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Intervalle de rafraîchissement"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Intervallo Aggiornamento"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "再読み込み間隔"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "刷新间隔"
}
}
}
},
"Round-off Data" : {
"comment" : "Round-off Data (temperature)",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Daten runden"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Arrondir les données"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Arrotonda Dati"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "データを四捨五入する"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "数据四舍五入"
}
}
}
},
"See Full Weather" : {
"comment" : "See Full Weather",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Komplettes Wetter anzeigen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Voir la météo complète"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Meteo Completo"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "全ての天気情報を見る"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "查看完整天气"
}
}
}
},
"Separate values with" : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Separa valori con"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "〜で値を区切る"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "分隔符号"
}
}
}
},
"Show Humidity" : {
"comment" : "Show humidity",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Luftfeuchtigkeit anzeigen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afficher l’humidité"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mostra Umidità"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "湿度を表示する"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "显示湿度"
}
}
}
},
"Show UV Index" : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mostra Indice UV"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "UVインデックスを表示する"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "紫外线指数"
}
}
}
},
"Show Weather Icon" : {
"comment" : "\"Show Weather Icon\"",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Weather Icon anzeigen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afficher l'icône météo"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mostra Icona Meteo"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "天気アイコンを表示する"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "显示天气图标"
}
}
}
},
"Snow" : {
"comment" : "Snow weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Schnee"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Neige"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Neve"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "雪"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "雪"
}
}
}
},
"Sunny" : {
"comment" : "Sunny weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sonnig"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ensoleillé"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sole"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "晴れ"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "阳光"
}
}
}
},
"Thunderstorm" : {
"comment" : "Thunderstorm weather condition",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gewitter"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Orage"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Temporale"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "雷雨"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "雷雨"
}
}
}
},
"Unhealthy" : {
"comment" : "Air Quality Index: Unhealthy",
"extractionState" : "manual",
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Malsana"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "不健康"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "中度污染"
}
}
}
},
"Unhealthy for sensitive groups" : {
"comment" : "Air Quality Index: Unhealthy for sensitive groups",
"extractionState" : "manual",
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Malsana per soggetti sensibili"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "敏感な人々にとって不健康"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "重度污染"
}
}
}
},
"Unit" : {
"comment" : "Temperature unit",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Einheit"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Unité"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sistema"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "単位"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "单位"
}
}
}
},
"Unknown" : {
"comment" : "Unknown location",
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Unbekannt"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Unknown"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Inconnu"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sconosciuta"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "不明"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "未知"
}
}
}
},
"Unknown Location" : {
"comment" : "Unknown weather location",
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Unbekannte Position"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Unknown Location"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Emplacement inconnu"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Posizione Sconosciuta"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "所在地不明"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "未知地区"
}
}
}
},
"UV Index" : {
"comment" : "UV Index",
"extractionState" : "manual",
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Indice UV"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "UVインデックス"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "紫外线指数"
}
}
}
},
"Very unhealthy" : {
"comment" : "Air Quality Index: Very unhealthy",
"extractionState" : "manual",
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Molto bassa"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "非常に不健康"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "严重污染"
}
}
}
},
"Weather Condition Position" : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Posizione Condizioni Meteo"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "気象条件の位置"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "天气文本位置"
}
}
}
},
"Weather Condition Text" : {
"comment" : "Weather Condition Text",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aktuelles Wetter Text"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Condition météo texte"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Testo Condizioni Meteo"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "気象条件テキスト"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "显示天气文本信息"
}
}
}
},
"Weather Source" : {
"comment" : "Source for fetching weather",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Wetterquelle"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Source de la météo"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fonte Meteo"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "気象情報源"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "获取天气方式"
}
}
}
}
},
"version" : "1.0"
}
================================================
FILE: DatWeatherDoe/UI/Configure/ConfigureOptionsView.swift
================================================
//
// ConfigureOptionsView.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 8/3/23.
// Copyright © 2023 Inder Dhir. All rights reserved.
//
import SwiftUI
struct ConfigureOptionsView: View {
@ObservedObject var viewModel: ConfigureViewModel
var body: some View {
Grid(verticalSpacing: 16) {
HStack {
Text(LocalizedStringKey("Unit"))
Spacer()
Picker("", selection: $viewModel.measurementUnit) {
Text(LocalizedStringKey("Metric")).tag(MeasurementUnit.metric)
Text(LocalizedStringKey("Imperial")).tag(MeasurementUnit.imperial)
Text(LocalizedStringKey("All")).tag(MeasurementUnit.all)
}
.frame(width: 120)
}
ConfigureWeatherOptionsView(viewModel: viewModel)
HStack {
Text(LocalizedStringKey("Refresh Interval"))
Spacer()
Picker("", selection: $viewModel.refreshInterval) {
Text(LocalizedStringKey("5 min")).tag(RefreshInterval.fiveMinutes)
Text(LocalizedStringKey("15 min")).tag(RefreshInterval.fifteenMinutes)
Text(LocalizedStringKey("30 min")).tag(RefreshInterval.thirtyMinutes)
Text(LocalizedStringKey("60 min")).tag(RefreshInterval.sixtyMinutes)
}
.frame(width: 120)
}
HStack {
Text(LocalizedStringKey("Show Weather Icon"))
Spacer()
Toggle(isOn: $viewModel.isShowingWeatherIcon) {}
}
HStack {
Text(LocalizedStringKey("Show Humidity"))
Spacer()
Toggle(isOn: $viewModel.isShowingHumidity) {}
}
HStack {
Text(LocalizedStringKey("Show UV Index"))
Spacer()
Toggle(isOn: $viewModel.isShowingUVIndex) {}
}
HStack {
Text(LocalizedStringKey("Round-off Data"))
Spacer()
Toggle(isOn: $viewModel.isRoundingOffData) {}
}
ConfigureUnitOptionsView(viewModel: viewModel)
ConfigureValueSeparatorOptionsView(viewModel: viewModel)
HStack {
Text(LocalizedStringKey("Weather Condition Text"))
Spacer()
Toggle(isOn: $viewModel.isWeatherConditionAsTextEnabled) {}
}
HStack {
Text(LocalizedStringKey("Weather Condition Position"))
Spacer()
Picker("", selection: $viewModel.weatherConditionPosition) {
Text(LocalizedStringKey("Before Temperature"))
.tag(WeatherConditionPosition.beforeTemperature)
Text(LocalizedStringKey("After Temperature"))
.tag(WeatherConditionPosition.afterTemperature)
}
.frame(maxWidth: 120)
}
.disabled(!viewModel.isWeatherConditionAsTextEnabled)
}
.padding()
}
}
#Preview {
ConfigureOptionsView(
viewModel: .init(configManager: ConfigManager())
)
.frame(width: 380)
}
================================================
FILE: DatWeatherDoe/UI/Configure/ConfigureUnitOptionsView.swift
================================================
//
// ConfigureUnitOptionsView.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 8/8/23.
// Copyright © 2023 Inder Dhir. All rights reserved.
//
import SwiftUI
struct ConfigureUnitOptionsView: View {
@ObservedObject var viewModel: ConfigureViewModel
var body: some View {
Group {
HStack {
Text(LocalizedStringKey("Hide unit letter"))
Spacer()
Toggle(isOn: $viewModel.isUnitLetterOff) {}
}
HStack {
Text(LocalizedStringKey("Hide unit ° symbol"))
Spacer()
Toggle(isOn: $viewModel.isUnitSymbolOff) {}
}
}
}
}
#Preview {
Grid {
ConfigureUnitOptionsView(
viewModel: .init(configManager: ConfigManager())
)
}
}
================================================
FILE: DatWeatherDoe/UI/Configure/ConfigureValueSeparatorOptionsView.swift
================================================
//
// ConfigureValueSeparatorOptionsView.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 8/8/23.
// Copyright © 2023 Inder Dhir. All rights reserved.
//
import SwiftUI
struct ConfigureValueSeparatorOptionsView: View {
@ObservedObject var viewModel: ConfigureViewModel
let valueSeparatorPlaceholder = "\u{007C}"
var body: some View {
HStack {
Text(LocalizedStringKey("Separate values with"))
Spacer()
TextField(valueSeparatorPlaceholder, text: $viewModel.valueSeparator)
.font(.body)
.foregroundColor(.primary)
.frame(width: 114)
}
}
}
#Preview {
ConfigureValueSeparatorOptionsView(
viewModel: .init(configManager: ConfigManager())
)
}
================================================
FILE: DatWeatherDoe/UI/Configure/ConfigureView.swift
================================================
//
// ConfigureView.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 2/18/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import SwiftUI
struct ConfigureView: View {
@ObservedObject var viewModel: ConfigureViewModel
let version: String
let onSave: () -> Void
let onQuit: () -> Void
var body: some View {
VStack {
ConfigureOptionsView(viewModel: viewModel)
HStack {
Text(version)
.font(.footnote)
.fontWeight(.thin)
.frame(maxWidth: .infinity, alignment: .leading)
CustomButton(
text: LocalizedStringKey("Done"),
shortcutKey: "d",
onClick: onSave
)
.frame(maxWidth: .infinity, alignment: .center)
Text(LocalizedStringKey("Quit"))
.foregroundStyle(Color.red)
.onTapGesture(perform: onQuit)
.frame(maxWidth: .infinity, alignment: .trailing)
}
.frame(maxWidth: .infinity)
.padding([.leading, .trailing])
}
.padding(.bottom)
.frame(width: 380)
}
}
#Preview {
ConfigureView(
viewModel: .init(configManager: ConfigManager()),
version: "5.0.0",
onSave: {},
onQuit: {}
)
}
================================================
FILE: DatWeatherDoe/UI/Configure/ConfigureViewModel.swift
================================================
//
// ConfigureViewModel.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 3/20/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Combine
import Foundation
final class ConfigureViewModel: ObservableObject {
@Published var measurementUnit: MeasurementUnit {
didSet { configManager.measurementUnit = measurementUnit.rawValue }
}
@Published var weatherSource: WeatherSource
@Published var weatherSourceText: String
@Published var refreshInterval: RefreshInterval {
didSet { configManager.refreshInterval = refreshInterval.rawValue }
}
@Published var isShowingWeatherIcon: Bool {
didSet { configManager.isShowingWeatherIcon = isShowingWeatherIcon }
}
@Published var isShowingHumidity: Bool {
didSet { configManager.isShowingHumidity = isShowingHumidity }
}
@Published var isShowingUVIndex: Bool {
didSet { configManager.isShowingUVIndex = isShowingUVIndex }
}
@Published var isRoundingOffData: Bool {
didSet { configManager.isRoundingOffData = isRoundingOffData }
}
@Published var isUnitLetterOff: Bool {
didSet { configManager.isUnitLetterOff = isUnitLetterOff }
}
@Published var isUnitSymbolOff: Bool {
didSet { configManager.isUnitSymbolOff = isUnitSymbolOff }
}
@Published var valueSeparator = "|" {
didSet { configManager.valueSeparator = valueSeparator }
}
@Published var isWeatherConditionAsTextEnabled: Bool {
didSet { configManager.isWeatherConditionAsTextEnabled = isWeatherConditionAsTextEnabled }
}
@Published var weatherConditionPosition: WeatherConditionPosition {
didSet { configManager.weatherConditionPosition = weatherConditionPosition.rawValue }
}
private let configManager: ConfigManagerType
init(configManager: ConfigManagerType) {
self.configManager = configManager
measurementUnit = configManager.parsedMeasurementUnit
weatherSource = WeatherSource(rawValue: configManager.weatherSource) ?? .location
weatherSourceText = configManager.weatherSourceText
switch configManager.refreshInterval {
case 300: refreshInterval = .fiveMinutes
case 900: refreshInterval = .fifteenMinutes
case 1800: refreshInterval = .thirtyMinutes
case 3600: refreshInterval = .sixtyMinutes
default: refreshInterval = .fifteenMinutes
}
isShowingWeatherIcon = configManager.isShowingWeatherIcon
isShowingHumidity = configManager.isShowingHumidity
isShowingUVIndex = configManager.isShowingUVIndex
isRoundingOffData = configManager.isRoundingOffData
isUnitLetterOff = configManager.isUnitLetterOff
isUnitSymbolOff = configManager.isUnitSymbolOff
isWeatherConditionAsTextEnabled = configManager.isWeatherConditionAsTextEnabled
weatherConditionPosition = WeatherConditionPosition(rawValue: configManager.weatherConditionPosition)
?? .beforeTemperature
}
func saveConfig() {
configManager.updateWeatherSource(weatherSource, sourceText: weatherSourceText)
weatherSourceText = configManager.weatherSourceText
configManager.setConfigOptions(
.init(
refreshInterval: refreshInterval,
isShowingHumidity: isShowingHumidity,
isShowingUVIndex: isShowingUVIndex,
isRoundingOffData: isRoundingOffData,
isUnitLetterOff: isUnitLetterOff,
isUnitSymbolOff: isUnitSymbolOff,
valueSeparator: valueSeparator,
isWeatherConditionAsTextEnabled: isWeatherConditionAsTextEnabled
)
)
}
}
================================================
FILE: DatWeatherDoe/UI/Configure/ConfigureWeatherOptionsView.swift
================================================
//
// ConfigureWeatherOptionsView.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 8/8/23.
// Copyright © 2023 Inder Dhir. All rights reserved.
//
import SwiftUI
struct ConfigureWeatherOptionsView: View {
@ObservedObject var viewModel: ConfigureViewModel
var body: some View {
Group {
HStack {
Text(LocalizedStringKey("Weather Source"))
Spacer()
Picker("", selection: $viewModel.weatherSource) {
Text(LocalizedStringKey("Location")).tag(WeatherSource.location)
Text(LocalizedStringKey("Lat/Long")).tag(WeatherSource.latLong)
}
.frame(width: 120)
}
HStack {
Text(viewModel.weatherSource.textHint)
.font(.caption)
.foregroundColor(.secondary)
Spacer()
TextField(viewModel.weatherSource.placeholder, text: $viewModel.weatherSourceText)
.font(.caption)
.foregroundColor(.secondary)
.disabled(viewModel.weatherSource == .location)
.frame(width: 114)
}
}
}
}
#Preview {
Grid {
ConfigureWeatherOptionsView(
viewModel: .init(configManager: ConfigManager())
)
}
}
================================================
FILE: DatWeatherDoe/UI/Configure/Options/MeasurementUnit.swift
================================================
//
// MeasurementUnit.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 2/7/23.
// Copyright © 2023 Inder Dhir. All rights reserved.
//
import Foundation
enum MeasurementUnit: String, CaseIterable, Identifiable {
case metric, imperial, all
var id: Self { self }
var temperatureUnit: TemperatureUnit {
switch self {
case .metric:
.celsius
case .imperial:
.fahrenheit
case .all:
.all
}
}
}
================================================
FILE: DatWeatherDoe/UI/Configure/Options/RefreshInterval.swift
================================================
//
// RefreshInterval.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 5/23/21.
// Copyright © 2021 Inder Dhir. All rights reserved.
//
import Foundation
enum RefreshInterval: TimeInterval, CaseIterable, Identifiable {
case fiveMinutes = 300
case fifteenMinutes = 900
case thirtyMinutes = 1800
case sixtyMinutes = 3600
var id: Self { self }
var title: String {
switch self {
case .fiveMinutes:
String(localized: "5 min")
case .fifteenMinutes:
String(localized: "15 min")
case .thirtyMinutes:
String(localized: "30 min")
case .sixtyMinutes:
String(localized: "60 min")
}
}
}
================================================
FILE: DatWeatherDoe/UI/Configure/Options/TemperatureUnit.swift
================================================
//
// TemperatureUnit.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 8/8/23.
// Copyright © 2023 Inder Dhir. All rights reserved.
//
import Foundation
enum TemperatureUnit: String, CaseIterable, Identifiable {
case fahrenheit, celsius, all
var id: Self { self }
var unitString: String {
switch self {
case .fahrenheit:
"F"
case .celsius:
"C"
case .all:
"All"
}
}
var degreesString: String {
"\u{00B0}\(unitString)"
}
}
================================================
FILE: DatWeatherDoe/UI/Configure/Options/WeatherConditionPosition.swift
================================================
//
// WeatherConditionPosition.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 3/27/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import Foundation
enum WeatherConditionPosition: String, Identifiable {
case beforeTemperature, afterTemperature
var id: Self { self }
var title: String {
switch self {
case .beforeTemperature:
String(localized: "Before Temperature")
case .afterTemperature:
String(localized: "After Temperature")
}
}
}
================================================
FILE: DatWeatherDoe/UI/Configure/Options/WeatherSource.swift
================================================
//
// WeatherSource.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 5/23/21.
// Copyright © 2021 Inder Dhir. All rights reserved.
//
import Foundation
enum WeatherSource: String, CaseIterable {
case location, latLong
var title: String {
switch self {
case .location:
String(localized: "Location")
case .latLong:
String(localized: "Lat/Long")
}
}
var placeholder: String {
switch self {
case .location:
""
case .latLong:
"42,42"
}
}
var textHint: String {
switch self {
case .location:
""
case .latLong:
String(localized: "[latitude],[longitude]")
}
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Condition/WeatherCondition.swift
================================================
//
// WeatherCondition.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/29/16.
// Copyright © 2016 Inder Dhir. All rights reserved.
//
import AppKit
import Foundation
enum WeatherCondition {
case cloudy, partlyCloudy, partlyCloudyNight
case sunny, clearNight
case snow
case heavyRain, freezingRain, lightRain, partlyCloudyRain
case thunderstorm
case mist, fog
static func getFallback(isDay: Bool) -> WeatherCondition {
isDay ? .sunny : .clearNight
}
var symbolName: String {
switch self {
case .cloudy:
"cloud"
case .partlyCloudy:
"cloud.sun"
case .partlyCloudyNight:
"cloud.moon"
case .sunny:
"sun.max"
case .clearNight:
"moon"
case .snow:
"cloud.snow"
case .lightRain, .heavyRain, .freezingRain:
"cloud.rain"
case .partlyCloudyRain:
"cloud.sun.rain"
case .thunderstorm:
"cloud.bolt.rain"
case .mist, .fog:
"cloud.fog"
}
}
var accessibilityLabel: String {
switch self {
case .cloudy:
"Cloudy"
case .partlyCloudy:
"Partly Cloudy"
case .partlyCloudyNight:
"Partly Cloudy"
case .sunny:
"Sunny"
case .clearNight:
"Clear"
case .snow:
"Snow"
case .lightRain, .heavyRain, .freezingRain:
"Rainy"
case .partlyCloudyRain:
"Partly cloudy with rain"
case .thunderstorm:
"Thunderstorm"
case .mist, .fog:
"Cloudy with Fog"
}
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Condition/WeatherConditionBuilder.swift
================================================
//
// WeatherConditionBuilder.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/11/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Foundation
protocol WeatherConditionBuilderType {
func build() -> WeatherCondition
}
final class WeatherConditionBuilder: WeatherConditionBuilderType {
private let response: WeatherAPIResponse
init(response: WeatherAPIResponse) {
self.response = response
}
func build() -> WeatherCondition {
switch response.weatherConditionCode {
case 1006, 1009:
.cloudy
case 1003:
response.isDay ? .partlyCloudy : .partlyCloudyNight
case 1000:
response.isDay ? .sunny : .clearNight
case 1030:
.mist
case 1135, 1147:
.fog
case 1066,
1114, 1117,
1210, 1213, 1216, 1219, 1222, 1225, 1237, 1249, 1252, 1255, 1258, 1261, 1264, 1279, 1282:
.snow
case 1192, 1195, 1243, 1246, 1276:
.heavyRain
case 1069, 1072, 1168, 1171, 1198, 1201, 1204, 1207:
.freezingRain
case 1063, 1150, 1153, 1180, 1183, 1186, 1189, 1240:
response.isDay ? .partlyCloudyRain : .lightRain
case 1087, 1273:
.thunderstorm
default:
WeatherCondition.getFallback(isDay: response.isDay)
}
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Condition/WeatherConditionTextMapper.swift
================================================
//
// WeatherConditionTextMapper.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/11/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Foundation
protocol WeatherConditionTextMapperType {
func map(_ condition: WeatherCondition) -> String
}
final class WeatherConditionTextMapper: WeatherConditionTextMapperType {
func map(_ condition: WeatherCondition) -> String {
switch condition {
case .cloudy:
String(localized: "Cloudy")
case .partlyCloudy, .partlyCloudyNight:
String(localized: "Partly cloudy")
case .sunny:
String(localized: "Sunny")
case .clearNight:
String(localized: "Clear")
case .snow:
String(localized: "Snow")
case .heavyRain:
String(localized: "Heavy rain")
case .freezingRain:
String(localized: "Freezing rain")
case .lightRain:
String(localized: "Light rain")
case .partlyCloudyRain:
String(localized: "Partly cloudy with rain")
case .thunderstorm:
String(localized: "Thunderstorm")
case .mist:
String(localized: "Mist")
case .fog:
String(localized: "Fog")
}
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Text/HumidityTextBuilder.swift
================================================
//
// HumidityTextBuilder.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/15/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Foundation
import OSLog
protocol HumidityTextBuilderType {
func build() -> String
}
final class HumidityTextBuilder: HumidityTextBuilderType {
private let initial: String
private let valueSeparator: String
private let humidity: Int
private let logger: Logger
private let percentString = "\u{0025}"
private let humidityFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .none
formatter.maximumFractionDigits = 0
return formatter
}()
init(
initial: String,
valueSeparator: String,
humidity: Int,
logger: Logger
) {
self.initial = initial
self.valueSeparator = valueSeparator
self.humidity = humidity
self.logger = logger
}
func build() -> String {
guard let humidityString = buildHumidity() else {
logger.error("Unable to construct humidity string")
return initial
}
return "\(initial) \(valueSeparator) \(humidityString)"
}
private func buildHumidity() -> String? {
guard let formattedString = buildFormattedString() else { return nil }
return "\(formattedString)\(percentString)"
}
private func buildFormattedString() -> String? {
humidityFormatter.string(from: NSNumber(value: humidity))
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Text/SunriseAndSunsetTextBuilder.swift
================================================
//
// SunriseAndSunsetTextBuilder.swift
// DatWeatherDoe
//
// Created by Markus Markus on 2022-07-26.
// Copyright © 2022 Markus Mayer.
//
import Foundation
protocol SunriseAndSunsetTextBuilderType {
func build() -> String
}
final class SunriseAndSunsetTextBuilder: SunriseAndSunsetTextBuilderType {
private let sunset: String
private let sunrise: String
private let upArrowStr = "⬆"
private let downArrowStr = "⬇"
init(sunset: String, sunrise: String) {
self.sunset = sunset
self.sunrise = sunrise
}
func build() -> String {
"\(upArrowStr)\(sunrise) \(downArrowStr)\(sunset)"
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Text/Temperature/TemperatureForecastTextBuilder.swift
================================================
//
// TemperatureForecastTextBuilder.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/25/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Foundation
protocol TemperatureForecastTextBuilderType {
func build() -> String
}
final class TemperatureForecastTextBuilder: TemperatureForecastTextBuilderType {
private let temperatureData: TemperatureData
private let forecastTemperatureData: ForecastTemperatureData
private let options: TemperatureTextBuilder.Options
private let upArrowStr = "⬆"
private let downArrowStr = "⬇"
private let degreeString = "\u{00B0}"
init(
temperatureData: TemperatureData,
forecastTemperatureData: ForecastTemperatureData,
options: TemperatureTextBuilder.Options
) {
self.temperatureData = temperatureData
self.forecastTemperatureData = forecastTemperatureData
self.options = options
}
func build() -> String {
if options.unit == .all {
buildTemperatureForAllUnits()
} else {
buildTemperatureForUnit(options.unit)
}
}
private func buildTemperatureForAllUnits() -> String {
let feelsLikeTempFahrenheit = buildFormattedTemperature(
temperatureData.feelsLikeTempFahrenheit, unit: .fahrenheit
)
let feelsLikeTempCelsius = buildFormattedTemperature(
temperatureData.feelsLikeTempCelsius, unit: .celsius
)
let feelsLikeTemperatureCombined = [feelsLikeTempFahrenheit, feelsLikeTempCelsius]
.compactMap { $0 }
.joined(separator: " / ")
let maxTempFahrenheit = buildFormattedTemperature(
forecastTemperatureData.maxTempF, unit: .fahrenheit
)
let maxTempCelsius = buildFormattedTemperature(
forecastTemperatureData.maxTempC, unit: .celsius
)
let maxTempCombined = [maxTempFahrenheit, maxTempCelsius]
.compactMap { $0 }
.joined(separator: " / ")
let maxTempStr = [upArrowStr, maxTempCombined]
.compactMap { $0 }
.joined()
let minTempFahrenheit = buildFormattedTemperature(
forecastTemperatureData.minTempF, unit: .fahrenheit
)
let minTempCelsius = buildFormattedTemperature(
forecastTemperatureData.minTempC, unit: .celsius
)
let minTempCombined = [minTempFahrenheit, minTempCelsius]
.compactMap { $0 }
.joined(separator: " / ")
let minTempStr = [downArrowStr, minTempCombined]
.compactMap { $0 }
.joined()
let maxAndMinTempStr = [maxTempStr, minTempStr]
.compactMap { $0 }
.joined(separator: " ")
return [feelsLikeTemperatureCombined, maxAndMinTempStr]
.compactMap { $0 }
.joined(separator: " - ")
}
private func buildTemperatureForUnit(_ unit: TemperatureUnit) -> String {
let maxTemp = unit == .fahrenheit ?
forecastTemperatureData.maxTempF : forecastTemperatureData.maxTempC
let formattedMaxTemp = buildFormattedTemperature(maxTemp, unit: unit)
let maxTempStr = [upArrowStr, formattedMaxTemp]
.compactMap { $0 }
.joined()
let minTemp = unit == .fahrenheit ?
forecastTemperatureData.minTempF : forecastTemperatureData.minTempC
let formatedMinTemp = buildFormattedTemperature(minTemp, unit: unit)
let minTempStr = [downArrowStr, formatedMinTemp]
.compactMap { $0 }
.joined()
let maxAndMinTempStr = [maxTempStr, minTempStr]
.compactMap { $0 }
.joined(separator: " ")
let feelsLikeTemp = unit == .fahrenheit ?
temperatureData.feelsLikeTempFahrenheit :
temperatureData.feelsLikeTempCelsius
let formattedFeelsLikeTemp = buildFormattedTemperature(feelsLikeTemp, unit: unit)
return [formattedFeelsLikeTemp, maxAndMinTempStr]
.compactMap { $0 }
.joined(separator: " - ")
}
private func buildFormattedTemperature(
_ temperatureForUnit: Double,
unit: TemperatureUnit
) -> String? {
guard let temperatureString = TemperatureFormatter()
.getFormattedTemperatureString(temperatureForUnit, isRoundingOff: options.isRoundingOff)
else {
return nil
}
return combineTemperatureWithUnitDegrees(
temperature: temperatureString,
unit: unit.unitString,
isUnitLetterOff: options.isUnitLetterOff,
isUnitSymbolOff: options.isUnitSymbolOff
)
}
private func combineTemperatureWithUnitDegrees(
temperature: String,
unit: String,
isUnitLetterOff: Bool,
isUnitSymbolOff: Bool
) -> String {
let unitLetter = isUnitLetterOff ? "" : unit
let unitSymbol = isUnitSymbolOff ? "" : degreeString
return [temperature, unitLetter].joined(separator: unitSymbol)
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Text/Temperature/TemperatureFormatter.swift
================================================
//
// TemperatureFormatter.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/12/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Foundation
protocol TemperatureFormatterType {
func getFormattedTemperatureString(
_ temperature: Double,
isRoundingOff: Bool
) -> String?
}
final class TemperatureFormatter: TemperatureFormatterType {
private let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 1
formatter.roundingMode = .halfUp
return formatter
}()
func getFormattedTemperatureString(
_ temperature: Double,
isRoundingOff: Bool
) -> String? {
setupTemperatureRounding(isRoundingOff: isRoundingOff)
return formatTemperatureString(temperature)
}
private func setupTemperatureRounding(isRoundingOff: Bool) {
formatter.maximumFractionDigits = isRoundingOff ? 0 : 1
}
private func formatTemperatureString(_ temperature: Double) -> String? {
let formattedTemperature = formatter.string(from: NSNumber(value: temperature))
return fixRoundingIssues(formattedTemperature)
}
private func fixRoundingIssues(_ temperature: String?) -> String? {
if temperature == "-0" {
return "0"
}
return temperature
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Text/Temperature/TemperatureTextBuilder.swift
================================================
//
// TemperatureTextBuilder.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/15/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
protocol TemperatureTextBuilderType {
func build() -> String?
}
final class TemperatureTextBuilder: TemperatureTextBuilderType {
struct Options {
let unit: TemperatureUnit
let isRoundingOff: Bool
let isUnitLetterOff: Bool
let isUnitSymbolOff: Bool
}
private let response: WeatherAPIResponse
private let options: Options
private let temperatureCreator: TemperatureWithDegreesCreatorType
private let degreeString = "\u{00B0}"
init(
response: WeatherAPIResponse,
options: Options,
temperatureCreator: TemperatureWithDegreesCreatorType
) {
self.response = response
self.options = options
self.temperatureCreator = temperatureCreator
}
func build() -> String? {
if options.unit == .all {
buildTemperatureTextForAllUnits()
} else {
buildTemperatureText(for: options.unit)
}
}
private func buildTemperatureTextForAllUnits() -> String? {
let temperatureWithDegrees = temperatureCreator.getTemperatureWithDegrees(
temperatureInMultipleUnits:
.init(
fahrenheit: response.temperatureData.tempFahrenheit,
celsius: response.temperatureData.tempCelsius
),
isRoundingOff: options.isRoundingOff,
isUnitLetterOff: options.isUnitLetterOff,
isUnitSymbolOff: options.isUnitSymbolOff
)
return temperatureWithDegrees
}
private func buildTemperatureText(for unit: TemperatureUnit) -> String? {
let temperatureForUnit = unit == .fahrenheit ?
response.temperatureData.tempFahrenheit :
response.temperatureData.tempCelsius
let temperatureWithDegrees = temperatureCreator.getTemperatureWithDegrees(
temperatureForUnit,
unit: unit,
isRoundingOff: options.isRoundingOff,
isUnitLetterOff: options.isUnitLetterOff,
isUnitSymbolOff: options.isUnitSymbolOff
)
return temperatureWithDegrees
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Text/Temperature/TemperatureWithDegreesCreator.swift
================================================
//
// TemperatureWithDegreesCreator.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 8/9/23.
// Copyright © 2023 Inder Dhir. All rights reserved.
//
import Foundation
struct TemperatureInMultipleUnits {
let fahrenheit: Double
let celsius: Double
}
protocol TemperatureWithDegreesCreatorType {
var degreeString: String { get }
func getTemperatureWithDegrees(
temperatureInMultipleUnits: TemperatureInMultipleUnits,
isRoundingOff: Bool,
isUnitLetterOff: Bool,
isUnitSymbolOff: Bool
) -> String?
func getTemperatureWithDegrees(
_ temperature: Double,
unit: TemperatureUnit,
isRoundingOff: Bool,
isUnitLetterOff: Bool,
isUnitSymbolOff: Bool
) -> String?
}
final class TemperatureWithDegreesCreator: TemperatureWithDegreesCreatorType {
let degreeString = "\u{00B0}"
func getTemperatureWithDegrees(
temperatureInMultipleUnits: TemperatureInMultipleUnits,
isRoundingOff: Bool,
isUnitLetterOff: Bool,
isUnitSymbolOff: Bool
) -> String? {
guard let fahrenheitString = TemperatureFormatter().getFormattedTemperatureString(
temperatureInMultipleUnits.fahrenheit,
isRoundingOff: isRoundingOff
) else {
return nil
}
guard let celsiusString = TemperatureFormatter().getFormattedTemperatureString(
temperatureInMultipleUnits.celsius,
isRoundingOff: isRoundingOff
) else {
return nil
}
let formattedFahrenheit = combineTemperatureWithUnitDegrees(
temperature: fahrenheitString,
unit: .fahrenheit,
isUnitLetterOff: isUnitLetterOff,
isUnitSymbolOff: isUnitSymbolOff
)
let formattedCelsius = combineTemperatureWithUnitDegrees(
temperature: celsiusString,
unit: .celsius,
isUnitLetterOff: isUnitLetterOff,
isUnitSymbolOff: isUnitSymbolOff
)
return [formattedFahrenheit, formattedCelsius]
.joined(separator: " / ")
}
func getTemperatureWithDegrees(
_ temperature: Double,
unit: TemperatureUnit,
isRoundingOff: Bool,
isUnitLetterOff: Bool,
isUnitSymbolOff: Bool
) -> String? {
guard let temperatureString = TemperatureFormatter().getFormattedTemperatureString(
temperature,
isRoundingOff: isRoundingOff
) else {
return nil
}
return combineTemperatureWithUnitDegrees(
temperature: temperatureString,
unit: unit,
isUnitLetterOff: isUnitLetterOff,
isUnitSymbolOff: isUnitSymbolOff
)
}
private func combineTemperatureWithUnitDegrees(
temperature: String,
unit: TemperatureUnit,
isUnitLetterOff: Bool,
isUnitSymbolOff: Bool
) -> String {
let unitLetter = isUnitLetterOff ? "" : unit.unitString
let unitSymbol = isUnitSymbolOff ? "" : degreeString
return [temperature, unitLetter].joined(separator: unitSymbol)
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Text/UVIndexTextBuilder.swift
================================================
//
// UVIndexTextBuilder.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 10/18/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import Foundation
final class UVIndexTextBuilder {
private let initial: String
private let separator: String
init(initial: String, separator: String) {
self.initial = initial
self.separator = separator
}
func build(from response: WeatherAPIResponse) -> String {
"\(initial) \(separator) \(constructUVString(from: response))"
}
func constructUVString(from response: WeatherAPIResponse) -> String {
let currentHour = Calendar.current.component(.hour, from: Date())
let currentUVIndex = response.getHourlyUVIndex(hour: currentHour)
return "UV Index: \(currentUVIndex)"
}
}
================================================
FILE: DatWeatherDoe/UI/Decorator/Text/WeatherTextBuilder.swift
================================================
//
// WeatherTextBuilder.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/15/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import OSLog
protocol WeatherTextBuilderType {
func build() -> String
}
final class WeatherTextBuilder: WeatherTextBuilderType {
struct Options {
let isWeatherConditionAsTextEnabled: Bool
let conditionPosition: WeatherConditionPosition
let valueSeparator: String
let temperatureOptions: TemperatureTextBuilder.Options
let isShowingHumidity: Bool
let isShowingUVIndex: Bool
}
private let response: WeatherAPIResponse
private let options: Options
private let logger: Logger
init(
response: WeatherAPIResponse,
options: Options,
logger: Logger
) {
self.response = response
self.options = options
self.logger = logger
}
func build() -> String {
let finalString = appendTemperatureAsText() |>
appendUVIndex |>
appendHumidityText |>
buildWeatherConditionAsText
return finalString
}
private func appendTemperatureAsText() -> String {
TemperatureTextBuilder(
response: response,
options: options.temperatureOptions,
temperatureCreator: TemperatureWithDegreesCreator()
).build() ?? ""
}
private func appendUVIndex(initial: String) -> String {
guard options.isShowingUVIndex else { return initial }
return UVIndexTextBuilder(
initial: initial,
separator: options.valueSeparator
).build(from: response)
}
private func appendHumidityText(initial: String) -> String {
guard options.isShowingHumidity else { return initial }
return HumidityTextBuilder(
initial: initial,
valueSeparator: options.valueSeparator,
humidity: response.humidity,
logger: logger
).build()
}
private func buildWeatherConditionAsText(initial: String) -> String {
guard options.isWeatherConditionAsTextEnabled else { return initial }
let weatherCondition = WeatherConditionBuilder(response: response).build()
let weatherConditionText = WeatherConditionTextMapper().map(weatherCondition)
let combinedString = options.conditionPosition == .beforeTemperature ?
[weatherConditionText, initial] :
[initial, weatherConditionText.lowercased()]
return combinedString
.compactMap { $0 }
.joined(separator: ", ")
}
}
precedencegroup ForwardPipe {
associativity: left
}
infix operator |>: ForwardPipe
private func |> <T, U>(value: T, function: (T) -> U) -> U {
function(value)
}
================================================
FILE: DatWeatherDoe/UI/Decorator/WeatherDataBuilder.swift
================================================
//
// WeatherDataBuilder.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 5/22/21.
// Copyright © 2021 Inder Dhir. All rights reserved.
//
import Foundation
import OSLog
protocol WeatherDataBuilderType: AnyObject {
func build() -> WeatherData
}
final class WeatherDataBuilder: WeatherDataBuilderType {
struct Options {
let unit: MeasurementUnit
let showWeatherIcon: Bool
let textOptions: WeatherTextBuilder.Options
}
private let response: WeatherAPIResponse
private let options: WeatherDataBuilder.Options
private let logger: Logger
init(
response: WeatherAPIResponse,
options: WeatherDataBuilder.Options,
logger: Logger
) {
self.response = response
self.options = options
self.logger = logger
}
func build() -> WeatherData {
.init(
showWeatherIcon: options.showWeatherIcon,
textualRepresentation: buildTextualRepresentation(),
weatherCondition: buildWeatherCondition(),
response: response
)
}
private func buildTextualRepresentation() -> String {
WeatherTextBuilder(
response: response,
options: options.textOptions,
logger: logger
).build()
}
private func buildWeatherCondition() -> WeatherCondition {
WeatherConditionBuilder(response: response).build()
}
}
================================================
FILE: DatWeatherDoe/UI/Forecaster/WeatherForecaster.swift
================================================
//
// WeatherForecaster.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 1/11/22.
// Copyright © 2022 Inder Dhir. All rights reserved.
//
import Cocoa
import Foundation
protocol WeatherForecasterType {
func seeForecastForCity()
}
final class WeatherForecaster: WeatherForecasterType {
private let fullWeatherUrl = URL(string: "https://www.weatherapi.com/weather/")!
func seeForecastForCity() {
NSWorkspace.shared.open(fullWeatherUrl)
}
}
================================================
FILE: DatWeatherDoe/UI/Menu Bar/CustomButton.swift
================================================
//
// CustomButton.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/21/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import SwiftUI
struct CustomButton: View {
let text: LocalizedStringKey
let textColor: Color
let shortcutKey: KeyEquivalent
let onClick: () -> Void
init(
text: LocalizedStringKey,
textColor: Color = Color.primary,
shortcutKey: KeyEquivalent,
onClick: @escaping () -> Void
) {
self.text = text
self.textColor = textColor
self.shortcutKey = shortcutKey
self.onClick = onClick
}
var body: some View {
Button(action: onClick) {
Text(text)
.foregroundStyle(textColor)
.frame(width: 110, height: 22)
}.keyboardShortcut(KeyboardShortcut(shortcutKey))
}
}
#Preview {
CustomButton(text: "See Full Weather", shortcutKey: "f") {}
}
================================================
FILE: DatWeatherDoe/UI/Menu Bar/DropdownIcon.swift
================================================
//
// DropdownIcon.swift
// DatWeatherDoe
//
// Created by Markus Mayer on 2022-11-21.
// Copyright © 2022 Markus Mayer.
//
// Icons used by the dropdown menu
enum DropdownIcon {
case location
case thermometer
case sun
case wind
case uvIndexAndAirQualityText
var symbolName: String {
switch self {
case .location:
"location.north.circle"
case .thermometer:
"thermometer.snowflake.circle"
case .sun:
"sun.horizon.circle"
case .wind:
"wind.circle"
case .uvIndexAndAirQualityText:
"sun.max.circle"
}
}
var accessibilityLabel: String {
switch self {
case .location:
"Location"
case .thermometer:
"Temperature"
case .sun:
"Sunrise and Sunset"
case .wind:
"Wind data"
case .uvIndexAndAirQualityText:
"UV Index and Air Quality Index"
}
}
}
================================================
FILE: DatWeatherDoe/UI/Menu Bar/MenuOptionsView.swift
================================================
//
// MenuOptionsView.swift
// DatWeatherDoe
//
// Created by Inder Dhir on 6/21/24.
// Copyright © 2024 Inder Dhir. All rights reserved.
//
import SwiftUI
struct MenuOptionData {
let locationText: String
let weatherText: String
let sunriseSunsetText: String
let tempHumidityWindText: String
let uvIndexAndAirQualityText: String
}
struct MenuOptionsView: View {
let data: MenuOptionData?
let onSeeWeather: () -> Void
let onRefresh: () -> Void
var body: some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading) {
NonInteractiveMenuOptionView(icon: .location, text: data?.locationText)
NonInteractive
gitextract_t2ij2trf/ ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── lint.yml ├── .gitignore ├── .swiftformat ├── .swiftlint.yml ├── CONTRIBUTING.md ├── DatWeatherDoe/ │ ├── API/ │ │ ├── NetworkClient.swift │ │ ├── Response/ │ │ │ ├── AirQuality.swift │ │ │ ├── ForecastData.swift │ │ │ ├── ForecastTemperatureData.swift │ │ │ ├── SunriseSunsetData.swift │ │ │ ├── TemperatureData.swift │ │ │ ├── WeatherAPIResponse.swift │ │ │ ├── WeatherAPIResponseParser.swift │ │ │ └── WindData.swift │ │ ├── WeatherData.swift │ │ └── WeatherError.swift │ ├── Config/ │ │ ├── APIKeyParser.swift │ │ ├── ConfigManager.swift │ │ └── ConfigOptions.swift │ ├── DatWeatherDoeApp.swift │ ├── Localization/ │ │ └── en.xcloc/ │ │ ├── Localized Contents/ │ │ │ └── en.xliff │ │ ├── Source Contents/ │ │ │ └── DatWeatherDoe/ │ │ │ ├── Resources/ │ │ │ │ └── en.lproj/ │ │ │ │ └── InfoPlist.strings │ │ │ └── UI/ │ │ │ └── Base.lproj/ │ │ │ └── MainMenu.xib │ │ └── contents.json │ ├── Reachability/ │ │ └── WeatherReachability.swift │ ├── Resources/ │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── DatWeatherDoe.entitlements │ │ ├── DevelopmentAssets/ │ │ │ └── TestData.swift │ │ ├── Info.plist │ │ └── Localization/ │ │ └── Localizable.xcstrings │ ├── UI/ │ │ ├── Configure/ │ │ │ ├── ConfigureOptionsView.swift │ │ │ ├── ConfigureUnitOptionsView.swift │ │ │ ├── ConfigureValueSeparatorOptionsView.swift │ │ │ ├── ConfigureView.swift │ │ │ ├── ConfigureViewModel.swift │ │ │ ├── ConfigureWeatherOptionsView.swift │ │ │ └── Options/ │ │ │ ├── MeasurementUnit.swift │ │ │ ├── RefreshInterval.swift │ │ │ ├── TemperatureUnit.swift │ │ │ ├── WeatherConditionPosition.swift │ │ │ └── WeatherSource.swift │ │ ├── Decorator/ │ │ │ ├── Condition/ │ │ │ │ ├── WeatherCondition.swift │ │ │ │ ├── WeatherConditionBuilder.swift │ │ │ │ └── WeatherConditionTextMapper.swift │ │ │ ├── Text/ │ │ │ │ ├── HumidityTextBuilder.swift │ │ │ │ ├── SunriseAndSunsetTextBuilder.swift │ │ │ │ ├── Temperature/ │ │ │ │ │ ├── TemperatureForecastTextBuilder.swift │ │ │ │ │ ├── TemperatureFormatter.swift │ │ │ │ │ ├── TemperatureTextBuilder.swift │ │ │ │ │ └── TemperatureWithDegreesCreator.swift │ │ │ │ ├── UVIndexTextBuilder.swift │ │ │ │ └── WeatherTextBuilder.swift │ │ │ └── WeatherDataBuilder.swift │ │ ├── Forecaster/ │ │ │ └── WeatherForecaster.swift │ │ ├── Menu Bar/ │ │ │ ├── CustomButton.swift │ │ │ ├── DropdownIcon.swift │ │ │ ├── MenuOptionsView.swift │ │ │ ├── MenuView.swift │ │ │ ├── NonInteractiveMenuOptionView.swift │ │ │ └── WindSpeedFormatter.swift │ │ └── Status Bar/ │ │ └── StatusBarView.swift │ └── ViewModel/ │ ├── Parser/ │ │ ├── CityWeatherResultParser.swift │ │ └── ZipCodeWeatherResultParser.swift │ ├── Repository/ │ │ ├── Coordinates/ │ │ │ ├── LocationCoordinatesWeatherRepository.swift │ │ │ ├── LocationParser.swift │ │ │ └── LocationValidator.swift │ │ ├── System/ │ │ │ ├── SystemLocationFetcher.swift │ │ │ ├── SystemLocationWeatherRepository.swift │ │ │ └── Task+Retry.swift │ │ ├── WeatherRepositoryFactory.swift │ │ ├── WeatherRepositoryType.swift │ │ ├── WeatherURLBuilder.swift │ │ └── WeatherValidatorType.swift │ ├── WeatherDataFormatter.swift │ ├── WeatherViewModel.swift │ └── WeatherViewModelType.swift ├── DatWeatherDoe.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm/ │ │ └── Package.resolved │ └── xcshareddata/ │ └── xcschemes/ │ └── DatWeatherDoe.xcscheme ├── DatWeatherDoeTests/ │ ├── API/ │ │ └── Repository/ │ │ ├── Location/ │ │ │ └── Coordinates/ │ │ │ └── LocationValidatorTests.swift │ │ └── WeatherURLBuilderTests.swift │ ├── DatWeatherDoe.xctestplan │ └── UI/ │ └── Configure/ │ └── Options/ │ ├── RefreshIntervalTests.swift │ ├── TemperatureUnitTests.swift │ └── WeatherSourceTests.swift ├── LICENSE └── README.md
Condensed preview — 91 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (331K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 65,
"preview": "# These are supported funding model platforms\n\ngithub: inderdhir\n"
},
{
"path": ".github/workflows/lint.yml",
"chars": 393,
"preview": "name: SwiftLint\n\non:\n pull_request:\n paths:\n - '.github/workflows/swiftlint.yml'\n - '.swiftlint.yml'\n "
},
{
"path": ".gitignore",
"chars": 756,
"preview": "# Xcode\n.DS_Store\n\n# Backup files\n*~\n\n# JetBrains\n.idea/\n\n## Build generated\nbuild/\nDerivedData\n\n## Various settings\n*.p"
},
{
"path": ".swiftformat",
"chars": 24,
"preview": "--disable trailingCommas"
},
{
"path": ".swiftlint.yml",
"chars": 1623,
"preview": "disabled_rules: # rule identifiers to exclude from running\n - colon\n - comma\n - control_statement\n - trailing_whites"
},
{
"path": "CONTRIBUTING.md",
"chars": 346,
"preview": "# Contributing\n\nContributions / pull requests are welcome!\n\nPlease note that the goal of this project to provide a light"
},
{
"path": "DatWeatherDoe/API/NetworkClient.swift",
"chars": 512,
"preview": "//\n// NetworkClient.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/14/22.\n// Copyright © 2022 Inder Dhir. A"
},
{
"path": "DatWeatherDoe/API/Response/AirQuality.swift",
"chars": 876,
"preview": "//\n// AirQuality.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 7/1/24.\n// Copyright © 2024 Inder Dhir. All r"
},
{
"path": "DatWeatherDoe/API/Response/ForecastData.swift",
"chars": 710,
"preview": "//\n// ForecastData.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/23/24.\n// Copyright © 2024 Inder Dhir. Al"
},
{
"path": "DatWeatherDoe/API/Response/ForecastTemperatureData.swift",
"chars": 519,
"preview": "//\n// ForecastTemperatureData.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/23/24.\n// Copyright © 2024 Ind"
},
{
"path": "DatWeatherDoe/API/Response/SunriseSunsetData.swift",
"chars": 254,
"preview": "//\n// SunriseSunsetData.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/22/24.\n// Copyright © 2024 Inder Dhi"
},
{
"path": "DatWeatherDoe/API/Response/TemperatureData.swift",
"chars": 573,
"preview": "//\n// TemperatureData.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/23/24.\n// Copyright © 2024 Inder Dhir."
},
{
"path": "DatWeatherDoe/API/Response/WeatherAPIResponse.swift",
"chars": 3858,
"preview": "//\n// WeatherAPIResponse.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 2/3/18.\n// Copyright © 2018 Inder Dhi"
},
{
"path": "DatWeatherDoe/API/Response/WeatherAPIResponseParser.swift",
"chars": 482,
"preview": "//\n// WeatherAPIResponseParser.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/10/22.\n// Copyright © 2022 In"
},
{
"path": "DatWeatherDoe/API/Response/WindData.swift",
"chars": 425,
"preview": "//\n// WindData.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/23/24.\n// Copyright © 2024 Inder Dhir. All ri"
},
{
"path": "DatWeatherDoe/API/WeatherData.swift",
"chars": 333,
"preview": "//\n// WeatherData.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 5/22/21.\n// Copyright © 2021 Inder Dhir. All"
},
{
"path": "DatWeatherDoe/API/WeatherError.swift",
"chars": 643,
"preview": "//\n// WeatherError.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 5/22/21.\n// Copyright © 2021 Inder Dhir. Al"
},
{
"path": "DatWeatherDoe/Config/APIKeyParser.swift",
"chars": 434,
"preview": "//\n// APIKeyParser.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/11/22.\n// Copyright © 2022 Inder Dhir. Al"
},
{
"path": "DatWeatherDoe/Config/ConfigManager.swift",
"chars": 3047,
"preview": "//\n// ConfigManager.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/29/16.\n// Copyright © 2016 Inder Dhir. A"
},
{
"path": "DatWeatherDoe/Config/ConfigOptions.swift",
"chars": 460,
"preview": "//\n// ConfigOptions.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 8/3/23.\n// Copyright © 2023 Inder Dhir. Al"
},
{
"path": "DatWeatherDoe/DatWeatherDoeApp.swift",
"chars": 2560,
"preview": "//\n// DatWeatherDoeApp.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/17/24.\n// Copyright © 2024 Inder Dhir"
},
{
"path": "DatWeatherDoe/Localization/en.xcloc/Localized Contents/en.xliff",
"chars": 33447,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" xmlns:xsi=\"http://www.w3.org"
},
{
"path": "DatWeatherDoe/Localization/en.xcloc/Source Contents/DatWeatherDoe/Resources/en.lproj/InfoPlist.strings",
"chars": 336,
"preview": "/* Bundle name */\n\"CFBundleName\" = \"DatWeatherDoe\";\n/* Copyright (human-readable) */\n\"NSHumanReadableCopyright\" = \"Copyr"
},
{
"path": "DatWeatherDoe/Localization/en.xcloc/Source Contents/DatWeatherDoe/UI/Base.lproj/MainMenu.xib",
"chars": 49798,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3"
},
{
"path": "DatWeatherDoe/Localization/en.xcloc/contents.json",
"chars": 265,
"preview": "{\n \"developmentRegion\" : \"en\",\n \"project\" : \"DatWeatherDoe.xcodeproj\",\n \"targetLocale\" : \"en\",\n \"toolInfo\" : {\n \""
},
{
"path": "DatWeatherDoe/Reachability/WeatherReachability.swift",
"chars": 1475,
"preview": "//\n// WeatherReachability.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/11/22.\n// Copyright © 2022 Inder D"
},
{
"path": "DatWeatherDoe/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1207,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"16.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\",\n \"size\" : \"16x16\"\n"
},
{
"path": "DatWeatherDoe/Resources/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "DatWeatherDoe/Resources/DatWeatherDoe.entitlements",
"chars": 181,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "DatWeatherDoe/Resources/DevelopmentAssets/TestData.swift",
"chars": 1042,
"preview": "//\n// TestData.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/25/24.\n// Copyright © 2024 Inder Dhir. All ri"
},
{
"path": "DatWeatherDoe/Resources/Info.plist",
"chars": 1442,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "DatWeatherDoe/Resources/Localization/Localizable.xcstrings",
"chars": 41393,
"preview": "{\n \"sourceLanguage\" : \"en\",\n \"strings\" : {\n \"\" : {\n\n },\n \"[latitude],[longitude]\" : {\n \"comment\" : \"Plac"
},
{
"path": "DatWeatherDoe/UI/Configure/ConfigureOptionsView.swift",
"chars": 3296,
"preview": "//\n// ConfigureOptionsView.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 8/3/23.\n// Copyright © 2023 Inder D"
},
{
"path": "DatWeatherDoe/UI/Configure/ConfigureUnitOptionsView.swift",
"chars": 826,
"preview": "//\n// ConfigureUnitOptionsView.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 8/8/23.\n// Copyright © 2023 Ind"
},
{
"path": "DatWeatherDoe/UI/Configure/ConfigureValueSeparatorOptionsView.swift",
"chars": 781,
"preview": "//\n// ConfigureValueSeparatorOptionsView.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 8/8/23.\n// Copyright "
},
{
"path": "DatWeatherDoe/UI/Configure/ConfigureView.swift",
"chars": 1408,
"preview": "//\n// ConfigureView.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 2/18/22.\n// Copyright © 2022 Inder Dhir. A"
},
{
"path": "DatWeatherDoe/UI/Configure/ConfigureViewModel.swift",
"chars": 3759,
"preview": "//\n// ConfigureViewModel.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 3/20/22.\n// Copyright © 2022 Inder Dh"
},
{
"path": "DatWeatherDoe/UI/Configure/ConfigureWeatherOptionsView.swift",
"chars": 1369,
"preview": "//\n// ConfigureWeatherOptionsView.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 8/8/23.\n// Copyright © 2023 "
},
{
"path": "DatWeatherDoe/UI/Configure/Options/MeasurementUnit.swift",
"chars": 492,
"preview": "//\n// MeasurementUnit.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 2/7/23.\n// Copyright © 2023 Inder Dhir. "
},
{
"path": "DatWeatherDoe/UI/Configure/Options/RefreshInterval.swift",
"chars": 711,
"preview": "//\n// RefreshInterval.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 5/23/21.\n// Copyright © 2021 Inder Dhir."
},
{
"path": "DatWeatherDoe/UI/Configure/Options/TemperatureUnit.swift",
"chars": 543,
"preview": "//\n// TemperatureUnit.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 8/8/23.\n// Copyright © 2023 Inder Dhir. "
},
{
"path": "DatWeatherDoe/UI/Configure/Options/WeatherConditionPosition.swift",
"chars": 533,
"preview": "//\n// WeatherConditionPosition.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 3/27/24.\n// Copyright © 2024 In"
},
{
"path": "DatWeatherDoe/UI/Configure/Options/WeatherSource.swift",
"chars": 764,
"preview": "//\n// WeatherSource.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 5/23/21.\n// Copyright © 2021 Inder Dhir. A"
},
{
"path": "DatWeatherDoe/UI/Decorator/Condition/WeatherCondition.swift",
"chars": 1720,
"preview": "//\n// WeatherCondition.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/29/16.\n// Copyright © 2016 Inder Dhir"
},
{
"path": "DatWeatherDoe/UI/Decorator/Condition/WeatherConditionBuilder.swift",
"chars": 1397,
"preview": "//\n// WeatherConditionBuilder.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/11/22.\n// Copyright © 2022 Ind"
},
{
"path": "DatWeatherDoe/UI/Decorator/Condition/WeatherConditionTextMapper.swift",
"chars": 1290,
"preview": "//\n// WeatherConditionTextMapper.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/11/22.\n// Copyright © 2022 "
},
{
"path": "DatWeatherDoe/UI/Decorator/Text/HumidityTextBuilder.swift",
"chars": 1534,
"preview": "//\n// HumidityTextBuilder.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/15/22.\n// Copyright © 2022 Inder D"
},
{
"path": "DatWeatherDoe/UI/Decorator/Text/SunriseAndSunsetTextBuilder.swift",
"chars": 652,
"preview": "//\n// SunriseAndSunsetTextBuilder.swift\n// DatWeatherDoe\n//\n// Created by Markus Markus on 2022-07-26.\n// Copyright "
},
{
"path": "DatWeatherDoe/UI/Decorator/Text/Temperature/TemperatureForecastTextBuilder.swift",
"chars": 5062,
"preview": "//\n// TemperatureForecastTextBuilder.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/25/22.\n// Copyright © 2"
},
{
"path": "DatWeatherDoe/UI/Decorator/Text/Temperature/TemperatureFormatter.swift",
"chars": 1411,
"preview": "//\n// TemperatureFormatter.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/12/22.\n// Copyright © 2022 Inder "
},
{
"path": "DatWeatherDoe/UI/Decorator/Text/Temperature/TemperatureTextBuilder.swift",
"chars": 2256,
"preview": "//\n// TemperatureTextBuilder.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/15/22.\n// Copyright © 2022 Inde"
},
{
"path": "DatWeatherDoe/UI/Decorator/Text/Temperature/TemperatureWithDegreesCreator.swift",
"chars": 3168,
"preview": "//\n// TemperatureWithDegreesCreator.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 8/9/23.\n// Copyright © 202"
},
{
"path": "DatWeatherDoe/UI/Decorator/Text/UVIndexTextBuilder.swift",
"chars": 804,
"preview": "//\n// UVIndexTextBuilder.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 10/18/24.\n// Copyright © 2024 Inder D"
},
{
"path": "DatWeatherDoe/UI/Decorator/Text/WeatherTextBuilder.swift",
"chars": 2785,
"preview": "//\n// WeatherTextBuilder.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/15/22.\n// Copyright © 2022 Inder Dh"
},
{
"path": "DatWeatherDoe/UI/Decorator/WeatherDataBuilder.swift",
"chars": 1433,
"preview": "//\n// WeatherDataBuilder.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 5/22/21.\n// Copyright © 2021 Inder Dh"
},
{
"path": "DatWeatherDoe/UI/Forecaster/WeatherForecaster.swift",
"chars": 475,
"preview": "//\n// WeatherForecaster.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/11/22.\n// Copyright © 2022 Inder Dhi"
},
{
"path": "DatWeatherDoe/UI/Menu Bar/CustomButton.swift",
"chars": 932,
"preview": "//\n// CustomButton.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/21/24.\n// Copyright © 2024 Inder Dhir. Al"
},
{
"path": "DatWeatherDoe/UI/Menu Bar/DropdownIcon.swift",
"chars": 1008,
"preview": "//\n// DropdownIcon.swift\n// DatWeatherDoe\n//\n// Created by Markus Mayer on 2022-11-21.\n// Copyright © 2022 Markus Ma"
},
{
"path": "DatWeatherDoe/UI/Menu Bar/MenuOptionsView.swift",
"chars": 1769,
"preview": "//\n// MenuOptionsView.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/21/24.\n// Copyright © 2024 Inder Dhir."
},
{
"path": "DatWeatherDoe/UI/Menu Bar/MenuView.swift",
"chars": 1512,
"preview": "//\n// MenuView.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/23/24.\n// Copyright © 2024 Inder Dhir. All ri"
},
{
"path": "DatWeatherDoe/UI/Menu Bar/NonInteractiveMenuOptionView.swift",
"chars": 935,
"preview": "//\n// NonInteractiveMenuOptionView.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/21/24.\n// Copyright © 202"
},
{
"path": "DatWeatherDoe/UI/Menu Bar/WindSpeedFormatter.swift",
"chars": 2670,
"preview": "//\n// WindSpeedFormatter.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 10/12/23.\n// Copyright © 2023 Inder D"
},
{
"path": "DatWeatherDoe/UI/Status Bar/StatusBarView.swift",
"chars": 1483,
"preview": "//\n// StatusBarView.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/23/24.\n// Copyright © 2024 Inder Dhir. A"
},
{
"path": "DatWeatherDoe/ViewModel/Parser/CityWeatherResultParser.swift",
"chars": 754,
"preview": "//\n// CityWeatherResultParser.swift\n// DatWeatherDoe\n//\n// Created by preckrasno on 14.02.2023.\n// Copyright © 2023 "
},
{
"path": "DatWeatherDoe/ViewModel/Parser/ZipCodeWeatherResultParser.swift",
"chars": 744,
"preview": "//\n// ZipCodeWeatherResultParser.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/17/22.\n// Copyright © 2022 "
},
{
"path": "DatWeatherDoe/ViewModel/Repository/Coordinates/LocationCoordinatesWeatherRepository.swift",
"chars": 1487,
"preview": "//\n// LocationCoordinatesWeatherRepository.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/14/22.\n// Copyrig"
},
{
"path": "DatWeatherDoe/ViewModel/Repository/Coordinates/LocationParser.swift",
"chars": 1288,
"preview": "//\n// LocationParser.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/10/22.\n// Copyright © 2022 Inder Dhir. "
},
{
"path": "DatWeatherDoe/ViewModel/Repository/Coordinates/LocationValidator.swift",
"chars": 519,
"preview": "//\n// LocationValidator.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/13/22.\n// Copyright © 2022 Inder Dhi"
},
{
"path": "DatWeatherDoe/ViewModel/Repository/System/SystemLocationFetcher.swift",
"chars": 4419,
"preview": "//\n// SystemLocationFetcher.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/9/22.\n// Copyright © 2022 Inder "
},
{
"path": "DatWeatherDoe/ViewModel/Repository/System/SystemLocationWeatherRepository.swift",
"chars": 1183,
"preview": "//\n// SystemLocationWeatherRepository.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/15/22.\n// Copyright © "
},
{
"path": "DatWeatherDoe/ViewModel/Repository/System/Task+Retry.swift",
"chars": 1042,
"preview": "//\n// Task+Retry.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 10/13/25.\n// Copyright © 2025 Inder Dhir. All"
},
{
"path": "DatWeatherDoe/ViewModel/Repository/WeatherRepositoryFactory.swift",
"chars": 1377,
"preview": "//\n// WeatherRepositoryFactory.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 8/7/23.\n// Copyright © 2023 Ind"
},
{
"path": "DatWeatherDoe/ViewModel/Repository/WeatherRepositoryType.swift",
"chars": 269,
"preview": "////\n//// WeatherRepositoryType.swift\n//// DatWeatherDoe\n////\n//// Created by Inder Dhir on 1/30/16.\n//// Copyright "
},
{
"path": "DatWeatherDoe/ViewModel/Repository/WeatherURLBuilder.swift",
"chars": 1423,
"preview": "//\n// WeatherURLBuilder.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/10/22.\n// Copyright © 2022 Inder Dhi"
},
{
"path": "DatWeatherDoe/ViewModel/Repository/WeatherValidatorType.swift",
"chars": 223,
"preview": "//\n// WeatherValidatorType.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/10/22.\n// Copyright © 2022 Inder "
},
{
"path": "DatWeatherDoe/ViewModel/WeatherDataFormatter.swift",
"chars": 2799,
"preview": "//\n// WeatherDataFormatter.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 6/24/24.\n// Copyright © 2024 Inder "
},
{
"path": "DatWeatherDoe/ViewModel/WeatherViewModel.swift",
"chars": 5669,
"preview": "//\n// WeatherViewModel.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/9/22.\n// Copyright © 2022 Inder Dhir."
},
{
"path": "DatWeatherDoe/ViewModel/WeatherViewModelType.swift",
"chars": 342,
"preview": "//\n// WeatherViewModelType.swift\n// DatWeatherDoe\n//\n// Created by Inder Dhir on 1/9/22.\n// Copyright © 2022 Inder D"
},
{
"path": "DatWeatherDoe.xcodeproj/project.pbxproj",
"chars": 57979,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "DatWeatherDoe.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "DatWeatherDoe.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "DatWeatherDoe.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
"chars": 3733,
"preview": "{\n \"originHash\" : \"cf72def9a17d22a974c73e8b0f0a4d80bccc1e736e0ce0e89a85e671a37bb5b6\",\n \"pins\" : [\n {\n \"identit"
},
{
"path": "DatWeatherDoe.xcodeproj/xcshareddata/xcschemes/DatWeatherDoe.xcscheme",
"chars": 3061,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"2600\"\n version = \"1.7\">\n <BuildAction\n "
},
{
"path": "DatWeatherDoeTests/API/Repository/Location/Coordinates/LocationValidatorTests.swift",
"chars": 592,
"preview": "//\n// LocationValidatorTests.swift\n// DatWeatherDoeTests\n//\n// Created by Inder Dhir on 1/26/22.\n// Copyright © 2022"
},
{
"path": "DatWeatherDoeTests/API/Repository/WeatherURLBuilderTests.swift",
"chars": 796,
"preview": "//\n// WeatherURLBuilderTests.swift\n// DatWeatherDoeTests\n//\n// Created by Inder Dhir on 1/25/22.\n// Copyright © 2022"
},
{
"path": "DatWeatherDoeTests/DatWeatherDoe.xctestplan",
"chars": 699,
"preview": "{\n \"configurations\" : [\n {\n \"id\" : \"8E1BA5A7-5E49-4CF1-8D13-2F0DA4BF0476\",\n \"name\" : \"Main Config\",\n "
},
{
"path": "DatWeatherDoeTests/UI/Configure/Options/RefreshIntervalTests.swift",
"chars": 852,
"preview": "//\n// RefreshIntervalTests.swift\n// DatWeatherDoeTests\n//\n// Created by Inder Dhir on 2/12/22.\n// Copyright © 2022 I"
},
{
"path": "DatWeatherDoeTests/UI/Configure/Options/TemperatureUnitTests.swift",
"chars": 850,
"preview": "//\n// TemperatureUnitTests.swift\n// DatWeatherDoeTests\n//\n// Created by Inder Dhir on 2/12/22.\n// Copyright © 2022 I"
},
{
"path": "DatWeatherDoeTests/UI/Configure/Options/WeatherSourceTests.swift",
"chars": 750,
"preview": "//\n// WeatherSourceTests.swift\n// DatWeatherDoeTests\n//\n// Created by Inder Dhir on 2/12/22.\n// Copyright © 2022 Ind"
},
{
"path": "LICENSE",
"chars": 10252,
"preview": "Apache License,\nVersion 2.0 Apache License Version 2.0,\nJanuary 2004 http://www.apache.org/licenses/\n\nTERMS AND CONDITIO"
},
{
"path": "README.md",
"chars": 1272,
"preview": "# [<img src=\"logo.png\" width=\"20\"/>](image.png) DatWeatherDoe\n\n> **Note**\nOpenWeatherMap API 2.5 support is ending in Ju"
}
]
About this extraction
This page contains the full source code of the inderdhir/DatWeatherDoe GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 91 files (296.0 KB), approximately 79.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.