@momo-kits/native-kits 0.112.1-beta.1 → 0.112.1-beta.10
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/compose/src/commonMain/kotlin/vn/momo/kits/application/Components.kt +7 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/{components → application}/HeaderRight.kt +60 -19
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +32 -34
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +4 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +6 -6
- package/ios/Application/Components.swift +223 -0
- package/ios/Application/FloatingButton.swift +148 -0
- package/ios/Application/HeaderRight.swift +231 -0
- package/ios/Application/Screen.swift +228 -0
- package/ios/Input/InputSearch.swift +56 -0
- package/package.json +1 -1
|
@@ -178,6 +178,7 @@ fun Header(
|
|
|
178
178
|
val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
|
|
179
179
|
val tintIconColor = AppTheme.current.colors.text.default
|
|
180
180
|
val backgroundButton = Colors.black_01.copy(alpha = 0.6f)
|
|
181
|
+
val miniAppContext = ApplicationContext.current
|
|
181
182
|
|
|
182
183
|
var colorFraction by remember { mutableStateOf(0f) }
|
|
183
184
|
|
|
@@ -242,10 +243,12 @@ fun Header(
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
if (inputSearchProps == null && miniAppContext != null) {
|
|
247
|
+
Box(Modifier.graphicsLayer {
|
|
248
|
+
alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
|
|
249
|
+
}) {
|
|
250
|
+
headerRight?.invoke()
|
|
251
|
+
}
|
|
249
252
|
}
|
|
250
253
|
}
|
|
251
254
|
|
package/compose/src/commonMain/kotlin/vn/momo/kits/{components → application}/HeaderRight.kt
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package vn.momo.kits.
|
|
1
|
+
package vn.momo.kits.application
|
|
2
2
|
|
|
3
3
|
import androidx.compose.foundation.background
|
|
4
4
|
import androidx.compose.foundation.border
|
|
@@ -23,32 +23,45 @@ import androidx.compose.ui.Modifier
|
|
|
23
23
|
import androidx.compose.ui.draw.clip
|
|
24
24
|
import androidx.compose.ui.graphics.Color
|
|
25
25
|
import androidx.compose.ui.unit.dp
|
|
26
|
-
import vn.momo.kits.
|
|
27
|
-
import vn.momo.kits.
|
|
26
|
+
import vn.momo.kits.components.BadgeDot
|
|
27
|
+
import vn.momo.kits.components.DotSize
|
|
28
|
+
import vn.momo.kits.components.Icon
|
|
28
29
|
import vn.momo.kits.const.Colors
|
|
29
30
|
|
|
30
31
|
data class HeaderRightData(
|
|
31
|
-
val useOnBoarding: Boolean = false,
|
|
32
|
-
val buttonOnBoarding: String? = null,
|
|
33
|
-
val onPress: (() -> Unit)? = null,
|
|
34
|
-
val tintColor: Color = Colors.black_20,
|
|
35
|
-
val preventClose: Any? = null,
|
|
36
32
|
val useShortcut: Boolean = false,
|
|
37
33
|
val useMore: Boolean = false,
|
|
38
34
|
val tools: List<ToolGroup> = emptyList(),
|
|
39
|
-
val
|
|
35
|
+
val toolCallback: ((String) -> Unit)? = { _ -> }
|
|
40
36
|
)
|
|
41
37
|
|
|
42
38
|
data class Tool(
|
|
43
39
|
val key: String,
|
|
44
40
|
val icon: String,
|
|
45
|
-
val
|
|
46
|
-
val
|
|
47
|
-
)
|
|
41
|
+
val showBadge: Boolean = false,
|
|
42
|
+
val name: Map<String, String> = emptyMap()
|
|
43
|
+
) {
|
|
44
|
+
fun toMap(): Map<String, Any> {
|
|
45
|
+
return mapOf(
|
|
46
|
+
"key" to key,
|
|
47
|
+
"icon" to icon,
|
|
48
|
+
"showBadge" to showBadge,
|
|
49
|
+
"name" to name
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
48
53
|
|
|
49
54
|
data class ToolGroup(
|
|
55
|
+
val title: Map<String, String> = emptyMap(),
|
|
50
56
|
val items: List<Tool>
|
|
51
|
-
)
|
|
57
|
+
) {
|
|
58
|
+
fun toMap(): Map<String, Any> {
|
|
59
|
+
return mapOf(
|
|
60
|
+
"title" to title,
|
|
61
|
+
"items" to items.map { it.toMap() }
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
52
65
|
|
|
53
66
|
data class NavigationButtonConfig(
|
|
54
67
|
val icon: String,
|
|
@@ -77,18 +90,46 @@ fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
|
|
|
77
90
|
var isFavorite by remember { mutableStateOf(false) }
|
|
78
91
|
val isLoading by remember { mutableStateOf(false) }
|
|
79
92
|
val api = PlatformApi.current as? ComposeApi
|
|
93
|
+
val context = ApplicationContext.current
|
|
80
94
|
|
|
81
95
|
fun onPressShortcut() {
|
|
82
|
-
|
|
96
|
+
api?.request("onToolAction", mapOf("item" to mapOf("key" to "onFavorite"))) {
|
|
97
|
+
isFavorite = !isFavorite
|
|
98
|
+
}
|
|
83
99
|
}
|
|
84
|
-
|
|
85
|
-
|
|
100
|
+
|
|
101
|
+
fun onPressHelpCenter() {
|
|
102
|
+
val paramMap = mapOf(
|
|
103
|
+
"appId" to context?.appId,
|
|
104
|
+
"code" to context?.appCode,
|
|
105
|
+
"name" to context?.appName,
|
|
106
|
+
"icon" to context?.appIcon,
|
|
107
|
+
"description" to context?.description
|
|
108
|
+
)
|
|
109
|
+
api?.request("showHelpCenter", paramMap) {
|
|
110
|
+
|
|
111
|
+
}
|
|
86
112
|
}
|
|
87
|
-
|
|
113
|
+
|
|
114
|
+
fun onPressClose() {
|
|
88
115
|
api?.request("dismiss", "")
|
|
89
116
|
}
|
|
117
|
+
|
|
90
118
|
fun onPressMore() {
|
|
91
|
-
api?.request("showTools",
|
|
119
|
+
api?.request("showTools",
|
|
120
|
+
mapOf(
|
|
121
|
+
"tools" to headerRight?.tools?.map { it.toMap() },
|
|
122
|
+
"context" to mapOf(
|
|
123
|
+
"appId" to context?.appId,
|
|
124
|
+
"code" to context?.appCode,
|
|
125
|
+
"name" to context?.appName,
|
|
126
|
+
"icon" to context?.appIcon,
|
|
127
|
+
"description" to context?.description
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
) {
|
|
131
|
+
|
|
132
|
+
}
|
|
92
133
|
}
|
|
93
134
|
|
|
94
135
|
fun getNavigationButtonConfig(
|
|
@@ -109,7 +150,7 @@ fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
|
|
|
109
150
|
if (singleTool != null) {
|
|
110
151
|
icon = singleTool.icon
|
|
111
152
|
onClickHandler = {
|
|
112
|
-
singleTool.
|
|
153
|
+
headerRight?.toolCallback?.invoke(singleTool.key)
|
|
113
154
|
}
|
|
114
155
|
}
|
|
115
156
|
}
|
|
@@ -22,40 +22,20 @@ import vn.momo.kits.const.defaultTheme
|
|
|
22
22
|
import vn.momo.kits.platform.disableOverscroll
|
|
23
23
|
import vn.momo.kits.platform.getStatusBarHeight
|
|
24
24
|
|
|
25
|
-
val PlatformApi = staticCompositionLocalOf<Any?> { null }
|
|
26
|
-
|
|
27
25
|
interface ComposeApi {
|
|
28
|
-
fun request(funcName: String, params:
|
|
29
|
-
fun request(funcName: String, params:
|
|
30
|
-
fun requestCallback(funcName: String, params:
|
|
26
|
+
fun request(funcName: String, params: Any?): String
|
|
27
|
+
fun request(funcName: String, params: Any?, onResponse: ((String) -> Unit)?): String
|
|
28
|
+
fun requestCallback(funcName: String, params: Any?, onResponse: ((String) -> Unit)?)
|
|
31
29
|
fun removeCallback(id: String)
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
val AppNavigator = staticCompositionLocalOf<Navigator> {
|
|
35
|
-
error("no navigator provided!")
|
|
36
|
-
}
|
|
37
|
-
|
|
38
32
|
class Navigator {
|
|
39
|
-
fun push(content: @Composable () -> Unit) {
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
fun
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
fun reset(content: @Composable () -> Unit) {
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
fun pop() {
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
fun replace(content: @Composable () -> Unit) {
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
fun popToTop() {
|
|
57
|
-
}
|
|
58
|
-
|
|
33
|
+
fun push(content: @Composable () -> Unit) {}
|
|
34
|
+
fun present(content: @Composable () -> Unit) {}
|
|
35
|
+
fun reset(content: @Composable () -> Unit) {}
|
|
36
|
+
fun pop() {}
|
|
37
|
+
fun replace(content: @Composable () -> Unit) {}
|
|
38
|
+
fun popToTop() {}
|
|
59
39
|
fun showModal(
|
|
60
40
|
onClose: () -> Unit = {},
|
|
61
41
|
canBackgroundClose: Boolean = true,
|
|
@@ -63,8 +43,25 @@ class Navigator {
|
|
|
63
43
|
) {
|
|
64
44
|
}
|
|
65
45
|
|
|
66
|
-
fun showBottomSheet(content: @Composable () -> Unit) {
|
|
67
|
-
|
|
46
|
+
fun showBottomSheet(content: @Composable () -> Unit) {}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
data class MiniAppContext(
|
|
50
|
+
val appIcon: String = "",
|
|
51
|
+
val appName: Any? = null,
|
|
52
|
+
val appId: String = "",
|
|
53
|
+
val appCode: String = "",
|
|
54
|
+
val description: Any? = null,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
val PlatformApi = staticCompositionLocalOf<Any?> { null }
|
|
58
|
+
|
|
59
|
+
val AppNavigator = staticCompositionLocalOf<Navigator> {
|
|
60
|
+
error("no navigator provided!")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
val ApplicationContext = staticCompositionLocalOf<MiniAppContext?> {
|
|
64
|
+
error("no context provided!")
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
@Composable
|
|
@@ -72,7 +69,8 @@ fun ApplicationContainer(
|
|
|
72
69
|
theme: Theme = defaultTheme,
|
|
73
70
|
composeApi: ComposeApi? = null,
|
|
74
71
|
statusBarHeight: Dp? = AppStatusBar.current ?: getStatusBarHeight(),
|
|
75
|
-
|
|
72
|
+
applicationContext: MiniAppContext? = null,
|
|
73
|
+
content: @Composable () -> Unit,
|
|
76
74
|
) {
|
|
77
75
|
var appTheme by remember { mutableStateOf(theme) }
|
|
78
76
|
val coroutineScope = rememberCoroutineScope()
|
|
@@ -97,8 +95,7 @@ fun ApplicationContainer(
|
|
|
97
95
|
}
|
|
98
96
|
}
|
|
99
97
|
}
|
|
100
|
-
}
|
|
101
|
-
catch (e: Exception) {
|
|
98
|
+
} catch (e: Exception) {
|
|
102
99
|
print("@@ == NavigationContainer fetch config error $e")
|
|
103
100
|
}
|
|
104
101
|
}
|
|
@@ -109,6 +106,7 @@ fun ApplicationContainer(
|
|
|
109
106
|
PlatformApi provides composeApi,
|
|
110
107
|
AppNavigator provides Navigator(),
|
|
111
108
|
AppStatusBar provides statusBarHeight,
|
|
109
|
+
ApplicationContext provides applicationContext,
|
|
112
110
|
) {
|
|
113
111
|
content()
|
|
114
112
|
}
|
|
@@ -22,10 +22,10 @@ fun Icon(
|
|
|
22
22
|
color: Color? = AppTheme.current.colors.text.default,
|
|
23
23
|
modifier: Modifier = Modifier,
|
|
24
24
|
) {
|
|
25
|
-
val icon =
|
|
26
|
-
source
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
val icon = if (source.contains("https")) {
|
|
26
|
+
source
|
|
27
|
+
} else {
|
|
28
|
+
Icons[source] ?: ""
|
|
29
29
|
}
|
|
30
30
|
Box(modifier = modifier.size(size).semantics {contentDescription = "img|$icon"}) {
|
|
31
31
|
AsyncImage(
|
|
@@ -39,15 +39,15 @@ fun getFont(font: String): FontFamily {
|
|
|
39
39
|
fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
|
|
40
40
|
val key = "$fontFamily-${fontWeight?.weight}"
|
|
41
41
|
val fontMap = mapOf(
|
|
42
|
-
"SFProText-100" to "sfprotext_thin.
|
|
43
|
-
"SFProText-200" to "sfprotext_ultralight.
|
|
42
|
+
"SFProText-100" to "sfprotext_thin.otf",
|
|
43
|
+
"SFProText-200" to "sfprotext_ultralight.otf",
|
|
44
44
|
"SFProText-300" to "sfprotext_light.ttf",
|
|
45
45
|
"SFProText-400" to "sfprotext_regular.ttf",
|
|
46
46
|
"SFProText-500" to "sfprotext_medium.ttf",
|
|
47
|
-
"SFProText-600" to "
|
|
48
|
-
"SFProText-700" to "
|
|
49
|
-
"SFProText-800" to "
|
|
50
|
-
"SFProText-900" to "
|
|
47
|
+
"SFProText-600" to "sfprotext_semibold.ttf",
|
|
48
|
+
"SFProText-700" to "sfprotext_bold.ttf",
|
|
49
|
+
"SFProText-800" to "sfprotext_heavy.ttf",
|
|
50
|
+
"SFProText-900" to "sfprotext_black.otf",
|
|
51
51
|
|
|
52
52
|
"MoMoSignature-100" to "momosignature.otf",
|
|
53
53
|
"MoMoSignature-200" to "momosignature.otf",
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Components.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by sophia on 24/1/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import SwiftUI
|
|
9
|
+
|
|
10
|
+
public enum TitlePosition {
|
|
11
|
+
case left, center
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public enum HeaderType {
|
|
15
|
+
case `default`, extended, none
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
public struct AnimatedHeader {
|
|
20
|
+
public var type: HeaderType = .default
|
|
21
|
+
public var composable: (_ scrollState: Int) -> AnyView = { _ in AnyView(EmptyView()) }
|
|
22
|
+
|
|
23
|
+
public init(
|
|
24
|
+
type: HeaderType = .default,
|
|
25
|
+
composable: @escaping (_ scrollState: Int) -> AnyView = { _ in AnyView(EmptyView()) }
|
|
26
|
+
) {
|
|
27
|
+
self.type = type
|
|
28
|
+
self.composable = composable
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func safeAreaTopInset() -> CGFloat {
|
|
33
|
+
if #available(iOS 15.0, *) {
|
|
34
|
+
return UIApplication.shared.connectedScenes
|
|
35
|
+
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.top }
|
|
36
|
+
.first ?? 0
|
|
37
|
+
} else {
|
|
38
|
+
return UIApplication.shared.connectedScenes
|
|
39
|
+
.compactMap { ($0 as? UIWindowScene)?.windows.first?.safeAreaInsets.top }
|
|
40
|
+
.first ?? 0
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
public struct HeaderBackground: View {
|
|
46
|
+
var headerType: HeaderType = .default
|
|
47
|
+
var scrollState: CGFloat
|
|
48
|
+
|
|
49
|
+
public var body: some View {
|
|
50
|
+
|
|
51
|
+
let statusBarHeight = safeAreaTopInset()
|
|
52
|
+
|
|
53
|
+
let opacity = 1 - CGFloat(scrollState) / 200
|
|
54
|
+
let backgroundColor = scrollState == 0 ? Color.clear : Colors.black01
|
|
55
|
+
|
|
56
|
+
Group {
|
|
57
|
+
if headerType == .default {
|
|
58
|
+
let height = statusBarHeight + 52
|
|
59
|
+
ZStack {
|
|
60
|
+
|
|
61
|
+
Rectangle()
|
|
62
|
+
.fill(backgroundColor)
|
|
63
|
+
.frame(height: height)
|
|
64
|
+
.shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
|
|
65
|
+
.overlay(Rectangle()
|
|
66
|
+
.fill(LinearGradient(
|
|
67
|
+
gradient: Gradient(colors: [
|
|
68
|
+
Color(red: 1, green: 0.8, blue: 0.87),
|
|
69
|
+
Color(red: 1, green: 0.8, blue: 0.87).opacity(0.5)
|
|
70
|
+
]),
|
|
71
|
+
startPoint: .top,
|
|
72
|
+
endPoint: .bottom))
|
|
73
|
+
.opacity(opacity)
|
|
74
|
+
.frame(height: height))
|
|
75
|
+
.overlay(Rectangle()
|
|
76
|
+
.fill(Colors.black04)
|
|
77
|
+
.frame(height: 1)
|
|
78
|
+
.frame(maxWidth: .infinity)
|
|
79
|
+
.alignmentGuide(.bottom) { _ in 0 }, alignment: .bottom
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
.frame(maxWidth: .infinity)
|
|
83
|
+
|
|
84
|
+
} else if headerType == .extended {
|
|
85
|
+
ZStack {
|
|
86
|
+
Rectangle()
|
|
87
|
+
.fill(LinearGradient(
|
|
88
|
+
gradient: Gradient(colors: [
|
|
89
|
+
Color(red: 1, green: 0.8, blue: 0.87),
|
|
90
|
+
Color(red: 1, green: 0.8, blue: 0.87).opacity(0)
|
|
91
|
+
]),
|
|
92
|
+
startPoint: .top,
|
|
93
|
+
endPoint: .bottom))
|
|
94
|
+
.opacity(opacity)
|
|
95
|
+
.frame(height: 154)
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public struct Header: View {
|
|
105
|
+
var headerType: HeaderType = .default
|
|
106
|
+
var titlePosition: TitlePosition
|
|
107
|
+
var title: String
|
|
108
|
+
var headerRight: (() -> AnyView)?
|
|
109
|
+
var goBack: (() -> Void)?
|
|
110
|
+
var opacity: CGFloat? = nil
|
|
111
|
+
var animatedHeader: AnimatedHeader? = nil
|
|
112
|
+
var scrollState: CGFloat = 0
|
|
113
|
+
var inputSearchProps: InputSearchProps? = nil
|
|
114
|
+
|
|
115
|
+
@State private var colorFraction: CGFloat = 0
|
|
116
|
+
|
|
117
|
+
public init(
|
|
118
|
+
headerType: HeaderType = .default,
|
|
119
|
+
titlePosition: TitlePosition,
|
|
120
|
+
title: String,
|
|
121
|
+
headerRight: (() -> AnyView)? = nil,
|
|
122
|
+
goBack: (() -> Void)? = nil,
|
|
123
|
+
opacity: CGFloat? = nil,
|
|
124
|
+
animatedHeader: AnimatedHeader? = nil,
|
|
125
|
+
scrollState: CGFloat = 0,
|
|
126
|
+
inputSearchProps: InputSearchProps? = nil
|
|
127
|
+
) {
|
|
128
|
+
self.headerType = headerType
|
|
129
|
+
self.titlePosition = titlePosition
|
|
130
|
+
self.title = title
|
|
131
|
+
self.headerRight = headerRight
|
|
132
|
+
self.goBack = goBack
|
|
133
|
+
self.opacity = opacity
|
|
134
|
+
self.animatedHeader = animatedHeader
|
|
135
|
+
self.scrollState = scrollState
|
|
136
|
+
self.inputSearchProps = inputSearchProps
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public var body: some View {
|
|
140
|
+
|
|
141
|
+
let statusBarHeight = safeAreaTopInset()
|
|
142
|
+
let tintIconColor = Colors.black17
|
|
143
|
+
let backgroundButton = Colors.black01.opacity(0.6)
|
|
144
|
+
|
|
145
|
+
if headerType != .none {
|
|
146
|
+
VStack(spacing: 0) {
|
|
147
|
+
ZStack {
|
|
148
|
+
HStack {
|
|
149
|
+
if let goBack = goBack {
|
|
150
|
+
SwiftUI.Button(action: goBack) {
|
|
151
|
+
Circle()
|
|
152
|
+
.stroke(Color.black.opacity(0.2), lineWidth: 0.2)
|
|
153
|
+
.background(Circle().fill(backgroundButton))
|
|
154
|
+
.frame(width: 28, height: 28)
|
|
155
|
+
.overlay(
|
|
156
|
+
Image(systemName: "arrow.backward")
|
|
157
|
+
.foregroundColor(.black)
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Spacer() // Pushes headerRight to the right
|
|
164
|
+
|
|
165
|
+
if let headerRight = headerRight {
|
|
166
|
+
headerRight()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
.frame(height: 52)
|
|
170
|
+
.padding(.horizontal)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
HStack {
|
|
175
|
+
if titlePosition == .left, goBack != nil {
|
|
176
|
+
Spacer().frame(width: 40)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if inputSearchProps != nil {
|
|
180
|
+
if let inputProps = inputSearchProps {
|
|
181
|
+
InputSearch(
|
|
182
|
+
text: inputProps.$text,
|
|
183
|
+
buttonText: inputProps.buttonText,
|
|
184
|
+
showButtonText: inputProps.showButtonText,
|
|
185
|
+
showBorder: inputProps.showBorder,
|
|
186
|
+
placeholder: inputProps.placeholder,
|
|
187
|
+
onChangeText: inputProps.onChangeText,
|
|
188
|
+
onPressButtonText: inputProps.onPressButtonText,
|
|
189
|
+
error: inputProps.error,
|
|
190
|
+
disabled: inputProps.disabled,
|
|
191
|
+
icon: inputProps.icon,
|
|
192
|
+
iconColor: inputProps.iconColor,
|
|
193
|
+
onRightIconPressed: inputProps.onRightIconPressed,
|
|
194
|
+
onFocus: inputProps.onFocus,
|
|
195
|
+
onBlur: inputProps.onBlur,
|
|
196
|
+
loading: inputProps.loading,
|
|
197
|
+
fontWeight: inputProps.fontWeight,
|
|
198
|
+
keyboardType: inputProps.keyboardType
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
Text(title)
|
|
203
|
+
.font(.headline)
|
|
204
|
+
.foregroundColor(tintIconColor)
|
|
205
|
+
.frame(maxWidth: titlePosition == .center ? .infinity : nil, alignment: titlePosition == .center ? .center : .leading)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
Spacer()
|
|
209
|
+
}
|
|
210
|
+
.frame(height: 52)
|
|
211
|
+
.padding(.horizontal)
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
.background(Color.clear)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
//
|
|
2
|
+
// FloatingButton.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by sophia on 6/2/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import SwiftUI
|
|
9
|
+
import Combine
|
|
10
|
+
|
|
11
|
+
public enum FABSize {
|
|
12
|
+
case small
|
|
13
|
+
case `default`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public enum FABPosition {
|
|
17
|
+
case end
|
|
18
|
+
case center
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public struct FabProps {
|
|
22
|
+
let icon: String
|
|
23
|
+
let iconColor: Color?
|
|
24
|
+
let label: String?
|
|
25
|
+
let onClick: () -> Void
|
|
26
|
+
let size: FABSize
|
|
27
|
+
let bottom: CGFloat?
|
|
28
|
+
let scrollOffset: CGFloat?
|
|
29
|
+
let position: FABPosition?
|
|
30
|
+
|
|
31
|
+
public init(
|
|
32
|
+
icon: String,
|
|
33
|
+
iconColor: Color? = nil,
|
|
34
|
+
label: String? = nil,
|
|
35
|
+
onClick: @escaping () -> Void,
|
|
36
|
+
size: FABSize = .default,
|
|
37
|
+
bottom: CGFloat? = nil,
|
|
38
|
+
scrollOffset: CGFloat? = nil,
|
|
39
|
+
position: FABPosition? = .end
|
|
40
|
+
) {
|
|
41
|
+
self.icon = icon
|
|
42
|
+
self.iconColor = iconColor
|
|
43
|
+
self.label = label
|
|
44
|
+
self.onClick = onClick
|
|
45
|
+
self.size = size
|
|
46
|
+
self.bottom = bottom
|
|
47
|
+
self.scrollOffset = scrollOffset
|
|
48
|
+
self.position = position
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public struct FloatingButton: View {
|
|
53
|
+
@State private var lastScrollOffset: CGFloat = 0
|
|
54
|
+
@State private var isExpanded: Bool = false
|
|
55
|
+
let scrollOffset: CGFloat
|
|
56
|
+
let bottomPadding: CGFloat
|
|
57
|
+
let onClick: () -> Void
|
|
58
|
+
let containerColor: Color
|
|
59
|
+
let contentColor: Color
|
|
60
|
+
let icon: String
|
|
61
|
+
let iconColor: Color?
|
|
62
|
+
let text: String?
|
|
63
|
+
let size: FABSize
|
|
64
|
+
let position: FABPosition
|
|
65
|
+
let keyboardOffset: CGFloat
|
|
66
|
+
|
|
67
|
+
public init(
|
|
68
|
+
scrollOffset: CGFloat = 0,
|
|
69
|
+
bottomPadding: CGFloat = 0,
|
|
70
|
+
onClick: @escaping () -> Void,
|
|
71
|
+
containerColor: Color = .blue,
|
|
72
|
+
contentColor: Color = .white,
|
|
73
|
+
icon: String,
|
|
74
|
+
iconColor: Color? = nil,
|
|
75
|
+
text: String? = nil,
|
|
76
|
+
size: FABSize = .default,
|
|
77
|
+
position: FABPosition = .end,
|
|
78
|
+
keyboardOffset: CGFloat = 0
|
|
79
|
+
) {
|
|
80
|
+
self.scrollOffset = scrollOffset
|
|
81
|
+
self.bottomPadding = bottomPadding
|
|
82
|
+
self.onClick = onClick
|
|
83
|
+
self.containerColor = containerColor
|
|
84
|
+
self.contentColor = contentColor
|
|
85
|
+
self.icon = icon
|
|
86
|
+
self.iconColor = iconColor
|
|
87
|
+
self.text = text
|
|
88
|
+
self.size = size
|
|
89
|
+
self.position = position
|
|
90
|
+
self.keyboardOffset = keyboardOffset
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public var body: some View {
|
|
94
|
+
VStack {
|
|
95
|
+
Spacer()
|
|
96
|
+
HStack {
|
|
97
|
+
if position == .center {
|
|
98
|
+
Spacer()
|
|
99
|
+
}
|
|
100
|
+
FloatingButtonContent()
|
|
101
|
+
.padding(.trailing, position == .end ? 12 : 0)
|
|
102
|
+
.padding(.bottom, bottomPadding)
|
|
103
|
+
.offset(y: -keyboardOffset)
|
|
104
|
+
if position == .end {
|
|
105
|
+
Spacer()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
.onReceive(Just(scrollOffset)) { newValue in
|
|
110
|
+
withAnimation(.easeInOut(duration: 0.3)) {
|
|
111
|
+
isExpanded = newValue > lastScrollOffset
|
|
112
|
+
lastScrollOffset = newValue
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@ViewBuilder
|
|
118
|
+
private func FloatingButtonContent() -> some View {
|
|
119
|
+
SwiftUI.Button(action: onClick) {
|
|
120
|
+
HStack {
|
|
121
|
+
Image(systemName: icon)
|
|
122
|
+
.foregroundColor(iconColor ?? .white)
|
|
123
|
+
.font(size == .small ? .system(size: 12) : .system(size: 24))
|
|
124
|
+
|
|
125
|
+
if isExpanded, let text = text {
|
|
126
|
+
Text(text)
|
|
127
|
+
.foregroundColor(.white)
|
|
128
|
+
.transition(.asymmetric(insertion: expandAnimation, removal: collapseAnimation))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
.padding(.horizontal, isExpanded ? 12 : 0)
|
|
132
|
+
.frame(width: isExpanded ? 80 : (size == .small ? 36 : 48), height: size == .small ? 36 : 48)
|
|
133
|
+
.background(containerColor)
|
|
134
|
+
.cornerRadius(24)
|
|
135
|
+
.shadow(radius: 4)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private var expandAnimation: AnyTransition {
|
|
140
|
+
AnyTransition.opacity.animation(.easeIn(duration: 0.2))
|
|
141
|
+
.combined(with: .move(edge: .leading))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private var collapseAnimation: AnyTransition {
|
|
145
|
+
AnyTransition.opacity.animation(.easeOut(duration: 0.5))
|
|
146
|
+
.combined(with: .move(edge: .trailing))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
public struct HeaderRightData {
|
|
4
|
+
var useShortcut: Bool
|
|
5
|
+
var useMore: Bool
|
|
6
|
+
var tools: [ToolGroup]
|
|
7
|
+
var toolCallback: ((String) -> Void)?
|
|
8
|
+
|
|
9
|
+
public init(
|
|
10
|
+
useShortcut: Bool = false,
|
|
11
|
+
useMore: Bool = false,
|
|
12
|
+
tools: [ToolGroup] = [],
|
|
13
|
+
toolCallback: ((String) -> Void)? = nil
|
|
14
|
+
) {
|
|
15
|
+
self.useShortcut = useShortcut
|
|
16
|
+
self.useMore = useMore
|
|
17
|
+
self.tools = tools
|
|
18
|
+
self.toolCallback = toolCallback
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public struct Tool {
|
|
23
|
+
var key: String
|
|
24
|
+
var icon: String
|
|
25
|
+
var showBadge: Bool
|
|
26
|
+
var name: [String: String]
|
|
27
|
+
|
|
28
|
+
public init(
|
|
29
|
+
key: String,
|
|
30
|
+
icon: String,
|
|
31
|
+
showBadge: Bool = false,
|
|
32
|
+
name: [String: String] = [:]
|
|
33
|
+
) {
|
|
34
|
+
self.key = key
|
|
35
|
+
self.icon = icon
|
|
36
|
+
self.showBadge = showBadge
|
|
37
|
+
self.name = name
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func toMap() -> [String: Any] {
|
|
41
|
+
return [
|
|
42
|
+
"key": key,
|
|
43
|
+
"icon": icon,
|
|
44
|
+
"showBadge": showBadge,
|
|
45
|
+
"name": name
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public struct ToolGroup {
|
|
51
|
+
var title: [String: String]
|
|
52
|
+
var items: [Tool]
|
|
53
|
+
|
|
54
|
+
public init(
|
|
55
|
+
title: [String: String] = [:],
|
|
56
|
+
items: [Tool]
|
|
57
|
+
) {
|
|
58
|
+
self.title = title
|
|
59
|
+
self.items = items
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func toMap() -> [String: Any] {
|
|
63
|
+
return [
|
|
64
|
+
"title": title,
|
|
65
|
+
"items": items.map { $0.toMap() }
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
struct NavigationButtonConfig {
|
|
71
|
+
let icon: String
|
|
72
|
+
let onPress: () -> Void
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// MARK: - Components
|
|
76
|
+
struct HeaderRight: View {
|
|
77
|
+
var headerRight: HeaderRightData?
|
|
78
|
+
|
|
79
|
+
var body: some View {
|
|
80
|
+
HStack(alignment: .center) {
|
|
81
|
+
ToolkitHeaderRight(headerRight: headerRight)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
struct ToolkitHeaderRight: View {
|
|
87
|
+
var headerRight: HeaderRightData?
|
|
88
|
+
@State private var isFavorite: Bool = false
|
|
89
|
+
@State private var isLoading: Bool = false
|
|
90
|
+
|
|
91
|
+
// MARK: - Actions
|
|
92
|
+
private func onPressShortcut() {
|
|
93
|
+
ComposeApi.shared.request(
|
|
94
|
+
funcName: "onToolAction",
|
|
95
|
+
params: ["item": ["key": "onFavorite"]]
|
|
96
|
+
) { _ in
|
|
97
|
+
isFavorite.toggle()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private func onPressHelpCenter() {
|
|
102
|
+
let context = ApplicationContext.shared
|
|
103
|
+
let paramMap: [String: Any] = [
|
|
104
|
+
"appId": context.appId,
|
|
105
|
+
"code": context.appCode,
|
|
106
|
+
"name": context.appName,
|
|
107
|
+
"icon": context.appIcon,
|
|
108
|
+
"description": context.description
|
|
109
|
+
]
|
|
110
|
+
ComposeApi.shared.request(
|
|
111
|
+
funcName: "showHelpCenter",
|
|
112
|
+
params: paramMap
|
|
113
|
+
) { _ in }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private func onPressClose() {
|
|
117
|
+
ComposeApi.shared.request(funcName: "dismiss", params: "")
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private func onPressMore() {
|
|
121
|
+
let context = ApplicationContext.shared
|
|
122
|
+
let params: [String: Any] = [
|
|
123
|
+
"tools": headerRight?.tools.map { $0.toMap() } ?? [],
|
|
124
|
+
"context": [
|
|
125
|
+
"appId": context.appId,
|
|
126
|
+
"code": context.appCode,
|
|
127
|
+
"name": context.appName,
|
|
128
|
+
"icon": context.appIcon,
|
|
129
|
+
"description": context.description
|
|
130
|
+
]
|
|
131
|
+
]
|
|
132
|
+
ComposeApi.shared.request(
|
|
133
|
+
funcName: "showTools",
|
|
134
|
+
params: params
|
|
135
|
+
) { _ in }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private func getNavigationButtonConfig() -> NavigationButtonConfig {
|
|
139
|
+
let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
|
|
140
|
+
var icon = isFavorite ? "pin_star_checked" : "pin_star"
|
|
141
|
+
var onClickHandler: () -> Void = onPressShortcut
|
|
142
|
+
|
|
143
|
+
if totalTools > 1 || headerRight?.useMore == true {
|
|
144
|
+
icon = "navigation_more_icon"
|
|
145
|
+
onClickHandler = onPressMore
|
|
146
|
+
} else if totalTools == 1, let singleTool = headerRight?.tools.first?.items.first {
|
|
147
|
+
icon = singleTool.icon
|
|
148
|
+
onClickHandler = {
|
|
149
|
+
headerRight?.toolCallback?(singleTool.key)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
var body: some View {
|
|
157
|
+
let navButtonConfig = getNavigationButtonConfig()
|
|
158
|
+
let showBadge = headerRight?.tools.contains { group in
|
|
159
|
+
group.items.contains { $0.showBadge }
|
|
160
|
+
} ?? false
|
|
161
|
+
|
|
162
|
+
HStack(alignment: .center) {
|
|
163
|
+
if headerRight?.useShortcut == true {
|
|
164
|
+
NavigationButton(
|
|
165
|
+
disabled: isLoading,
|
|
166
|
+
icon: navButtonConfig.icon,
|
|
167
|
+
showBadge: showBadge,
|
|
168
|
+
onClick: navButtonConfig.onPress
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
HStack(alignment: .center, spacing: 0) {
|
|
173
|
+
Icon(source: "help_center", size: 20)
|
|
174
|
+
.padding(4)
|
|
175
|
+
.onTapGesture {
|
|
176
|
+
onPressHelpCenter()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
Rectangle()
|
|
180
|
+
.fill(Colors.black20)
|
|
181
|
+
.frame(width: 0.5, height: 12)
|
|
182
|
+
|
|
183
|
+
Icon(source: "16_navigation_close_circle", size: 20)
|
|
184
|
+
.padding(4)
|
|
185
|
+
.onTapGesture {
|
|
186
|
+
onPressClose()
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
.frame(width: 65, height: 28)
|
|
190
|
+
.background(Color.white.opacity(0.6))
|
|
191
|
+
.cornerRadius(14)
|
|
192
|
+
.overlay(
|
|
193
|
+
RoundedRectangle(cornerRadius: 14)
|
|
194
|
+
.stroke(Color.black.opacity(0.2), lineWidth: 0.2)
|
|
195
|
+
)
|
|
196
|
+
.padding(.leading, 8)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
struct NavigationButton: View {
|
|
202
|
+
var disabled: Bool
|
|
203
|
+
var icon: String
|
|
204
|
+
var showBadge: Bool
|
|
205
|
+
var onClick: () -> Void
|
|
206
|
+
|
|
207
|
+
var body: some View {
|
|
208
|
+
ZStack {
|
|
209
|
+
Circle()
|
|
210
|
+
.fill(Color.white.opacity(0.6))
|
|
211
|
+
.overlay(
|
|
212
|
+
Circle()
|
|
213
|
+
.stroke(Color.black.opacity(0.2), lineWidth: 0.2)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
Icon(source: icon, size: 16)
|
|
217
|
+
|
|
218
|
+
if showBadge {
|
|
219
|
+
BadgeDot(size: .small)
|
|
220
|
+
.offset(x: -2, y: -2)
|
|
221
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
.frame(width: 28, height: 28)
|
|
225
|
+
.onTapGesture {
|
|
226
|
+
if !disabled {
|
|
227
|
+
onClick()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Screen.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by sophia on 5/2/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import SwiftUI
|
|
9
|
+
|
|
10
|
+
public struct Screen<Content: View>: View {
|
|
11
|
+
var backgroundColor: Color?
|
|
12
|
+
var isBack: Bool
|
|
13
|
+
var headerType: HeaderType
|
|
14
|
+
var verticalAlignment: VerticalAlignment
|
|
15
|
+
var horizontalAlignment: HorizontalAlignment
|
|
16
|
+
var title: String
|
|
17
|
+
var titlePosition: TitlePosition
|
|
18
|
+
var goBack: (() -> Void)?
|
|
19
|
+
var scrollable: Bool
|
|
20
|
+
var onContentLayout: ((CGRect) -> Void)?
|
|
21
|
+
var useAvoidKeyboard: Bool
|
|
22
|
+
var footer: (() -> AnyView)?
|
|
23
|
+
var headerRight: HeaderRightData?
|
|
24
|
+
var animatedHeader: AnimatedHeader? = nil
|
|
25
|
+
var layoutOffset: CGFloat
|
|
26
|
+
var inputSearchProps: InputSearchProps? = nil
|
|
27
|
+
var fabProps: FabProps? = nil
|
|
28
|
+
var content: () -> Content
|
|
29
|
+
|
|
30
|
+
public init(
|
|
31
|
+
backgroundColor: Color? = nil,
|
|
32
|
+
isBack: Bool = true,
|
|
33
|
+
headerType: HeaderType = .default,
|
|
34
|
+
verticalAlignment: VerticalAlignment = .top,
|
|
35
|
+
horizontalAlignment: HorizontalAlignment = .leading,
|
|
36
|
+
title: String = "Stack",
|
|
37
|
+
titlePosition: TitlePosition = .center,
|
|
38
|
+
goBack: (() -> Void)? = nil,
|
|
39
|
+
scrollable: Bool = true,
|
|
40
|
+
onContentLayout: ((CGRect) -> Void)? = nil,
|
|
41
|
+
useAvoidKeyboard: Bool = true,
|
|
42
|
+
footer: (() -> AnyView)? = nil,
|
|
43
|
+
headerRight: HeaderRightData? = nil,
|
|
44
|
+
animatedHeader: AnimatedHeader? = nil,
|
|
45
|
+
layoutOffset: CGFloat = 56,
|
|
46
|
+
inputSearchProps: InputSearchProps? = nil,
|
|
47
|
+
fabProps: FabProps? = nil,
|
|
48
|
+
@ViewBuilder content: @escaping () -> Content
|
|
49
|
+
) {
|
|
50
|
+
self.backgroundColor = backgroundColor
|
|
51
|
+
self.isBack = isBack
|
|
52
|
+
self.headerType = headerType
|
|
53
|
+
self.verticalAlignment = verticalAlignment
|
|
54
|
+
self.horizontalAlignment = horizontalAlignment
|
|
55
|
+
self.title = title
|
|
56
|
+
self.titlePosition = titlePosition
|
|
57
|
+
self.goBack = goBack
|
|
58
|
+
self.scrollable = scrollable
|
|
59
|
+
self.onContentLayout = onContentLayout
|
|
60
|
+
self.useAvoidKeyboard = useAvoidKeyboard
|
|
61
|
+
self.footer = footer
|
|
62
|
+
self.headerRight = headerRight
|
|
63
|
+
self.animatedHeader = animatedHeader
|
|
64
|
+
self.layoutOffset = layoutOffset
|
|
65
|
+
self.inputSearchProps = inputSearchProps
|
|
66
|
+
self.fabProps = fabProps
|
|
67
|
+
self.content = content
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@State private var scrollOffset: CGFloat = 0
|
|
71
|
+
|
|
72
|
+
public var body: some View {
|
|
73
|
+
GeometryReader { geometry in
|
|
74
|
+
let safeAreaInsets = geometry.safeAreaInsets
|
|
75
|
+
let statusBarHeight = safeAreaInsets.top
|
|
76
|
+
let keyboardHeight: CGFloat = 0
|
|
77
|
+
let keyboardSize: CGFloat = useAvoidKeyboard ? max(keyboardHeight, 0) : 0
|
|
78
|
+
|
|
79
|
+
ZStack {
|
|
80
|
+
(backgroundColor ?? Color.white).edgesIgnoringSafeArea(.all)
|
|
81
|
+
|
|
82
|
+
VStack(spacing: 0) {
|
|
83
|
+
ZStack {
|
|
84
|
+
if animatedHeader == nil {
|
|
85
|
+
HeaderBackground(headerType: headerType, scrollState: scrollOffset)
|
|
86
|
+
} else {
|
|
87
|
+
Color.white.opacity(Double(min(scrollOffset / 114, 1)))
|
|
88
|
+
.overlay(HeaderBackground(headerType: headerType, scrollState: scrollOffset))
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Header(
|
|
93
|
+
headerType: headerType,
|
|
94
|
+
titlePosition: titlePosition,
|
|
95
|
+
title: title,
|
|
96
|
+
headerRight: headerRight,
|
|
97
|
+
goBack: goBack,
|
|
98
|
+
opacity: min(scrollOffset / 114, 1),
|
|
99
|
+
animatedHeader: animatedHeader,
|
|
100
|
+
scrollState: scrollOffset,
|
|
101
|
+
inputSearchProps: inputSearchProps
|
|
102
|
+
).padding(.bottom, headerType == .extended ? 0 : -safeAreaTopInset())
|
|
103
|
+
}.padding(.top, -safeAreaTopInset())
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
VStack(alignment: horizontalAlignment, spacing: 0) {
|
|
107
|
+
if animatedHeader != nil {
|
|
108
|
+
Spacer().frame(height: statusBarHeight + 52 + layoutOffset)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if scrollable {
|
|
112
|
+
ScrollView(.vertical, showsIndicators: false) {
|
|
113
|
+
VStack {
|
|
114
|
+
content()
|
|
115
|
+
}
|
|
116
|
+
.background(GeometryReader { geo -> Color in
|
|
117
|
+
DispatchQueue.main.async {
|
|
118
|
+
self.scrollOffset = -geo.frame(in: .global).origin.y
|
|
119
|
+
}
|
|
120
|
+
return Color.clear
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
content()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
.padding(.bottom, keyboardSize)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if let footerView = footer?() {
|
|
131
|
+
ZStack(alignment: .top) {
|
|
132
|
+
Rectangle()
|
|
133
|
+
.fill(Colors.black20)
|
|
134
|
+
.frame(height: 4)
|
|
135
|
+
.shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
|
|
136
|
+
|
|
137
|
+
Footer(footerView: footerView, keyboardSize: keyboardSize)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if let fabProps = fabProps {
|
|
142
|
+
FloatingButton(
|
|
143
|
+
scrollOffset: scrollOffset,
|
|
144
|
+
bottomPadding: fabProps.bottom ?? 16,
|
|
145
|
+
onClick: fabProps.onClick,
|
|
146
|
+
containerColor: .blue,
|
|
147
|
+
contentColor: .white,
|
|
148
|
+
icon: fabProps.icon,
|
|
149
|
+
iconColor: fabProps.iconColor,
|
|
150
|
+
text: fabProps.label,
|
|
151
|
+
size: fabProps.size,
|
|
152
|
+
position: fabProps.position ?? .end,
|
|
153
|
+
keyboardOffset: keyboardSize
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
struct Footer: View {
|
|
164
|
+
var footerView: AnyView
|
|
165
|
+
var keyboardSize: CGFloat
|
|
166
|
+
|
|
167
|
+
var body: some View {
|
|
168
|
+
VStack {
|
|
169
|
+
footerView
|
|
170
|
+
}
|
|
171
|
+
.padding(.bottom, keyboardSize)
|
|
172
|
+
.background(Colors.black01)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public struct Header: View {
|
|
177
|
+
var headerType: HeaderType = .default
|
|
178
|
+
var titlePosition: TitlePosition
|
|
179
|
+
var title: String
|
|
180
|
+
var headerRight: HeaderRightData?
|
|
181
|
+
var goBack: (() -> Void)?
|
|
182
|
+
var opacity: CGFloat? = nil
|
|
183
|
+
var animatedHeader: AnimatedHeader? = nil
|
|
184
|
+
var scrollState: CGFloat = 0
|
|
185
|
+
var inputSearchProps: InputSearchProps? = nil
|
|
186
|
+
|
|
187
|
+
@State private var colorFraction: CGFloat = 0
|
|
188
|
+
|
|
189
|
+
public init(
|
|
190
|
+
headerType: HeaderType = .default,
|
|
191
|
+
titlePosition: TitlePosition,
|
|
192
|
+
title: String,
|
|
193
|
+
headerRight: HeaderRightData? = nil,
|
|
194
|
+
goBack: (() -> Void)? = nil,
|
|
195
|
+
opacity: CGFloat? = nil,
|
|
196
|
+
animatedHeader: AnimatedHeader? = nil,
|
|
197
|
+
scrollState: CGFloat = 0,
|
|
198
|
+
inputSearchProps: InputSearchProps? = nil
|
|
199
|
+
) {
|
|
200
|
+
self.headerType = headerType
|
|
201
|
+
self.titlePosition = titlePosition
|
|
202
|
+
self.title = title
|
|
203
|
+
self.headerRight = headerRight
|
|
204
|
+
self.goBack = goBack
|
|
205
|
+
self.opacity = opacity
|
|
206
|
+
self.animatedHeader = animatedHeader
|
|
207
|
+
self.scrollState = scrollState
|
|
208
|
+
self.inputSearchProps = inputSearchProps
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public var body: some View {
|
|
212
|
+
HStack {
|
|
213
|
+
if let goBack = goBack {
|
|
214
|
+
Button(action: goBack) {
|
|
215
|
+
Text("Back")
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
Spacer()
|
|
220
|
+
|
|
221
|
+
if headerRight != nil {
|
|
222
|
+
HeaderRight(headerRight: headerRight)
|
|
223
|
+
.opacity(animatedHeader != nil ? opacity ?? 1 : 1)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
@@ -37,6 +37,62 @@ struct RenderRightIconSearch: View {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
public struct InputSearchProps {
|
|
41
|
+
@Binding var text: String
|
|
42
|
+
var buttonText: String = ""
|
|
43
|
+
var showButtonText: Bool = false
|
|
44
|
+
var showBorder: Bool = true
|
|
45
|
+
var placeholder: String = ""
|
|
46
|
+
var onChangeText: (String) -> Void = { _ in }
|
|
47
|
+
var onPressButtonText: () -> Void = {}
|
|
48
|
+
var error: String = ""
|
|
49
|
+
var disabled: Bool = false
|
|
50
|
+
var icon: String = ""
|
|
51
|
+
var iconColor: Color = Colors.text
|
|
52
|
+
var onRightIconPressed: () -> Void = {}
|
|
53
|
+
var onFocus: () -> Void = {}
|
|
54
|
+
var onBlur: () -> Void = {}
|
|
55
|
+
var loading: Bool = false
|
|
56
|
+
var fontWeight: Font.Weight = .regular
|
|
57
|
+
var keyboardType: UIKeyboardType = .default
|
|
58
|
+
|
|
59
|
+
public init(text: Binding<String>,
|
|
60
|
+
buttonText: String = "",
|
|
61
|
+
showButtonText: Bool = false,
|
|
62
|
+
showBorder: Bool = true,
|
|
63
|
+
placeholder: String = "",
|
|
64
|
+
onChangeText: @escaping (String) -> Void = { _ in },
|
|
65
|
+
onPressButtonText: @escaping () -> Void = {},
|
|
66
|
+
error: String = "",
|
|
67
|
+
disabled: Bool = false,
|
|
68
|
+
icon: String = "",
|
|
69
|
+
iconColor: Color = Colors.text,
|
|
70
|
+
onRightIconPressed: @escaping () -> Void = {},
|
|
71
|
+
onFocus: @escaping () -> Void = {},
|
|
72
|
+
onBlur: @escaping () -> Void = {},
|
|
73
|
+
loading: Bool = false,
|
|
74
|
+
fontWeight: Font.Weight = .regular,
|
|
75
|
+
keyboardType: UIKeyboardType = .default) {
|
|
76
|
+
self._text = text
|
|
77
|
+
self.buttonText = buttonText
|
|
78
|
+
self.showButtonText = showButtonText
|
|
79
|
+
self.showBorder = showBorder
|
|
80
|
+
self.placeholder = placeholder
|
|
81
|
+
self.onChangeText = onChangeText
|
|
82
|
+
self.onPressButtonText = onPressButtonText
|
|
83
|
+
self.error = error
|
|
84
|
+
self.disabled = disabled
|
|
85
|
+
self.icon = icon
|
|
86
|
+
self.iconColor = iconColor
|
|
87
|
+
self.onRightIconPressed = onRightIconPressed
|
|
88
|
+
self.onFocus = onFocus
|
|
89
|
+
self.onBlur = onBlur
|
|
90
|
+
self.loading = loading
|
|
91
|
+
self.fontWeight = fontWeight
|
|
92
|
+
self.keyboardType = keyboardType
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
40
96
|
// MARK: - InputSearch
|
|
41
97
|
|
|
42
98
|
public struct InputSearch: View {
|