@momo-kits/native-kits 0.163.1-beta.16-debug → 0.163.1-beta.17-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.
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.163.1-beta.16-debug"
43
+ version = "0.163.1-beta.17-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.163.1-beta.16-debug'
3
+ spec.version = '0.163.1-beta.10'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -1,6 +1,6 @@
1
1
  package vn.momo.kits.layout
2
2
 
3
- import androidx.compose.foundation.layout.Box
3
+ import androidx.compose.foundation.layout.Column
4
4
  import androidx.compose.foundation.layout.height
5
5
  import androidx.compose.foundation.layout.width
6
6
  import androidx.compose.runtime.Composable
@@ -32,5 +32,7 @@ internal fun Item(
32
32
  m = m.width(grid.sizeForSpan(widthSpan))
33
33
  if (heightSpan > 0) m = m.height(grid.sizeForSpan(heightSpan))
34
34
  }
35
- Box(modifier = m) { content() }
35
+ // A Column (not a Box) so multiple children flow VERTICALLY instead of stacking
36
+ // on top of each other — matches the React Item's flex-column content.
37
+ Column(modifier = m) { content() }
36
38
  }
@@ -28,6 +28,7 @@ import androidx.compose.runtime.remember
28
28
  import androidx.compose.runtime.rememberCoroutineScope
29
29
  import androidx.compose.ui.Alignment
30
30
  import androidx.compose.ui.Modifier
31
+ import androidx.compose.ui.graphics.Brush
31
32
  import androidx.compose.ui.graphics.Color
32
33
  import androidx.compose.ui.input.pointer.pointerInput
33
34
  import androidx.compose.ui.platform.LocalDensity
