小组件换行文案更新

This commit is contained in:
吕新雨
2026-02-10 13:35:32 +08:00
29 changed files with 751 additions and 208 deletions

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 70;
objects = {
/* Begin PBXBuildFile section */
@@ -11,13 +11,13 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
1A1DE01D4133812B2E2BA692 /* libPods-client.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E3328F0E595C1F4A244DF238 /* libPods-client.a */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
C0A1B2C3D4E5F60718293A4E /* Screen_page.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A1B2C3D4E5F60718293A4D /* Screen_page.png */; };
A1B2C3D4E5F60718293A4B5C /* EmotionWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B5B /* EmotionWidget.swift */; };
A1B2C3D4E5F60718293A4B5C /* 情绪小组件/EmotionWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B5B /* 情绪小组件/EmotionWidget.swift */; };
A8C1D2E3F4A5B6C7D8E9F0A2 /* AppGroupStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C1D2E3F4A5B6C7D8E9F0A1 /* AppGroupStorage.swift */; };
A8C1D2E3F4A5B6C7D8E9F0A3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB3DAF802F2A4B8D00450593 /* WidgetKit.framework */; };
A8C1D2E3F4A5B6C7D8E9F0B2 /* AppGroupStorageBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = A8C1D2E3F4A5B6C7D8E9F0B1 /* AppGroupStorageBridge.m */; };
B5A7FE9A125F7C79753EC5BF /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DB40C26E3A46F6D06769EA /* ExpoModulesProvider.swift */; };
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
C0A1B2C3D4E5F60718293A4E /* Screen_page.png in Resources */ = {isa = PBXBuildFile; fileRef = C0A1B2C3D4E5F60718293A4D /* Screen_page.png */; };
EB3DAF812F2A4B8E00450593 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB3DAF802F2A4B8D00450593 /* WidgetKit.framework */; };
EB3DAF832F2A4B8E00450593 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB3DAF822F2A4B8E00450593 /* SwiftUI.framework */; };
EB3DAF942F2A4B8F00450593 /* 情绪小组件Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = EB3DAF7F2F2A4B8D00450593 /* 情绪小组件Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -54,12 +54,12 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = client/Info.plist; sourceTree = "<group>"; };
3C76CA16D0801CBF0D731C7C /* Pods-client.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-client.release.xcconfig"; path = "Target Support Files/Pods-client/Pods-client.release.xcconfig"; sourceTree = "<group>"; };
75F52ADE07CAE9D9736D7671 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = client/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
A1B2C3D4E5F60718293A4B5B /* EmotionWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "情绪小组件/EmotionWidget.swift"; sourceTree = "<group>"; };
A1B2C3D4E5F60718293A4B5B /* 情绪小组件/EmotionWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "情绪小组件/EmotionWidget.swift"; sourceTree = "<group>"; };
A8C1D2E3F4A5B6C7D8E9F0A1 /* AppGroupStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppGroupStorage.swift; path = client/AppGroupStorage.swift; sourceTree = "<group>"; };
A8C1D2E3F4A5B6C7D8E9F0B1 /* AppGroupStorageBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = AppGroupStorageBridge.m; path = client/AppGroupStorageBridge.m; sourceTree = "<group>"; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = client/SplashScreen.storyboard; sourceTree = "<group>"; };
C0A1B2C3D4E5F60718293A4D /* Screen_page.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Screen_page.png; path = ../assets/images/Screen_page.png; sourceTree = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
C0A1B2C3D4E5F60718293A4D /* Screen_page.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Screen_page.png; path = ../assets/images/Screen_page.png; sourceTree = "<group>"; };
C7DB40C26E3A46F6D06769EA /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-client/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
E3328F0E595C1F4A244DF238 /* libPods-client.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-client.a"; sourceTree = BUILT_PRODUCTS_DIR; };
EB3DAF7F2F2A4B8D00450593 /* 情绪小组件Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "情绪小组件Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -74,7 +74,7 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
EB3DAF952F2A4B8F00450593 /* Exceptions for "情绪小组件" folder in "情绪小组件Extension" target */ = {
EB3DAF952F2A4B8F00450593 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
EmotionWidget.swift,
@@ -85,18 +85,7 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
EB3DAF842F2A4B8E00450593 /* 情绪小组件 */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
EB3DAF952F2A4B8F00450593 /* Exceptions for "情绪小组件" folder in "情绪小组件Extension" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = "情绪小组件";
sourceTree = "<group>";
};
EB3DAF842F2A4B8E00450593 /* 情绪小组件 */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (EB3DAF952F2A4B8F00450593 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = "情绪小组件"; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -213,7 +202,7 @@
EB3DAFD42F2A5FC100450593 /* Recovered References */ = {
isa = PBXGroup;
children = (
A1B2C3D4E5F60718293A4B5B /* EmotionWidget.swift */,
A1B2C3D4E5F60718293A4B5B /* 情绪小组件/EmotionWidget.swift */,
);
name = "Recovered References";
sourceTree = "<group>";
@@ -301,6 +290,7 @@
knownRegions = (
en,
Base,
"zh-Hant",
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
@@ -481,7 +471,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A1B2C3D4E5F60718293A4B5C /* EmotionWidget.swift in Sources */,
A1B2C3D4E5F60718293A4B5C /* 情绪小组件/EmotionWidget.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -9,7 +9,7 @@ private let keyWidgetConfig = "widget.config.v1"
private let keyWidgetUserProfile = "widget.userProfile.v1_2"
private let keyWidgetDailyReco = "widget.dailyReco.v1"
private let fallbackTextTC = "你已很努力了,今天也值得被温柔对待。"
private let fallbackTextTC = "你已很努力了,今天也值得被溫柔對待。"
private let fallbackTextEN = "Youve been doing great — you deserve kindness today."
private func defaults() -> UserDefaults? {
@@ -29,17 +29,24 @@ private func localDayKey(_ date: Date = Date()) -> String {
}
private func resolveLang() -> String {
// en/tc
let preferred = Locale.preferredLanguages.first?.lowercased() ?? "en"
return preferred.hasPrefix("zh") ? "tc" : "en"
// en/tc
// - Hant / TW / HK / MO=> tc
// - zh-Hans / zh-CN=> en
let preferred = (Locale.preferredLanguages.first ?? "en").lowercased()
if preferred.hasPrefix("zh-hant") { return "tc" }
if preferred.hasPrefix("zh-tw") { return "tc" }
if preferred.hasPrefix("zh-hk") { return "tc" }
if preferred.hasPrefix("zh-mo") { return "tc" }
return "en"
}
private func resolveTitle(lang: String) -> String {
lang == "en" ? "Mindfulness" : "正念"
// Hey Mama
return "Hey Mama"
}
private func resolveFooterHint(lang: String) -> String {
lang == "en" ? "Tap to open the app" : "我回到 App"
lang == "en" ? "Tap to open the app" : "我回到 App"
}
private func joinUrl(base: String, path: String) -> String {
@@ -76,16 +83,44 @@ private func writeJsonDict(_ dict: [String: Any], forKey key: String) {
defaults()?.set(raw, forKey: key)
}
private func readCachedText() -> (dayKey: String?, lang: String, text: String)? {
private func readCachedText(family: WidgetFamily) -> (dayKey: String?, lang: String, text: String)? {
guard let d = readJsonDict(forKey: keyWidgetDailyReco) else { return nil }
let lang = (d["lang"] as? String) ?? resolveLang()
let dayKey = d["day_key"] as? String
if let item = d["item"] as? [String: Any], let text = item["text"] as? String, !text.isEmpty {
return (dayKey: dayKey, lang: lang, text: text)
if let item = d["item"] as? [String: Any] {
if let text = pickWidgetText(item: item, family: family), !text.isEmpty {
return (dayKey: dayKey, lang: lang, text: text)
}
}
return nil
}
private func familyKey(_ family: WidgetFamily) -> String {
switch family {
case .systemSmall:
return "small"
case .systemMedium:
return "medium"
case .systemLarge:
return "large"
default:
return "small"
}
}
private func pickWidgetText(item: [String: Any], family: WidgetFamily?) -> String? {
// 使 App wrapped_text_by_family退 raw text
if let family = family,
let wrappedByFamily = item["wrapped_text_by_family"] as? [String: Any] {
let key = familyKey(family)
if let v = wrappedByFamily[key] as? String, !v.isEmpty {
return v
}
}
if let raw = item["text"] as? String, !raw.isEmpty { return raw }
return nil
}
private func readApiBaseUrl() -> String? {
guard let d = readJsonDict(forKey: keyWidgetConfig) else { return nil }
let base = d["apiBaseUrl"] as? String
@@ -174,7 +209,7 @@ struct EmotionProvider: TimelineProvider {
let today = localDayKey(Date())
// 1)
if let cached = readCachedText(), cached.dayKey == today {
if let cached = readCachedText(family: context.family), cached.dayKey == today {
let entry = EmotionEntry(
date: Date(),
lang: cached.lang,
@@ -201,7 +236,7 @@ struct EmotionProvider: TimelineProvider {
}
// 3)
if let cached = readCachedText() {
if let cached = readCachedText(family: context.family) {
let entry = EmotionEntry(
date: Date(),
lang: cached.lang,
@@ -332,8 +367,9 @@ struct EmotionWidget: Widget {
StaticConfiguration(kind: kind, provider: EmotionProvider()) { entry in
EmotionWidgetView(entry: entry)
}
.configurationDisplayName("情绪小组件")
.description("一段温柔提醒,陪你回到当下。")
// /使 Widget Extension Localizable.strings
.configurationDisplayName("WIDGET_DISPLAY_NAME")
.description("WIDGET_DESCRIPTION")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}

View File

@@ -0,0 +1,3 @@
"WIDGET_DISPLAY_NAME" = "Emotion Widget";
"WIDGET_DESCRIPTION" = "A gentle reminder to return to the present.";

View File

@@ -0,0 +1,3 @@
"WIDGET_DISPLAY_NAME" = "情緒小組件";
"WIDGET_DESCRIPTION" = "一段溫柔提醒,陪你回到當下。";