├── Clocks
├── Assets.xcassets
│ ├── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── AppIcon-20.0x20.0@2x.png
│ │ ├── AppIcon-20.0x20.0@3x.png
│ │ ├── AppIcon-29.0x29.0@2x.png
│ │ ├── AppIcon-29.0x29.0@3x.png
│ │ ├── AppIcon-40.0x40.0@2x.png
│ │ ├── AppIcon-40.0x40.0@3x.png
│ │ ├── AppIcon-60.0x60.0@2x.png
│ │ ├── AppIcon-60.0x60.0@3x.png
│ │ ├── AppIcon-1024.0x1024.0@1x.png
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── ClocksApp.swift
├── Extensions
│ ├── Date.swift
│ ├── DateFormatter.swift
│ ├── EnvironmentValues.swift
│ ├── Color.swift
│ └── UIDevice.swift
├── Clocks.entitlements
├── Protocol
│ └── ClockWidget.swift
├── Utils
│ ├── WidgetDetailConfigFields.swift
│ ├── DefaultClockStyle.swift
│ ├── UserDefaults.swift
│ ├── Image.swift
│ └── DeviceWidget.swift
├── Views
│ ├── ContentView.swift
│ ├── Helper
│ │ ├── WidgetBackground.swift
│ │ ├── WidgetFamilyProvider.swift
│ │ ├── ImagePicker.swift
│ │ └── WidgetPreviews.swift
│ ├── CurrentWidget
│ │ ├── CurrentClockPreviewView.swift
│ │ └── CurrentClocksView.swift
│ ├── WidgetList
│ │ ├── WidgetListPreviewView.swift
│ │ └── WidgetListView.swift
│ ├── WidgetViews
│ │ ├── ClockWidgetBundleView.swift
│ │ ├── SimplePicture.swift
│ │ ├── SimpleClockView.swift
│ │ └── SimpleClock1View.swift
│ └── WidgetDetail
│ │ ├── ClocksDetailView.swift
│ │ ├── ClockDetailImageCropView.swift
│ │ └── ClockDetailImageEditView.swift
├── ViewModels
│ ├── ConfigKeysViewModel.swift
│ ├── StorableClockConfig.swift
│ └── ClockConfigViewModel.swift
└── Info.plist
├── ClocksWidget
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── WidgetBackground.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── ClocksWidget.swift
└── ClocksWidgetIntents.intentdefinition
├── Clocks.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── Clocks.xcscheme
│ │ └── ClocksWidgetExtension.xcscheme
└── project.pbxproj
├── README.md
├── ClocksWidgetExtension.entitlements
├── ClocksWidgetIntents
├── ClocksWidgetIntents.entitlements
├── IntentHandler.swift
└── Info.plist
├── LICENSE
└── .gitignore
/Clocks/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ClocksWidget/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Clocks/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-20.0x20.0@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-20.0x20.0@2x.png
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-20.0x20.0@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-20.0x20.0@3x.png
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-29.0x29.0@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-29.0x29.0@2x.png
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-29.0x29.0@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-29.0x29.0@3x.png
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-40.0x40.0@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-40.0x40.0@2x.png
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-40.0x40.0@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-40.0x40.0@3x.png
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-60.0x60.0@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-60.0x60.0@2x.png
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-60.0x60.0@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-60.0x60.0@3x.png
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.0x1024.0@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangyu1818/clocks-widget/HEAD/Clocks/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.0x1024.0@1x.png
--------------------------------------------------------------------------------
/Clocks.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 时钟小组件
2 |
3 | [笔记](https://github.com/zhangyu1818/blog/issues/39)
4 |
5 | [AppStore](https://apps.apple.com/cn/app/时钟小组件-简约时钟/id1548485543)
6 |
7 | 时间过于久远,超过苹果的多年不更新政策,已经被强制下架了。时过境迁,API也应该发生了很多变化,此仓库代码只做留念了。
8 |
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ClocksWidget/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ClocksWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Clocks/ClocksApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClocksApp.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2020/12/30.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct ClocksApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Clocks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Clocks/Extensions/Date.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/11.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Date {
11 | var endDayOfMonth: Int {
12 | let range = Calendar.current.range(of: .day, in: .month, for: self)!
13 | return range.count
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Clocks/Clocks.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.zhangyu1818.clocks
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ClocksWidgetExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.zhangyu1818.clocks
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ClocksWidgetIntents/ClocksWidgetIntents.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.zhangyu1818.clocks
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Clocks/Protocol/ClockWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClockWidget.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/11.
6 | //
7 |
8 | import SwiftUI
9 | import WidgetKit
10 |
11 | protocol ClockWidget {
12 | static var clockName: String { get }
13 | static var nonConfigurableFields: [String] { get }
14 |
15 | var config: WidgetClockConfig { get }
16 |
17 | init(date: Date, config widgetConfig: WidgetClockConfig)
18 | }
19 |
--------------------------------------------------------------------------------
/Clocks/Utils/WidgetDetailConfigFields.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NonConfigurableFields.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/2/5.
6 | //
7 |
8 | enum WidgetDetailConfigFields {
9 | static var textColor = "TEXT_COLOR"
10 | static var backgroundColor = "BACKGROUND_COLOR"
11 | static var is12Hour = "IS_12_HOUR"
12 | static var showDateInfo = "SHOW_DATE_INFO"
13 | static var backgroundImage = "BACKGROUND_IMAGE"
14 | }
15 |
--------------------------------------------------------------------------------
/Clocks/Views/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2020/12/30.
6 | //
7 | import SwiftUI
8 |
9 | struct ContentView: View {
10 | var body: some View {
11 | NavigationView {
12 | WidgetListView()
13 | }
14 | }
15 | }
16 |
17 | struct ContentView_Previews: PreviewProvider {
18 | static var previews: some View {
19 | ContentView()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Clocks/Extensions/DateFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateFormatter+TimeFormatter.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2020/12/30.
6 | //
7 |
8 | import Foundation
9 |
10 | extension DateFormatter {
11 | static func timeFormatter(_ dateFormat: String) -> DateFormatter {
12 | let formatter = DateFormatter()
13 | formatter.dateFormat = dateFormat
14 | formatter.locale = Locale(identifier: "zh_CN")
15 | return formatter
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Clocks/Views/Helper/WidgetBackground.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WidgetBackground.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/9.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WidgetBackground: View {
11 | let uiImage: UIImage?
12 | let blur: Bool
13 |
14 | var body: some View {
15 | if let image = uiImage {
16 | Image(uiImage: image)
17 | .resizable()
18 | .padding(.all, blur ? -35 : 0)
19 | .aspectRatio(contentMode: .fill)
20 | .blur(radius: blur ? 20.0 : 0)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Clocks/Utils/DefaultClockStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultClockStyle.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/12.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct DefaultStyle {
11 | var textColor: Color
12 | var backgroundColor: Color
13 | }
14 |
15 | let defaultClockStyle = [
16 | SimpleClockView.clockName: DefaultStyle(textColor: Color(hexString: "#ffffff"), backgroundColor: Color(UIColor.systemIndigo)),
17 | SimpleClock1View.clockName: DefaultStyle(textColor: Color(hexString: "#ffffff"), backgroundColor: Color(UIColor.systemTeal)),
18 | SimplePicture.clockName: DefaultStyle(textColor: Color(hexString: "#ffffff"), backgroundColor: Color.yellow),
19 | ]
20 |
--------------------------------------------------------------------------------
/Clocks/Extensions/EnvironmentValues.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EnvironmentValues.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/6.
6 | //
7 | import SwiftUI
8 | import WidgetKit
9 |
10 | enum PreviewsFamily {
11 | case small
12 | case medium
13 | case large
14 | }
15 |
16 | struct PreviewsFamilyEnvironmentKey: EnvironmentKey {
17 | static let defaultValue: WidgetFamily = .systemMedium
18 | }
19 |
20 | extension EnvironmentValues {
21 | var previewsFamily: WidgetFamily {
22 | get {
23 | return self[PreviewsFamilyEnvironmentKey]
24 | }
25 | set {
26 | self[PreviewsFamilyEnvironmentKey] = newValue
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Clocks/ViewModels/ConfigKeysViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserWidgetViewModel.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/9.
6 | //
7 | import SwiftUI
8 |
9 | /**
10 | 对已保存的小组件的configKey做管理
11 | */
12 | class ConfigKeysViewModel: ObservableObject {
13 | static let shared = ConfigKeysViewModel()
14 |
15 | @Published private(set) var configKeys: [String] {
16 | didSet {
17 | saveConfigKeys(configKeys: configKeys)
18 | }
19 | }
20 |
21 | private init() {
22 | configKeys = getSavedConfigKeys()
23 | }
24 |
25 | // 新增configKey
26 | func add(configKey: String) {
27 | configKeys.append(configKey)
28 | }
29 |
30 | // 删除configKey
31 | func delete(configKey: String) {
32 | configKeys = configKeys.filter { $0 != configKey }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Clocks/Views/Helper/WidgetFamilyProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WidgetFamilyProvider.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/6.
6 | //
7 |
8 | import SwiftUI
9 | import WidgetKit
10 |
11 | /**
12 | 将widgetFamily转为自定义的previewsFamily传递
13 | */
14 | struct WidgetFamilyProvider: View {
15 | @Environment(\.widgetFamily) private var widgetFamily
16 |
17 | let widget: Widget
18 |
19 | init(@ViewBuilder _ widget: @escaping () -> Widget) {
20 | self.widget = widget()
21 | }
22 |
23 | var body: some View {
24 | widget.environment(\.previewsFamily, widgetFamily)
25 | }
26 | }
27 |
28 | struct WidgetFamilyProvider_Previews: PreviewProvider {
29 | static var previews: some View {
30 | WidgetFamilyProvider {
31 | Text("Provider")
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Clocks/Views/CurrentWidget/CurrentClockPreviewView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CurrentClockPreviewView.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/10.
6 | //
7 |
8 | import SwiftUI
9 | import WidgetKit
10 |
11 | /**
12 | 展示已保存的Clock
13 | */
14 | struct CurrentClockPreview: View {
15 | @StateObject private var config: ClockConfigViewModel
16 |
17 | init(key: String) {
18 | let config = ClockConfigManager.shared.getConfigViewModel(key)
19 | _config = StateObject(wrappedValue: config)
20 | }
21 |
22 | var body: some View {
23 | NavigationLink(
24 | destination: ClocksDetailView(config: config.clone()) { detailConfig in
25 | ClockWidgetBundleView(date: Date(), config: detailConfig)
26 | },
27 | label: {
28 | WidgetPreviews(widgetFamily: [.systemMedium]) {
29 | ClockWidgetBundleView(date: Date(), config: config.toWidgetConfig())
30 | }
31 | }
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ClocksWidget/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | ClocksWidget
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSExtension
24 |
25 | NSExtensionPointIdentifier
26 | com.apple.widgetkit-extension
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 ZHANGYU
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Clocks/Views/CurrentWidget/CurrentClocksView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CurrentClocksView.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/9.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CurrentClocksView: View {
11 | @StateObject private var widgetConfigs = ConfigKeysViewModel.shared
12 |
13 | var body: some View {
14 | if !widgetConfigs.configKeys.isEmpty {
15 | VStack {
16 | HStack {
17 | Text("保存的小组件").font(.headline)
18 | Spacer()
19 | }
20 | ScrollView(.horizontal, showsIndicators: false) {
21 | HStack(spacing: 12) {
22 | ForEach(0 ..< widgetConfigs.configKeys.count, id: \.self) { index in
23 | let key = widgetConfigs.configKeys[index]
24 | CurrentClockPreview(key: key)
25 | }
26 | }
27 | .frame(height: 220)
28 | }
29 |
30 | Divider()
31 | }
32 | .padding(.horizontal)
33 | }
34 | }
35 | }
36 |
37 | struct CurrentClocksView_Previews: PreviewProvider {
38 | static var previews: some View {
39 | CurrentClocksView()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/ClocksWidgetIntents/IntentHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IntentHandler.swift
3 | // ClocksWidgetIntents
4 | //
5 | // Created by ZhangYu on 2021/1/9.
6 | //
7 |
8 | import Intents
9 |
10 | class IntentHandler: INExtension, ClocksWidgetIntentHandling {
11 | func provideCurrentWidgetOptionsCollection(for _: ClocksWidgetIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) {
12 | let savedConfig = getSavedConfigKeys()
13 | let widgetConfigs = savedConfig.enumerated().map { index, key in WidgetConfig(identifier: key, display: "小组件 \(index + 1)") }
14 | let collection = INObjectCollection(items: widgetConfigs)
15 |
16 | completion(collection, nil)
17 | }
18 |
19 | func defaultCurrentWidget(for _: ClocksWidgetIntent) -> WidgetConfig? {
20 | let savedConfig = getSavedConfigKeys()
21 | guard let lastConfigKey = savedConfig.last else {
22 | return nil
23 | }
24 | return WidgetConfig(identifier: lastConfigKey, display: "小组件 \(savedConfig.count)")
25 | }
26 |
27 | override func handler(for _: INIntent) -> Any {
28 | // This is the default implementation. If you want different objects to handle different intents,
29 | // you can override this and return the handler you want for that particular intent.
30 |
31 | return self
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Clocks/Views/WidgetList/WidgetListPreviewView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WidgetListPreviewView.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2020/12/30.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /**
11 | 新的组件每次需要一个不同的ConfigViewModel,所以得用一个新的View包一下
12 | */
13 | struct NewDetail: View {
14 | let clockName: String
15 |
16 | var body: some View {
17 | ClocksDetailView(config: ClockConfigManager.shared.createConfigViewModel(clockName: clockName)) { detailConfig in
18 | ClockWidgetBundleView(date: Date(), config: detailConfig)
19 | }
20 | }
21 | }
22 |
23 | struct WidgetListPreviewView: View {
24 | let title: String
25 | let content: Content
26 |
27 | init(title: String, @ViewBuilder content: @escaping () -> Content) {
28 | self.title = title
29 | self.content = content()
30 | }
31 |
32 | var body: some View {
33 | VStack(alignment: .leading) {
34 | Text(title)
35 | .font(.headline)
36 | .padding(.horizontal)
37 |
38 | VStack(spacing: 30) {
39 | self.content
40 | }
41 | .padding(.horizontal)
42 | }
43 | }
44 | }
45 |
46 | struct ClocksListRowView_Previews: PreviewProvider {
47 | static var previews: some View {
48 | WidgetListPreviewView(title: "标题") {}
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Clocks/Views/WidgetViews/ClockWidgetBundleView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClockWidgetBundleView.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/11.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ClockWidgetBundleView: ClockWidget, View {
11 | static let clockName: String = ""
12 | static let nonConfigurableFields: [String] = []
13 |
14 | let date: Date
15 | let config: WidgetClockConfig
16 |
17 | let clockName: String
18 |
19 | init(date: Date, config widgetConfig: WidgetClockConfig = WidgetClockConfig.createEmpty(clockName: Self.clockName)) {
20 | self.date = date
21 | config = widgetConfig
22 |
23 | clockName = config.clockName
24 | }
25 |
26 | var body: some View {
27 | switch clockName {
28 | case SimpleClockView.clockName:
29 | SimpleClockView(date: date, config: config)
30 | case SimpleClock1View.clockName:
31 | SimpleClock1View(date: date, config: config)
32 | case SimplePicture.clockName:
33 | SimplePicture(date: date, config: config)
34 | default:
35 | GeometryReader { geo in
36 | Text("请选择要显示的小组件")
37 | .font(.subheadline)
38 | .foregroundColor(.primary)
39 | .background(Color.clear)
40 | .frame(width: geo.size.width, height: geo.size.height)
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Clocks/Views/Helper/ImagePicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePicker.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/6.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ImagePicker: UIViewControllerRepresentable {
11 | @Environment(\.presentationMode) var presentationMode
12 |
13 | var onSelect: ((UIImage) -> Void)?
14 |
15 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController {
16 | let imagePicker = UIImagePickerController()
17 | imagePicker.delegate = context.coordinator
18 | return imagePicker
19 | }
20 |
21 | func updateUIViewController(_: UIImagePickerController, context _: UIViewControllerRepresentableContext) {}
22 |
23 | func makeCoordinator() -> Coordinator {
24 | Coordinator(self)
25 | }
26 |
27 | class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
28 | let parent: ImagePicker
29 |
30 | init(_ parent: ImagePicker) {
31 | self.parent = parent
32 | }
33 |
34 | func imagePickerController(_: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
35 | if let uiImage = info[.originalImage] as? UIImage {
36 | parent.onSelect?(uiImage)
37 | }
38 |
39 | parent.presentationMode.wrappedValue.dismiss()
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Clocks/Views/WidgetViews/SimplePicture.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimplePicture.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/2/5.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SimplePicture: ClockWidget, View {
11 | static let clockName = "简单图片"
12 | static let nonConfigurableFields: [String] = [
13 | WidgetDetailConfigFields.textColor,
14 | WidgetDetailConfigFields.is12Hour,
15 | WidgetDetailConfigFields.showDateInfo,
16 | ]
17 |
18 | @Environment(\.colorScheme) private var colorScheme
19 |
20 | let config: WidgetClockConfig
21 |
22 | private var preferredBackgroundImage: UIImage? {
23 | config.preferredBackgroundImage(colorScheme: colorScheme)
24 | }
25 |
26 | init(date _: Date, config widgetConfig: WidgetClockConfig = WidgetClockConfig.createEmpty(clockName: Self.clockName)) {
27 | config = widgetConfig
28 | }
29 |
30 | var body: some View {
31 | GeometryReader { geo in
32 | ZStack {
33 | WidgetBackground(uiImage: preferredBackgroundImage, blur: config.blur)
34 | .frame(width: geo.size.width, height: geo.size.height)
35 | }
36 | }
37 | .background(preferredBackgroundImage != nil ? Color.clear : config.backgroundColor)
38 | }
39 | }
40 |
41 | struct SimplePicture_Previews: PreviewProvider {
42 | static var previews: some View {
43 | SimplePicture(date: Date())
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/ClocksWidgetIntents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | ClocksWidgetIntents
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSExtension
24 |
25 | NSExtensionAttributes
26 |
27 | IntentsRestrictedWhileLocked
28 |
29 | IntentsRestrictedWhileProtectedDataUnavailable
30 |
31 | IntentsSupported
32 |
33 | ClocksWidgetIntent
34 |
35 |
36 | NSExtensionPointIdentifier
37 | com.apple.intents-service
38 | NSExtensionPrincipalClass
39 | $(PRODUCT_MODULE_NAME).IntentHandler
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Clocks/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIcon-20.0x20.0@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "AppIcon-20.0x20.0@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "AppIcon-29.0x29.0@2x.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "AppIcon-29.0x29.0@3x.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "AppIcon-40.0x40.0@2x.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "AppIcon-40.0x40.0@3x.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "AppIcon-60.0x60.0@2x.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "AppIcon-60.0x60.0@3x.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "AppIcon-1024.0x1024.0@1x.png",
53 | "idiom" : "ios-marketing",
54 | "scale" : "1x",
55 | "size" : "1024x1024"
56 | }
57 | ],
58 | "info" : {
59 | "author" : "xcode",
60 | "version" : 1
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Clocks/Views/Helper/WidgetPreviews.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WidgetPreviews.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2020/12/30.
6 | //
7 |
8 | import SwiftUI
9 | import WidgetKit
10 |
11 | /**
12 | 根据WidgetFamily显示不同大小的widget
13 | */
14 | struct WidgetPreviews: View {
15 | let widget: Widget
16 | let widgetFamily: [WidgetFamily]
17 |
18 | private let widgetSizeSmall = DeviceWidgetSize.small
19 | private let widgetSizeMeduim = DeviceWidgetSize.meduim
20 |
21 | init(widgetFamily: [WidgetFamily] = [.systemSmall, .systemMedium], @ViewBuilder _ widget: @escaping () -> Widget) {
22 | self.widget = widget()
23 | self.widgetFamily = widgetFamily
24 | }
25 |
26 | var body: some View {
27 | HStack(spacing: 24) {
28 | Group {
29 | if widgetFamily.contains(.systemSmall) {
30 | widget
31 | .frame(width: widgetSizeSmall.width, height: widgetSizeSmall.height)
32 | .environment(\.previewsFamily, .systemSmall)
33 | }
34 | if widgetFamily.contains(.systemMedium) {
35 | widget
36 | .frame(width: widgetSizeMeduim.width, height: widgetSizeMeduim.height)
37 | .environment(\.previewsFamily, .systemMedium)
38 | }
39 | }
40 | .cornerRadius(24)
41 | }
42 | }
43 | }
44 |
45 | struct WidgetPreviews_Previews: PreviewProvider {
46 | static var previews: some View {
47 | WidgetPreviews {
48 | Text("preview")
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Clocks/Utils/UserDefaults.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaults.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/7.
6 | //
7 |
8 | import Foundation
9 |
10 | func appUserDefaults() -> UserDefaults? {
11 | UserDefaults(suiteName: "group.zhangyu1818.clocks")
12 | }
13 |
14 | func getWidgetConfig(_ key: String, defaultValue: T, parseConfig: (StorableClockConfig) -> T) -> T {
15 | guard let userDefaults = appUserDefaults() else {
16 | return defaultValue
17 | }
18 |
19 | guard let data = userDefaults.object(forKey: key) as? Data else {
20 | return defaultValue
21 | }
22 |
23 | guard let config = try? JSONDecoder().decode(StorableClockConfig.self, from: data) else {
24 | return defaultValue
25 | }
26 |
27 | return parseConfig(config)
28 | }
29 |
30 | let CONFIG_KEY_NAME = "savedWidgetConfig"
31 |
32 | func getSavedConfigKeys() -> [String] {
33 | guard let userDefaults = appUserDefaults() else {
34 | return []
35 | }
36 |
37 | guard let data = userDefaults.array(forKey: CONFIG_KEY_NAME) as? [String] else {
38 | return []
39 | }
40 | return data
41 | }
42 |
43 | func saveConfigKeys(configKeys: [String]) {
44 | guard let userDefaults = appUserDefaults() else {
45 | return
46 | }
47 |
48 | userDefaults.setValue(configKeys, forKey: CONFIG_KEY_NAME)
49 | }
50 |
51 | func addConfigKeys(key: String) {
52 | var configKeys = getSavedConfigKeys()
53 | configKeys.append(key)
54 | saveConfigKeys(configKeys: configKeys)
55 | }
56 |
57 | func deleteConfigKeys(key: String) {
58 | guard let userDefaults = appUserDefaults() else { return }
59 | userDefaults.removeObject(forKey: key)
60 | }
61 |
--------------------------------------------------------------------------------
/Clocks/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleAllowMixedLocalizations
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 |
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleLocalizations
18 |
19 | zh
20 |
21 | CFBundleName
22 | $(PRODUCT_NAME)
23 | CFBundlePackageType
24 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
25 | CFBundleShortVersionString
26 | $(MARKETING_VERSION)
27 | CFBundleVersion
28 | $(CURRENT_PROJECT_VERSION)
29 | LSRequiresIPhoneOS
30 |
31 | NSUserActivityTypes
32 |
33 | ClocksWidgetIntent
34 |
35 | UIApplicationSceneManifest
36 |
37 | UIApplicationSupportsMultipleScenes
38 |
39 |
40 | UIApplicationSupportsIndirectInputEvents
41 |
42 | UILaunchScreen
43 |
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 |
52 | UISupportedInterfaceOrientations~ipad
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationPortraitUpsideDown
56 | UIInterfaceOrientationLandscapeLeft
57 | UIInterfaceOrientationLandscapeRight
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/ClocksWidget/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Clocks/Extensions/Color.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Color.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/1.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension Color {
11 | static let systemBackground = Color(UIColor.systemBackground)
12 | static let secondarySystemBackground = Color(UIColor.secondarySystemBackground)
13 | static let tertiarySystemBackground = Color(UIColor.tertiarySystemBackground)
14 |
15 | static let systemGroupedBackground = Color(UIColor.systemGroupedBackground)
16 | static let secondarySystemGroupedBackground = Color(UIColor.secondarySystemGroupedBackground)
17 | static let tertiarySystemGroupedBackground = Color(UIColor.tertiarySystemGroupedBackground)
18 |
19 | static let separator = Color(UIColor.separator)
20 | }
21 |
22 | extension Color {
23 | init(hexString: String) {
24 | var cString: String = hexString.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
25 |
26 | if cString.hasPrefix("#") {
27 | cString.remove(at: cString.startIndex)
28 | }
29 |
30 | var rgbValue: UInt64 = 0
31 | Scanner(string: cString).scanHexInt64(&rgbValue)
32 |
33 | self.init(UIColor(
34 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
35 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
36 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
37 | alpha: CGFloat(1.0)
38 | ))
39 | }
40 |
41 | func toHexString() -> String {
42 | var r: CGFloat = 0
43 | var g: CGFloat = 0
44 | var b: CGFloat = 0
45 | var a: CGFloat = 0
46 |
47 | let uiColor = UIColor(self)
48 | uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
49 | let rgb: Int = (Int)(r * 255) << 16 | (Int)(g * 255) << 8 | (Int)(b * 255) << 0
50 | return String(format: "#%06x", rgb)
51 | }
52 |
53 | var contrastColor: Color {
54 | let ciColor = CIColor(color: UIColor(self))
55 |
56 | // get the current values and make the difference from white:
57 | let compRed: CGFloat = 1.0 - ciColor.red
58 | let compGreen: CGFloat = 1.0 - ciColor.green
59 | let compBlue: CGFloat = 1.0 - ciColor.blue
60 |
61 | return Color(UIColor(red: compRed, green: compGreen, blue: compBlue, alpha: 1.0))
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Clocks/Views/WidgetList/WidgetListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WidgetListView.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2020/12/30.
6 | //
7 |
8 | import SwiftUI
9 | import WidgetKit
10 |
11 | struct WidgetListView: View {
12 | var body: some View {
13 | ScrollView {
14 | CurrentClocksView()
15 | .padding(.bottom)
16 |
17 | WidgetListPreviewView(title: "时间") {
18 | NavigationLink(
19 | destination: NewDetail(clockName: SimpleClockView.clockName),
20 | label: {
21 | WidgetPreviews(widgetFamily: [.systemMedium]) {
22 | SimpleClockView(date: Date())
23 | }
24 | }
25 | )
26 | NavigationLink(
27 | destination: NewDetail(clockName: SimpleClock1View.clockName),
28 | label: {
29 | WidgetPreviews(widgetFamily: [.systemMedium]) {
30 | SimpleClock1View(date: Date())
31 | }
32 | }
33 | )
34 | }
35 |
36 | Spacer().padding()
37 |
38 | WidgetListPreviewView(title: "图片") {
39 | NavigationLink(
40 | destination: NewDetail(clockName: SimplePicture.clockName),
41 | label: {
42 | WidgetPreviews(widgetFamily: [.systemMedium]) {
43 | SimplePicture(date: Date())
44 | }
45 | }
46 | )
47 | }
48 | }
49 | .toolbar {
50 | ToolbarItem(placement: .primaryAction) {
51 | HStack {
52 | Button(action: {
53 | WidgetCenter.shared.reloadAllTimelines()
54 | }) {
55 | VStack {
56 | Image(systemName: "arrow.clockwise.circle")
57 | Text("刷新")
58 | .font(.subheadline)
59 | }
60 | }
61 | .frame(width: 44, height: 44)
62 | }
63 | }
64 | }
65 | .navigationBarTitle("小组件列表")
66 | }
67 | }
68 |
69 | struct ClocksListView_Previews: PreviewProvider {
70 | static var previews: some View {
71 | WidgetListView()
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
92 | .DS_Store
93 |
--------------------------------------------------------------------------------
/Clocks/Utils/Image.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Image.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/6.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | func removeImages(_ imageNames: [String?]) {
12 | guard let documentsDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.zhangyu1818.clocks") else { return }
13 |
14 | imageNames.forEach { imageName in
15 | if let fileName = imageName {
16 | let fileURL = documentsDirectory.appendingPathComponent(fileName)
17 |
18 | // Checks if file exists, removes it if so.
19 | if FileManager.default.fileExists(atPath: fileURL.path) {
20 | do {
21 | try FileManager.default.removeItem(atPath: fileURL.path)
22 | print("Removed image")
23 | } catch let removeError {
24 | print("couldn't remove file at path", removeError)
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
31 | func saveImage(imageName: String, image: UIImage, onCompleted: ((URL) -> Void)?) {
32 | guard let documentsDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.zhangyu1818.clocks") else { return }
33 |
34 | let fileName = imageName
35 | let fileURL = documentsDirectory.appendingPathComponent(fileName)
36 | guard let data = image.jpegData(compressionQuality: 1) else { return }
37 |
38 | // Checks if file exists, removes it if so.
39 | if FileManager.default.fileExists(atPath: fileURL.path) {
40 | do {
41 | try FileManager.default.removeItem(atPath: fileURL.path)
42 | print("Removed old image")
43 | } catch let removeError {
44 | print("couldn't remove file at path", removeError)
45 | }
46 | }
47 |
48 | do {
49 | try data.write(to: fileURL)
50 | onCompleted?(fileURL)
51 | } catch {
52 | print("error saving file with error", error)
53 | }
54 | }
55 |
56 | func cropImage(
57 | _ inputImage: UIImage,
58 | toRect cropRect: CGRect,
59 | viewWidth: CGFloat = UIScreen.main.bounds.width,
60 | viewHeight: CGFloat = UIScreen.main.bounds.height
61 | ) -> UIImage? {
62 | let imageViewScale = max(inputImage.size.width / viewWidth,
63 | inputImage.size.height / viewHeight)
64 |
65 | // Scale cropRect to handle images larger than shown-on-screen size
66 | let cropZone = CGRect(x: cropRect.origin.x * imageViewScale,
67 | y: cropRect.origin.y * imageViewScale,
68 | width: cropRect.size.width * imageViewScale,
69 | height: cropRect.size.height * imageViewScale)
70 |
71 | // Perform cropping in Core Graphics
72 | guard let cutImageRef: CGImage = inputImage.cgImage?.cropping(to: cropZone)
73 | else {
74 | return nil
75 | }
76 |
77 | // Return image to UIImage
78 | let croppedImage = UIImage(cgImage: cutImageRef)
79 | return croppedImage
80 | }
81 |
--------------------------------------------------------------------------------
/Clocks.xcodeproj/xcshareddata/xcschemes/Clocks.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Clocks/Extensions/UIDevice.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIDevice.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/11.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension UIDevice {
11 | enum Model: String {
12 | // Simulator
13 | case simulator = "simulator/sandbox",
14 |
15 | // iPod
16 | iPod7 = "iPod 7",
17 |
18 | // iPhone
19 | iPhone6S = "iPhone 6S",
20 | iPhone6SPlus = "iPhone 6S Plus",
21 | iPhoneSE = "iPhone SE",
22 | iPhone7 = "iPhone 7",
23 | iPhone7Plus = "iPhone 7 Plus",
24 | iPhone8 = "iPhone 8",
25 | iPhone8Plus = "iPhone 8 Plus",
26 | iPhoneX = "iPhone X",
27 | iPhoneXS = "iPhone XS",
28 | iPhoneXSMax = "iPhone XS Max",
29 | iPhoneXR = "iPhone XR",
30 | iPhone11 = "iPhone 11",
31 | iPhone11Pro = "iPhone 11 Pro",
32 | iPhone11ProMax = "iPhone 11 Pro Max",
33 | iPhoneSE2 = "iPhone SE 2nd gen",
34 | iPhone12Mini = "iPhone 12 Mini",
35 | iPhone12 = "iPhone 12",
36 | iPhone12Pro = "iPhone 12 Pro",
37 | iPhone12ProMax = "iPhone 12 Pro Max",
38 |
39 | unrecognized = "?unrecognized?"
40 | }
41 |
42 | var type: Model {
43 | var systemInfo = utsname()
44 | uname(&systemInfo)
45 | let modelCode = withUnsafePointer(to: &systemInfo.machine) {
46 | $0.withMemoryRebound(to: CChar.self, capacity: 1) {
47 | ptr in String(validatingUTF8: ptr)
48 | }
49 | }
50 |
51 | let modelMap: [String: Model] = [
52 | // Simulator
53 | "i386": .simulator,
54 | "x86_64": .simulator,
55 |
56 | // iPod
57 | "iPod9,1": .iPod7,
58 |
59 | // iPhone
60 | "iPhone8,1": .iPhone6S,
61 | "iPhone8,2": .iPhone6SPlus,
62 | "iPhone8,4": .iPhoneSE,
63 | "iPhone9,1": .iPhone7,
64 | "iPhone9,3": .iPhone7,
65 | "iPhone9,2": .iPhone7Plus,
66 | "iPhone9,4": .iPhone7Plus,
67 | "iPhone10,1": .iPhone8,
68 | "iPhone10,4": .iPhone8,
69 | "iPhone10,2": .iPhone8Plus,
70 | "iPhone10,5": .iPhone8Plus,
71 | "iPhone10,3": .iPhoneX,
72 | "iPhone10,6": .iPhoneX,
73 | "iPhone11,2": .iPhoneXS,
74 | "iPhone11,4": .iPhoneXSMax,
75 | "iPhone11,6": .iPhoneXSMax,
76 | "iPhone11,8": .iPhoneXR,
77 | "iPhone12,1": .iPhone11,
78 | "iPhone12,3": .iPhone11Pro,
79 | "iPhone12,5": .iPhone11ProMax,
80 | "iPhone12,8": .iPhoneSE2,
81 | "iPhone13,1": .iPhone12Mini,
82 | "iPhone13,2": .iPhone12,
83 | "iPhone13,3": .iPhone12Pro,
84 | "iPhone13,4": .iPhone12ProMax,
85 | ]
86 |
87 | if let model = modelMap[String(validatingUTF8: modelCode!)!] {
88 | if model == .simulator {
89 | if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] {
90 | if let simModel = modelMap[String(validatingUTF8: simModelCode)!] {
91 | return simModel
92 | }
93 | }
94 | }
95 | return model
96 | }
97 | return Model.unrecognized
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/ClocksWidget/ClocksWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClocksWidget.swift
3 | // ClocksWidget
4 | //
5 | // Created by ZhangYu on 2020/12/30.
6 | //
7 |
8 | import SwiftUI
9 | import WidgetKit
10 |
11 | func getEntries(widgetKey: String?) -> ([WidgetEntry], Date) {
12 | let times = 15
13 |
14 | let currentDate = Date()
15 |
16 | let updatesDate = Calendar.current.date(byAdding: .minute, value: times, to: currentDate)!
17 |
18 | var config = WidgetClockConfig.createEmpty(clockName: "defaultClock")
19 |
20 | if widgetKey != nil {
21 | config = getWidgetConfig(widgetKey!, defaultValue: config) { config in
22 | WidgetClockConfig(fromStorableConfig: config)
23 | }
24 | }
25 |
26 | var entries = [WidgetEntry]()
27 |
28 | for offset in 0 ..< 60 * times {
29 | let entryDate = Calendar.current.date(byAdding: .second, value: offset, to: currentDate)!
30 | entries.append(WidgetEntry(date: entryDate, config: config))
31 | }
32 |
33 | return (entries, updatesDate)
34 | }
35 |
36 | struct Provider: IntentTimelineProvider {
37 | func placeholder(in _: Context) -> WidgetEntry {
38 | WidgetEntry(date: Date())
39 | }
40 |
41 | func getSnapshot(for _: ClocksWidgetIntent, in _: Context, completion: @escaping (WidgetEntry) -> Void) {
42 | let entry = WidgetEntry(date: Date())
43 | completion(entry)
44 | }
45 |
46 | func getTimeline(for configuration: ClocksWidgetIntent, in _: Context, completion: @escaping (Timeline) -> Void) {
47 | let currentWidgetKey = configuration.currentWidget?.identifier
48 |
49 | let (entries, updatesDate) = getEntries(widgetKey: currentWidgetKey)
50 |
51 | print("entries length:\(entries.count),updates date:\(DateFormatter.timeFormatter("yyyy-MM-dd HH:mm:ss").string(from: updatesDate))")
52 |
53 | let timeline = Timeline(entries: entries, policy: .after(updatesDate))
54 | completion(timeline)
55 | }
56 | }
57 |
58 | struct WidgetEntry: TimelineEntry {
59 | let date: Date
60 | let config: WidgetClockConfig?
61 | init(date: Date, config: WidgetClockConfig? = nil) {
62 | self.date = date
63 | self.config = config
64 | }
65 | }
66 |
67 | struct ClocksWidgetEntryView: View {
68 | var entry: Provider.Entry
69 |
70 | var body: some View {
71 | WidgetFamilyProvider {
72 | if let config = entry.config {
73 | ClockWidgetBundleView(date: entry.date, config: config)
74 | } else {
75 | ClockWidgetBundleView(date: entry.date)
76 | }
77 | }
78 | }
79 | }
80 |
81 | @main
82 | struct ClocksWidget: Widget {
83 | let kind: String = "ClocksWidget"
84 |
85 | var body: some WidgetConfiguration {
86 | IntentConfiguration(kind: kind, intent: ClocksWidgetIntent.self, provider: Provider()) { entry in
87 | ClocksWidgetEntryView(entry: entry)
88 | }
89 | .configurationDisplayName("时钟小组件")
90 | .description("添加小组件来显示时间")
91 | .supportedFamilies([.systemSmall, .systemMedium])
92 | }
93 | }
94 |
95 | struct ClocksWidget_Previews: PreviewProvider {
96 | static var previews: some View {
97 | ClocksWidgetEntryView(entry: WidgetEntry(date: Date()))
98 | .previewContext(WidgetPreviewContext(family: .systemSmall))
99 | ClocksWidgetEntryView(entry: WidgetEntry(date: Date()))
100 | .previewContext(WidgetPreviewContext(family: .systemMedium))
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Clocks/Views/WidgetViews/SimpleClockView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleClockView.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2020/12/30.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SimpleClockView: ClockWidget, View {
11 | static let clockName = "简单时钟"
12 | static let nonConfigurableFields: [String] = []
13 |
14 | @Environment(\.previewsFamily) private var previewsFamily
15 | @Environment(\.colorScheme) private var colorScheme
16 |
17 | let config: WidgetClockConfig
18 |
19 | private let currentTime: String
20 | private let period: String
21 | private let dateInfo: String
22 |
23 | private var preferredBackgroundImage: UIImage? {
24 | config.preferredBackgroundImage(colorScheme: colorScheme)
25 | }
26 |
27 | init(date: Date, config widgetConfig: WidgetClockConfig = WidgetClockConfig.createEmpty(clockName: Self.clockName)) {
28 | config = widgetConfig
29 |
30 | let hourFormat = config.is12Hour ? "h" : "H"
31 |
32 | let formatter = DateFormatter.timeFormatter("\(hourFormat):mm/a/M月d日 E")
33 | formatter.amSymbol = "AM"
34 | formatter.pmSymbol = "PM"
35 |
36 | let times = formatter
37 | .string(from: date)
38 | .components(separatedBy: "/")
39 |
40 | currentTime = times[0]
41 | period = times[1]
42 | dateInfo = times[2]
43 | }
44 |
45 | private func getFontSize(_ geo: GeometryProxy) -> CGFloat {
46 | switch previewsFamily {
47 | case .systemSmall:
48 | return geo.size.width / 4
49 | case .systemMedium:
50 | return geo.size.width / 6
51 | default:
52 | return 72
53 | }
54 | }
55 |
56 | var body: some View {
57 | GeometryReader { geo in
58 | ZStack {
59 | WidgetBackground(uiImage: preferredBackgroundImage, blur: config.blur)
60 | .frame(width: geo.size.width, height: geo.size.height)
61 | Group {
62 | switch previewsFamily {
63 | case .systemSmall:
64 | VStack(alignment: .leading) {
65 | Text(currentTime)
66 | Text(period)
67 | if config.showDateInfo {
68 | Text(dateInfo)
69 | .font(.subheadline)
70 | .fontWeight(.bold)
71 | .padding(.top)
72 | }
73 | }
74 | default:
75 | VStack {
76 | Text("\(currentTime) \(period)")
77 | if config.showDateInfo {
78 | Text(dateInfo)
79 | .font(.subheadline)
80 | .fontWeight(.bold)
81 | }
82 | }
83 | }
84 | }
85 | .foregroundColor(config.textColor)
86 | .font(Font.system(size: getFontSize(geo)).weight(.bold))
87 | }
88 | .frame(width: geo.size.width, height: geo.size.height)
89 | .background(preferredBackgroundImage != nil ? Color.clear : config.backgroundColor)
90 | }
91 | // .clipShape(ContainerRelativeShape())
92 | }
93 | }
94 |
95 | struct SimpleText_Previews: PreviewProvider {
96 | static var previews: some View {
97 | WidgetPreviews {
98 | WidgetFamilyProvider {
99 | SimpleClockView(date: Date())
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/Clocks/ViewModels/StorableClockConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClockConfig.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/6.
6 | //
7 | import SwiftUI
8 |
9 | /**
10 | 储存在UserDefaults里的原始数据
11 | */
12 | struct StorableClockConfig: Codable {
13 | var configKey: String
14 | var clockName: String
15 | var textColor: String = "#ffffff"
16 | var backgroundColor: String = "#000000"
17 |
18 | var is12Hour: Bool = false
19 | var showDateInfo: Bool = true
20 |
21 | var blur: Bool = false
22 |
23 | var lightBasicImgPath: String?
24 | var darkBasicImgPath: String?
25 |
26 | var lightMaskImgPath: String?
27 | var darkMaskImgPath: String?
28 | }
29 |
30 | /**
31 | 组件读取的config信息
32 | */
33 | struct WidgetClockConfig {
34 | var clockName: String
35 | var textColor: Color
36 | var backgroundColor: Color
37 |
38 | var is12Hour: Bool = false
39 | var showDateInfo: Bool = true
40 |
41 | var lightBasicImg: UIImage?
42 | var darkBasicImg: UIImage?
43 |
44 | var lightMaskImg: UIImage?
45 | var darkMaskImg: UIImage?
46 |
47 | var blur: Bool = false
48 |
49 | init(
50 | clockName: String,
51 | textColor: Color,
52 | backgroundColor: Color,
53 | is12Hour: Bool,
54 | showDateInfo: Bool,
55 | blur: Bool,
56 | lightBasicImg: UIImage?,
57 | darkBasicImg: UIImage?,
58 | lightMaskImg: UIImage?,
59 | darkMaskImg: UIImage?
60 | ) {
61 | self.clockName = clockName
62 | self.textColor = textColor
63 | self.backgroundColor = backgroundColor
64 | self.is12Hour = is12Hour
65 | self.showDateInfo = showDateInfo
66 | self.blur = blur
67 | self.lightBasicImg = lightBasicImg
68 | self.darkBasicImg = darkBasicImg
69 | self.lightMaskImg = lightMaskImg
70 | self.darkMaskImg = darkMaskImg
71 | }
72 |
73 | // 通过StorableClockConfig来初始化
74 | init(fromStorableConfig config: StorableClockConfig) {
75 | var lightBasicImg: UIImage?
76 | var darkBasicImg: UIImage?
77 |
78 | if let imgPath = config.lightBasicImgPath {
79 | lightBasicImg = UIImage(contentsOfFile: imgPath)
80 | }
81 | if let imgPath = config.darkBasicImgPath {
82 | darkBasicImg = UIImage(contentsOfFile: imgPath)
83 | }
84 |
85 | var lightMaskImg: UIImage?
86 | var darkMaskImg: UIImage?
87 |
88 | if let imgPath = config.lightMaskImgPath {
89 | lightMaskImg = UIImage(contentsOfFile: imgPath)
90 | }
91 | if let imgPath = config.darkMaskImgPath {
92 | darkMaskImg = UIImage(contentsOfFile: imgPath)
93 | }
94 |
95 | self.init(
96 | clockName: config.clockName,
97 | textColor: Color(hexString: config.textColor),
98 | backgroundColor: Color(hexString: config.backgroundColor),
99 | is12Hour: config.is12Hour,
100 | showDateInfo: config.showDateInfo,
101 | blur: config.blur,
102 | lightBasicImg: lightBasicImg,
103 | darkBasicImg: darkBasicImg,
104 | lightMaskImg: lightMaskImg,
105 | darkMaskImg: darkMaskImg
106 | )
107 | }
108 |
109 | func preferredBackgroundImage(colorScheme: ColorScheme) -> UIImage? {
110 | if colorScheme == .light {
111 | return lightMaskImg ?? lightBasicImg ?? darkMaskImg ?? darkBasicImg
112 | } else if colorScheme == .dark {
113 | return darkMaskImg ?? darkBasicImg ?? lightMaskImg ?? lightBasicImg
114 | }
115 | return nil
116 | }
117 |
118 | static func createEmpty(clockName: String) -> WidgetClockConfig {
119 | let defaultStyle = defaultClockStyle[clockName]
120 | if let style = defaultStyle {
121 | return Self(fromStorableConfig: StorableClockConfig(configKey: "defaultKey", clockName: clockName, textColor: style.textColor.toHexString(), backgroundColor: style.backgroundColor.toHexString()))
122 | }
123 | return Self(fromStorableConfig: StorableClockConfig(configKey: "defaultKey", clockName: "defaultName"))
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Clocks/Views/WidgetViews/SimpleClock1View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SimpleClock1View.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/12.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SimpleClock1View: ClockWidget, View {
11 | static let clockName = "简单时钟1"
12 | static let nonConfigurableFields: [String] = []
13 |
14 | @Environment(\.previewsFamily) private var previewsFamily
15 | @Environment(\.colorScheme) private var colorScheme
16 |
17 | let config: WidgetClockConfig
18 |
19 | private let currentTime: String
20 | private let period: String
21 | private let dateInfo: String
22 |
23 | private var preferredBackgroundImage: UIImage? {
24 | config.preferredBackgroundImage(colorScheme: colorScheme)
25 | }
26 |
27 | private var dateInfoSize: Font { previewsFamily == .systemSmall ? .headline : .title3 }
28 |
29 | private func getPeriodSize(_ geo: GeometryProxy) -> Font {
30 | previewsFamily == .systemSmall ? Font.system(size: geo.size.width / 1.8).weight(.medium) : Font.system(size: geo.size.width / 2).weight(.medium)
31 | }
32 |
33 | init(date: Date, config widgetConfig: WidgetClockConfig = WidgetClockConfig.createEmpty(clockName: Self.clockName)) {
34 | config = widgetConfig
35 |
36 | let hourFormat = config.is12Hour ? "h" : "H"
37 |
38 | let formatter = DateFormatter.timeFormatter("\(hourFormat):mm/a/M月d日,E")
39 | formatter.amSymbol = "AM"
40 | formatter.pmSymbol = "PM"
41 |
42 | let times = formatter
43 | .string(from: date)
44 | .components(separatedBy: "/")
45 |
46 | currentTime = times[0]
47 | period = times[1]
48 | dateInfo = times[2]
49 | }
50 |
51 | private func getFontSize(_ geo: GeometryProxy) -> CGFloat {
52 | switch previewsFamily {
53 | case .systemSmall:
54 | return geo.size.width / 4
55 | case .systemMedium:
56 | return geo.size.width / 6
57 | default:
58 | return 72
59 | }
60 | }
61 |
62 | var body: some View {
63 | GeometryReader { geo in
64 | ZStack {
65 | WidgetBackground(uiImage: preferredBackgroundImage, blur: config.blur)
66 | .frame(width: geo.size.width, height: geo.size.height)
67 | VStack {
68 | if previewsFamily == .systemSmall {
69 | Spacer()
70 | }
71 | HStack {
72 | Spacer()
73 | Text(period)
74 | .font(getPeriodSize(geo))
75 | .fixedSize()
76 | .foregroundColor(Color.white.opacity(0.2))
77 | }
78 | }
79 | .frame(height: geo.size.height)
80 | HStack {
81 | VStack(alignment: .leading) {
82 | if config.showDateInfo {
83 | Text("\(dateInfo)")
84 | .font(dateInfoSize)
85 | .fontWeight(.medium)
86 | }
87 | Spacer()
88 | Text(currentTime)
89 | .font(Font.system(size: geo.size.width / (previewsFamily == .systemSmall ? 3 : 4.5)).weight(.semibold))
90 | .fixedSize()
91 | .offset(y: geo.size.width / 15)
92 | }
93 | Spacer()
94 | }
95 | .foregroundColor(config.textColor)
96 | .padding()
97 | }
98 | .frame(width: geo.size.width, height: geo.size.height)
99 | .background(preferredBackgroundImage != nil ? Color.clear : config.backgroundColor)
100 | }
101 | // .clipShape(ContainerRelativeShape())
102 | }
103 | }
104 |
105 | struct SimpleClock1View_Previews: PreviewProvider {
106 | static var previews: some View {
107 | WidgetPreviews {
108 | WidgetFamilyProvider {
109 | SimpleClock1View(date: Date())
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Clocks/Views/WidgetDetail/ClocksDetailView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClocksDetail.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2020/12/31.
6 | //
7 |
8 | import SwiftUI
9 | import WidgetKit
10 |
11 | struct ClocksDetailView: View {
12 | @Environment(\.presentationMode) private var presentationMode
13 |
14 | @StateObject private var config: ClockConfigViewModel
15 |
16 | @State private var showAlert = false
17 |
18 | let clockContent: (WidgetClockConfig) -> Content
19 | let clockName: String
20 |
21 | init(config: ClockConfigViewModel, @ViewBuilder content: @escaping (WidgetClockConfig) -> Content) {
22 | clockContent = content
23 |
24 | clockName = config.clockName
25 |
26 | _config = StateObject(wrappedValue: config)
27 | }
28 |
29 | private func configable(_ fieldName: String) -> Bool {
30 | !config.nonConfigurableFields.contains(fieldName)
31 | }
32 |
33 | var body: some View {
34 | VStack {
35 | // 小组件展示
36 | ScrollView(.horizontal, showsIndicators: false) {
37 | WidgetPreviews {
38 | clockContent(config.toWidgetConfig())
39 | .shadow(radius: 4)
40 | }
41 | .padding()
42 | }
43 |
44 | // 小组件配置信息
45 | Form {
46 | Section(header: Text("基础设置")) {
47 | if configable(WidgetDetailConfigFields.textColor) {
48 | ColorPicker("字体色", selection: $config.textColor, supportsOpacity: false)
49 | }
50 | if configable(WidgetDetailConfigFields.backgroundColor) {
51 | ColorPicker("背景色", selection: $config.backgroundColor, supportsOpacity: false)
52 | }
53 | if configable(WidgetDetailConfigFields.is12Hour) {
54 | Toggle(isOn: $config.is12Hour) {
55 | Text("12小时制")
56 | }
57 | }
58 | if configable(WidgetDetailConfigFields.showDateInfo) {
59 | Toggle(isOn: $config.showDateInfo) {
60 | Text("显示日期")
61 | }
62 | }
63 | }
64 |
65 | if configable(WidgetDetailConfigFields.backgroundImage) {
66 | ClockDetailImageEditView(config)
67 | }
68 |
69 | if !config.isNewConfig {
70 | Section {
71 | GeometryReader { geo in
72 | Button("删除小组件") { showAlert = true }
73 | .frame(width: geo.size.width, height: geo.size.height)
74 | .foregroundColor(.red)
75 | }
76 | }
77 | }
78 | }
79 | }
80 | .alert(isPresented: $showAlert) {
81 | Alert(
82 | title: Text("确定要删除吗?"),
83 | primaryButton: .destructive(Text("删除")) {
84 | ClockConfigManager.shared.deleteConfig(config: config)
85 |
86 | presentationMode.wrappedValue.dismiss()
87 |
88 | WidgetCenter.shared.reloadAllTimelines()
89 | },
90 | secondaryButton: .cancel(Text("取消"))
91 | )
92 | }
93 | .toolbar {
94 | ToolbarItem(placement: .confirmationAction) {
95 | Button(config.isNewConfig ? "添加小组件" : "保存") {
96 | ClockConfigManager.shared.updateConfig(newConfig: config)
97 |
98 | presentationMode.wrappedValue.dismiss()
99 |
100 | WidgetCenter.shared.reloadAllTimelines()
101 | }
102 | }
103 | }
104 | .navigationBarTitle(clockName, displayMode: .inline)
105 | }
106 | }
107 |
108 | struct ClocksDetail_Previews: PreviewProvider {
109 | static var previews: some View {
110 | ClocksDetailView(config: ClockConfigManager.shared.createConfigViewModel(clockName: "简单时钟")) { _ in
111 | SimpleClockView(date: Date())
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Clocks.xcodeproj/xcshareddata/xcschemes/ClocksWidgetExtension.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
75 |
76 |
80 |
81 |
85 |
86 |
87 |
88 |
95 |
97 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/ClocksWidget/ClocksWidgetIntents.intentdefinition:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | INEnums
6 |
7 | INIntentDefinitionModelVersion
8 | 1.2
9 | INIntentDefinitionNamespace
10 | 3dzq2X
11 | INIntentDefinitionSystemVersion
12 | 20C69
13 | INIntentDefinitionToolsBuildVersion
14 | 12C33
15 | INIntentDefinitionToolsVersion
16 | 12.3
17 | INIntents
18 |
19 |
20 | INIntentCategory
21 | information
22 | INIntentDescriptionID
23 | SgtSZa
24 | INIntentEligibleForWidgets
25 |
26 | INIntentIneligibleForSuggestions
27 |
28 | INIntentLastParameterTag
29 | 2
30 | INIntentName
31 | ClocksWidget
32 | INIntentParameters
33 |
34 |
35 | INIntentParameterConfigurable
36 |
37 | INIntentParameterDisplayName
38 | 当前小组件
39 | INIntentParameterDisplayNameID
40 | KL9kET
41 | INIntentParameterDisplayPriority
42 | 1
43 | INIntentParameterName
44 | currentWidget
45 | INIntentParameterObjectType
46 | WidgetConfig
47 | INIntentParameterObjectTypeNamespace
48 | 3dzq2X
49 | INIntentParameterPromptDialogs
50 |
51 |
52 | INIntentParameterPromptDialogCustom
53 |
54 | INIntentParameterPromptDialogType
55 | Configuration
56 |
57 |
58 | INIntentParameterPromptDialogCustom
59 |
60 | INIntentParameterPromptDialogType
61 | Primary
62 |
63 |
64 | INIntentParameterSupportsDynamicEnumeration
65 |
66 | INIntentParameterTag
67 | 2
68 | INIntentParameterType
69 | Object
70 |
71 |
72 | INIntentResponse
73 |
74 | INIntentResponseCodes
75 |
76 |
77 | INIntentResponseCodeName
78 | success
79 | INIntentResponseCodeSuccess
80 |
81 |
82 |
83 | INIntentResponseCodeName
84 | failure
85 |
86 |
87 |
88 | INIntentTitle
89 | Clocks Widget
90 | INIntentTitleID
91 | ljjxwi
92 | INIntentType
93 | Custom
94 | INIntentVerb
95 | View
96 |
97 |
98 | INTypes
99 |
100 |
101 | INTypeDisplayName
102 | Widget Config
103 | INTypeDisplayNameID
104 | FmdyZ9
105 | INTypeLastPropertyTag
106 | 99
107 | INTypeName
108 | WidgetConfig
109 | INTypeProperties
110 |
111 |
112 | INTypePropertyDefault
113 |
114 | INTypePropertyDisplayPriority
115 | 1
116 | INTypePropertyName
117 | identifier
118 | INTypePropertyTag
119 | 1
120 | INTypePropertyType
121 | String
122 |
123 |
124 | INTypePropertyDefault
125 |
126 | INTypePropertyDisplayPriority
127 | 2
128 | INTypePropertyName
129 | displayString
130 | INTypePropertyTag
131 | 2
132 | INTypePropertyType
133 | String
134 |
135 |
136 | INTypePropertyDefault
137 |
138 | INTypePropertyDisplayPriority
139 | 3
140 | INTypePropertyName
141 | pronunciationHint
142 | INTypePropertyTag
143 | 3
144 | INTypePropertyType
145 | String
146 |
147 |
148 | INTypePropertyDefault
149 |
150 | INTypePropertyDisplayPriority
151 | 4
152 | INTypePropertyName
153 | alternativeSpeakableMatches
154 | INTypePropertySupportsMultipleValues
155 |
156 | INTypePropertyTag
157 | 4
158 | INTypePropertyType
159 | SpeakableString
160 |
161 |
162 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/Clocks/Views/WidgetDetail/ClockDetailImageCropView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClockDetailImageCropView.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/8.
6 | //
7 |
8 | /**
9 | iphone 11 prox max 宽414,小组件横间距22,屏幕边距27,上小组件上边距76
10 | 小
11 | 左上 CGRect(x: 27, y: 76, width: 169, height: 169)
12 | 右上 CGRect(x: 218, y: 76, width: 169, height: 169)
13 | 左中 CGRect(x: 27, y: 286, width: 169, height: 169)
14 | 右中 CGRect(x: 218, y: 286, width: 169, height: 169)
15 | 左下 CGRect(x: 27, y: 495.3, width: 169, height: 169)
16 | 右下 CGRect(x: 218, y: 495.3, width: 169, height: 169)
17 | */
18 |
19 | import SwiftUI
20 | import WidgetKit
21 |
22 | struct ClockDetailImageCropView: View {
23 | @Environment(\.presentationMode) private var presentationMode
24 |
25 | @State private var widgetFamily: WidgetFamily = .systemSmall
26 |
27 | let lightUIImage: UIImage?
28 | let darkUIImage: UIImage?
29 | let onMaskImageCrop: ((UIImage?, UIImage?)) -> Void
30 |
31 | enum WidgetCropPostion {
32 | case smallTopLeft
33 | case smallTopRight
34 | case smallCenterLeft
35 | case smallCenterRight
36 | case smallBottomLeft
37 | case smallBottomRight
38 |
39 | case mediumTop
40 | case mediumCenter
41 | case mediumBottom
42 |
43 | func getRect() -> CGRect {
44 | switch self {
45 | case .smallTopLeft:
46 | return CGRect(origin: DeviceWidgetPosition.smallTopLeft, size: DeviceWidgetSize.small)
47 | case .smallTopRight:
48 | return CGRect(origin: DeviceWidgetPosition.smallTopRight, size: DeviceWidgetSize.small)
49 | case .smallCenterLeft:
50 | return CGRect(origin: DeviceWidgetPosition.smallCenterLeft, size: DeviceWidgetSize.small)
51 | case .smallCenterRight:
52 | return CGRect(origin: DeviceWidgetPosition.smallCenterRight, size: DeviceWidgetSize.small)
53 | case .smallBottomLeft:
54 | return CGRect(origin: DeviceWidgetPosition.smallBottomLeft, size: DeviceWidgetSize.small)
55 | case .smallBottomRight:
56 | return CGRect(origin: DeviceWidgetPosition.smallBottomRight, size: DeviceWidgetSize.small)
57 | case .mediumTop:
58 | return CGRect(origin: DeviceWidgetPosition.mediumTop, size: DeviceWidgetSize.meduim)
59 | case .mediumCenter:
60 | return CGRect(origin: DeviceWidgetPosition.mediumCenter, size: DeviceWidgetSize.meduim)
61 | case .mediumBottom:
62 | return CGRect(origin: DeviceWidgetPosition.mediumBottom, size: DeviceWidgetSize.meduim)
63 | }
64 | }
65 | }
66 |
67 | var body: some View {
68 | ZStack {
69 | if lightUIImage != nil || darkUIImage != nil {
70 | Image(uiImage: lightUIImage ?? darkUIImage!)
71 | .resizable()
72 | }
73 |
74 | if widgetFamily == .systemSmall {
75 | TapRectangle("上左", ratio: DeviceWidgetSize.small, postion: .smallTopLeft)
76 | TapRectangle("上右", ratio: DeviceWidgetSize.small, postion: .smallTopRight)
77 | TapRectangle("中左", ratio: DeviceWidgetSize.small, postion: .smallCenterLeft)
78 | TapRectangle("中右", ratio: DeviceWidgetSize.small, postion: .smallCenterRight)
79 | // iphone se 都不显示6个
80 | if UIDevice().type != .iPhoneSE {
81 | TapRectangle("下左", ratio: DeviceWidgetSize.small, postion: .smallBottomLeft)
82 | TapRectangle("下右", ratio: DeviceWidgetSize.small, postion: .smallBottomRight)
83 | }
84 | }
85 | if widgetFamily == .systemMedium {
86 | TapRectangle("上方", ratio: DeviceWidgetSize.meduim, postion: .mediumTop)
87 | TapRectangle("中间", ratio: DeviceWidgetSize.meduim, postion: .mediumCenter)
88 | if UIDevice().type != .iPhoneSE {
89 | TapRectangle("下方", ratio: DeviceWidgetSize.meduim, postion: .mediumBottom)
90 | }
91 | }
92 |
93 | VStack {
94 | Spacer()
95 | VStack {
96 | Text("小组件会呈现对应区域的透明效果")
97 | .foregroundColor(.secondary)
98 | .font(.system(size: 12))
99 | Picker("组件大小", selection: $widgetFamily) {
100 | Text("小组件").tag(WidgetFamily.systemSmall)
101 | Text("中组件").tag(WidgetFamily.systemMedium)
102 | }
103 | .pickerStyle(SegmentedPickerStyle())
104 |
105 | Button("返回") {
106 | onMaskImageCrop((nil, nil))
107 | presentationMode.wrappedValue.dismiss()
108 | }
109 | .foregroundColor(.red)
110 | .font(.subheadline)
111 | }
112 | .padding()
113 | .background(Color.tertiarySystemGroupedBackground)
114 | .cornerRadius(16.0)
115 | .padding(24)
116 | }
117 | }
118 | .ignoresSafeArea(.all)
119 | .navigationTitle("设置透明背景")
120 | }
121 |
122 | @ViewBuilder
123 | func TapRectangle(_ positionName: String, ratio: CGSize, postion: WidgetCropPostion) -> some View {
124 | let ox = postion.getRect().origin.x
125 | let oy = postion.getRect().origin.y
126 | let width = postion.getRect().width
127 | let height = postion.getRect().height
128 | Rectangle()
129 | .fill(Color.tertiarySystemGroupedBackground)
130 | .overlay(
131 | ZStack {
132 | Text("\(Image(systemName: "paintbrush")) 点击设置\(positionName)")
133 | .font(.subheadline)
134 | .foregroundColor(.secondary)
135 | }
136 | )
137 | .aspectRatio(ratio, contentMode: .fit)
138 | .cornerRadius(16)
139 | .onTapGesture {
140 | let croppedLightBgImg = lightUIImage != nil ? cropImage(lightUIImage!, toRect: postion.getRect()) : nil
141 | let croppedDarkBgImg = darkUIImage != nil ? cropImage(darkUIImage!, toRect: postion.getRect()) : nil
142 |
143 | onMaskImageCrop((croppedLightBgImg, croppedDarkBgImg))
144 |
145 | presentationMode.wrappedValue.dismiss()
146 | }
147 | .frame(width: width, height: height)
148 | .position(x: ox + width / 2, y: oy + height / 2)
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/Clocks/ViewModels/ClockConfigViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClockConfigViewModel.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/4.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /**
11 | 小组件所需要的StateObject
12 | */
13 | class ClockConfigViewModel: ObservableObject {
14 | @Published var textColor: Color
15 | @Published var backgroundColor: Color
16 |
17 | @Published var is12Hour: Bool
18 | @Published var showDateInfo: Bool
19 | @Published var blur: Bool
20 |
21 | @Published var lightBasicImgPath: String?
22 | @Published var darkBasicImgPath: String?
23 |
24 | @Published var lightMaskImgPath: String?
25 | @Published var darkMaskImgPath: String?
26 |
27 | // configKey作为userdefaults的键
28 | fileprivate(set) var configKey: String
29 | fileprivate(set) var clockName: String
30 | fileprivate(set) var isNewConfig = false
31 |
32 | init(
33 | configKey: String,
34 | clockName: String,
35 | textColor: Color = Color.clear,
36 | backgroundColor: Color = Color.clear,
37 | is12Hour: Bool = false,
38 | showDateInfo: Bool = true,
39 | blur: Bool = false,
40 | lightBasicImgPath: String? = nil,
41 | darkBasicImgPath: String? = nil,
42 | lightMaskImgPath: String? = nil,
43 | darkMaskImgPath: String? = nil
44 | ) {
45 | self.configKey = configKey
46 |
47 | self.clockName = clockName
48 | self.textColor = textColor
49 | self.backgroundColor = backgroundColor
50 | self.is12Hour = is12Hour
51 | self.showDateInfo = showDateInfo
52 | self.blur = blur
53 | self.lightBasicImgPath = lightBasicImgPath
54 | self.darkBasicImgPath = darkBasicImgPath
55 | self.lightMaskImgPath = lightMaskImgPath
56 | self.darkMaskImgPath = darkMaskImgPath
57 |
58 | guard let defaultStyle = defaultClockStyle[clockName] else { return }
59 | if self.textColor == Color.clear {
60 | self.textColor = defaultStyle.textColor
61 | }
62 | if self.backgroundColor == Color.clear {
63 | self.backgroundColor = defaultStyle.backgroundColor
64 | }
65 | }
66 |
67 | // 转为可储存在UserDefaults里的config类型
68 | func toStorableConfig() -> StorableClockConfig {
69 | StorableClockConfig(
70 | configKey: configKey,
71 | clockName: clockName,
72 | textColor: textColor.toHexString(),
73 | backgroundColor: backgroundColor.toHexString(),
74 | is12Hour: is12Hour,
75 | showDateInfo: showDateInfo,
76 | blur: blur,
77 | lightBasicImgPath: lightBasicImgPath,
78 | darkBasicImgPath: darkBasicImgPath,
79 | lightMaskImgPath: lightMaskImgPath,
80 | darkMaskImgPath: darkMaskImgPath
81 | )
82 | }
83 |
84 | // 转为小组件所需类型的配置信息
85 | func toWidgetConfig() -> WidgetClockConfig {
86 | WidgetClockConfig(fromStorableConfig: toStorableConfig())
87 | }
88 |
89 | // clone一份在详情页面里使用
90 | func clone() -> ClockConfigViewModel {
91 | ClockConfigViewModel(
92 | configKey: configKey,
93 | clockName: clockName,
94 | textColor: textColor,
95 | backgroundColor: backgroundColor,
96 | is12Hour: is12Hour,
97 | showDateInfo: showDateInfo,
98 | blur: blur,
99 | lightBasicImgPath: lightBasicImgPath,
100 | darkBasicImgPath: darkBasicImgPath,
101 | lightMaskImgPath: lightMaskImgPath,
102 | darkMaskImgPath: darkMaskImgPath
103 | )
104 | }
105 |
106 | // 删除所有图片
107 | func deleteImage() {
108 | // 从本地删除图片
109 | removeImages(
110 | [
111 | lightBasicImgPath,
112 | lightMaskImgPath,
113 | darkBasicImgPath,
114 | darkMaskImgPath,
115 | ]
116 | )
117 |
118 | lightBasicImgPath = nil
119 | lightMaskImgPath = nil
120 | darkBasicImgPath = nil
121 | darkMaskImgPath = nil
122 | }
123 |
124 | var nonConfigurableFields: [String] {
125 | switch clockName {
126 | case SimpleClockView.clockName:
127 | return SimpleClockView.nonConfigurableFields
128 | case SimpleClock1View.clockName:
129 | return SimpleClock1View.nonConfigurableFields
130 | case SimplePicture.clockName:
131 | return SimplePicture.nonConfigurableFields
132 | default:
133 | return []
134 | }
135 | }
136 | }
137 |
138 | /**
139 | 管理小组件的配置
140 | */
141 | class ClockConfigManager {
142 | static let shared = ClockConfigManager()
143 |
144 | private var configs = [String: ClockConfigViewModel]()
145 |
146 | private init() {}
147 |
148 | // 创建一个新的ViewModel,作为新增配置时使用
149 | func createConfigViewModel(clockName: String) -> ClockConfigViewModel {
150 | let newConfigKey = UUID().uuidString
151 | let config = ClockConfigViewModel(configKey: newConfigKey, clockName: clockName)
152 | config.isNewConfig = true
153 | return config
154 | }
155 |
156 | // 通过configKey从现有的获取
157 | func getConfigViewModel(_ configKey: String) -> ClockConfigViewModel {
158 | guard let config = configs[configKey] else {
159 | let newConfig = readConfig(key: configKey)
160 | configs[configKey] = newConfig
161 | return configs[configKey]!
162 | }
163 | return config
164 | }
165 |
166 | // 更新config
167 | func updateConfig(newConfig: ClockConfigViewModel) {
168 | // 如果是新的config需要重新分配一个uuid
169 | if newConfig.isNewConfig {
170 | newConfig.isNewConfig = false
171 | }
172 | // 更新已存在的
173 | if let config = configs[newConfig.configKey] {
174 | config.textColor = newConfig.textColor
175 | config.backgroundColor = newConfig.backgroundColor
176 | config.is12Hour = newConfig.is12Hour
177 | config.showDateInfo = newConfig.showDateInfo
178 | config.blur = newConfig.blur
179 |
180 | config.lightBasicImgPath = newConfig.lightBasicImgPath
181 | config.darkBasicImgPath = newConfig.darkBasicImgPath
182 | config.lightMaskImgPath = newConfig.lightMaskImgPath
183 | config.darkMaskImgPath = newConfig.darkMaskImgPath
184 | } else {
185 | // 不存在添加新的配置
186 | let configKey = newConfig.configKey
187 | configs[configKey] = newConfig
188 |
189 | // 同步更新ConfigKeys到ConfigKeysViewModel
190 | ConfigKeysViewModel.shared.add(configKey: configKey)
191 | }
192 |
193 | // 保存
194 | saveConfig(key: newConfig.configKey, config: newConfig.toStorableConfig())
195 | }
196 |
197 | // 删除config
198 | func deleteConfig(config: ClockConfigViewModel) {
199 | let configKey = config.configKey
200 | // 删除字典里的config
201 | configs.removeValue(forKey: configKey)
202 | // 删除userdefaults里的config
203 | deleteConfigKeys(key: configKey)
204 | // 删除保存组件列表里的config
205 | ConfigKeysViewModel.shared.delete(configKey: configKey)
206 | }
207 |
208 | // 删除所有config
209 | func deleteAllConfig() {
210 | configs.values.forEach { config in deleteConfig(config: config) }
211 | }
212 |
213 | // 从userDefaults读取配置,如果没有就返回一个默认值
214 | private func readConfig(key: String) -> ClockConfigViewModel {
215 | let defaultValue = ClockConfigViewModel(configKey: "defaultKey", clockName: "defaultName")
216 |
217 | return getWidgetConfig(key, defaultValue: defaultValue) { config in
218 | ClockConfigViewModel(
219 | configKey: config.configKey,
220 | clockName: config.clockName,
221 | textColor: Color(hexString: config.textColor),
222 | backgroundColor: Color(hexString: config.backgroundColor),
223 | is12Hour: config.is12Hour,
224 | showDateInfo: config.showDateInfo,
225 | blur: config.blur,
226 | lightBasicImgPath: config.lightBasicImgPath,
227 | darkBasicImgPath: config.darkBasicImgPath,
228 | lightMaskImgPath: config.lightMaskImgPath,
229 | darkMaskImgPath: config.darkMaskImgPath
230 | )
231 | }
232 | }
233 |
234 | // 保存配置到userDefaults
235 | private func saveConfig(key: String, config: StorableClockConfig) {
236 | let data = try? JSONEncoder().encode(config)
237 | if let userDefaults = appUserDefaults() {
238 | userDefaults.setValue(data, forKey: key)
239 | }
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/Clocks/Views/WidgetDetail/ClockDetailImageEditView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClockDetailImageEditView.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/7.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /**
11 | 小组件详情的背景图片添加和删除
12 | */
13 | struct ClockDetailImageEditView: View {
14 | enum BackgroundType: String, Identifiable {
15 | var id: String { rawValue }
16 | case light
17 | case dark
18 | }
19 |
20 | @StateObject private var config: ClockConfigViewModel
21 |
22 | @State private var backgroundType: BackgroundType = .light
23 |
24 | @State private var maskImageToggle: Bool
25 |
26 | // 当前Detail的背景图片和遮罩图片
27 | @State private var lightBasicImg: UIImage?
28 | @State private var darkBasicImg: UIImage?
29 |
30 | @State private var lightMaskImg: UIImage?
31 | @State private var darkMaskImg: UIImage?
32 |
33 | // 控制选择添加图片类型的ActionSheet弹出
34 | @State private var showImagePicker = false
35 | // 图片裁剪弹出
36 | @State private var showCropEditor = false
37 |
38 | private let imagePrefix: String
39 |
40 | // 是否有存在的图片
41 | private var isBasicImageExist: Bool {
42 | lightBasicImg != nil || darkBasicImg != nil
43 | }
44 |
45 | private var isBasicImageAllExist: Bool {
46 | lightBasicImg != nil && darkBasicImg != nil
47 | }
48 |
49 | // 是否浅色深色遮罩图都存在
50 | private var isAllMaskImageExist: Bool {
51 | lightMaskImg != nil && darkMaskImg != nil
52 | }
53 |
54 | init(_ config: ClockConfigViewModel) {
55 | imagePrefix = config.configKey
56 |
57 | _config = StateObject(wrappedValue: config)
58 |
59 | let lightBasicImg = config.lightBasicImgPath != nil ? UIImage(contentsOfFile: config.lightBasicImgPath!) : nil
60 | let darkBasicImg = config.darkBasicImgPath != nil ? UIImage(contentsOfFile: config.darkBasicImgPath!) : nil
61 |
62 | let lightMaskImg = config.lightMaskImgPath != nil ? UIImage(contentsOfFile: config.lightMaskImgPath!) : nil
63 | let darkMaskImg = config.darkMaskImgPath != nil ? UIImage(contentsOfFile: config.darkMaskImgPath!) : nil
64 |
65 | _maskImageToggle = State(initialValue: lightMaskImg != nil || darkMaskImg != nil)
66 |
67 | _lightBasicImg = State(initialValue: lightBasicImg)
68 | _darkBasicImg = State(initialValue: darkBasicImg)
69 |
70 | _lightMaskImg = State(initialValue: lightMaskImg)
71 | _darkMaskImg = State(initialValue: darkMaskImg)
72 | }
73 |
74 | private func deleteAllImage() {
75 | lightBasicImg = nil
76 | lightMaskImg = nil
77 | darkBasicImg = nil
78 | darkMaskImg = nil
79 |
80 | // Todo 删除bug
81 | config.lightBasicImgPath = nil
82 | config.lightMaskImgPath = nil
83 | config.darkBasicImgPath = nil
84 | config.darkMaskImgPath = nil
85 | // 删除图片还原图片效果
86 | config.blur = false
87 | }
88 |
89 | private func deleteMaskImage() {
90 | lightMaskImg = nil
91 | darkMaskImg = nil
92 |
93 | config.lightMaskImgPath = nil
94 | config.darkMaskImgPath = nil
95 | }
96 |
97 | // 把图片存到本地
98 | private func updateImagPath(_ image: UIImage?, imageName: String, onCompleted: @escaping (URL) -> Void) {
99 | if let image = image {
100 | saveImage(imageName: imageName, image: image, onCompleted: onCompleted)
101 | }
102 | }
103 |
104 | var body: some View {
105 | Section(header: HStack {
106 | Text("背景图片")
107 | Spacer()
108 | if isBasicImageExist {
109 | Button("删除图片") {
110 | deleteAllImage()
111 | }
112 | .foregroundColor(.red)
113 | }
114 | }) {
115 | VStack {
116 | Picker("外观", selection: $backgroundType) {
117 | Text("浅色外观").tag(BackgroundType.light)
118 | Text("深色外观").tag(BackgroundType.dark)
119 | }
120 | .pickerStyle(SegmentedPickerStyle())
121 | Divider()
122 | ZStack {
123 | if !isBasicImageAllExist {
124 | Text("\(Image(systemName: "photo")) 点击添加图片")
125 | .foregroundColor(.accentColor)
126 | .contentShape(Rectangle())
127 | .onTapGesture {
128 | showImagePicker = true
129 | }
130 | }
131 |
132 | if backgroundType == .light && lightBasicImg != nil {
133 | GeometryReader { geo in
134 | Image(uiImage: lightBasicImg!)
135 | .resizable()
136 | .aspectRatio(contentMode: .fill)
137 | .frame(width: geo.size.width, height: geo.size.height)
138 | }
139 | }
140 |
141 | if backgroundType == .dark && darkBasicImg != nil {
142 | GeometryReader { geo in
143 | Image(uiImage: darkBasicImg!)
144 | .resizable()
145 | .aspectRatio(contentMode: .fill)
146 | .frame(width: geo.size.width, height: geo.size.height)
147 | }
148 | }
149 | Rectangle().fill(Color.clear)
150 | .fullScreenCover(isPresented: $showCropEditor) {
151 | ClockDetailImageCropView(lightUIImage: lightBasicImg, darkUIImage: darkBasicImg) { lightMaskImg, darkMaskImg in
152 | // 保存浅色外观原始图片
153 | updateImagPath(lightMaskImg, imageName: "\(imagePrefix)_lightMaskImg") { fileURL in
154 | config.lightMaskImgPath = fileURL.path
155 | }
156 | // 保存深色外观原始图片
157 | updateImagPath(darkMaskImg, imageName: "\(imagePrefix)_darkMaskImg") { fileURL in
158 | config.darkMaskImgPath = fileURL.path
159 | }
160 |
161 | // 如果关闭裁剪窗时候一个都没设置,就要把开关给关了
162 | if config.lightMaskImgPath == nil, config.darkMaskImgPath == nil {
163 | maskImageToggle = false
164 | }
165 | }
166 | }
167 | Rectangle().fill(Color.clear)
168 | // 背景图选择的View
169 | .sheet(isPresented: $showImagePicker) {
170 | ImagePicker { image in
171 | if backgroundType == .light {
172 | withAnimation {
173 | lightBasicImg = image
174 | }
175 | // 保存浅色外观原始图片
176 | updateImagPath(lightBasicImg!, imageName: "\(imagePrefix)_lightBasicImg") { fileURL in
177 | config.lightBasicImgPath = fileURL.path
178 | }
179 | }
180 | if backgroundType == .dark {
181 | withAnimation {
182 | darkBasicImg = image
183 | }
184 | // 保存深色外观原始图片
185 | updateImagPath(darkBasicImg!, imageName: "\(imagePrefix)_darkBasicImg") { fileURL in
186 | config.darkBasicImgPath = fileURL.path
187 | }
188 | }
189 | }
190 | }
191 | }
192 | .aspectRatio(2.13, contentMode: .fill)
193 | .cornerRadius(8.0)
194 | .clipped()
195 | }
196 | }
197 | if isBasicImageExist {
198 | Section(header: Text("图片效果")) {
199 | Toggle(isOn: $config.blur, label: {
200 | Text("高斯模糊")
201 | })
202 |
203 | // 要把image path转换成bool的开关,逻辑绕了
204 | Toggle(isOn: $maskImageToggle, label: {
205 | Text("透明背景")
206 | })
207 | .onTapGesture {
208 | // 为false的情况才需要弹出裁剪
209 | if !maskImageToggle {
210 | showCropEditor = true
211 | }
212 | }
213 | .onChange(of: maskImageToggle, perform: { value in
214 | // 值变为false就需要删除所有的mask image
215 | if !value {
216 | deleteMaskImage()
217 | }
218 | })
219 | }
220 | }
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/Clocks/Utils/DeviceWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeviceWidget.swift
3 | // Clocks
4 | //
5 | // Created by ZhangYu on 2021/1/11.
6 | //
7 |
8 | import SwiftUI
9 |
10 | // 小组件在屏幕上的位置
11 | struct DeviceWidgetPosition {
12 | // 小组件 左上
13 | static var smallTopLeft: CGPoint {
14 | switch UIDevice().type {
15 | case .iPhone12ProMax:
16 | return CGPoint(x: 32, y: 82)
17 | case .iPhone12Pro, .iPhone12:
18 | return CGPoint(x: 26, y: 77)
19 | case .iPhone12Mini:
20 | return CGPoint(x: 23, y: 77)
21 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
22 | return CGPoint(x: 27, y: 76)
23 | case .iPhone11Pro, .iPhoneXS, .iPhoneX:
24 | return CGPoint(x: 23, y: 71)
25 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
26 | return CGPoint(x: 33, y: 38)
27 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
28 | return CGPoint(x: 28, y: 30)
29 | case .iPhoneSE, .iPod7:
30 | return CGPoint(x: 14, y: 30)
31 | default:
32 | return CGPoint(x: 27, y: 76)
33 | }
34 | }
35 |
36 | // 小组件 右上
37 | static var smallTopRight: CGPoint {
38 | switch UIDevice().type {
39 | case .iPhone12ProMax:
40 | return CGPoint(x: 226, y: 82)
41 | case .iPhone12Pro, .iPhone12:
42 | return CGPoint(x: 206, y: 77)
43 | case .iPhone12Mini:
44 | return CGPoint(x: 197, y: 77)
45 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
46 | return CGPoint(x: 218, y: 76)
47 | case .iPhone11Pro, .iPhoneXS, .iPhoneX:
48 | return CGPoint(x: 197, y: 71)
49 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
50 | return CGPoint(x: 224, y: 38)
51 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
52 | return CGPoint(x: 200, y: 30)
53 | case .iPhoneSE, .iPod7:
54 | return CGPoint(x: 165, y: 30)
55 | default:
56 | return CGPoint(x: 218, y: 76)
57 | }
58 | }
59 |
60 | // 小组件 中左
61 | static var smallCenterLeft: CGPoint {
62 | switch UIDevice().type {
63 | case .iPhone12ProMax:
64 | return CGPoint(x: 32, y: 294)
65 | case .iPhone12Pro, .iPhone12:
66 | return CGPoint(x: 26, y: 273)
67 | case .iPhone12Mini:
68 | return CGPoint(x: 23, y: 267)
69 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
70 | return CGPoint(x: 27, y: 286)
71 | case .iPhone11Pro, .iPhoneXS, .iPhoneX:
72 | return CGPoint(x: 23, y: 261)
73 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
74 | return CGPoint(x: 33, y: 232)
75 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
76 | return CGPoint(x: 27, y: 206)
77 | case .iPhoneSE, .iPod7:
78 | return CGPoint(x: 14, y: 200)
79 | default:
80 | return CGPoint(x: 218, y: 76)
81 | }
82 | }
83 |
84 | // 小组件 中右
85 | static var smallCenterRight: CGPoint {
86 | switch UIDevice().type {
87 | case .iPhone12ProMax:
88 | return CGPoint(x: 226, y: 294)
89 | case .iPhone12Pro, .iPhone12:
90 | return CGPoint(x: 206, y: 273)
91 | case .iPhone12Mini:
92 | return CGPoint(x: 197, y: 267)
93 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
94 | return CGPoint(x: 218, y: 286)
95 | case .iPhone11Pro, .iPhoneXS, .iPhoneX:
96 | return CGPoint(x: 197, y: 261)
97 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
98 | return CGPoint(x: 224, y: 232)
99 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
100 | return CGPoint(x: 200, y: 206)
101 | case .iPhoneSE, .iPod7:
102 | return CGPoint(x: 165, y: 200)
103 | default:
104 | return CGPoint(x: 218, y: 76)
105 | }
106 | }
107 |
108 | // 小组件 下左
109 | static var smallBottomLeft: CGPoint {
110 | switch UIDevice().type {
111 | case .iPhone12ProMax:
112 | return CGPoint(x: 32, y: 506)
113 | case .iPhone12Pro, .iPhone12:
114 | return CGPoint(x: 26, y: 469)
115 | case .iPhone12Mini:
116 | return CGPoint(x: 23, y: 457)
117 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
118 | return CGPoint(x: 27, y: 495.3333)
119 | case .iPhone11Pro, .iPhoneXS, .iPhoneX:
120 | return CGPoint(x: 23, y: 451)
121 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
122 | return CGPoint(x: 33, y: 426)
123 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
124 | return CGPoint(x: 27, y: 382)
125 | case .iPhoneSE, .iPod7:
126 | return CGPoint(x: 0, y: 0)
127 | default:
128 | return CGPoint(x: 218, y: 76)
129 | }
130 | }
131 |
132 | // 小组件 下右
133 | static var smallBottomRight: CGPoint {
134 | switch UIDevice().type {
135 | case .iPhone12ProMax:
136 | return CGPoint(x: 226, y: 506)
137 | case .iPhone12Pro, .iPhone12:
138 | return CGPoint(x: 206, y: 469)
139 | case .iPhone12Mini:
140 | return CGPoint(x: 197, y: 457)
141 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
142 | return CGPoint(x: 218, y: 495.3333)
143 | case .iPhone11Pro, .iPhoneXS, .iPhoneX:
144 | return CGPoint(x: 197, y: 451)
145 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
146 | return CGPoint(x: 224, y: 426)
147 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
148 | return CGPoint(x: 200, y: 382)
149 | case .iPhoneSE, .iPod7:
150 | return CGPoint(x: 0, y: 0)
151 | default:
152 | return CGPoint(x: 218, y: 76)
153 | }
154 | }
155 |
156 | // 中组件 上方
157 | static var mediumTop: CGPoint {
158 | switch UIDevice().type {
159 | case .iPhone12ProMax:
160 | return CGPoint(x: 32, y: 82)
161 | case .iPhone12Pro, .iPhone12:
162 | return CGPoint(x: 26, y: 77)
163 | case .iPhone12Mini:
164 | return CGPoint(x: 23, y: 77)
165 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
166 | return CGPoint(x: 27, y: 76)
167 | case .iPhone11Pro, .iPhoneXS, .iPhoneX:
168 | return CGPoint(x: 23, y: 71)
169 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
170 | return CGPoint(x: 33, y: 38)
171 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
172 | return CGPoint(x: 28, y: 30)
173 | case .iPhoneSE, .iPod7:
174 | return CGPoint(x: 14, y: 30)
175 | default:
176 | return CGPoint(x: 218, y: 76)
177 | }
178 | }
179 |
180 | // 中组件 中间
181 | static var mediumCenter: CGPoint {
182 | switch UIDevice().type {
183 | case .iPhone12ProMax:
184 | return CGPoint(x: 32, y: 294)
185 | case .iPhone12Pro, .iPhone12:
186 | return CGPoint(x: 26, y: 273)
187 | case .iPhone12Mini:
188 | return CGPoint(x: 23, y: 267)
189 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
190 | return CGPoint(x: 27, y: 286)
191 | case .iPhone11Pro, .iPhoneXS, .iPhoneX:
192 | return CGPoint(x: 23, y: 261)
193 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
194 | return CGPoint(x: 33, y: 232)
195 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
196 | return CGPoint(x: 27, y: 206)
197 | case .iPhoneSE, .iPod7:
198 | return CGPoint(x: 14, y: 200)
199 | default:
200 | return CGPoint(x: 218, y: 76)
201 | }
202 | }
203 |
204 | // 中组件 下方
205 | static var mediumBottom: CGPoint {
206 | switch UIDevice().type {
207 | case .iPhone12ProMax:
208 | return CGPoint(x: 32, y: 506)
209 | case .iPhone12Pro, .iPhone12:
210 | return CGPoint(x: 26, y: 469)
211 | case .iPhone12Mini:
212 | return CGPoint(x: 23, y: 457)
213 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
214 | return CGPoint(x: 27, y: 495.3333)
215 | case .iPhone11Pro, .iPhoneXS, .iPhoneX:
216 | return CGPoint(x: 23, y: 451)
217 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
218 | return CGPoint(x: 33, y: 426)
219 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
220 | return CGPoint(x: 27, y: 382)
221 | case .iPhoneSE, .iPod7:
222 | return CGPoint(x: 0, y: 0)
223 | default:
224 | return CGPoint(x: 218, y: 76)
225 | }
226 | }
227 | }
228 |
229 | // 小组件的大小
230 | enum DeviceWidgetSize {
231 | static var small: CGSize {
232 | switch UIDevice().type {
233 | case .iPhone12ProMax:
234 | return CGSize(width: 170, height: 170)
235 | case .iPhone12Pro, .iPhone12:
236 | return CGSize(width: 158, height: 158)
237 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
238 | return CGSize(width: 169, height: 169)
239 | case .iPhone12Mini, .iPhone11Pro, .iPhoneXS, .iPhoneX:
240 | return CGSize(width: 155, height: 155)
241 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
242 | return CGSize(width: 159, height: 159)
243 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
244 | return CGSize(width: 148, height: 148)
245 | case .iPhoneSE, .iPod7:
246 | return CGSize(width: 141, height: 144)
247 | default:
248 | return CGSize(width: 169, height: 169)
249 | }
250 | }
251 |
252 | static var meduim: CGSize {
253 | switch UIDevice().type {
254 | case .iPhone12ProMax:
255 | return CGSize(width: 364, height: 170)
256 | case .iPhone12Pro, .iPhone12:
257 | return CGSize(width: 338, height: 158)
258 | case .iPhone11ProMax, .iPhone11, .iPhoneXSMax, .iPhoneXR:
259 | return CGSize(width: 360, height: 169)
260 | case .iPhone12Mini, .iPhone11Pro, .iPhoneXS, .iPhoneX:
261 | return CGSize(width: 329, height: 155)
262 | case .iPhone6SPlus, .iPhone7Plus, .iPhone8Plus:
263 | return CGSize(width: 348, height: 159)
264 | case .iPhone6S, .iPhone7, .iPhone8, .iPhoneSE2:
265 | return CGSize(width: 322, height: 148)
266 | case .iPhoneSE, .iPod7:
267 | return CGSize(width: 291, height: 144)
268 | default:
269 | return CGSize(width: 360, height: 169)
270 | }
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/Clocks.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 2A1B4C19259DB90D0082FD18 /* ClocksDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C18259DB90D0082FD18 /* ClocksDetailView.swift */; };
11 | 2A1B4C37259EE8880082FD18 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C36259EE8880082FD18 /* Color.swift */; };
12 | 2A1B4C7925A2F1180082FD18 /* ClockConfigViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C7825A2F1180082FD18 /* ClockConfigViewModel.swift */; };
13 | 2A29B38F259C5998003B7085 /* ClocksApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B38E259C5998003B7085 /* ClocksApp.swift */; };
14 | 2A29B393259C599A003B7085 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A29B392259C599A003B7085 /* Assets.xcassets */; };
15 | 2A29B396259C599A003B7085 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A29B395259C599A003B7085 /* Preview Assets.xcassets */; };
16 | 2A29B3A5259C59B5003B7085 /* SimpleClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */; };
17 | 2A29B3A6259C59B5003B7085 /* WidgetListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A1259C59B5003B7085 /* WidgetListView.swift */; };
18 | 2A29B3A7259C59B5003B7085 /* WidgetPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */; };
19 | 2A29B3A8259C59B5003B7085 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A4259C59B5003B7085 /* ContentView.swift */; };
20 | 2A29B3B3259C5A09003B7085 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A29B3B2259C5A09003B7085 /* WidgetKit.framework */; };
21 | 2A29B3B5259C5A09003B7085 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A29B3B4259C5A09003B7085 /* SwiftUI.framework */; };
22 | 2A29B3B8259C5A09003B7085 /* ClocksWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3B7259C5A09003B7085 /* ClocksWidget.swift */; };
23 | 2A29B3BA259C5A0A003B7085 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A29B3B9259C5A0A003B7085 /* Assets.xcassets */; };
24 | 2A29B3BE259C5A0A003B7085 /* ClocksWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 2A29B3B0259C5A09003B7085 /* ClocksWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
25 | 2A29B3CA259C5A44003B7085 /* SimpleClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */; };
26 | 2A29B3D5259C5D1A003B7085 /* WidgetListPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3D4259C5D1A003B7085 /* WidgetListPreviewView.swift */; };
27 | 2A6D80F325ADC6830071F823 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */; };
28 | 2A6D80F725ADC6830071F823 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */; };
29 | 2A6D810225ADC69D0071F823 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C36259EE8880082FD18 /* Color.swift */; };
30 | 2A6D810625ADC6A00071F823 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C36259EE8880082FD18 /* Color.swift */; };
31 | 2A6D810D25ADC6A30071F823 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */; };
32 | 2A6D810E25ADC6A40071F823 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */; };
33 | 2A6D811525ADC6A80071F823 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D8A1F25AC1627007A8D38 /* Date.swift */; };
34 | 2A6D811625ADC6A90071F823 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D8A1F25AC1627007A8D38 /* Date.swift */; };
35 | 2A6D811D25ADC6AC0071F823 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */; };
36 | 2A6D811E25ADC6AD0071F823 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */; };
37 | 2A6D812525ADC7220071F823 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */; };
38 | 2A6D812925ADC72A0071F823 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */; };
39 | 2A6D812D25ADC7300071F823 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D8A1F25AC1627007A8D38 /* Date.swift */; };
40 | 2A6D813125ADC7330071F823 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */; };
41 | 2A6D813525ADC79A0071F823 /* ClockWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */; };
42 | 2A6D813925ADC7AB0071F823 /* WidgetBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */; };
43 | 2A6D813D25ADC7B80071F823 /* WidgetPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */; };
44 | 2A6D814125ADC7BB0071F823 /* WidgetFamilyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */; };
45 | 2A6D814525ADC7D90071F823 /* DeviceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */; };
46 | 2A6D814A25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */; };
47 | 2A6D814B25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */; };
48 | 2A6D814C25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */; };
49 | 2A6D815325ADC9020071F823 /* SimpleClock1View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */; };
50 | 2A6D815425ADC9020071F823 /* SimpleClock1View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */; };
51 | 2A6D815525ADC9030071F823 /* SimpleClock1View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */; };
52 | 2A7443EE25A5860C00A7C944 /* WidgetFamilyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */; };
53 | 2A7443EF25A5860C00A7C944 /* WidgetFamilyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */; };
54 | 2A7443F925A592D900A7C944 /* StorableClockConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */; };
55 | 2A7443FA25A592D900A7C944 /* StorableClockConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */; };
56 | 2A74440025A5B00E00A7C944 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443FF25A5B00E00A7C944 /* ImagePicker.swift */; };
57 | 2A74440825A5B8B400A7C944 /* WidgetPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */; };
58 | 2A8C0DC925A5EC6A00B94216 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DC825A5EC6A00B94216 /* Image.swift */; };
59 | 2A8C0DD025A5F13E00B94216 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DC825A5EC6A00B94216 /* Image.swift */; };
60 | 2A8C0DE025A6C29700B94216 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */; };
61 | 2A8C0DE125A6C29700B94216 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */; };
62 | 2A8C0DF925A6FA1100B94216 /* ClockDetailImageEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DF825A6FA1100B94216 /* ClockDetailImageEditView.swift */; };
63 | 2A8C0E2525A87CD900B94216 /* ClockDetailImageCropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E2425A87CD900B94216 /* ClockDetailImageCropView.swift */; };
64 | 2A8C0E3125A9662900B94216 /* WidgetBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */; };
65 | 2A8C0E3225A9662900B94216 /* WidgetBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */; };
66 | 2A8C0E3A25A99CB500B94216 /* ClocksWidgetIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */; };
67 | 2A8C0E3B25A99CB500B94216 /* ClocksWidgetIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */; };
68 | 2A8C0EDE25A9AA8500B94216 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A8C0EDD25A9AA8500B94216 /* Intents.framework */; };
69 | 2A8C0EE125A9AA8500B94216 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0EE025A9AA8500B94216 /* IntentHandler.swift */; };
70 | 2A8C0EE525A9AA8500B94216 /* ClocksWidgetIntents.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 2A8C0EDC25A9AA8500B94216 /* ClocksWidgetIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
71 | 2A8C0EF825A9ACFF00B94216 /* ClocksWidgetIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */; };
72 | 2A8C0F1825A9AF0800B94216 /* StorableClockConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */; };
73 | 2A8C0F2625A9AF3C00B94216 /* ClockConfigViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1B4C7825A2F1180082FD18 /* ClockConfigViewModel.swift */; };
74 | 2A8C0F3825A9B1A500B94216 /* CurrentClocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0F3725A9B1A500B94216 /* CurrentClocksView.swift */; };
75 | 2A8C8AAA25ADC48900C3E148 /* SimpleClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */; };
76 | 2A8C8AD925ADC54D00C3E148 /* ClockWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */; };
77 | 2AB6A96F25AC0B6100B85BAE /* ClockWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */; };
78 | 2ABCCC9325A9B66D00E6F96D /* ConfigKeysViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ABCCC9225A9B66D00E6F96D /* ConfigKeysViewModel.swift */; };
79 | 2ABCCCA725AA1C2D00E6F96D /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */; };
80 | 2ABCCCB925AAB92A00E6F96D /* ConfigKeysViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ABCCC9225A9B66D00E6F96D /* ConfigKeysViewModel.swift */; };
81 | 2ABCCCBE25AABF6E00E6F96D /* CurrentClockPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ABCCCBD25AABF6E00E6F96D /* CurrentClockPreviewView.swift */; };
82 | 2ACE48C725AC3E5E006A7FB9 /* ClockWidgetBundleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48C625AC3E5E006A7FB9 /* ClockWidgetBundleView.swift */; };
83 | 2ACE48C825AC3E5E006A7FB9 /* ClockWidgetBundleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48C625AC3E5E006A7FB9 /* ClockWidgetBundleView.swift */; };
84 | 2ACE48D025AC5FA4006A7FB9 /* DeviceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */; };
85 | 2ACE48D125AC5FA4006A7FB9 /* DeviceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */; };
86 | 2AF9604A25CD11060033766D /* SimplePicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9604925CD11060033766D /* SimplePicture.swift */; };
87 | 2AF9604B25CD11060033766D /* SimplePicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9604925CD11060033766D /* SimplePicture.swift */; };
88 | 2AF9604C25CD11060033766D /* SimplePicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9604925CD11060033766D /* SimplePicture.swift */; };
89 | 2AF9605125CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */; };
90 | 2AF9605225CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */; };
91 | 2AF9605325CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */; };
92 | /* End PBXBuildFile section */
93 |
94 | /* Begin PBXContainerItemProxy section */
95 | 2A29B3BC259C5A0A003B7085 /* PBXContainerItemProxy */ = {
96 | isa = PBXContainerItemProxy;
97 | containerPortal = 2A29B383259C5998003B7085 /* Project object */;
98 | proxyType = 1;
99 | remoteGlobalIDString = 2A29B3AF259C5A09003B7085;
100 | remoteInfo = ClocksWidgetExtension;
101 | };
102 | 2A8C0EE325A9AA8500B94216 /* PBXContainerItemProxy */ = {
103 | isa = PBXContainerItemProxy;
104 | containerPortal = 2A29B383259C5998003B7085 /* Project object */;
105 | proxyType = 1;
106 | remoteGlobalIDString = 2A8C0EDB25A9AA8500B94216;
107 | remoteInfo = ClocksWidgetIntents;
108 | };
109 | /* End PBXContainerItemProxy section */
110 |
111 | /* Begin PBXCopyFilesBuildPhase section */
112 | 2A29B3C2259C5A0A003B7085 /* Embed App Extensions */ = {
113 | isa = PBXCopyFilesBuildPhase;
114 | buildActionMask = 2147483647;
115 | dstPath = "";
116 | dstSubfolderSpec = 13;
117 | files = (
118 | 2A29B3BE259C5A0A003B7085 /* ClocksWidgetExtension.appex in Embed App Extensions */,
119 | 2A8C0EE525A9AA8500B94216 /* ClocksWidgetIntents.appex in Embed App Extensions */,
120 | );
121 | name = "Embed App Extensions";
122 | runOnlyForDeploymentPostprocessing = 0;
123 | };
124 | /* End PBXCopyFilesBuildPhase section */
125 |
126 | /* Begin PBXFileReference section */
127 | 2A1B4C18259DB90D0082FD18 /* ClocksDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClocksDetailView.swift; sourceTree = ""; };
128 | 2A1B4C36259EE8880082FD18 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; };
129 | 2A1B4C3A25A2E7210082FD18 /* Clocks.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Clocks.entitlements; sourceTree = ""; };
130 | 2A1B4C4725A2E8D30082FD18 /* ClocksWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClocksWidgetExtension.entitlements; sourceTree = ""; };
131 | 2A1B4C7825A2F1180082FD18 /* ClockConfigViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockConfigViewModel.swift; sourceTree = ""; };
132 | 2A29B38B259C5998003B7085 /* Clocks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Clocks.app; sourceTree = BUILT_PRODUCTS_DIR; };
133 | 2A29B38E259C5998003B7085 /* ClocksApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClocksApp.swift; sourceTree = ""; };
134 | 2A29B392259C599A003B7085 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
135 | 2A29B395259C599A003B7085 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
136 | 2A29B397259C599A003B7085 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
137 | 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleClockView.swift; sourceTree = ""; };
138 | 2A29B3A1259C59B5003B7085 /* WidgetListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetListView.swift; sourceTree = ""; };
139 | 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetPreviews.swift; sourceTree = ""; };
140 | 2A29B3A4259C59B5003B7085 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
141 | 2A29B3B0259C5A09003B7085 /* ClocksWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ClocksWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
142 | 2A29B3B2259C5A09003B7085 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
143 | 2A29B3B4259C5A09003B7085 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
144 | 2A29B3B7259C5A09003B7085 /* ClocksWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClocksWidget.swift; sourceTree = ""; };
145 | 2A29B3B9259C5A0A003B7085 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
146 | 2A29B3BB259C5A0A003B7085 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
147 | 2A29B3D4259C5D1A003B7085 /* WidgetListPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetListPreviewView.swift; sourceTree = ""; };
148 | 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatter.swift; sourceTree = ""; };
149 | 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultClockStyle.swift; sourceTree = ""; };
150 | 2A6D8A1F25AC1627007A8D38 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; };
151 | 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; };
152 | 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetFamilyProvider.swift; sourceTree = ""; };
153 | 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorableClockConfig.swift; sourceTree = ""; };
154 | 2A7443FF25A5B00E00A7C944 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; };
155 | 2A8C0DC825A5EC6A00B94216 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; };
156 | 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; };
157 | 2A8C0DF825A6FA1100B94216 /* ClockDetailImageEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockDetailImageEditView.swift; sourceTree = ""; };
158 | 2A8C0E2425A87CD900B94216 /* ClockDetailImageCropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockDetailImageCropView.swift; sourceTree = ""; };
159 | 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBackground.swift; sourceTree = ""; };
160 | 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = ClocksWidgetIntents.intentdefinition; sourceTree = ""; };
161 | 2A8C0EDC25A9AA8500B94216 /* ClocksWidgetIntents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ClocksWidgetIntents.appex; sourceTree = BUILT_PRODUCTS_DIR; };
162 | 2A8C0EDD25A9AA8500B94216 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; };
163 | 2A8C0EE025A9AA8500B94216 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; };
164 | 2A8C0EE225A9AA8500B94216 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
165 | 2A8C0EFF25A9AE5F00B94216 /* ClocksWidgetIntents.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClocksWidgetIntents.entitlements; sourceTree = ""; };
166 | 2A8C0F3725A9B1A500B94216 /* CurrentClocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentClocksView.swift; sourceTree = ""; };
167 | 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockWidget.swift; sourceTree = ""; };
168 | 2ABCCC9225A9B66D00E6F96D /* ConfigKeysViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigKeysViewModel.swift; sourceTree = ""; };
169 | 2ABCCCBD25AABF6E00E6F96D /* CurrentClockPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentClockPreviewView.swift; sourceTree = ""; };
170 | 2ACE48C625AC3E5E006A7FB9 /* ClockWidgetBundleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockWidgetBundleView.swift; sourceTree = ""; };
171 | 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceWidget.swift; sourceTree = ""; };
172 | 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; };
173 | 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleClock1View.swift; sourceTree = ""; };
174 | 2AF9604925CD11060033766D /* SimplePicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplePicture.swift; sourceTree = ""; };
175 | 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDetailConfigFields.swift; sourceTree = ""; };
176 | /* End PBXFileReference section */
177 |
178 | /* Begin PBXFrameworksBuildPhase section */
179 | 2A29B388259C5998003B7085 /* Frameworks */ = {
180 | isa = PBXFrameworksBuildPhase;
181 | buildActionMask = 2147483647;
182 | files = (
183 | );
184 | runOnlyForDeploymentPostprocessing = 0;
185 | };
186 | 2A29B3AD259C5A09003B7085 /* Frameworks */ = {
187 | isa = PBXFrameworksBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | 2A29B3B5259C5A09003B7085 /* SwiftUI.framework in Frameworks */,
191 | 2A29B3B3259C5A09003B7085 /* WidgetKit.framework in Frameworks */,
192 | );
193 | runOnlyForDeploymentPostprocessing = 0;
194 | };
195 | 2A8C0ED925A9AA8500B94216 /* Frameworks */ = {
196 | isa = PBXFrameworksBuildPhase;
197 | buildActionMask = 2147483647;
198 | files = (
199 | 2A8C0EDE25A9AA8500B94216 /* Intents.framework in Frameworks */,
200 | );
201 | runOnlyForDeploymentPostprocessing = 0;
202 | };
203 | /* End PBXFrameworksBuildPhase section */
204 |
205 | /* Begin PBXGroup section */
206 | 2A29B382259C5998003B7085 = {
207 | isa = PBXGroup;
208 | children = (
209 | 2A1B4C4725A2E8D30082FD18 /* ClocksWidgetExtension.entitlements */,
210 | 2A29B38D259C5998003B7085 /* Clocks */,
211 | 2A29B3B6259C5A09003B7085 /* ClocksWidget */,
212 | 2A8C0EDF25A9AA8500B94216 /* ClocksWidgetIntents */,
213 | 2A29B3B1259C5A09003B7085 /* Frameworks */,
214 | 2A29B38C259C5998003B7085 /* Products */,
215 | );
216 | sourceTree = "";
217 | };
218 | 2A29B38C259C5998003B7085 /* Products */ = {
219 | isa = PBXGroup;
220 | children = (
221 | 2A29B38B259C5998003B7085 /* Clocks.app */,
222 | 2A29B3B0259C5A09003B7085 /* ClocksWidgetExtension.appex */,
223 | 2A8C0EDC25A9AA8500B94216 /* ClocksWidgetIntents.appex */,
224 | );
225 | name = Products;
226 | sourceTree = "";
227 | };
228 | 2A29B38D259C5998003B7085 /* Clocks */ = {
229 | isa = PBXGroup;
230 | children = (
231 | 2AB6A96D25AC0B4E00B85BAE /* Protocol */,
232 | 2A1B4C3A25A2E7210082FD18 /* Clocks.entitlements */,
233 | 2A29B397259C599A003B7085 /* Info.plist */,
234 | 2A29B38E259C5998003B7085 /* ClocksApp.swift */,
235 | 2A29B392259C599A003B7085 /* Assets.xcassets */,
236 | 2A29B3EA259C6DCA003B7085 /* Extensions */,
237 | 2A29B394259C599A003B7085 /* Preview Content */,
238 | 2A8C0DC725A5EC5400B94216 /* Utils */,
239 | 2A29B3F1259C713B003B7085 /* ViewModels */,
240 | 2A29B39E259C59B5003B7085 /* Views */,
241 | );
242 | path = Clocks;
243 | sourceTree = "";
244 | };
245 | 2A29B394259C599A003B7085 /* Preview Content */ = {
246 | isa = PBXGroup;
247 | children = (
248 | 2A29B395259C599A003B7085 /* Preview Assets.xcassets */,
249 | );
250 | path = "Preview Content";
251 | sourceTree = "";
252 | };
253 | 2A29B39E259C59B5003B7085 /* Views */ = {
254 | isa = PBXGroup;
255 | children = (
256 | 2A29B3A4259C59B5003B7085 /* ContentView.swift */,
257 | 2A29B39F259C59B5003B7085 /* WidgetViews */,
258 | 2ABCCCC525AAC05400E6F96D /* WidgetDetail */,
259 | 2ABCCCC725AAC08A00E6F96D /* WidgetList */,
260 | 2ABCCCC625AAC07300E6F96D /* CurrentWidget */,
261 | 2A29B3A2259C59B5003B7085 /* Helper */,
262 | );
263 | path = Views;
264 | sourceTree = "";
265 | };
266 | 2A29B39F259C59B5003B7085 /* WidgetViews */ = {
267 | isa = PBXGroup;
268 | children = (
269 | 2A29B3A0259C59B5003B7085 /* SimpleClockView.swift */,
270 | 2ACE48C625AC3E5E006A7FB9 /* ClockWidgetBundleView.swift */,
271 | 2ACE48E725AD5198006A7FB9 /* SimpleClock1View.swift */,
272 | 2AF9604925CD11060033766D /* SimplePicture.swift */,
273 | );
274 | path = WidgetViews;
275 | sourceTree = "";
276 | };
277 | 2A29B3A2259C59B5003B7085 /* Helper */ = {
278 | isa = PBXGroup;
279 | children = (
280 | 2A29B3A3259C59B5003B7085 /* WidgetPreviews.swift */,
281 | 2A7443ED25A5860C00A7C944 /* WidgetFamilyProvider.swift */,
282 | 2A7443FF25A5B00E00A7C944 /* ImagePicker.swift */,
283 | 2A8C0E3025A9662900B94216 /* WidgetBackground.swift */,
284 | );
285 | path = Helper;
286 | sourceTree = "";
287 | };
288 | 2A29B3B1259C5A09003B7085 /* Frameworks */ = {
289 | isa = PBXGroup;
290 | children = (
291 | 2A29B3B2259C5A09003B7085 /* WidgetKit.framework */,
292 | 2A29B3B4259C5A09003B7085 /* SwiftUI.framework */,
293 | 2A8C0EDD25A9AA8500B94216 /* Intents.framework */,
294 | );
295 | name = Frameworks;
296 | sourceTree = "";
297 | };
298 | 2A29B3B6259C5A09003B7085 /* ClocksWidget */ = {
299 | isa = PBXGroup;
300 | children = (
301 | 2A29B3B7259C5A09003B7085 /* ClocksWidget.swift */,
302 | 2A29B3B9259C5A0A003B7085 /* Assets.xcassets */,
303 | 2A29B3BB259C5A0A003B7085 /* Info.plist */,
304 | 2A8C0E3925A99CB500B94216 /* ClocksWidgetIntents.intentdefinition */,
305 | );
306 | path = ClocksWidget;
307 | sourceTree = "";
308 | };
309 | 2A29B3EA259C6DCA003B7085 /* Extensions */ = {
310 | isa = PBXGroup;
311 | children = (
312 | 2A29B3EB259C6E02003B7085 /* DateFormatter.swift */,
313 | 2A1B4C36259EE8880082FD18 /* Color.swift */,
314 | 2A7443E625A5849E00A7C944 /* EnvironmentValues.swift */,
315 | 2A6D8A1F25AC1627007A8D38 /* Date.swift */,
316 | 2ACE48D525AC6226006A7FB9 /* UIDevice.swift */,
317 | );
318 | path = Extensions;
319 | sourceTree = "";
320 | };
321 | 2A29B3F1259C713B003B7085 /* ViewModels */ = {
322 | isa = PBXGroup;
323 | children = (
324 | 2A1B4C7825A2F1180082FD18 /* ClockConfigViewModel.swift */,
325 | 2A7443F825A592D900A7C944 /* StorableClockConfig.swift */,
326 | 2ABCCC9225A9B66D00E6F96D /* ConfigKeysViewModel.swift */,
327 | );
328 | path = ViewModels;
329 | sourceTree = "";
330 | };
331 | 2A8C0DC725A5EC5400B94216 /* Utils */ = {
332 | isa = PBXGroup;
333 | children = (
334 | 2A8C0DC825A5EC6A00B94216 /* Image.swift */,
335 | 2A8C0DDF25A6C29700B94216 /* UserDefaults.swift */,
336 | 2ACE48CF25AC5FA4006A7FB9 /* DeviceWidget.swift */,
337 | 2A6D814925ADC85E0071F823 /* DefaultClockStyle.swift */,
338 | 2AF9605025CD181B0033766D /* WidgetDetailConfigFields.swift */,
339 | );
340 | path = Utils;
341 | sourceTree = "";
342 | };
343 | 2A8C0EDF25A9AA8500B94216 /* ClocksWidgetIntents */ = {
344 | isa = PBXGroup;
345 | children = (
346 | 2A8C0EFF25A9AE5F00B94216 /* ClocksWidgetIntents.entitlements */,
347 | 2A8C0EE025A9AA8500B94216 /* IntentHandler.swift */,
348 | 2A8C0EE225A9AA8500B94216 /* Info.plist */,
349 | );
350 | path = ClocksWidgetIntents;
351 | sourceTree = "";
352 | };
353 | 2AB6A96D25AC0B4E00B85BAE /* Protocol */ = {
354 | isa = PBXGroup;
355 | children = (
356 | 2AB6A96E25AC0B6100B85BAE /* ClockWidget.swift */,
357 | );
358 | path = Protocol;
359 | sourceTree = "";
360 | };
361 | 2ABCCCC525AAC05400E6F96D /* WidgetDetail */ = {
362 | isa = PBXGroup;
363 | children = (
364 | 2A1B4C18259DB90D0082FD18 /* ClocksDetailView.swift */,
365 | 2A8C0DF825A6FA1100B94216 /* ClockDetailImageEditView.swift */,
366 | 2A8C0E2425A87CD900B94216 /* ClockDetailImageCropView.swift */,
367 | );
368 | path = WidgetDetail;
369 | sourceTree = "";
370 | };
371 | 2ABCCCC625AAC07300E6F96D /* CurrentWidget */ = {
372 | isa = PBXGroup;
373 | children = (
374 | 2A8C0F3725A9B1A500B94216 /* CurrentClocksView.swift */,
375 | 2ABCCCBD25AABF6E00E6F96D /* CurrentClockPreviewView.swift */,
376 | );
377 | path = CurrentWidget;
378 | sourceTree = "";
379 | };
380 | 2ABCCCC725AAC08A00E6F96D /* WidgetList */ = {
381 | isa = PBXGroup;
382 | children = (
383 | 2A29B3D4259C5D1A003B7085 /* WidgetListPreviewView.swift */,
384 | 2A29B3A1259C59B5003B7085 /* WidgetListView.swift */,
385 | );
386 | path = WidgetList;
387 | sourceTree = "";
388 | };
389 | /* End PBXGroup section */
390 |
391 | /* Begin PBXNativeTarget section */
392 | 2A29B38A259C5998003B7085 /* Clocks */ = {
393 | isa = PBXNativeTarget;
394 | buildConfigurationList = 2A29B39A259C599A003B7085 /* Build configuration list for PBXNativeTarget "Clocks" */;
395 | buildPhases = (
396 | 2A29B387259C5998003B7085 /* Sources */,
397 | 2A29B388259C5998003B7085 /* Frameworks */,
398 | 2A29B389259C5998003B7085 /* Resources */,
399 | 2A29B3C2259C5A0A003B7085 /* Embed App Extensions */,
400 | );
401 | buildRules = (
402 | );
403 | dependencies = (
404 | 2A29B3BD259C5A0A003B7085 /* PBXTargetDependency */,
405 | 2A8C0EE425A9AA8500B94216 /* PBXTargetDependency */,
406 | );
407 | name = Clocks;
408 | productName = Clocks;
409 | productReference = 2A29B38B259C5998003B7085 /* Clocks.app */;
410 | productType = "com.apple.product-type.application";
411 | };
412 | 2A29B3AF259C5A09003B7085 /* ClocksWidgetExtension */ = {
413 | isa = PBXNativeTarget;
414 | buildConfigurationList = 2A29B3BF259C5A0A003B7085 /* Build configuration list for PBXNativeTarget "ClocksWidgetExtension" */;
415 | buildPhases = (
416 | 2A29B3AC259C5A09003B7085 /* Sources */,
417 | 2A29B3AD259C5A09003B7085 /* Frameworks */,
418 | 2A29B3AE259C5A09003B7085 /* Resources */,
419 | );
420 | buildRules = (
421 | );
422 | dependencies = (
423 | );
424 | name = ClocksWidgetExtension;
425 | productName = ClocksWidgetExtension;
426 | productReference = 2A29B3B0259C5A09003B7085 /* ClocksWidgetExtension.appex */;
427 | productType = "com.apple.product-type.app-extension";
428 | };
429 | 2A8C0EDB25A9AA8500B94216 /* ClocksWidgetIntents */ = {
430 | isa = PBXNativeTarget;
431 | buildConfigurationList = 2A8C0EE625A9AA8500B94216 /* Build configuration list for PBXNativeTarget "ClocksWidgetIntents" */;
432 | buildPhases = (
433 | 2A8C0ED825A9AA8500B94216 /* Sources */,
434 | 2A8C0ED925A9AA8500B94216 /* Frameworks */,
435 | 2A8C0EDA25A9AA8500B94216 /* Resources */,
436 | );
437 | buildRules = (
438 | );
439 | dependencies = (
440 | );
441 | name = ClocksWidgetIntents;
442 | productName = ClocksWidgetIntents;
443 | productReference = 2A8C0EDC25A9AA8500B94216 /* ClocksWidgetIntents.appex */;
444 | productType = "com.apple.product-type.app-extension";
445 | };
446 | /* End PBXNativeTarget section */
447 |
448 | /* Begin PBXProject section */
449 | 2A29B383259C5998003B7085 /* Project object */ = {
450 | isa = PBXProject;
451 | attributes = {
452 | LastSwiftUpdateCheck = 1230;
453 | LastUpgradeCheck = 1230;
454 | TargetAttributes = {
455 | 2A29B38A259C5998003B7085 = {
456 | CreatedOnToolsVersion = 12.3;
457 | };
458 | 2A29B3AF259C5A09003B7085 = {
459 | CreatedOnToolsVersion = 12.3;
460 | };
461 | 2A8C0EDB25A9AA8500B94216 = {
462 | CreatedOnToolsVersion = 12.3;
463 | };
464 | };
465 | };
466 | buildConfigurationList = 2A29B386259C5998003B7085 /* Build configuration list for PBXProject "Clocks" */;
467 | compatibilityVersion = "Xcode 9.3";
468 | developmentRegion = en;
469 | hasScannedForEncodings = 0;
470 | knownRegions = (
471 | en,
472 | Base,
473 | );
474 | mainGroup = 2A29B382259C5998003B7085;
475 | productRefGroup = 2A29B38C259C5998003B7085 /* Products */;
476 | projectDirPath = "";
477 | projectRoot = "";
478 | targets = (
479 | 2A29B38A259C5998003B7085 /* Clocks */,
480 | 2A29B3AF259C5A09003B7085 /* ClocksWidgetExtension */,
481 | 2A8C0EDB25A9AA8500B94216 /* ClocksWidgetIntents */,
482 | );
483 | };
484 | /* End PBXProject section */
485 |
486 | /* Begin PBXResourcesBuildPhase section */
487 | 2A29B389259C5998003B7085 /* Resources */ = {
488 | isa = PBXResourcesBuildPhase;
489 | buildActionMask = 2147483647;
490 | files = (
491 | 2A29B396259C599A003B7085 /* Preview Assets.xcassets in Resources */,
492 | 2A29B393259C599A003B7085 /* Assets.xcassets in Resources */,
493 | );
494 | runOnlyForDeploymentPostprocessing = 0;
495 | };
496 | 2A29B3AE259C5A09003B7085 /* Resources */ = {
497 | isa = PBXResourcesBuildPhase;
498 | buildActionMask = 2147483647;
499 | files = (
500 | 2A29B3BA259C5A0A003B7085 /* Assets.xcassets in Resources */,
501 | );
502 | runOnlyForDeploymentPostprocessing = 0;
503 | };
504 | 2A8C0EDA25A9AA8500B94216 /* Resources */ = {
505 | isa = PBXResourcesBuildPhase;
506 | buildActionMask = 2147483647;
507 | files = (
508 | );
509 | runOnlyForDeploymentPostprocessing = 0;
510 | };
511 | /* End PBXResourcesBuildPhase section */
512 |
513 | /* Begin PBXSourcesBuildPhase section */
514 | 2A29B387259C5998003B7085 /* Sources */ = {
515 | isa = PBXSourcesBuildPhase;
516 | buildActionMask = 2147483647;
517 | files = (
518 | 2A1B4C19259DB90D0082FD18 /* ClocksDetailView.swift in Sources */,
519 | 2ACE48D025AC5FA4006A7FB9 /* DeviceWidget.swift in Sources */,
520 | 2A29B3D5259C5D1A003B7085 /* WidgetListPreviewView.swift in Sources */,
521 | 2A6D814A25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */,
522 | 2ACE48C725AC3E5E006A7FB9 /* ClockWidgetBundleView.swift in Sources */,
523 | 2A6D811525ADC6A80071F823 /* Date.swift in Sources */,
524 | 2A6D811E25ADC6AD0071F823 /* UIDevice.swift in Sources */,
525 | 2A1B4C37259EE8880082FD18 /* Color.swift in Sources */,
526 | 2A6D80F325ADC6830071F823 /* DateFormatter.swift in Sources */,
527 | 2AF9605125CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */,
528 | 2A8C0E3125A9662900B94216 /* WidgetBackground.swift in Sources */,
529 | 2A8C0DE025A6C29700B94216 /* UserDefaults.swift in Sources */,
530 | 2AB6A96F25AC0B6100B85BAE /* ClockWidget.swift in Sources */,
531 | 2A7443EE25A5860C00A7C944 /* WidgetFamilyProvider.swift in Sources */,
532 | 2A6D815325ADC9020071F823 /* SimpleClock1View.swift in Sources */,
533 | 2A29B3A5259C59B5003B7085 /* SimpleClockView.swift in Sources */,
534 | 2A29B3A6259C59B5003B7085 /* WidgetListView.swift in Sources */,
535 | 2A7443F925A592D900A7C944 /* StorableClockConfig.swift in Sources */,
536 | 2A8C0DF925A6FA1100B94216 /* ClockDetailImageEditView.swift in Sources */,
537 | 2A6D810D25ADC6A30071F823 /* EnvironmentValues.swift in Sources */,
538 | 2ABCCC9325A9B66D00E6F96D /* ConfigKeysViewModel.swift in Sources */,
539 | 2ABCCCBE25AABF6E00E6F96D /* CurrentClockPreviewView.swift in Sources */,
540 | 2A8C0DC925A5EC6A00B94216 /* Image.swift in Sources */,
541 | 2A29B3A8259C59B5003B7085 /* ContentView.swift in Sources */,
542 | 2A29B3A7259C59B5003B7085 /* WidgetPreviews.swift in Sources */,
543 | 2AF9604A25CD11060033766D /* SimplePicture.swift in Sources */,
544 | 2A74440025A5B00E00A7C944 /* ImagePicker.swift in Sources */,
545 | 2A8C0F3825A9B1A500B94216 /* CurrentClocksView.swift in Sources */,
546 | 2A8C0E3A25A99CB500B94216 /* ClocksWidgetIntents.intentdefinition in Sources */,
547 | 2A8C0E2525A87CD900B94216 /* ClockDetailImageCropView.swift in Sources */,
548 | 2A29B38F259C5998003B7085 /* ClocksApp.swift in Sources */,
549 | 2A1B4C7925A2F1180082FD18 /* ClockConfigViewModel.swift in Sources */,
550 | );
551 | runOnlyForDeploymentPostprocessing = 0;
552 | };
553 | 2A29B3AC259C5A09003B7085 /* Sources */ = {
554 | isa = PBXSourcesBuildPhase;
555 | buildActionMask = 2147483647;
556 | files = (
557 | 2A7443FA25A592D900A7C944 /* StorableClockConfig.swift in Sources */,
558 | 2A8C0E3B25A99CB500B94216 /* ClocksWidgetIntents.intentdefinition in Sources */,
559 | 2A6D80F725ADC6830071F823 /* DateFormatter.swift in Sources */,
560 | 2A8C0DE125A6C29700B94216 /* UserDefaults.swift in Sources */,
561 | 2A74440825A5B8B400A7C944 /* WidgetPreviews.swift in Sources */,
562 | 2ACE48C825AC3E5E006A7FB9 /* ClockWidgetBundleView.swift in Sources */,
563 | 2A8C0F2625A9AF3C00B94216 /* ClockConfigViewModel.swift in Sources */,
564 | 2A6D815425ADC9020071F823 /* SimpleClock1View.swift in Sources */,
565 | 2A8C0E3225A9662900B94216 /* WidgetBackground.swift in Sources */,
566 | 2ABCCCB925AAB92A00E6F96D /* ConfigKeysViewModel.swift in Sources */,
567 | 2AF9604B25CD11060033766D /* SimplePicture.swift in Sources */,
568 | 2A6D811625ADC6A90071F823 /* Date.swift in Sources */,
569 | 2A8C8AD925ADC54D00C3E148 /* ClockWidget.swift in Sources */,
570 | 2A6D810225ADC69D0071F823 /* Color.swift in Sources */,
571 | 2A29B3CA259C5A44003B7085 /* SimpleClockView.swift in Sources */,
572 | 2A6D810E25ADC6A40071F823 /* EnvironmentValues.swift in Sources */,
573 | 2A6D811D25ADC6AC0071F823 /* UIDevice.swift in Sources */,
574 | 2A6D814B25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */,
575 | 2A29B3B8259C5A09003B7085 /* ClocksWidget.swift in Sources */,
576 | 2A7443EF25A5860C00A7C944 /* WidgetFamilyProvider.swift in Sources */,
577 | 2AF9605225CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */,
578 | 2ACE48D125AC5FA4006A7FB9 /* DeviceWidget.swift in Sources */,
579 | 2A8C0DD025A5F13E00B94216 /* Image.swift in Sources */,
580 | );
581 | runOnlyForDeploymentPostprocessing = 0;
582 | };
583 | 2A8C0ED825A9AA8500B94216 /* Sources */ = {
584 | isa = PBXSourcesBuildPhase;
585 | buildActionMask = 2147483647;
586 | files = (
587 | 2A6D812925ADC72A0071F823 /* DateFormatter.swift in Sources */,
588 | 2A6D815525ADC9030071F823 /* SimpleClock1View.swift in Sources */,
589 | 2A6D814525ADC7D90071F823 /* DeviceWidget.swift in Sources */,
590 | 2A6D812D25ADC7300071F823 /* Date.swift in Sources */,
591 | 2A8C0EE125A9AA8500B94216 /* IntentHandler.swift in Sources */,
592 | 2A6D814C25ADC85E0071F823 /* DefaultClockStyle.swift in Sources */,
593 | 2AF9605325CD181B0033766D /* WidgetDetailConfigFields.swift in Sources */,
594 | 2ABCCCA725AA1C2D00E6F96D /* UserDefaults.swift in Sources */,
595 | 2A6D813D25ADC7B80071F823 /* WidgetPreviews.swift in Sources */,
596 | 2A6D810625ADC6A00071F823 /* Color.swift in Sources */,
597 | 2A6D814125ADC7BB0071F823 /* WidgetFamilyProvider.swift in Sources */,
598 | 2A6D813925ADC7AB0071F823 /* WidgetBackground.swift in Sources */,
599 | 2A6D813125ADC7330071F823 /* UIDevice.swift in Sources */,
600 | 2A8C0F1825A9AF0800B94216 /* StorableClockConfig.swift in Sources */,
601 | 2A6D813525ADC79A0071F823 /* ClockWidget.swift in Sources */,
602 | 2AF9604C25CD11060033766D /* SimplePicture.swift in Sources */,
603 | 2A8C8AAA25ADC48900C3E148 /* SimpleClockView.swift in Sources */,
604 | 2A8C0EF825A9ACFF00B94216 /* ClocksWidgetIntents.intentdefinition in Sources */,
605 | 2A6D812525ADC7220071F823 /* EnvironmentValues.swift in Sources */,
606 | );
607 | runOnlyForDeploymentPostprocessing = 0;
608 | };
609 | /* End PBXSourcesBuildPhase section */
610 |
611 | /* Begin PBXTargetDependency section */
612 | 2A29B3BD259C5A0A003B7085 /* PBXTargetDependency */ = {
613 | isa = PBXTargetDependency;
614 | target = 2A29B3AF259C5A09003B7085 /* ClocksWidgetExtension */;
615 | targetProxy = 2A29B3BC259C5A0A003B7085 /* PBXContainerItemProxy */;
616 | };
617 | 2A8C0EE425A9AA8500B94216 /* PBXTargetDependency */ = {
618 | isa = PBXTargetDependency;
619 | target = 2A8C0EDB25A9AA8500B94216 /* ClocksWidgetIntents */;
620 | targetProxy = 2A8C0EE325A9AA8500B94216 /* PBXContainerItemProxy */;
621 | };
622 | /* End PBXTargetDependency section */
623 |
624 | /* Begin XCBuildConfiguration section */
625 | 2A29B398259C599A003B7085 /* Debug */ = {
626 | isa = XCBuildConfiguration;
627 | buildSettings = {
628 | ALWAYS_SEARCH_USER_PATHS = NO;
629 | CLANG_ANALYZER_NONNULL = YES;
630 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
631 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
632 | CLANG_CXX_LIBRARY = "libc++";
633 | CLANG_ENABLE_MODULES = YES;
634 | CLANG_ENABLE_OBJC_ARC = YES;
635 | CLANG_ENABLE_OBJC_WEAK = YES;
636 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
637 | CLANG_WARN_BOOL_CONVERSION = YES;
638 | CLANG_WARN_COMMA = YES;
639 | CLANG_WARN_CONSTANT_CONVERSION = YES;
640 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
641 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
642 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
643 | CLANG_WARN_EMPTY_BODY = YES;
644 | CLANG_WARN_ENUM_CONVERSION = YES;
645 | CLANG_WARN_INFINITE_RECURSION = YES;
646 | CLANG_WARN_INT_CONVERSION = YES;
647 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
648 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
649 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
650 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
651 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
652 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
653 | CLANG_WARN_STRICT_PROTOTYPES = YES;
654 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
655 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
656 | CLANG_WARN_UNREACHABLE_CODE = YES;
657 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
658 | COPY_PHASE_STRIP = NO;
659 | DEBUG_INFORMATION_FORMAT = dwarf;
660 | ENABLE_STRICT_OBJC_MSGSEND = YES;
661 | ENABLE_TESTABILITY = YES;
662 | GCC_C_LANGUAGE_STANDARD = gnu11;
663 | GCC_DYNAMIC_NO_PIC = NO;
664 | GCC_NO_COMMON_BLOCKS = YES;
665 | GCC_OPTIMIZATION_LEVEL = 0;
666 | GCC_PREPROCESSOR_DEFINITIONS = (
667 | "DEBUG=1",
668 | "$(inherited)",
669 | );
670 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
671 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
672 | GCC_WARN_UNDECLARED_SELECTOR = YES;
673 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
674 | GCC_WARN_UNUSED_FUNCTION = YES;
675 | GCC_WARN_UNUSED_VARIABLE = YES;
676 | IPHONEOS_DEPLOYMENT_TARGET = 14.3;
677 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
678 | MTL_FAST_MATH = YES;
679 | ONLY_ACTIVE_ARCH = YES;
680 | SDKROOT = iphoneos;
681 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
682 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
683 | };
684 | name = Debug;
685 | };
686 | 2A29B399259C599A003B7085 /* Release */ = {
687 | isa = XCBuildConfiguration;
688 | buildSettings = {
689 | ALWAYS_SEARCH_USER_PATHS = NO;
690 | CLANG_ANALYZER_NONNULL = YES;
691 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
692 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
693 | CLANG_CXX_LIBRARY = "libc++";
694 | CLANG_ENABLE_MODULES = YES;
695 | CLANG_ENABLE_OBJC_ARC = YES;
696 | CLANG_ENABLE_OBJC_WEAK = YES;
697 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
698 | CLANG_WARN_BOOL_CONVERSION = YES;
699 | CLANG_WARN_COMMA = YES;
700 | CLANG_WARN_CONSTANT_CONVERSION = YES;
701 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
702 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
703 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
704 | CLANG_WARN_EMPTY_BODY = YES;
705 | CLANG_WARN_ENUM_CONVERSION = YES;
706 | CLANG_WARN_INFINITE_RECURSION = YES;
707 | CLANG_WARN_INT_CONVERSION = YES;
708 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
709 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
710 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
711 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
712 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
713 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
714 | CLANG_WARN_STRICT_PROTOTYPES = YES;
715 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
716 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
717 | CLANG_WARN_UNREACHABLE_CODE = YES;
718 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
719 | COPY_PHASE_STRIP = NO;
720 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
721 | ENABLE_NS_ASSERTIONS = NO;
722 | ENABLE_STRICT_OBJC_MSGSEND = YES;
723 | GCC_C_LANGUAGE_STANDARD = gnu11;
724 | GCC_NO_COMMON_BLOCKS = YES;
725 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
726 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
727 | GCC_WARN_UNDECLARED_SELECTOR = YES;
728 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
729 | GCC_WARN_UNUSED_FUNCTION = YES;
730 | GCC_WARN_UNUSED_VARIABLE = YES;
731 | IPHONEOS_DEPLOYMENT_TARGET = 14.3;
732 | MTL_ENABLE_DEBUG_INFO = NO;
733 | MTL_FAST_MATH = YES;
734 | SDKROOT = iphoneos;
735 | SWIFT_COMPILATION_MODE = wholemodule;
736 | SWIFT_OPTIMIZATION_LEVEL = "-O";
737 | VALIDATE_PRODUCT = YES;
738 | };
739 | name = Release;
740 | };
741 | 2A29B39B259C599A003B7085 /* Debug */ = {
742 | isa = XCBuildConfiguration;
743 | buildSettings = {
744 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
745 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
746 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
747 | CODE_SIGN_ENTITLEMENTS = Clocks/Clocks.entitlements;
748 | CODE_SIGN_STYLE = Automatic;
749 | CURRENT_PROJECT_VERSION = 1;
750 | DEVELOPMENT_ASSET_PATHS = "\"Clocks/Preview Content\"";
751 | DEVELOPMENT_TEAM = SRD476XUQA;
752 | ENABLE_PREVIEWS = YES;
753 | INFOPLIST_FILE = Clocks/Info.plist;
754 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
755 | LD_RUNPATH_SEARCH_PATHS = (
756 | "$(inherited)",
757 | "@executable_path/Frameworks",
758 | );
759 | MARKETING_VERSION = 1.2;
760 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget";
761 | PRODUCT_NAME = "$(TARGET_NAME)";
762 | SWIFT_VERSION = 5.0;
763 | TARGETED_DEVICE_FAMILY = 1;
764 | };
765 | name = Debug;
766 | };
767 | 2A29B39C259C599A003B7085 /* Release */ = {
768 | isa = XCBuildConfiguration;
769 | buildSettings = {
770 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
771 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
772 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
773 | CODE_SIGN_ENTITLEMENTS = Clocks/Clocks.entitlements;
774 | CODE_SIGN_STYLE = Automatic;
775 | CURRENT_PROJECT_VERSION = 1;
776 | DEVELOPMENT_ASSET_PATHS = "\"Clocks/Preview Content\"";
777 | DEVELOPMENT_TEAM = SRD476XUQA;
778 | ENABLE_PREVIEWS = YES;
779 | INFOPLIST_FILE = Clocks/Info.plist;
780 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
781 | LD_RUNPATH_SEARCH_PATHS = (
782 | "$(inherited)",
783 | "@executable_path/Frameworks",
784 | );
785 | MARKETING_VERSION = 1.2;
786 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget";
787 | PRODUCT_NAME = "$(TARGET_NAME)";
788 | SWIFT_VERSION = 5.0;
789 | TARGETED_DEVICE_FAMILY = 1;
790 | };
791 | name = Release;
792 | };
793 | 2A29B3C0259C5A0A003B7085 /* Debug */ = {
794 | isa = XCBuildConfiguration;
795 | buildSettings = {
796 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
797 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
798 | CODE_SIGN_ENTITLEMENTS = ClocksWidgetExtension.entitlements;
799 | CODE_SIGN_STYLE = Automatic;
800 | CURRENT_PROJECT_VERSION = 1;
801 | DEVELOPMENT_TEAM = SRD476XUQA;
802 | INFOPLIST_FILE = ClocksWidget/Info.plist;
803 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
804 | LD_RUNPATH_SEARCH_PATHS = (
805 | "$(inherited)",
806 | "@executable_path/Frameworks",
807 | "@executable_path/../../Frameworks",
808 | );
809 | MARKETING_VERSION = 1.2;
810 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget.widget-extension";
811 | PRODUCT_NAME = "$(TARGET_NAME)";
812 | SKIP_INSTALL = YES;
813 | SWIFT_VERSION = 5.0;
814 | TARGETED_DEVICE_FAMILY = 1;
815 | };
816 | name = Debug;
817 | };
818 | 2A29B3C1259C5A0A003B7085 /* Release */ = {
819 | isa = XCBuildConfiguration;
820 | buildSettings = {
821 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
822 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
823 | CODE_SIGN_ENTITLEMENTS = ClocksWidgetExtension.entitlements;
824 | CODE_SIGN_STYLE = Automatic;
825 | CURRENT_PROJECT_VERSION = 1;
826 | DEVELOPMENT_TEAM = SRD476XUQA;
827 | INFOPLIST_FILE = ClocksWidget/Info.plist;
828 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
829 | LD_RUNPATH_SEARCH_PATHS = (
830 | "$(inherited)",
831 | "@executable_path/Frameworks",
832 | "@executable_path/../../Frameworks",
833 | );
834 | MARKETING_VERSION = 1.2;
835 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget.widget-extension";
836 | PRODUCT_NAME = "$(TARGET_NAME)";
837 | SKIP_INSTALL = YES;
838 | SWIFT_VERSION = 5.0;
839 | TARGETED_DEVICE_FAMILY = 1;
840 | };
841 | name = Release;
842 | };
843 | 2A8C0EE725A9AA8500B94216 /* Debug */ = {
844 | isa = XCBuildConfiguration;
845 | buildSettings = {
846 | CODE_SIGN_ENTITLEMENTS = ClocksWidgetIntents/ClocksWidgetIntents.entitlements;
847 | CODE_SIGN_STYLE = Automatic;
848 | CURRENT_PROJECT_VERSION = 1;
849 | DEVELOPMENT_TEAM = SRD476XUQA;
850 | INFOPLIST_FILE = ClocksWidgetIntents/Info.plist;
851 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
852 | LD_RUNPATH_SEARCH_PATHS = (
853 | "$(inherited)",
854 | "@executable_path/Frameworks",
855 | "@executable_path/../../Frameworks",
856 | );
857 | MARKETING_VERSION = 1.2;
858 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget.widget-intents";
859 | PRODUCT_NAME = "$(TARGET_NAME)";
860 | SKIP_INSTALL = YES;
861 | SWIFT_VERSION = 5.0;
862 | TARGETED_DEVICE_FAMILY = 1;
863 | };
864 | name = Debug;
865 | };
866 | 2A8C0EE825A9AA8500B94216 /* Release */ = {
867 | isa = XCBuildConfiguration;
868 | buildSettings = {
869 | CODE_SIGN_ENTITLEMENTS = ClocksWidgetIntents/ClocksWidgetIntents.entitlements;
870 | CODE_SIGN_STYLE = Automatic;
871 | CURRENT_PROJECT_VERSION = 1;
872 | DEVELOPMENT_TEAM = SRD476XUQA;
873 | INFOPLIST_FILE = ClocksWidgetIntents/Info.plist;
874 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
875 | LD_RUNPATH_SEARCH_PATHS = (
876 | "$(inherited)",
877 | "@executable_path/Frameworks",
878 | "@executable_path/../../Frameworks",
879 | );
880 | MARKETING_VERSION = 1.2;
881 | PRODUCT_BUNDLE_IDENTIFIER = "com.zhangyu1818.clocks-widget.widget-intents";
882 | PRODUCT_NAME = "$(TARGET_NAME)";
883 | SKIP_INSTALL = YES;
884 | SWIFT_VERSION = 5.0;
885 | TARGETED_DEVICE_FAMILY = 1;
886 | };
887 | name = Release;
888 | };
889 | /* End XCBuildConfiguration section */
890 |
891 | /* Begin XCConfigurationList section */
892 | 2A29B386259C5998003B7085 /* Build configuration list for PBXProject "Clocks" */ = {
893 | isa = XCConfigurationList;
894 | buildConfigurations = (
895 | 2A29B398259C599A003B7085 /* Debug */,
896 | 2A29B399259C599A003B7085 /* Release */,
897 | );
898 | defaultConfigurationIsVisible = 0;
899 | defaultConfigurationName = Release;
900 | };
901 | 2A29B39A259C599A003B7085 /* Build configuration list for PBXNativeTarget "Clocks" */ = {
902 | isa = XCConfigurationList;
903 | buildConfigurations = (
904 | 2A29B39B259C599A003B7085 /* Debug */,
905 | 2A29B39C259C599A003B7085 /* Release */,
906 | );
907 | defaultConfigurationIsVisible = 0;
908 | defaultConfigurationName = Release;
909 | };
910 | 2A29B3BF259C5A0A003B7085 /* Build configuration list for PBXNativeTarget "ClocksWidgetExtension" */ = {
911 | isa = XCConfigurationList;
912 | buildConfigurations = (
913 | 2A29B3C0259C5A0A003B7085 /* Debug */,
914 | 2A29B3C1259C5A0A003B7085 /* Release */,
915 | );
916 | defaultConfigurationIsVisible = 0;
917 | defaultConfigurationName = Release;
918 | };
919 | 2A8C0EE625A9AA8500B94216 /* Build configuration list for PBXNativeTarget "ClocksWidgetIntents" */ = {
920 | isa = XCConfigurationList;
921 | buildConfigurations = (
922 | 2A8C0EE725A9AA8500B94216 /* Debug */,
923 | 2A8C0EE825A9AA8500B94216 /* Release */,
924 | );
925 | defaultConfigurationIsVisible = 0;
926 | defaultConfigurationName = Release;
927 | };
928 | /* End XCConfigurationList section */
929 | };
930 | rootObject = 2A29B383259C5998003B7085 /* Project object */;
931 | }
932 |
--------------------------------------------------------------------------------