@@ -57,6 +58,9 @@ internal fun BottomSheet(
57
58
  header: BottomHeader,
58
59
  isSurface: Boolean = false,
59
60
  barrierDismissible: Boolean = true,
61
+ draggable: Boolean = true,
62
+ useBottomInset: Boolean = true,
63
+ footerComponent: (@Composable () -> Unit)? = null,
60
64
  onDismiss: (() -> Unit)?
61
65
  ) {
62
66
  val screenHeightDp = getScreenHeight()
@@ -161,30 +165,35 @@ internal fun BottomSheet(
161
165
  .noFeedbackClickable { },
162
166
  contentAlignment = Alignment.BottomCenter
163
167
  ) {
164
- Column(Modifier.padding(bottom = AppNavigationBar.current)) {
168
+ val bottomInset = if (useBottomInset) {
169
+ (AppNavigationBar.current - keyboardSizeState().value).coerceAtLeast(0.dp)
170
+ } else 0.dp
171
+ Column(Modifier.padding(bottom = bottomInset)) {
165
172
  Box(
166
173
  modifier = Modifier
167
174
  .height(72.dp)
168
175
  .fillMaxWidth()
169
- .pointerInput(Unit) {
170
- detectDragGestures(
171
- onDrag = { change, dragAmount ->
172
- change.consume()
173
- coroutineScope.launch {
174
- val newOffset = (sheetOffset.value + dragAmount.y).coerceAtLeast(0f)
175
- sheetOffset.snapTo(newOffset)
176
- }
177
- },
178
- onDragEnd = {
179
- coroutineScope.launch {
180
- if (sheetOffset.value > sheetCloseOffset) {
181
- closeEvent()
182
- } else {
183
- sheetOffset.animateTo(0f)
176
+ .conditional(draggable) {
177
+ pointerInput(Unit) {
178
+ detectDragGestures(
179
+ onDrag = { change, dragAmount ->
180
+ change.consume()
181
+ coroutineScope.launch {
182
+ val newOffset = (sheetOffset.value + dragAmount.y).coerceAtLeast(0f)
183
+ sheetOffset.snapTo(newOffset)
184
+ }
185
+ },
186
+ onDragEnd = {
187
+ coroutineScope.launch {
188
+ if (sheetOffset.value > sheetCloseOffset) {
189
+ closeEvent()
190
+ } else {
191
+ sheetOffset.animateTo(0f)
192
+ }
184
193
  }
185
194
  }
186
- }
187
- )
195
+ )
196
+ }
188
197
  }
189
198
  ) {
190
199
  Column(
@@ -235,12 +244,50 @@ internal fun BottomSheet(
235
244
  }
236
245
  }
237
246
  Divider()
238
- content()
247
+ if (footerComponent != null) {
248
+ Box(Modifier.weight(1f, fill = false)) {
249
+ content()
250
+ }
251
+ BottomSheetFooter(footerComponent)
252
+ } else {
253
+ content()
254
+ }
239
255
  }
240
256
  }
241
257
  }
242
258
  }
243
259
 
260
+ @Composable
261
+ private fun BottomSheetFooter(footerComponent: @Composable () -> Unit) {
262
+ val shadowBrush = remember {
263
+ Brush.verticalGradient(
264
+ colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
265
+ )
266
+ }
267
+
268
+ Box {
269
+ Box(
270
+ Modifier
271
+ .fillMaxWidth()
272
+ .background(AppTheme.current.colors.background.surface)
273
+ .conditional(IsShowBaseLineDebug) {
274
+ border(1.dp, Colors.blue_03)
275
+ }
276
+ .padding(vertical = Spacing.S)
277
+ ) {
278
+ footerComponent()
279
+ }
280
+
281
+ Box(
282
+ modifier = Modifier
283
+ .fillMaxWidth()
284
+ .height(6.dp)
285
+ .offset(x = 0.dp, y = (-6).dp)
286
+ .background(shadowBrush)
287
+ )
288
+ }
289
+ }
290
+
244
291
  sealed class BottomHeader {
245
292
  data class Title(
246
293
  val data: String = "Bottom Sheet Title",
@@ -139,10 +139,13 @@ class Navigator(
139
139
  isSurface: Boolean = false,
140
140
  barrierDismissible: Boolean = true,
141
141
  onDismiss: (() -> Unit)? = null,
142
- bottomSheetHeader: BottomHeader? = null
142
+ bottomSheetHeader: BottomHeader? = null,
143
+ draggable: Boolean = true,
144
+ useBottomInset: Boolean = true,
145
+ footerComponent: (@Composable () -> Unit)? = null
143
146
  ){
144
147
  val id = currentScreen()?.id ?: -1
145
- OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader)
148
+ OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader, draggable, useBottomInset, footerComponent)
146
149
  }
147
150
 
148
151
  fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
@@ -257,6 +260,9 @@ sealed class OverplayComponentParams {
257
260
  val onDismiss: (() -> Unit)? = null,
258
261
  val barrierDismissible: Boolean = true,
259
262
  val bottomSheetHeader: BottomHeader? = null,
263
+ val draggable: Boolean = true,
264
+ val useBottomInset: Boolean = true,
265
+ val footerComponent: (@Composable () -> Unit)? = null,
260
266
  ) : OverplayComponentParams()
261
267
 
262
268
  class SnackBar : OverplayComponentParams()
@@ -288,10 +294,13 @@ object OverplayComponentRegistry {
288
294
  barrierDismissible: Boolean = true,
289
295
  onDismiss: (() -> Unit)?,
290
296
  bottomSheetHeader: BottomHeader? = null,
297
+ draggable: Boolean = true,
298
+ useBottomInset: Boolean = true,
299
+ footerComponent: (@Composable () -> Unit)? = null,
291
300
  ){
292
301
  val params = when(type){
293
302
  OverplayComponentType.MODAL -> OverplayComponentParams.Modal(onDismiss, barrierDismissible)
294
- OverplayComponentType.BOTTOM_SHEET -> OverplayComponentParams.BottomSheet(isSurface, onDismiss, barrierDismissible, bottomSheetHeader)
303
+ OverplayComponentType.BOTTOM_SHEET -> OverplayComponentParams.BottomSheet(isSurface, onDismiss, barrierDismissible, bottomSheetHeader, draggable, useBottomInset, footerComponent)
295
304
  OverplayComponentType.SNACK_BAR -> OverplayComponentParams.SnackBar()
296
305
  }
297
306
 
@@ -333,6 +342,9 @@ object OverplayComponentRegistry {
333
342
  header = params.bottomSheetHeader ?: Title(),
334
343
  isSurface = params.isSurface,
335
344
  barrierDismissible = params.barrierDismissible,
345
+ draggable = params.draggable,
346
+ useBottomInset = params.useBottomInset,
347
+ footerComponent = params.footerComponent,
336
348
  onDismiss = params.onDismiss
337
349
  )
338
350
  }
package/gradle.properties CHANGED
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.163.1-beta.16
21
+ version=0.163.1-sp.3
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
@@ -100,20 +100,6 @@ public struct HeaderBackground: View {
100
100
  .frame(height: height)
101
101
  .shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
102
102
  .background(backgroundColor)
103
- .overlay(
104
- headerTransparent ? AnyView(EmptyView()) :
105
- AnyView(
106
- Rectangle()
107
- .fill(LinearGradient(
108
- gradient: Gradient(colors: [
109
- Color(red: 1, green: 0.8, blue: 0.87),
110
- Color(red: 1, green: 0.8, blue: 0.87).opacity(0.5)
111
- ]),
112
- startPoint: .top,
113
- endPoint: .bottom))
114
- .opacity(opacity)
115
- .frame(height: height))
116
- )
117
103
  if !headerTransparent {
118
104
  Rectangle()
119
105
  .fill(Color.black.opacity(0.1))
@@ -146,6 +132,17 @@ public struct HeaderBackground: View {
146
132
  }
147
133
  }
148
134
 
135
+ // MARK: - Header right width preference
136
+
137
+ /// Reports the measured width of the header-right area so the animated search
138
+ /// header can compute its trailing inset (mirrors Compose's headerRightWidthPx).
139
+ public struct HeaderRightWidthKey: PreferenceKey {
140
+ public static var defaultValue: CGFloat = 0
141
+ public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
142
+ value = max(value, nextValue())
143
+ }
144
+ }
145
+
149
146
  // MARK: - Bottom-tab root preference
150
147
 
151
148
  /// Set by `BottomTab` on appear so its enclosing `StackScreen` can detect that
@@ -235,6 +232,9 @@ public struct Header: View {
235
232
 
236
233
  if let headerRight = headerRight {
237
234
  AnyView(headerRight())
235
+ .background(GeometryReader { geo in
236
+ Color.clear.preference(key: HeaderRightWidthKey.self, value: geo.size.width)
237
+ })
238
238
  }
239
239
  }
240
240
  .padding(.horizontal, 12)
@@ -101,69 +101,98 @@ struct ToolkitHeaderRight: View {
101
101
  var tintColor: Color?
102
102
  @State private var isFavorite: Bool = false
103
103
  @State private var isLoading: Bool = false
104
- @EnvironmentObject private var environment: ApplicationEnvironment
104
+ @Environment(\.applicationEnvironment) private var environment
105
+
106
+ private func apiValue(_ value: Any?) -> Any {
107
+ value ?? NSNull()
108
+ }
109
+
110
+ private var contextMap: [String: Any] {
111
+ let context = environment.applicationContext
112
+ return [
113
+ "appId": apiValue(context?.appId),
114
+ "code": apiValue(context?.appCode),
115
+ "name": apiValue(context?.appName),
116
+ "icon": apiValue(context?.appIcon),
117
+ "description": apiValue(context?.description),
118
+ "support": apiValue(context?.support),
119
+ "toolkitConfig": apiValue(context?.toolkitConfig),
120
+ "providerId": apiValue(context?.providerId),
121
+ "permissions": apiValue(context?.permissions)
122
+ ]
123
+ }
124
+
125
+ private var shouldShowShortcut: Bool {
126
+ headerRight?.useShortcut == true && !(headerRight?.useMore == true && environment.applicationContext == nil)
127
+ }
128
+
129
+ private func parseResponse(_ response: String) -> Any? {
130
+ guard let jsonData = response.data(using: .utf8),
131
+ let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
132
+ return nil
133
+ }
134
+ return json["response"]
135
+ }
105
136
 
106
137
  // MARK: - Actions
138
+ private func loadFavoriteState() {
139
+ environment.composeApi?.request(
140
+ funcName: "isFavoriteApp",
141
+ params: ["code": apiValue(environment.applicationContext?.appCode)]
142
+ ) { response in
143
+ if let isFavorite = parseResponse(response) as? Bool {
144
+ self.isFavorite = isFavorite
145
+ }
146
+ }
147
+ }
148
+
107
149
  private func onPressShortcut() {
108
150
  environment.composeApi?.request(
109
151
  funcName: "onToolAction",
110
- params: ["item": ["key": "onFavorite"]]
111
- ) { _ in
112
- isFavorite.toggle()
152
+ params: [
153
+ "item": ["key": "onFavorite"],
154
+ "context": contextMap
155
+ ]
156
+ ) { response in
157
+ if let result = parseResponse(response) as? [String: Any],
158
+ result["success"] as? Bool == true {
159
+ isFavorite.toggle()
160
+ }
113
161
  }
114
162
  }
115
163
 
116
164
  private func onPressHelpCenter() {
117
- let context = environment.applicationContext
118
- let paramMap: [String: Any] = [
119
- "appId": context?.appId,
120
- "code": context?.appCode,
121
- "name": context?.appName,
122
- "icon": context?.appIcon,
123
- "description": context?.description
124
- ]
125
165
  environment.composeApi?.request(
126
166
  funcName: "showHelpCenter",
127
- params: paramMap
167
+ params: [
168
+ "appId": apiValue(environment.applicationContext?.appId),
169
+ "code": apiValue(environment.applicationContext?.appCode),
170
+ "name": apiValue(environment.applicationContext?.appName),
171
+ "icon": apiValue(environment.applicationContext?.appIcon),
172
+ "description": apiValue(environment.applicationContext?.description)
173
+ ]
128
174
  ) { _ in }
129
175
  }
130
176
 
131
177
  private func onPressClose() {
132
178
  environment.composeApi?.request(
133
179
  funcName: "dismissAll",
134
- params: ""
180
+ params: nil
135
181
  ) { _ in }
136
182
  }
137
183
 
138
184
  private func onPressMore() {
139
- let context = environment.applicationContext
140
185
  let params: [String: Any] = [
141
- "useSystemTools": headerRight?.useSystemTools,
186
+ "useSystemTools": headerRight?.useSystemTools ?? true,
142
187
  "tools": headerRight?.tools.map { $0.toMap() } ?? [],
143
- "context": [
144
- "appId": context?.appId,
145
- "code": context?.appCode,
146
- "name": context?.appName,
147
- "icon": context?.appIcon,
148
- "description": context?.description,
149
- "support": context?.support,
150
- "toolkitConfig": context?.toolkitConfig,
151
- "providerId": context?.providerId,
152
- "permissions": context?.permissions
153
- ]
188
+ "context": contextMap
154
189
  ]
155
190
  environment.composeApi?.request(
156
191
  funcName: "showTools",
157
192
  params: params
158
193
  ) { response in
159
- do {
160
- if let jsonData = response.data(using: .utf8),
161
- let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
162
- let toolResponse = json["response"] as? String {
163
- headerRight?.toolCallback?(toolResponse)
164
- }
165
- } catch {
166
- print("Error parsing response:", error)
194
+ if let toolResponse = parseResponse(response) as? String {
195
+ headerRight?.toolCallback?(toolResponse)
167
196
  }
168
197
  }
169
198
  }
@@ -195,7 +224,7 @@ struct ToolkitHeaderRight: View {
195
224
  } ?? false
196
225
 
197
226
  HStack(alignment: .center, spacing: 0) {
198
- if headerRight?.useShortcut == true {
227
+ if shouldShowShortcut {
199
228
  NavigationButton(
200
229
  disabled: isLoading,
201
230
  icon: navButtonConfig.icon,
@@ -206,15 +235,17 @@ struct ToolkitHeaderRight: View {
206
235
  }
207
236
 
208
237
  HStack(alignment: .center, spacing: 0) {
209
- Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
210
- .padding(4)
211
- .onTapGesture {
212
- onPressHelpCenter()
213
- }
214
-
215
- Rectangle()
216
- .fill(tintColor ?? Colors.black20)
217
- .frame(width: 0.5, height: 12)
238
+ if environment.applicationContext != nil {
239
+ Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
240
+ .padding(4)
241
+ .onTapGesture {
242
+ onPressHelpCenter()
243
+ }
244
+
245
+ Rectangle()
246
+ .fill(tintColor ?? Colors.black20)
247
+ .frame(width: 0.5, height: 12)
248
+ }
218
249
 
219
250
  Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
220
251
  .padding(4)
@@ -231,6 +262,9 @@ struct ToolkitHeaderRight: View {
231
262
  )
232
263
  .padding(.leading, Spacing.S)
233
264
  }
265
+ .onAppear {
266
+ loadFavoriteState()
267
+ }
234
268
  }
235
269
  }
236
270
 
@@ -22,7 +22,7 @@ public struct NavigationHeader: View {
22
22
  /// Optional rich title (mirrors Compose's `HeaderTitle`); `.user` renders an
23
23
  /// avatar group, `.default`/nil renders plain `title`.
24
24
  var headerTitle: HeaderTitle?
25
- var headerRight: (() -> any View)? = { NavigationHeaderRight() }
25
+ var headerRight: (() -> any View)? = { HeaderRight() }
26
26
  var goBack: (() -> Void)?
27
27
  var opacity: CGFloat = 1
28
28
  var animatedHeader: AnimatedHeader?
@@ -281,19 +281,25 @@ private struct ButtonPressFeedbackStyle: ButtonStyle {
281
281
  func makeBody(configuration: Configuration) -> some View {
282
282
  let pressed = configuration.isPressed && isEnabled
283
283
 
284
- configuration.label
285
- .opacity(pressed ? 0.5 : 1)
286
- .background(
287
- RoundedRectangle(cornerRadius: radius)
288
- .fill(background)
289
- .overlay(
290
- RoundedRectangle(cornerRadius: radius)
291
- .strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
292
- )
293
- .padding(.horizontal, pressed ? 2 : 0)
294
- )
295
- .frame(height: height)
296
- .clipShape(RoundedRectangle(cornerRadius: radius))
297
- .animation(.easeInOut(duration: 0.1), value: pressed)
284
+ ZStack {
285
+ RoundedRectangle(cornerRadius: radius)
286
+ .fill(background)
287
+ .overlay(
288
+ RoundedRectangle(cornerRadius: radius)
289
+ .strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
290
+ )
291
+ .padding(.horizontal, pressed ? 2 : 0)
292
+
293
+ configuration.label
294
+ .opacity(pressed ? 0.5 : 1)
295
+ }
296
+ // Pin the whole button to its intended height explicitly: the bare
297
+ // RoundedRectangle background has no intrinsic size of its own, so left
298
+ // unconstrained in a ZStack inside a non-height-constraining container
299
+ // (e.g. a footer VStack) it can report an oversized ideal height and
300
+ // stretch the button — and its container — far taller than intended.
301
+ .frame(height: height)
302
+ .clipShape(RoundedRectangle(cornerRadius: radius))
303
+ .animation(.easeInOut(duration: 0.1), value: pressed)
298
304
  }
299
305
  }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKits'
3
- s.version = '0.163.1-beta.16'
3
+ s.version = '0.163.1-sp.3'
4
4
  s.summary = 'MoMoUIKits for iOS'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKitsDemo'
3
- s.version = '0.163.1-beta.16'
3
+ s.version = '0.163.1-sp.3'
4
4
  s.summary = 'Demo browser for MoMoUIKits SwiftUI components'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -1,245 +1,100 @@
1
1
  //
2
- // ButtonDemo.swift
3
- // MoMoUIKitsDemo
2
+ // Foundation.swift
3
+ // Example
4
4
  //
5
- // SwiftUI port of the Compose demo `sample/shared/.../screens/ButtonUsage.kt`:
6
- // Preview tab mirrors `PreviewButton`, Playground tab mirrors `PlaygroundButton`.
5
+ // Created by Wem on 13/12/2022.
7
6
  //
8
7
 
9
8
  import Foundation
9
+ import Lottie
10
10
  import MoMoUIKits
11
11
  import SwiftUI
12
12
 
13
- // Mirror the option lists in Compose `DemoScreen.kt`.
14
- private let buttonTypeList: [(key: String, value: ButtonType)] = [
15
- ("primary", .primary),
16
- ("secondary", .secondary),
17
- ("tonal", .tonal),
18
- ("outline", .outline),
19
- ("danger", .danger),
20
- ("text", .text),
21
- ("disabled", .disabled),
22
- ]
23
-
24
- private let sizeList: [(key: String, value: ButtonSize)] = [
25
- ("large", .large),
26
- ("medium", .medium),
27
- ("small", .small),
28
- ]
29
-
30
- // Subset of the Compose `Icons` map — names resolved via `Icon(source:)` / icon.json.
31
- private let iconOptionList: [String] = [
32
- "none",
33
- "arrow_arrow-back",
34
- "arrow_arrow-next",
35
- "Phonebook",
36
- ]
37
-
38
13
  // MARK: - ButtonDemo
39
14
 
40
15
  struct ButtonDemo: View {
41
- @State private var tab = 0
42
-
43
- var body: some View {
44
- VStack(spacing: 0) {
45
- // Stand-in for Compose `DemoScreen`'s Preview/Playground bottom tabs.
46
- Picker("", selection: $tab) {
47
- Text("Preview").tag(0)
48
- Text("Playground").tag(1)
49
- }
50
- .pickerStyle(.segmented)
51
- .padding(.horizontal, 12)
52
- .padding(.top, 8)
53
-
54
- if tab == 0 {
55
- PreviewButton()
56
- } else {
57
- PlaygroundButton()
58
- }
59
- }
60
- }
61
- }
62
-
63
- // MARK: - PreviewButton
64
-
65
- private struct PreviewButton: View {
66
- private let url = "https://static-00.iconduck.com/assets.00/external-link-icon-512x512-h2zzryfc.png"
16
+ // MARK: Internal
67
17
 
68
18
  var body: some View {
69
19
  ScrollView {
70
- VStack(spacing: 0) {
71
- ButtonSectionBox(title: "Title") {
72
- Button(title: "Open", action: {})
73
- }
20
+ VStack {
21
+ Toggle("Disabled", isOn: $disabled)
74
22
 
75
- ButtonSectionBox(title: "Type") {
76
- Button(title: "PRIMARY", action: {}, type: .primary)
77
- Button(title: "DANGER", action: {}, type: .danger)
78
- Button(title: "SECONDARY", action: {}, type: .secondary)
79
- Button(title: "TONAL", action: {}, type: .tonal)
80
- Button(title: "OUTLINE", action: {}, type: .outline)
81
- Button(title: "TEXT", action: {}, type: .text)
82
- Button(title: "DISABLED", action: {}, type: .disabled)
23
+ Group {
24
+ Button(title: "Button", action: onPress, type: disabled ? .disabled : .primary, size: .large,
25
+ iconRight: AnyView(Image(systemName: "checkmark.circle")))
26
+ Button(title: "Button", action: onPress, size: .small)
83
27
  }
84
28
 
85
- ButtonSectionBox(title: "Size") {
86
- Button(title: "LARGE", action: {}, size: .large)
87
- Button(title: "MEDIUM", action: {}, size: .medium)
88
- Button(title: "SMALL", action: {}, size: .small)
29
+ Group {
30
+ Button(title: "Button", action: onPress, type: .danger,
31
+ iconLeft: AnyView(Image(systemName: "checkmark.circle")))
32
+ Button(title: "Button", action: onPress, type: .danger, size: .medium)
33
+ Button(title: "Button", action: onPress, type: .danger, size: .small)
89
34
  }
90
-
91
- ButtonSectionBox(title: "Loading") {
92
- Button(title: "PRIMARY", action: {}, type: .primary, loading: true)
93
- Button(title: "DANGER", action: {}, type: .danger, loading: true)
94
- Button(title: "SECONDARY", action: {}, type: .secondary, loading: true)
95
- Button(title: "TONAL", action: {}, type: .tonal, loading: true)
96
- Button(title: "OUTLINE", action: {}, type: .outline, loading: true)
97
- Button(title: "TEXT", action: {}, type: .text, loading: true)
98
- Button(title: "DISABLED", action: {}, type: .disabled, loading: true)
35
+
36
+ Group {
37
+ Button(title: "Button", action: onPress, type: .danger, iconLeft: AnyView(Image(systemName: "checkmark.circle")), isFull: false)
38
+ Button(title: "Button", action: onPress, type: .danger, size: .medium, isFull: false)
39
+ Button(title: "Button", action: onPress, type: .danger, size: .small, isFull: false)
99
40
  }
100
41
 
101
- ButtonSectionBox(title: "Icon Right") {
102
- Button(title: "Icon", action: {}, iconRight: AnyView(ImageView(url, loading: false)))
103
- }
104
42
 
105
- ButtonSectionBox(title: "Icon Left") {
106
- Button(title: "Icon", action: {}, iconLeft: AnyView(ImageView(url, loading: false)))
43
+ Group {
44
+ Button(title: "Button", action: onPress, type: .tonal, size: .large)
45
+ Button(title: "Button", action: onPress, type: .tonal, size: .medium)
46
+ Button(title: "Button", action: onPress, type: .tonal, size: .small)
107
47
  }
108
- }
109
- }
110
- }
111
- }
112
-
113
- // MARK: - PlaygroundButton
114
48
 
115
- private struct PlaygroundButton: View {
116
- @State private var title: String = "Button"
117
- @State private var type: AnyHashable = "primary"
118
- @State private var size: AnyHashable = "large"
119
- @State private var iconRight: AnyHashable = "none"
120
- @State private var iconLeft: AnyHashable = "none"
121
- @State private var full = true
122
- @State private var loading = false
123
- @State private var useTintColor = true
124
-
125
- var body: some View {
126
- VStack(spacing: 0) {
127
- ButtonSectionBox(title: "Component", alignment: .leading) {
128
- Button(
129
- title: title,
130
- action: {},
131
- type: buttonTypeList.first { $0.key == type }?.value ?? .primary,
132
- size: sizeList.first { $0.key == size }?.value ?? .large,
133
- iconLeft: iconView(iconLeft),
134
- iconRight: iconView(iconRight),
135
- isFull: full,
136
- loading: loading,
137
- useTintColor: useTintColor
138
- )
139
- }
140
-
141
- ScrollView {
142
- ButtonSectionBox(title: "Props", alignment: .leading) {
143
- PropTitle("Children")
144
- Input(text: $title)
145
- PropGap()
146
-
147
- PropTitle("Type")
148
- RadioGroup(options: buttonTypeList.map(\.key), selection: $type)
149
- PropGap()
150
-
151
- PropTitle("Size")
152
- RadioGroup(options: sizeList.map(\.key), selection: $size)
153
- PropGap()
154
-
155
- PropTitle("Full")
156
- Checkbox($full, title: "\(full)")
157
- PropGap()
158
-
159
- PropTitle("Iconright")
160
- RadioGroup(options: iconOptionList, selection: $iconRight)
161
- PropGap()
162
-
163
- PropTitle("Iconleft")
164
- RadioGroup(options: iconOptionList, selection: $iconLeft)
165
- PropGap()
166
-
167
- PropTitle("Loading")
168
- Checkbox($loading, title: "\(loading)")
169
- PropGap()
170
-
171
- PropTitle("Usetinecolor")
172
- Checkbox($useTintColor, title: "\(useTintColor)")
49
+ Group {
50
+ Button(title: "Tinted icon", action: onPress, type: .primary,
51
+ iconLeft: AnyView(Image(systemName: "checkmark.circle")), useTintColor: true)
52
+ Button(title: "Original icon", action: onPress, type: .primary,
53
+ iconLeft: AnyView(Image(systemName: "checkmark.circle")), useTintColor: false)
173
54
  }
174
- }
175
- }
176
- }
177
-
178
- private func iconView(_ selection: AnyHashable) -> AnyView? {
179
- guard let name = selection as? String, name != "none" else { return nil }
180
- return AnyView(Icon(source: name))
181
- }
182
- }
183
-
184
- // MARK: - Playground helpers
185
55
 
186
- private struct PropTitle: View {
187
- let title: String
188
-
189
- init(_ title: String) { self.title = title }
190
-
191
- var body: some View {
192
- MomoText(title, typography: .headerDefaultBold)
193
- }
194
- }
56
+ Group {
57
+ Button(title: "Button", action: onPress, type: .secondary, size: .large)
58
+ Button(title: "Button", action: onPress, type: .secondary, size: .medium)
59
+ Button(title: "Button", action: onPress, type: .secondary, size: .small)
60
+ }
195
61
 
196
- private struct PropGap: View {
197
- var body: some View {
198
- Spacer().frame(height: Spacing.L)
199
- }
200
- }
62
+ Group {
63
+ Button(title: "Button", action: onPress, type: .text, size: .large)
64
+ Button(title: "Button", action: onPress, type: .text, size: .medium)
65
+ Button(title: "Button", action: onPress, type: .text, size: .small)
66
+ }
201
67
 
202
- private struct RadioGroup: View {
203
- let options: [String]
204
- @Binding var selection: AnyHashable
68
+ Group {
69
+ Button(title: "Button", action: onPress, type: .outline, size: .large)
70
+ Button(title: "Button", action: onPress, type: .outline, size: .medium)
71
+ Button(title: "Button", action: onPress, type: .outline, size: .small)
72
+ }
205
73
 
206
- var body: some View {
207
- ForEach(options, id: \.self) { key in
208
- Radio(value: key, groupValue: $selection, label: key)
209
- }
74
+ Group {
75
+ Button(title: "Button", action: onPress, type: .disabled, size: .large)
76
+ Button(title: "Button", action: onPress, type: .disabled, size: .medium)
77
+ Button(title: "Button", action: onPress, type: .disabled, size: .small)
78
+ }
79
+ Group {
80
+ Button(title: "Button", action: onPress, type: .primary, size: .medium, loading: true)
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
+ Button(title: "Button", action: onPress, type: .secondary, size: .medium, loading: true)
85
+ Button(title: "Button", action: onPress, type: .text, size: .medium, loading: true)
86
+ Button(title: "Button", action: onPress, type: .tonal, size: .medium, loading: true)
87
+ }
88
+ }.padding(16)
89
+ }.padding(.top)
210
90
  }
211
- }
212
91
 
213
- // MARK: - ButtonSectionBox
214
-
215
- /// Titled rounded-card section container, mirroring Compose's `BasicColumnBox`.
216
- private struct ButtonSectionBox<Content: View>: View {
217
- @Environment(\.appTheme) private var theme
218
- let title: String
219
- var alignment: HorizontalAlignment = .center
220
- @ViewBuilder let content: () -> Content
221
-
222
- var body: some View {
223
- VStack(alignment: .leading, spacing: 8) {
224
- if !title.isEmpty {
225
- MomoText(title, typography: .headerMBold)
226
- .padding(.top, 8)
227
- }
228
-
229
- VStack(alignment: alignment, spacing: 8) {
230
- content()
231
- }
232
- .frame(maxWidth: .infinity, alignment: alignment == .leading ? .leading : .center)
233
- .padding(12)
234
- .background(theme.colors.background.surface)
235
- .clipShape(RoundedRectangle(cornerRadius: Radius.M))
236
- }
237
- .padding(12)
92
+ func onPress() {
93
+ print("Button")
238
94
  }
239
- }
240
95
 
241
- // MARK: - Preview
96
+ // MARK: Private
242
97
 
243
- #Preview {
244
- ButtonDemo()
98
+ @State private var value: String = ""
99
+ @State private var disabled: Bool = false
245
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.163.1-beta.16-debug",
3
+ "version": "0.163.1-beta.17-debug",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},
@@ -1,85 +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
- ]
84
- }
85
- }
@@ -1,11 +0,0 @@
1
- import SwiftUI
2
-
3
- /// Reports the measured width of the header-right area so the animated search
4
- /// header can compute its trailing inset (mirrors Compose's headerRightWidthPx).
5
- /// Written by the header views (`NavigationHeader`) and read by `StackScreen`.
6
- public struct HeaderRightWidthKey: PreferenceKey {
7
- public static var defaultValue: CGFloat = 0
8
- public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
9
- value = max(value, nextValue())
10
- }
11
- }
@@ -1,196 +0,0 @@
1
- import SwiftUI
2
- import Foundation
3
-
4
- /// Keyed (`@Environment(\.applicationEnvironment)`) variant of `HeaderRight`, for hosts
5
- /// wired through `NavigationContainer`/`KitContainer` (the injection style introduced by
6
- /// MR 227 / DS-637). Reuses the shared data types (`HeaderRightData`, `Tool`, `ToolGroup`)
7
- /// and `NavigationButton` from `HeaderRight.swift`. The legacy `HeaderRight` there stays on
8
- /// `@EnvironmentObject` for hosts that render `Screen` directly with
9
- /// `.environmentObject(ApplicationEnvironment(...))`. Opt in per screen with
10
- /// `headerRight: { NavigationHeaderRight() }`.
11
- public struct NavigationHeaderRight: View {
12
- public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
13
- self.headerRight = headerRight
14
- self.tintColor = tintColor
15
- }
16
- var headerRight: HeaderRightData?
17
- var tintColor: Color? = nil
18
-
19
- public var body: some View {
20
- HStack(alignment: .center) {
21
- ToolkitNavigationHeaderRight(headerRight: headerRight, tintColor: tintColor)
22
- }
23
- }
24
- }
25
-
26
- struct ToolkitNavigationHeaderRight: View {
27
- var headerRight: HeaderRightData?
28
- var tintColor: Color?
29
- @State private var isFavorite: Bool = false
30
- @State private var isLoading: Bool = false
31
- @Environment(\.applicationEnvironment) private var environment
32
-
33
- private func apiValue(_ value: Any?) -> Any {
34
- value ?? NSNull()
35
- }
36
-
37
- private var contextMap: [String: Any] {
38
- let context = environment.applicationContext
39
- return [
40
- "appId": apiValue(context?.appId),
41
- "code": apiValue(context?.appCode),
42
- "name": apiValue(context?.appName),
43
- "icon": apiValue(context?.appIcon),
44
- "description": apiValue(context?.description),
45
- "support": apiValue(context?.support),
46
- "toolkitConfig": apiValue(context?.toolkitConfig),
47
- "providerId": apiValue(context?.providerId),
48
- "permissions": apiValue(context?.permissions)
49
- ]
50
- }
51
-
52
- private var shouldShowShortcut: Bool {
53
- headerRight?.useShortcut == true && !(headerRight?.useMore == true && environment.applicationContext == nil)
54
- }
55
-
56
- private func parseResponse(_ response: String) -> Any? {
57
- guard let jsonData = response.data(using: .utf8),
58
- let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
59
- return nil
60
- }
61
- return json["response"]
62
- }
63
-
64
- // MARK: - Actions
65
- private func loadFavoriteState() {
66
- environment.composeApi?.request(
67
- funcName: "isFavoriteApp",
68
- params: ["code": apiValue(environment.applicationContext?.appCode)]
69
- ) { response in
70
- if let isFavorite = parseResponse(response) as? Bool {
71
- self.isFavorite = isFavorite
72
- }
73
- }
74
- }
75
-
76
- private func onPressShortcut() {
77
- environment.composeApi?.request(
78
- funcName: "onToolAction",
79
- params: [
80
- "item": ["key": "onFavorite"],
81
- "context": contextMap
82
- ]
83
- ) { response in
84
- if let result = parseResponse(response) as? [String: Any],
85
- result["success"] as? Bool == true {
86
- isFavorite.toggle()
87
- }
88
- }
89
- }
90
-
91
- private func onPressHelpCenter() {
92
- environment.composeApi?.request(
93
- funcName: "showHelpCenter",
94
- params: [
95
- "appId": apiValue(environment.applicationContext?.appId),
96
- "code": apiValue(environment.applicationContext?.appCode),
97
- "name": apiValue(environment.applicationContext?.appName),
98
- "icon": apiValue(environment.applicationContext?.appIcon),
99
- "description": apiValue(environment.applicationContext?.description)
100
- ]
101
- ) { _ in }
102
- }
103
-
104
- private func onPressClose() {
105
- environment.composeApi?.request(
106
- funcName: "dismissAll",
107
- params: nil
108
- ) { _ in }
109
- }
110
-
111
- private func onPressMore() {
112
- let params: [String: Any] = [
113
- "useSystemTools": headerRight?.useSystemTools ?? true,
114
- "tools": headerRight?.tools.map { $0.toMap() } ?? [],
115
- "context": contextMap
116
- ]
117
- environment.composeApi?.request(
118
- funcName: "showTools",
119
- params: params
120
- ) { response in
121
- if let toolResponse = parseResponse(response) as? String {
122
- headerRight?.toolCallback?(toolResponse)
123
- }
124
- }
125
- }
126
-
127
- private func getNavigationButtonConfig() -> NavigationButtonConfig {
128
- let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
129
- var icon = isFavorite ? "pin_star_checked" : "pin_star"
130
- var onClickHandler: () -> Void = onPressShortcut
131
-
132
- if totalTools > 1 || headerRight?.useMore == true {
133
- icon = "navigation_more_icon"
134
- onClickHandler = onPressMore
135
- } else if totalTools == 1, let singleTool = headerRight?.tools.first?.items.first {
136
- icon = singleTool.icon
137
- onClickHandler = {
138
- headerRight?.toolCallback?(singleTool.key)
139
- }
140
- }
141
-
142
- return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
143
- }
144
-
145
- var body: some View {
146
- let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
147
- let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
148
- let navButtonConfig = getNavigationButtonConfig()
149
- let showBadge = headerRight?.tools.contains { group in
150
- group.items.contains { $0.showBadge }
151
- } ?? false
152
-
153
- HStack(alignment: .center, spacing: 0) {
154
- if shouldShowShortcut {
155
- NavigationButton(
156
- disabled: isLoading,
157
- icon: navButtonConfig.icon,
158
- showBadge: showBadge,
159
- onClick: navButtonConfig.onPress,
160
- tintColor: tintColor
161
- )
162
- }
163
-
164
- HStack(alignment: .center, spacing: 0) {
165
- if environment.applicationContext != nil {
166
- Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
167
- .padding(4)
168
- .onTapGesture {
169
- onPressHelpCenter()
170
- }
171
-
172
- Rectangle()
173
- .fill(tintColor ?? Colors.black20)
174
- .frame(width: 0.5, height: 12)
175
- }
176
-
177
- Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
178
- .padding(4)
179
- .onTapGesture {
180
- onPressClose()
181
- }
182
- }
183
- .frame(width: 65, height: 28)
184
- .background(backgroundButtonColor)
185
- .cornerRadius(14)
186
- .overlay(
187
- RoundedRectangle(cornerRadius: 14)
188
- .stroke(borderColor, lineWidth: 0.2)
189
- )
190
- .padding(.leading, Spacing.S)
191
- }
192
- .onAppear {
193
- loadFavoriteState()
194
- }
195
- }
196
- }
package/local.properties DELETED
@@ -1,8 +0,0 @@
1
- ## This file must *NOT* be checked into Version Control Systems,
2
- # as it contains information specific to your local configuration.
3
- #
4
- # Location of the SDK. This is only used by Gradle.
5
- # For customization when using a Version Control System, please read the
6
- # header note.
7
- #Tue Jun 02 17:05:32 ICT 2026
8
- sdk.dir=/Users/sophia/Library/Android/sdk