@momo-kits/native-kits 0.163.1-sp.8-debug → 0.163.2-beta.1-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codegraph/.gitignore +5 -0
- package/CLAUDE.md +0 -10
- package/compose/build.gradle.kts +1 -1
- package/compose/compose.podspec +1 -1
- package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Example.xcscheme +32 -0
- package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/WorkspaceSettings.xcsettings +16 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +6 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +5 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Pods-Example.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImage.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SkeletonUI.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios-LottiePrivacyInfo.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +60 -0
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +4 -6
- package/ios/Application/HeaderRight.swift +17 -1
- package/ios/Application/Localize.swift +4 -73
- package/ios/Application/Navigation/NavigationContainer.swift +18 -25
- package/ios/Application/Navigation/Navigator.swift +29 -91
- package/ios/Application/StackScreen.swift +21 -18
- package/ios/Badge/BadgeRibbon.swift +1 -4
- package/ios/Button/Button.swift +61 -41
- package/ios/Icon/Icon.swift +4 -13
- package/ios/native-kits.podspec +1 -2
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +8 -8
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +2 -3
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +12 -5
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LocalizeDemo.swift +4 -51
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +2 -14
- package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +1 -3
- package/local.properties +2 -2
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -158
- package/ios/Application/LanguageSource.swift +0 -93
- package/ios/Application/LocalizeModifier.swift +0 -30
- package/ios/Application/Navigation/NavigatorManager.swift +0 -49
- package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
- package/ios/Layout/GridContext.swift +0 -30
- package/ios/Layout/Item.swift +0 -42
- package/ios/Layout/Section.swift +0 -134
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconDemo.swift +0 -104
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SectionDemo.swift +0 -100
package/ios/Icon/Icon.swift
CHANGED
|
@@ -26,24 +26,17 @@ let iconSources: [String: IconSource] = loadIconSources()
|
|
|
26
26
|
// Icons that must never be tinted (mirror Compose `noThemeIcons`)
|
|
27
27
|
let noThemeIcons: [String] = ["ic_round_momo_tiny"]
|
|
28
28
|
|
|
29
|
-
// Compose defaults `color` to the theme text color while `color = null` means "no tint".
|
|
30
|
-
// Swift can't tell an omitted argument from an explicit `nil`, so the default is this
|
|
31
|
-
// unrepresentable sentinel: omitted -> theme text color, `nil` -> untinted.
|
|
32
|
-
public let iconThemeColor = Color(.sRGB, red: -1, green: -1, blue: -1, opacity: -1)
|
|
33
|
-
|
|
34
29
|
// Icon ViewModel
|
|
35
30
|
public struct Icon: View {
|
|
36
|
-
@Environment(\.appTheme) private var theme
|
|
37
|
-
|
|
38
31
|
var source: String
|
|
39
32
|
var size: CGFloat
|
|
40
33
|
var color: Color?
|
|
41
34
|
var accessibilityLabel: String?
|
|
42
|
-
|
|
35
|
+
|
|
43
36
|
public init(
|
|
44
37
|
source: String,
|
|
45
38
|
size: CGFloat = 24,
|
|
46
|
-
color: Color? =
|
|
39
|
+
color: Color? = nil,
|
|
47
40
|
accessibilityLabel: String? = nil
|
|
48
41
|
) {
|
|
49
42
|
self.source = source
|
|
@@ -53,11 +46,9 @@ public struct Icon: View {
|
|
|
53
46
|
}
|
|
54
47
|
|
|
55
48
|
public var body: some View {
|
|
56
|
-
|
|
57
|
-
if let uri = source.contains("https") ? source : iconSources[source]?.uri {
|
|
58
|
-
let requestedColor = color == iconThemeColor ? theme.colors.text.default : color
|
|
49
|
+
if let uri = iconSources[source]?.uri {
|
|
59
50
|
// Mirror Compose: certain icons (e.g. branded glyphs) are never tinted
|
|
60
|
-
let resolvedColor = noThemeIcons.contains(source) ? nil :
|
|
51
|
+
let resolvedColor = noThemeIcons.contains(source) ? nil : color
|
|
61
52
|
ImageView(uri, color: resolvedColor)
|
|
62
53
|
.frame(width: size, height: size)
|
|
63
54
|
// Mirrors Compose's auto-generated `contentDescription = "img|$iconUrl"`
|
package/ios/native-kits.podspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Pod::Spec.new do |s|
|
|
2
2
|
s.name = 'MoMoUIKits'
|
|
3
|
-
s.version = '0.163.
|
|
3
|
+
s.version = '0.163.2-beta.1'
|
|
4
4
|
s.summary = 'MoMoUIKits for iOS'
|
|
5
5
|
s.homepage = 'https://momo.vn'
|
|
6
6
|
s.license = { :type => 'MIT' }
|
|
@@ -34,7 +34,6 @@ Pod::Spec.new do |s|
|
|
|
34
34
|
"InputDropDown/**/*.{swift,m,h}",
|
|
35
35
|
"InputMoney/**/*.{swift,m,h}",
|
|
36
36
|
"InputOTP/**/*.{swift,m,h}",
|
|
37
|
-
"Layout/**/*.{swift,m,h}",
|
|
38
37
|
"Loader/**/*.{swift,m,h}",
|
|
39
38
|
"Lottie/**/*.{swift,m,h}",
|
|
40
39
|
"OTPKeyboard/**/*.{swift,m,h}",
|
|
@@ -22,19 +22,19 @@ struct ButtonDemo: View {
|
|
|
22
22
|
|
|
23
23
|
Group {
|
|
24
24
|
Button(title: "Button", action: onPress, type: disabled ? .disabled : .primary, size: .large,
|
|
25
|
-
iconRight: "
|
|
25
|
+
iconRight: AnyView(Image(systemName: "checkmark.circle")))
|
|
26
26
|
Button(title: "Button", action: onPress, size: .small)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
Group {
|
|
30
30
|
Button(title: "Button", action: onPress, type: .danger,
|
|
31
|
-
iconLeft: "
|
|
31
|
+
iconLeft: AnyView(Image(systemName: "checkmark.circle")))
|
|
32
32
|
Button(title: "Button", action: onPress, type: .danger, size: .medium)
|
|
33
33
|
Button(title: "Button", action: onPress, type: .danger, size: .small)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
Group {
|
|
37
|
-
Button(title: "Button", action: onPress, type: .danger, iconLeft: "
|
|
37
|
+
Button(title: "Button", action: onPress, type: .danger, iconLeft: AnyView(Image(systemName: "checkmark.circle")), isFull: false)
|
|
38
38
|
Button(title: "Button", action: onPress, type: .danger, size: .medium, isFull: false)
|
|
39
39
|
Button(title: "Button", action: onPress, type: .danger, size: .small, isFull: false)
|
|
40
40
|
}
|
|
@@ -48,9 +48,9 @@ struct ButtonDemo: View {
|
|
|
48
48
|
|
|
49
49
|
Group {
|
|
50
50
|
Button(title: "Tinted icon", action: onPress, type: .primary,
|
|
51
|
-
iconLeft: "
|
|
51
|
+
iconLeft: AnyView(Image(systemName: "checkmark.circle")), useTintColor: true)
|
|
52
52
|
Button(title: "Original icon", action: onPress, type: .primary,
|
|
53
|
-
iconLeft: "
|
|
53
|
+
iconLeft: AnyView(Image(systemName: "checkmark.circle")), useTintColor: false)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
Group {
|
|
@@ -78,9 +78,9 @@ struct ButtonDemo: View {
|
|
|
78
78
|
}
|
|
79
79
|
Group {
|
|
80
80
|
Button(title: "Button", action: onPress, type: .primary, size: .medium, loading: true)
|
|
81
|
-
Button(title: "Button", action: onPress, type: .danger, size: .medium, iconLeft: "
|
|
82
|
-
Button(title: "Button", action: onPress, type: .disabled, size: .medium, iconRight: "
|
|
83
|
-
Button(title: "Button", action: onPress, type: .outline, size: .medium, iconLeft: "
|
|
81
|
+
Button(title: "Button", action: onPress, type: .danger, size: .medium, iconLeft: AnyView(Image(systemName: "checkmark.circle")), loading: true)
|
|
82
|
+
Button(title: "Button", action: onPress, type: .disabled, size: .medium, iconRight: AnyView(Image(systemName: "checkmark.circle")), loading: true)
|
|
83
|
+
Button(title: "Button", action: onPress, type: .outline, size: .medium, iconLeft: AnyView(Image(systemName: "checkmark.circle")), iconRight: AnyView(Image(systemName: "checkmark.circle")), loading: true)
|
|
84
84
|
Button(title: "Button", action: onPress, type: .secondary, size: .medium, loading: true)
|
|
85
85
|
Button(title: "Button", action: onPress, type: .text, size: .medium, loading: true)
|
|
86
86
|
Button(title: "Button", action: onPress, type: .tonal, size: .medium, loading: true)
|
|
@@ -12,11 +12,10 @@ struct CheckboxDemo: View {
|
|
|
12
12
|
|
|
13
13
|
var body: some View {
|
|
14
14
|
VStack(alignment: .leading, spacing: 20) {
|
|
15
|
-
// Interactive checkbox
|
|
16
|
-
// onChange only reports the new value — don't flip state again here.
|
|
15
|
+
// Interactive checkbox
|
|
17
16
|
Checkbox(
|
|
18
17
|
$checked,
|
|
19
|
-
onChange: {
|
|
18
|
+
onChange: { _ in checked = !checked },
|
|
20
19
|
title: "Check Box"
|
|
21
20
|
)
|
|
22
21
|
|
|
@@ -34,22 +34,29 @@ public func getIcon(icon: String) -> String {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
public let ScreenUsages: [ScreenUsage] = [
|
|
37
|
+
// ScreenUsage(title: "ScreenUsage", destination: AnyView(ContentView()), icon: getIcon(icon: "Layout")),
|
|
37
38
|
ScreenUsage(title: "Badge", destination: AnyView(BadgeDemo()), icon: getIcon(icon: "Badge")),
|
|
38
39
|
ScreenUsage(title: "Button", destination: AnyView(ButtonDemo()), icon: getIcon(icon: "Button")),
|
|
39
40
|
ScreenUsage(title: "CheckBox", destination: AnyView(CheckboxDemo()), icon: getIcon(icon: "Checkbox")),
|
|
40
41
|
ScreenUsage(title: "Chip", destination: AnyView(ChipDemo()), icon: getIcon(icon: "Chip")),
|
|
41
|
-
ScreenUsage(title: "
|
|
42
|
+
// ScreenUsage(title: "IconButton", destination: AnyView(ContentView()), icon: getIcon(icon: "Button")),
|
|
43
|
+
// ScreenUsage(title: "Icon", destination: AnyView(ContentView()), icon: getIcon(icon: "Icon")),
|
|
42
44
|
ScreenUsage(title: "Image", destination: AnyView(ImageDemo()), icon: getIcon(icon: "Layout")),
|
|
43
45
|
ScreenUsage(title: "Baseline View", destination: AnyView(BaselineViewDemo()), icon: getIcon(icon: "Layout")),
|
|
44
|
-
ScreenUsage(title: "
|
|
46
|
+
// ScreenUsage(title: "InputMoney", destination: AnyView(ContentView()), icon: getIcon(icon: "FormInput")),
|
|
45
47
|
ScreenUsage(title: "Input", destination: AnyView(InputDemo()), icon: getIcon(icon: "FormInput")),
|
|
46
48
|
ScreenUsage(title: "Input Phone Number", destination: AnyView(InputPhoneNumberDemo()), icon: getIcon(icon: "FormInput")),
|
|
47
49
|
ScreenUsage(title: "InputTextArea", destination: AnyView(InputTextAreaDemo()), icon: getIcon(icon: "FormInput")),
|
|
48
50
|
ScreenUsage(title: "InputSearch", destination: AnyView(InputSearchDemo()), icon: getIcon(icon: "FormInput")),
|
|
51
|
+
// ScreenUsage(title: "Pagination", destination: AnyView(ContentView()), icon: getIcon(icon: "Badge")),
|
|
52
|
+
// ScreenUsage(title: "InputDropdown", destination: AnyView(ContentView()), icon: getIcon(icon: "FormInput")),
|
|
49
53
|
ScreenUsage(title: "PopupNotify", destination: AnyView(PopupNotifyDemo()), icon: getIcon(icon: "Popup")),
|
|
50
54
|
ScreenUsage(title: "PopupPromotion", destination: AnyView(PopupPromotionDemo()), icon: getIcon(icon: "Popup")),
|
|
51
55
|
ScreenUsage(title: "SnackBar", destination: AnyView(SnackBarDemo()), icon: getIcon(icon: "Popup")),
|
|
56
|
+
// ScreenUsage(title: "Radio", destination: AnyView(ContentView()), icon: getIcon(icon: "Radio")),
|
|
57
|
+
// ScreenUsage(title: "Skeleton", destination: AnyView(ContentView()), icon: getIcon(icon: "Skeleton")),
|
|
52
58
|
ScreenUsage(title: "Switch", destination: AnyView(SwitchDemo()), icon: getIcon(icon: "Toggle")),
|
|
59
|
+
// ScreenUsage(title: "Tag", destination: AnyView(ContentView()), icon: getIcon(icon: "Skeleton")),
|
|
53
60
|
ScreenUsage(title: "Text", destination: AnyView(TypographyDemo()), icon: getIcon(icon: "Typography")),
|
|
54
61
|
// ScreenUsage(title: "TrustBanner", destination: AnyView(TrustBanner(onPress: {_ in })), icon: getIcon(icon: "Typography")),
|
|
55
62
|
ScreenUsage(title: "Information", destination: AnyView(InformationDemo()), icon: getIcon(icon: "Typography")),
|
|
@@ -127,11 +134,11 @@ public struct HomeView: View {
|
|
|
127
134
|
headerRight: {
|
|
128
135
|
HeaderRight(headerRight: HeaderRightData(useShortcut: true))
|
|
129
136
|
},
|
|
130
|
-
// footerComponent = { Button("
|
|
137
|
+
// footerComponent = { Button("Footer Button") }
|
|
131
138
|
footer: {
|
|
132
139
|
AnyView(
|
|
133
140
|
MoMoUIKits.Button(
|
|
134
|
-
title: "
|
|
141
|
+
title: "Footer Button",
|
|
135
142
|
action: onOpenCompose,
|
|
136
143
|
type: .primary,
|
|
137
144
|
size: .large
|
|
@@ -260,7 +267,7 @@ public struct HomeItemView: View {
|
|
|
260
267
|
Divider()
|
|
261
268
|
}
|
|
262
269
|
.padding(.horizontal, 16)
|
|
263
|
-
.padding(.vertical,
|
|
270
|
+
.padding(.vertical, 8)
|
|
264
271
|
}
|
|
265
272
|
}
|
|
266
273
|
|
|
@@ -24,72 +24,27 @@ private let AppTranslations = LocalizationObject(
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
struct LocalizeDemo: View {
|
|
27
|
-
|
|
28
|
-
@Environment(\.localize) private var localize
|
|
29
|
-
@Environment(\.applicationEnvironment) private var applicationEnvironment
|
|
30
|
-
// AI-GENERATED END: observe the app language and drive it through the real platform API
|
|
27
|
+
@EnvironmentObject private var localize: Localize
|
|
31
28
|
|
|
32
29
|
private var isEnglish: Bool { localize.currentLanguage == Localize.EN }
|
|
33
30
|
|
|
34
|
-
// AI-GENERATED START: observe the app language and drive it through the real platform API
|
|
35
|
-
private var storedLanguage: String {
|
|
36
|
-
UserDefaults.standard.string(forKey: UserDefaultsLanguageSource.defaultKey) ?? "nil"
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private func setLanguage(_ language: String) {
|
|
40
|
-
// Platform API -> KMP LanguageApi.SetLanguage -> LocalizationProvider.set, which also
|
|
41
|
-
// mirrors AppleLanguages. KVO then pushes the change back into Localize. The real wire.
|
|
42
|
-
if let composeApi = applicationEnvironment.composeApi {
|
|
43
|
-
_ = composeApi.request(funcName: "setLanguage", params: language)
|
|
44
|
-
} else {
|
|
45
|
-
localize.changeLanguage(language) // #Preview / sample app: no host bridge
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// AI-GENERATED END: observe the app language and drive it through the real platform API
|
|
49
|
-
|
|
50
31
|
var body: some View {
|
|
51
32
|
ScrollView {
|
|
52
33
|
VStack(alignment: .leading, spacing: 0) {
|
|
53
34
|
|
|
54
35
|
LocalizeSectionBox(title: "Đổi ngôn ngữ (toàn app)") {
|
|
55
36
|
MomoText(
|
|
56
|
-
"
|
|
57
|
-
+ "qua UserDefaults nên mọi màn hình đang đọc translate() đều cập nhật.",
|
|
37
|
+
"localize.changeLanguage(...) đổi ngôn ngữ cho cả NavigationContainer — mọi màn hình đang đọc translate() sẽ cập nhật theo.",
|
|
58
38
|
typography: .descriptionDefaultRegular
|
|
59
39
|
)
|
|
60
40
|
Switch(
|
|
61
41
|
.constant(isEnglish),
|
|
62
|
-
onChange: { _ in
|
|
42
|
+
onChange: { _ in localize.changeLanguage(isEnglish ? Localize.VI : Localize.EN) },
|
|
63
43
|
title: "English"
|
|
64
44
|
)
|
|
65
45
|
MomoText("currentLanguage = \(localize.currentLanguage)", typography: .labelDefaultMedium)
|
|
66
46
|
}
|
|
67
47
|
|
|
68
|
-
// AI-GENERATED START: prove the observation loop, not kit-local state, drives updates
|
|
69
|
-
LocalizeSectionBox(title: "Nguồn thật: UserDefaults") {
|
|
70
|
-
MomoText(
|
|
71
|
-
"Ghi thẳng vào UserDefaults, bỏ qua mọi API của kit — màn hình vẫn đổi, chứng minh "
|
|
72
|
-
+ "Localize đang observe ngôn ngữ app.",
|
|
73
|
-
typography: .descriptionDefaultRegular
|
|
74
|
-
)
|
|
75
|
-
TranslationRow(key: UserDefaultsLanguageSource.defaultKey, value: storedLanguage)
|
|
76
|
-
Button(
|
|
77
|
-
title: "Ghi thẳng UserDefaults = \"en\"",
|
|
78
|
-
action: {
|
|
79
|
-
// Quoted on purpose: proves normalize() strips the JSON quoting
|
|
80
|
-
// RN and KMP persist.
|
|
81
|
-
UserDefaults.standard.set(
|
|
82
|
-
"\"en\"",
|
|
83
|
-
forKey: UserDefaultsLanguageSource.defaultKey
|
|
84
|
-
)
|
|
85
|
-
},
|
|
86
|
-
type: .outline,
|
|
87
|
-
size: .medium,
|
|
88
|
-
isFull: false
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
// AI-GENERATED END: prove the observation loop, not kit-local state, drives updates
|
|
92
|
-
|
|
93
48
|
LocalizeSectionBox(title: "translate(key) — từ điển mặc định của kit") {
|
|
94
49
|
TranslationRow(key: "confirm", value: localize.translate("confirm"))
|
|
95
50
|
TranslationRow(key: "cancel", value: localize.translate("cancel"))
|
|
@@ -224,10 +179,8 @@ private struct TranslationRow: View {
|
|
|
224
179
|
#Preview {
|
|
225
180
|
if #available(iOS 16.0, *) {
|
|
226
181
|
NavigationStack {
|
|
227
|
-
// AI-GENERATED START: preview needs the observing provider or the toggle will not update
|
|
228
182
|
LocalizeDemo()
|
|
229
|
-
.
|
|
230
|
-
// AI-GENERATED END: preview needs the observing provider or the toggle will not update
|
|
183
|
+
.environmentObject(Localize())
|
|
231
184
|
}
|
|
232
185
|
} else {
|
|
233
186
|
// Fallback on earlier versions
|
|
@@ -101,21 +101,9 @@ private struct NavigationDemoContent: View {
|
|
|
101
101
|
|
|
102
102
|
Divider().padding(.vertical, 4)
|
|
103
103
|
|
|
104
|
-
row("Custom Back Handler") {
|
|
105
|
-
// The screen owns back: the header button and the edge swipe both
|
|
106
|
-
// run this instead of popping (mirrors Compose's onBackHandler).
|
|
107
|
-
navigator.push(
|
|
108
|
-
{ DemoLeafScreen(title: "Back is intercepted — swipe from the edge", color: Colors.blue09) },
|
|
109
|
-
options: NavigationOptions(
|
|
110
|
-
title: "Custom Back",
|
|
111
|
-
onBackHandler: { print("back intercepted") }
|
|
112
|
-
)
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
104
|
row("Prevent Back") {
|
|
117
|
-
//
|
|
118
|
-
//
|
|
105
|
+
// Tapping the header back button shows a confirm popup before
|
|
106
|
+
// leaving (mirrors Compose's headerBackProps.preventBack).
|
|
119
107
|
navigator.push(
|
|
120
108
|
{ DemoLeafScreen(title: "Tap the header back button", color: Colors.pink09) },
|
|
121
109
|
options: NavigationOptions(
|
|
@@ -7,7 +7,7 @@ struct SwiftUIDemoBrowserView: View {
|
|
|
7
7
|
@State private var searchText = ""
|
|
8
8
|
|
|
9
9
|
var body: some View {
|
|
10
|
-
|
|
10
|
+
Group {
|
|
11
11
|
if #available(iOS 16.0, *) {
|
|
12
12
|
HomeView(
|
|
13
13
|
onOpenCompose: onOpenCompose,
|
|
@@ -16,8 +16,6 @@ struct SwiftUIDemoBrowserView: View {
|
|
|
16
16
|
} else {
|
|
17
17
|
fallbackBrowser
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
BaselineView()
|
|
21
19
|
}
|
|
22
20
|
.environment(\.appTheme, defaultTheme)
|
|
23
21
|
}
|
package/local.properties
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
# Location of the SDK. This is only used by Gradle.
|
|
5
5
|
# For customization when using a Version Control System, please read the
|
|
6
6
|
# header note.
|
|
7
|
-
#Tue
|
|
8
|
-
sdk.dir=/Users/
|
|
7
|
+
#Tue Apr 08 11:13:31 ICT 2025
|
|
8
|
+
sdk.dir=/Users/sonnguyen/Library/Android/sdk
|
package/package.json
CHANGED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(curl -s -o /dev/null -w \"%{http_code}\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
|
|
5
|
-
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/maven-metadata.xml\")",
|
|
6
|
-
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/maven-metadata.xml\")",
|
|
7
|
-
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/0.144.6/skiko-0.144.6.module\")",
|
|
8
|
-
"Bash(curl -s -o /dev/null -w '%{http_code}' https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.__TRACKED_VAR__)",
|
|
9
|
-
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/\")",
|
|
10
|
-
"Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/\")",
|
|
11
|
-
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.148.1/skiko-android-0.148.1.pom\")",
|
|
12
|
-
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.5/skiko-android-0.144.5.pom\")",
|
|
13
|
-
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://dl.google.com/dl/android/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
|
|
14
|
-
"Bash(curl -s \"https://search.maven.org/solrsearch/select?q=a:skiko-android&rows=5&wt=json\")",
|
|
15
|
-
"Bash(curl -sL -o /dev/null -w \"%{http_code} %{url_effective}\\\\n\" \"https://repo1.maven.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
|
|
16
|
-
"Bash(curl *)",
|
|
17
|
-
"Bash(./gradlew :sample:androidApp:dependencies --configuration debugRuntimeClasspath)",
|
|
18
|
-
"Bash(./gradlew :sample:shared:compileDebugKotlinAndroid)",
|
|
19
|
-
"Bash(./gradlew :sample:androidApp:compileDebugKotlin)",
|
|
20
|
-
"Bash(ls -d *.xcodeproj)",
|
|
21
|
-
"Bash(grep -c ChipDemo.swift __TRACKED_VAR__/project.pbxproj)",
|
|
22
|
-
"Bash(grep -c PBXFileSystemSynchronizedRootGroup __TRACKED_VAR__/project.pbxproj)",
|
|
23
|
-
"Bash(grep -c InformationDemo.swift __TRACKED_VAR__/project.pbxproj)",
|
|
24
|
-
"Bash(plutil -lint project.pbxproj)",
|
|
25
|
-
"Bash(xcode-select -p)",
|
|
26
|
-
"Bash(/usr/bin/xcrun xcodebuild *)",
|
|
27
|
-
"Bash(echo \"exit: $?\")",
|
|
28
|
-
"Bash(/Applications/Xcode26.app/Contents/Developer/usr/bin/xcodebuild -version)",
|
|
29
|
-
"Bash(./gradlew :compose:tasks --all)",
|
|
30
|
-
"Bash(grep -n \"vanniktech\\\\|mavenPublishing\\\\|^publishing\\\\|version = gitlabVersion\\\\|namespace =\\\\|compileSdk =\\\\|minSdk =\\\\|GitLabPackages\\\\|name = \\\\\"\\\\${gitlabRepo}Packages\\\\\"\")",
|
|
31
|
-
"Bash(git checkout *)",
|
|
32
|
-
"Bash(./gradlew :compose:publishAllPublicationsToGitLabPackagesRepository --dry-run)",
|
|
33
|
-
"Bash(git fetch *)",
|
|
34
|
-
"Bash(git pull *)",
|
|
35
|
-
"Bash(git push *)",
|
|
36
|
-
"Bash(git branch *)",
|
|
37
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -list)",
|
|
38
|
-
"Bash(xcrun simctl *)",
|
|
39
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
|
|
40
|
-
"Bash(pod install *)",
|
|
41
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'id=1E6F9A79-5E64-4590-BEA2-828D75D99393' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
|
|
42
|
-
"Bash(xcrun swiftc *)",
|
|
43
|
-
"Bash(echo \"=== exit: $? \\(1=none found\\) ===\")",
|
|
44
|
-
"Bash(cat Radius.swift)",
|
|
45
|
-
"Bash(cat Spacing.swift)",
|
|
46
|
-
"Bash(echo \"exit=$?\")",
|
|
47
|
-
"Bash(ruby *)",
|
|
48
|
-
"Bash(xcodebuild -list -workspace iosApp.xcworkspace)",
|
|
49
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' build)",
|
|
50
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
|
|
51
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
|
|
52
|
-
"Bash(xcodebuild build *)",
|
|
53
|
-
"Bash(git stash *)",
|
|
54
|
-
"Bash(plutil -lint sample/iosApp/iosApp.xcodeproj/project.pbxproj)",
|
|
55
|
-
"Bash(git add *)",
|
|
56
|
-
"Bash(git commit -m 'feat: DS-646 [ci build] *)",
|
|
57
|
-
"Bash(git commit -q -m 'fix: DS-656 [ci build] *)",
|
|
58
|
-
"Bash(xargs git log --oneline -1)",
|
|
59
|
-
"Bash(git rev-list *)",
|
|
60
|
-
"Bash(git status *)",
|
|
61
|
-
"Bash(git rebase *)",
|
|
62
|
-
"Bash(git merge *)",
|
|
63
|
-
"Bash(git reset *)",
|
|
64
|
-
"Bash(awk '/^ *)",
|
|
65
|
-
"Bash(git ls-tree *)",
|
|
66
|
-
"Bash(awk *)",
|
|
67
|
-
"Read(//Users/sophia/Workspace/momo/momo-app/**)",
|
|
68
|
-
"Bash(git log *)",
|
|
69
|
-
"Bash(xcodebuild -version)",
|
|
70
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
|
|
71
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
|
|
72
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug clean build CODE_SIGNING_ALLOWED=NO)",
|
|
73
|
-
"Bash(git commit *)",
|
|
74
|
-
"Bash(npm view *)",
|
|
75
|
-
"Bash(echo \"EXIT=$? -> not resolvable\")",
|
|
76
|
-
"Bash(yarn install *)",
|
|
77
|
-
"Bash(RCT_NEW_ARCH_ENABLED=1 pod install)",
|
|
78
|
-
"Bash(xcodebuild -workspace MoMoPlatform.xcworkspace -list)",
|
|
79
|
-
"Bash(git --no-pager diff --stat ios/Application/Localize.swift ios/Application/Navigation/NavigationContainer.swift ios/Popup/PopupDisplay.swift ios/Popup/PopupNotify.swift ios/Template/TrustBanner/TrustBanner.swift)",
|
|
80
|
-
"Bash(git rm *)",
|
|
81
|
-
"Bash(echo \"EXIT=$?\")",
|
|
82
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits diff sample/iosApp/iosApp/Demo/ButtonDemo.swift)",
|
|
83
|
-
"Bash(python3 *)",
|
|
84
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits status --short)",
|
|
85
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits log --oneline -3)",
|
|
86
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits log --oneline -5 -- ios/Button/Button.swift)",
|
|
87
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits diff ios/native-kits.podspec compose/compose.podspec gradle.properties package.json)",
|
|
88
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits show HEAD:ios/Button/Button.swift)",
|
|
89
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits diff package.json ios/native-kits.podspec)",
|
|
90
|
-
"Bash(grep -n \"must be a \\\\`.background\\\\` of the label\" /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios/Button/Button.swift)",
|
|
91
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits show -s --format=\"%h %ci %s\" 953b234d)",
|
|
92
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-app show -s --format=\"%h %ci %s\" f8757a14437)",
|
|
93
|
-
"Bash(git -C /Users/sophia/Workspace/momo/momo-app log -S '\"@momo-kits/native-kits\": \"0.163.1-beta.18\"' --format=\"%h %ci %s\" -- package.json)",
|
|
94
|
-
"Bash(swiftc -O repro_isfull.swift -o repro_isfull)",
|
|
95
|
-
"Bash(./repro_isfull)",
|
|
96
|
-
"Bash(grep -n '^\\\\[32;1m$\\\\|section_start\\\\|xcodebuild.*-workspace\\\\|xcodebuild.*archive\\\\|Prebuilt\\\\|prebuilt\\\\|framework.*download\\\\|rugby\\\\|Rugby' /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/7e2b5bd7-f8b3-42f7-b242-127fa8eabd12/scratchpad/job_trace.log)",
|
|
97
|
-
"Bash(grep -n '\\\\[32;1m\\\\$' /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/7e2b5bd7-f8b3-42f7-b242-127fa8eabd12/scratchpad/job_trace.log)",
|
|
98
|
-
"Bash(npm pack *)",
|
|
99
|
-
"Bash(mkdir -p b13 b18)",
|
|
100
|
-
"Bash(tar xzf *)",
|
|
101
|
-
"Bash(git *)",
|
|
102
|
-
"Bash(tar -tzf momo-kits-native-kits-0.163.1-beta.18.tgz)",
|
|
103
|
-
"Bash(tar -xzf momo-kits-native-kits-0.163.1-beta.18.tgz package/ios/Button/Button.swift)",
|
|
104
|
-
"Bash(tar -xzf momo-kits-native-kits-0.162.28.tgz package/ios/Button/Button.swift -C .)",
|
|
105
|
-
"Bash(mkdir -p old)",
|
|
106
|
-
"Bash(tar -xzf momo-kits-native-kits-0.162.28.tgz -C old package/ios/Button/Button.swift)",
|
|
107
|
-
"Bash(nm '/private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/7e2b5bd7-f8b3-42f7-b242-127fa8eabd12/scratchpad/ipa_extract/Payload/MoMoPlatform UAT.app/MoMoPlatform UAT')",
|
|
108
|
-
"Bash(xcodebuild -workspace /Users/sophia/Workspace/momo/momo-native-kits/sample/iosApp/iosApp.xcworkspace -list)",
|
|
109
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' build)",
|
|
110
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build-verify build)",
|
|
111
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build-verify build)",
|
|
112
|
-
"Bash(./gradlew :sample:shared:syncComposeResourcesForIos)",
|
|
113
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' -derivedDataPath build-verify build)",
|
|
114
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -quiet build)",
|
|
115
|
-
"Bash(xcodebuild -workspace sample/iosApp/iosApp.xcworkspace -list)",
|
|
116
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -destination 'id=0228A4B2-57E0-4E56-BFAD-DB9E95044AD9' -derivedDataPath /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/6986983a-795d-419d-9d78-fcc100e87876/scratchpad/dd build)",
|
|
117
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -destination 'id=1E6F9A79-5E64-4590-BEA2-828D75D99393' -derivedDataPath /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/6986983a-795d-419d-9d78-fcc100e87876/scratchpad/dd build)",
|
|
118
|
-
"Bash([ -s $S/kb.png ])",
|
|
119
|
-
"Bash(sips -g pixelWidth -g pixelHeight /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/6986983a-795d-419d-9d78-fcc100e87876/scratchpad/kb.png)",
|
|
120
|
-
"Bash(defaults read *)",
|
|
121
|
-
"Bash(defaults write *)",
|
|
122
|
-
"Bash(xcrun xcodebuild *)",
|
|
123
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -quiet build)",
|
|
124
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' -quiet build)",
|
|
125
|
-
"Read(//tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/3e8adc08-a7e7-4169-aee6-8b22a6996f38/scratchpad/**)",
|
|
126
|
-
"Bash(echo \"parse-exit=$?\")",
|
|
127
|
-
"Bash(echo \"=== exit $?\")",
|
|
128
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug build)",
|
|
129
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' -configuration Debug build)",
|
|
130
|
-
"Bash(./gradlew :sample:shared:compileCommonMainKotlinMetadata -q)",
|
|
131
|
-
"Bash(echo \"gradle=$?\")",
|
|
132
|
-
"Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'id=0228A4B2-57E0-4E56-BFAD-DB9E95044AD9' -derivedDataPath build/dd build)",
|
|
133
|
-
"Bash(osascript *)",
|
|
134
|
-
"Bash(screencapture -x -R253,53,452,950 win.png)",
|
|
135
|
-
"Bash(sips -g pixelWidth -g pixelHeight win.png)",
|
|
136
|
-
"Bash(screencapture -x -R\"253,53,452,950\" win.png)",
|
|
137
|
-
"Bash(screencapture -x full.png)",
|
|
138
|
-
"Bash(sips -g pixelWidth -g pixelHeight full.png)",
|
|
139
|
-
"Bash(sips -c 1900 904 --cropOffset 106 506 full.png --out crop.png)",
|
|
140
|
-
"Bash(swiftc -O mouse.swift -o mouse)",
|
|
141
|
-
"Bash(./mouse click *)",
|
|
142
|
-
"Bash(./mouse drag *)",
|
|
143
|
-
"Bash(sips -c 900 924 --cropOffset 0 0 s12.png --out s12crop.png)",
|
|
144
|
-
"Bash(sips -c 300 1170 --cropOffset 300 0 s12.png --out s12top.png)",
|
|
145
|
-
"Bash(sips -c 700 1170 --cropOffset 250 0 t1.png --out t1c.png)",
|
|
146
|
-
"Bash(sips -c 700 1170 --cropOffset 250 0 t2.png --out t2c.png)",
|
|
147
|
-
"Bash(sips -c 700 1170 --cropOffset 150 0 t3.png --out t3c.png)",
|
|
148
|
-
"Bash(sips -c 1200 1170 --cropOffset 600 0 t4.png --out t4c.png)",
|
|
149
|
-
"Bash(sips -c 600 1170 --cropOffset 150 0 t5.png --out t5c.png)",
|
|
150
|
-
"Bash(sips -c 500 1170 --cropOffset 150 0 u1.png --out u1c.png)",
|
|
151
|
-
"Bash(cp /Users/sophia/Workspace/momo/momo-native-kits/ios/Application/Navigation/NavigationContainer.swift /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios/Application/Navigation/NavigationContainer.swift)",
|
|
152
|
-
"Bash(cp /Users/sophia/Workspace/momo/momo-native-kits/ios/Application/Navigation/Navigator.swift /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios/Application/Navigation/Navigator.swift)",
|
|
153
|
-
"Bash(cp /Users/sophia/Workspace/momo/momo-native-kits/ios/Application/Navigation/NavigatorManager.swift /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios/Application/Navigation/NavigatorManager.swift)",
|
|
154
|
-
"Bash(cp /Users/sophia/Workspace/momo/momo-native-kits/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift)",
|
|
155
|
-
"Bash(xcodebuild -workspace MoMoPlatform.xcworkspace -scheme MoMoPlatform-UAT -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build/DerivedData build)"
|
|
156
|
-
]
|
|
157
|
-
}
|
|
158
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// File: ios/Application/LanguageSource.swift
|
|
2
|
-
// Created At: 2026-07-29 10:00:00 +07:00
|
|
3
|
-
// Created By: AI
|
|
4
|
-
// AI Agent: Claude Code
|
|
5
|
-
// Model: claude-opus-5[1m]
|
|
6
|
-
|
|
7
|
-
// AI-GENERATED START: observe the host app's active language so Localize can follow it
|
|
8
|
-
import Foundation
|
|
9
|
-
|
|
10
|
-
/// Read-only view of the host app's active language, plus change delivery.
|
|
11
|
-
/// Values handed to `onChange` are already normalized to `Localize.VI` / `Localize.EN`.
|
|
12
|
-
public protocol LanguageSource: AnyObject {
|
|
13
|
-
var currentLanguage: String { get }
|
|
14
|
-
/// `onChange` may be invoked on any thread; `Localize` dedupes and hops to main.
|
|
15
|
-
func startObserving(_ onChange: @escaping (String) -> Void)
|
|
16
|
-
func stopObserving()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/// Reads the language the MoMo host app persists in `UserDefaults`. All three host writers
|
|
20
|
-
/// (the `RNResource` RN bridge, `JSONLocalization`, and the KMP `LocalizationProvider`) write the
|
|
21
|
-
/// same key on `UserDefaults.standard` in-process, so KVO on it sees every one of them.
|
|
22
|
-
public final class UserDefaultsLanguageSource: NSObject, LanguageSource {
|
|
23
|
-
public static let defaultKey = "language"
|
|
24
|
-
|
|
25
|
-
private let defaults: UserDefaults
|
|
26
|
-
private let key: String
|
|
27
|
-
private var onChange: ((String) -> Void)?
|
|
28
|
-
private var isObserving = false
|
|
29
|
-
private var kvoContext = 0
|
|
30
|
-
|
|
31
|
-
/// - Note: `key` must not contain `.` — `forKeyPath:` would read it as a key path.
|
|
32
|
-
public init(
|
|
33
|
-
defaults: UserDefaults = .standard,
|
|
34
|
-
key: String = UserDefaultsLanguageSource.defaultKey
|
|
35
|
-
) {
|
|
36
|
-
self.defaults = defaults
|
|
37
|
-
self.key = key
|
|
38
|
-
super.init()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
public var currentLanguage: String {
|
|
42
|
-
Localize.normalize(defaults.string(forKey: key))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public func startObserving(_ onChange: @escaping (String) -> Void) {
|
|
46
|
-
guard !isObserving else { return }
|
|
47
|
-
isObserving = true
|
|
48
|
-
self.onChange = onChange
|
|
49
|
-
defaults.addObserver(self, forKeyPath: key, options: [.new], context: &kvoContext)
|
|
50
|
-
// Safety net for writes KVO can miss (e.g. `removeObject`). Double delivery is free:
|
|
51
|
-
// `Localize` dedupes before touching `@Published`.
|
|
52
|
-
NotificationCenter.default.addObserver(
|
|
53
|
-
self,
|
|
54
|
-
selector: #selector(emit),
|
|
55
|
-
name: UserDefaults.didChangeNotification,
|
|
56
|
-
object: defaults
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
public func stopObserving() {
|
|
61
|
-
guard isObserving else { return }
|
|
62
|
-
isObserving = false
|
|
63
|
-
defaults.removeObserver(self, forKeyPath: key, context: &kvoContext)
|
|
64
|
-
NotificationCenter.default.removeObserver(
|
|
65
|
-
self,
|
|
66
|
-
name: UserDefaults.didChangeNotification,
|
|
67
|
-
object: defaults
|
|
68
|
-
)
|
|
69
|
-
onChange = nil
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
deinit {
|
|
73
|
-
stopObserving()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
public override func observeValue(
|
|
77
|
-
forKeyPath keyPath: String?,
|
|
78
|
-
of object: Any?,
|
|
79
|
-
change: [NSKeyValueChangeKey: Any]?,
|
|
80
|
-
context: UnsafeMutableRawPointer?
|
|
81
|
-
) {
|
|
82
|
-
guard context == &kvoContext else {
|
|
83
|
-
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
emit()
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@objc private func emit() {
|
|
90
|
-
onChange?(currentLanguage)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// AI-GENERATED END: observe the host app's active language so Localize can follow it
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// File: ios/Application/LocalizeModifier.swift
|
|
2
|
-
// Created At: 2026-07-29 10:00:00 +07:00
|
|
3
|
-
// Created By: AI
|
|
4
|
-
// AI Agent: Claude Code
|
|
5
|
-
// Model: claude-opus-5[1m]
|
|
6
|
-
|
|
7
|
-
// AI-GENERATED START: inject a language-observing Localize into any SwiftUI subtree
|
|
8
|
-
import SwiftUI
|
|
9
|
-
|
|
10
|
-
public extension View {
|
|
11
|
-
/// Injects an app-language-observing `Localize` and re-renders this subtree on change.
|
|
12
|
-
/// Use at every `UIHostingController` root that is not inside a `NavigationContainer`.
|
|
13
|
-
func momoLocalize(_ localize: Localize = .shared) -> some View {
|
|
14
|
-
modifier(MomoLocalizeModifier(localize: localize))
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/// `@ObservedObject` is load-bearing: `@Environment(\.localize)` only invalidates when the boxed
|
|
19
|
-
/// value changes, which requires *this* body to re-run on every publish. A plain helper that just
|
|
20
|
-
/// set `\.localize` would inject a snapshot that never updates.
|
|
21
|
-
private struct MomoLocalizeModifier: ViewModifier {
|
|
22
|
-
@ObservedObject var localize: Localize
|
|
23
|
-
|
|
24
|
-
func body(content: Content) -> some View {
|
|
25
|
-
content
|
|
26
|
-
.environmentObject(localize)
|
|
27
|
-
.environment(\.localize, localize)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// AI-GENERATED END: inject a language-observing Localize into any SwiftUI subtree
|