小组件换行文案更新
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 = "You’ve 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])
|
||||
}
|
||||
}
|
||||
|
||||
3
client/ios/情绪小组件/en.lproj/Localizable.strings
Normal file
3
client/ios/情绪小组件/en.lproj/Localizable.strings
Normal file
@@ -0,0 +1,3 @@
|
||||
"WIDGET_DISPLAY_NAME" = "Emotion Widget";
|
||||
"WIDGET_DESCRIPTION" = "A gentle reminder to return to the present.";
|
||||
|
||||
3
client/ios/情绪小组件/zh-Hant.lproj/Localizable.strings
Normal file
3
client/ios/情绪小组件/zh-Hant.lproj/Localizable.strings
Normal file
@@ -0,0 +1,3 @@
|
||||
"WIDGET_DISPLAY_NAME" = "情緒小組件";
|
||||
"WIDGET_DESCRIPTION" = "一段溫柔提醒,陪你回到當下。";
|
||||
|
||||
Reference in New Issue
Block a user