Skip to content

Commit

Permalink
Improvment on theme mgmt and Text widget
Browse files Browse the repository at this point in the history
1. Default theme will change display name based on language chosen
2. Text widget on iOS and watchOS is now customizable
3. Code gardening
  • Loading branch information
LEOYoon-Tsaw committed Jan 28, 2024
1 parent b85c6b7 commit 8ba08a7
Show file tree
Hide file tree
Showing 30 changed files with 715 additions and 551 deletions.
18 changes: 18 additions & 0 deletions .periphery.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
project: Chinendar.xcodeproj
retain_objc_accessible: true
schemes:
- Chinendar Mac
- Chinendar Vision
- Chinendar Watch
- Chinendar iOS
- Mac Widget Extension
- Watch Widget Extension
- iOS Widget Extension
targets:
- Chinendar Mac
- Chinendar Vision
- Chinendar Watch
- Chinendar iOS
- Mac Widget Extension
- Watch Widget Extension
- iOS Widget Extension
24 changes: 20 additions & 4 deletions Chinendar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@
B3BEB4C62A489A99000751D5 /* SwiftUIUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B32243EF2A0DBEF400E7AED5 /* SwiftUIUtilities.swift */; };
B3BFA2572A05E0590018F99E /* WatchConnectivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BFA2562A05E0590018F99E /* WatchConnectivity.swift */; };
B3BFA2582A05E0590018F99E /* WatchConnectivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BFA2562A05E0590018F99E /* WatchConnectivity.swift */; };
B3C68B192B5DDC4B00FC08E3 /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3C68B182B5DDC4B00FC08E3 /* Card.swift */; };
B3C68B1A2B5DDC4B00FC08E3 /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3C68B182B5DDC4B00FC08E3 /* Card.swift */; };
B3C68B1C2B5DE90800FC08E3 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3C68B1B2B5DE90800FC08E3 /* Protocols.swift */; };
B3C68B1D2B5DE90800FC08E3 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3C68B1B2B5DE90800FC08E3 /* Protocols.swift */; };
B3C68B1E2B5DE94300FC08E3 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3C68B1B2B5DE90800FC08E3 /* Protocols.swift */; };
B3CC8B9C2A0B30BB0063DE44 /* iOSWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3CC8B9B2A0B30BB0063DE44 /* iOSWidgetBundle.swift */; };
B3CC8BA12A0B30BC0063DE44 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B3CC8BA02A0B30BC0063DE44 /* Assets.xcassets */; };
B3CC8BA72A0B30BC0063DE44 /* Chinendar Widget.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = B3CC8B972A0B30BB0063DE44 /* Chinendar Widget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -366,6 +371,8 @@
B3BCCEE72A48746000F5745E /* Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = "<group>"; };
B3BEB4C22A48994C000751D5 /* WatchFace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchFace.swift; sourceTree = "<group>"; };
B3BFA2562A05E0590018F99E /* WatchConnectivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConnectivity.swift; sourceTree = "<group>"; };
B3C68B182B5DDC4B00FC08E3 /* Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = "<group>"; };
B3C68B1B2B5DE90800FC08E3 /* Protocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = "<group>"; };
B3CC8B972A0B30BB0063DE44 /* Chinendar Widget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Chinendar Widget.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
B3CC8B9B2A0B30BB0063DE44 /* iOSWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSWidgetBundle.swift; sourceTree = "<group>"; };
B3CC8BA02A0B30BC0063DE44 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
Expand Down Expand Up @@ -601,6 +608,7 @@
B383A6D02A4D02E2002FADCF /* Dual.swift */,
B3CC8BB62A0B330C0063DE44 /* Full.swift */,
9E57427E2AA501E70052AE70 /* TaskGroup.swift */,
B3C68B1B2B5DE90800FC08E3 /* Protocols.swift */,
B3E8A5142A4CF64A00302473 /* WatchWidgets */,
);
path = Widget;
Expand Down Expand Up @@ -635,6 +643,7 @@
B3E8A5152A4CF67700302473 /* Circular.swift */,
B3E8A5182A4CF6BD00302473 /* CountDown.swift */,
B3CC8BF22A0C7E300063DE44 /* TextDesp.swift */,
B3C68B182B5DDC4B00FC08E3 /* Card.swift */,
B3E8A51B2A4CF77000302473 /* Corner.swift */,
B32243E42A0D8B6C00E7AED5 /* WatchWidgetBasic.swift */,
B395B5A02A0ED7EF003206E7 /* IconView.swift */,
Expand Down Expand Up @@ -1091,8 +1100,10 @@
files = (
B3CC8BB32A0B31BE0063DE44 /* MetaLayout.swift in Sources */,
B395B5A82A0F22CF003206E7 /* IconView.swift in Sources */,
B3C68B192B5DDC4B00FC08E3 /* Card.swift in Sources */,
9E5742812AA504D20052AE70 /* TaskGroup.swift in Sources */,
B3CC8BB02A0B31B70063DE44 /* Model.swift in Sources */,
B3C68B1C2B5DE90800FC08E3 /* Protocols.swift in Sources */,
B34009482A352FEA003F50F7 /* WatchFaceView.swift in Sources */,
B3CC8BB12A0B31B90063DE44 /* PlanetModel.swift in Sources */,
B383A6CF2A4D02D8002FADCF /* Single.swift in Sources */,
Expand Down Expand Up @@ -1126,13 +1137,15 @@
B3E8A51A2A4CF6BD00302473 /* CountDown.swift in Sources */,
B38E96B62A0D3AA0002FD662 /* MetaLayout.swift in Sources */,
B395B5A62A0F1A4A003206E7 /* IconView.swift in Sources */,
B3C68B1A2B5DDC4B00FC08E3 /* Card.swift in Sources */,
B32243F32A0DBEF400E7AED5 /* SwiftUIUtilities.swift in Sources */,
B38E96B42A0D3A8C002FD662 /* Data.swift in Sources */,
B32999322A4F9C8700B71579 /* LocationManager.swift in Sources */,
B38E96B32A0D3A8A002FD662 /* PlanetModel.swift in Sources */,
B32243E82A0D8E5000E7AED5 /* WatchWidgetBundle.swift in Sources */,
B383A6D62A4D1C7B002FADCF /* Relevance.swift in Sources */,
B32243E52A0D8B6C00E7AED5 /* WatchWidgetBasic.swift in Sources */,
B3C68B1D2B5DE90800FC08E3 /* Protocols.swift in Sources */,
9E5742822AA504D30052AE70 /* TaskGroup.swift in Sources */,
B3E8A5172A4CF67700302473 /* Circular.swift in Sources */,
B3CC8BF32A0C7E300063DE44 /* TextDesp.swift in Sources */,
Expand All @@ -1159,6 +1172,7 @@
B3CC8BBD2A0B40E00063DE44 /* Full.swift in Sources */,
B34009472A352FEA003F50F7 /* WatchFaceView.swift in Sources */,
B3E1D6DE2A0AC89300F2905A /* Model.swift in Sources */,
B3C68B1E2B5DE94300FC08E3 /* Protocols.swift in Sources */,
B3E1D6DF2A0AC89600F2905A /* PlanetModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1675,8 +1689,7 @@
D2E4E0F326F7C73F002F3716 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 113;
APP_BUILD = 117;
APP_VERSION = 5.3;
ASSETCATALOG_COMPILER_APPICON_NAME = "";
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOL_FRAMEWORKS = SwiftUI;
Expand Down Expand Up @@ -1734,11 +1747,13 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_OUTPUT_FORMAT = binary;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PLIST_FILE_OUTPUT_FORMAT = binary;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
STRINGS_FILE_OUTPUT_ENCODING = binary;
Expand All @@ -1754,8 +1769,7 @@
D2E4E0F426F7C73F002F3716 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 113;
APP_BUILD = 117;
APP_VERSION = 5.3;
ASSETCATALOG_COMPILER_APPICON_NAME = "";
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOL_FRAMEWORKS = SwiftUI;
Expand Down Expand Up @@ -1808,12 +1822,14 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_OUTPUT_FORMAT = binary;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LLVM_LTO = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = NO;
PLIST_FILE_OUTPUT_FORMAT = binary;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
STRINGS_FILE_OUTPUT_ENCODING = binary;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
<key>CustomBuildLocationType</key>
<string>RelativeToDerivedData</string>
<key>DerivedDataLocationStyle</key>
<string>Default</string>
<key>ShowSharedSchemesAutomaticallyEnabled</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@
ReferencedContainer = "container:Chinendar.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLDebug 0"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.MigrationDebug 0"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.ConcurrencyDebug 0"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.Logging.stderr 0"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = " -com.apple.CoreData.Logging.stderr 0"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
12 changes: 6 additions & 6 deletions Shared/DataModel/MetaLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,12 @@ extension String {
}

func loadDefault(context: ModelContext, local: Bool = false) {
let defaultName = ThemeData.defaultName
let defaultName = AppInfo.defaultName
let predicate = {
let deviceName = if local {
try? LocalData.read()?.deviceName ?? ThemeData.deviceName
try? LocalData.read()?.deviceName ?? AppInfo.deviceName
} else {
ThemeData.deviceName
AppInfo.deviceName
}
return #Predicate<ThemeData> { data in
data.name == defaultName && data.deviceName == deviceName
Expand Down Expand Up @@ -450,8 +450,8 @@ extension String {
}

func saveDefault(context: ModelContext) {
let defaultName = ThemeData.defaultName
let deviceName = ThemeData.deviceName
let defaultName = AppInfo.defaultName
let deviceName = AppInfo.deviceName
try? LocalData.write(deviceName: deviceName)

let predicate = #Predicate<ThemeData> { data in
Expand All @@ -474,7 +474,7 @@ extension String {
}

if !found {
let defaultTheme = ThemeData(name: ThemeData.defaultName, code: self.encode())
let defaultTheme = ThemeData(name: AppInfo.defaultName, code: self.encode())
context.insert(defaultTheme)
}
}
Expand Down
88 changes: 67 additions & 21 deletions Shared/DataModel/ThemeData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ import WatchKit
import VisionKit
#endif

typealias ThemeData = DataSchemaV2.Layout

extension ThemeData: Identifiable, Hashable {
static var version: Int {
intVersion(DataSchemaV2.versionIdentifier)
}

struct AppInfo {
#if os(macOS)
static let groupId = Bundle.main.object(forInfoDictionaryKey: "GroupID") as! String
#elseif os(iOS) || os(visionOS)
Expand All @@ -42,12 +36,18 @@ extension ThemeData: Identifiable, Hashable {
#elseif os(visionOS)
@MainActor static let deviceName = UIDevice.current.name
#endif

static let defaultName = NSLocalizedString("Default", comment: "Default save file name")
static let defaultName = "__current_theme__"
}

typealias ThemeData = DataSchemaV3.Layout
extension ThemeData {
static var version: Int {
intVersion(DataSchemaV3.versionIdentifier)
}

static let container = {
let fullSchema = Schema(versionedSchema: DataSchemaV2.self)
let baseUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: ThemeData.groupId)!
let fullSchema = Schema(versionedSchema: DataSchemaV3.self)
let baseUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppInfo.groupId)!
#if os(macOS)
let url = baseUrl.appendingPathComponent("ChineseTime")
#else
Expand All @@ -57,26 +57,26 @@ extension ThemeData: Identifiable, Hashable {
return createContainer(schema: fullSchema, migrationPlan: DataMigrationPlan.self, configurations: [modelConfig])
}()

static let context = ModelContext(ThemeData.container)
static let context = ModelContext(container)

static func latestVersion() -> Int {
let deviceName = ThemeData.deviceName
let deviceName = AppInfo.deviceName
let predicate = #Predicate<ThemeData> { data in
data.deviceName == deviceName && data.version != nil
}
var descriptor = FetchDescriptor(predicate: predicate, sortBy: [SortDescriptor(\.modifiedDate, order: .reverse)])
descriptor.fetchLimit = 1
let version = try? ThemeData.context.fetch(descriptor).first?.version
let version = try? context.fetch(descriptor).first?.version
return version ?? 0
}
static func experienced() -> Bool {
let predicate = #Predicate<ThemeData> { data in
data.modifiedDate != nil
}
var descriptor = FetchDescriptor(predicate: predicate, sortBy: [SortDescriptor(\.modifiedDate)])
let counts = try? ThemeData.context.fetchCount(descriptor)
let counts = try? context.fetchCount(descriptor)
descriptor.fetchLimit = 1
let date = try? ThemeData.context.fetch(descriptor).first?.modifiedDate
let date = try? context.fetch(descriptor).first?.modifiedDate

if let date = date, let counts = counts, counts > 1, date.distance(to: .now) > 3600 * 24 * 30 {
return true
Expand All @@ -93,7 +93,9 @@ extension ThemeData: Identifiable, Hashable {
if self.code != code {
self.code = code
self.modifiedDate = Date.now
self.version = ThemeData.version
}
if (self.version ?? 0) < Self.version {
self.version = Self.version
}
}
}
Expand All @@ -115,6 +117,29 @@ private func createContainer(schema: Schema, migrationPlan: SchemaMigrationPlan.
}
}

enum DataSchemaV3: VersionedSchema {
static let versionIdentifier: Schema.Version = .init(1, 2, 1)
static var models: [any PersistentModel.Type] {
[Layout.self]
}

@Model final class Layout {
var code: String?
var deviceName: String?
var modifiedDate: Date?
@Attribute(hashModifier: "v3") var name: String?
var version: Int?

init(name: String, code: String) {
self.name = name
self.deviceName = AppInfo.deviceName
self.code = code
self.modifiedDate = Date.now
self.version = intVersion(DataSchemaV3.versionIdentifier)
}
}
}

enum DataSchemaV2: VersionedSchema {
static let versionIdentifier: Schema.Version = .init(1, 1, 1)
static var models: [any PersistentModel.Type] {
Expand All @@ -130,7 +155,7 @@ enum DataSchemaV2: VersionedSchema {

init(name: String, code: String) {
self.name = name
self.deviceName = Layout.deviceName
self.deviceName = AppInfo.deviceName
self.code = code
self.modifiedDate = Date.now
self.version = intVersion(DataSchemaV2.versionIdentifier)
Expand Down Expand Up @@ -161,12 +186,33 @@ enum DataSchemaV1: VersionedSchema {

enum DataMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[DataSchemaV1.self, DataSchemaV2.self]
[DataSchemaV1.self, DataSchemaV2.self, DataSchemaV3.self]
}

static var stages: [MigrationStage] { [migrateV1toV2] }
static var stages: [MigrationStage] { [migrateV1toV2, migrateV2toV3] }

static let migrateV1toV2 = MigrationStage.lightweight(fromVersion: DataSchemaV1.self, toVersion: DataSchemaV2.self)
static let migrateV2toV3 = MigrationStage.custom(
fromVersion: DataSchemaV2.self, toVersion: DataSchemaV3.self,
willMigrate: { context in
let legacyDefaultName = NSLocalizedString("Default", comment: "Legacy default theme name")
let deviceName = AppInfo.deviceName
let predicate = #Predicate<DataSchemaV2.Layout> { data in
data.name == legacyDefaultName && data.deviceName == deviceName
}
var descriptor = FetchDescriptor(predicate: predicate)
do {
let themes = try context.fetch(descriptor)
for theme in themes {
theme.name = AppInfo.defaultName
}
try context.save()
} catch {
print(error.localizedDescription)
}
},
didMigrate: nil
)
}

enum LocalSchemaV1: VersionedSchema {
Expand Down Expand Up @@ -197,7 +243,7 @@ extension LocalData: Identifiable, Hashable {

static let container = {
let localSchema = Schema(versionedSchema: LocalSchemaV1.self)
let modelConfig = ModelConfiguration("ChineseTimeLocal", schema: localSchema, groupContainer: .identifier(ThemeData.groupId), cloudKitDatabase: .none)
let modelConfig = ModelConfiguration("ChineseTimeLocal", schema: localSchema, groupContainer: .identifier(AppInfo.groupId), cloudKitDatabase: .none)
return createContainer(schema: localSchema, migrationPlan: nil, configurations: [modelConfig])
}()

Expand Down
Loading

0 comments on commit 8ba08a7

Please sign in to comment.