@momo-kits/native-kits 0.151.2-test.10 → 0.151.2-test.12
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/MoMoComposeKits.podspec +54 -0
- package/compose/build.gradle.kts +31 -3
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +23 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +3 -23
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +2 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +10 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +42 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +57 -2
- package/ios/Button/Button.swift +134 -92
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Pod::Spec.new do |spec|
|
|
2
|
+
spec.name = 'MoMoComposeKits'
|
|
3
|
+
spec.version = '0.0.1'
|
|
4
|
+
spec.homepage = 'MoMoComposeKits'
|
|
5
|
+
spec.source = { :http=> ''}
|
|
6
|
+
spec.authors = 'M_SERVICE'
|
|
7
|
+
spec.license = ''
|
|
8
|
+
spec.summary = 'MoMoComposeKits'
|
|
9
|
+
spec.vendored_frameworks = 'build/cocoapods/framework/compose.framework'
|
|
10
|
+
spec.libraries = 'c++'
|
|
11
|
+
spec.ios.deployment_target = '13.0'
|
|
12
|
+
spec.dependency 'lottie-ios', '4.4.3'
|
|
13
|
+
|
|
14
|
+
if !Dir.exist?('build/cocoapods/framework/compose.framework') || Dir.empty?('build/cocoapods/framework/compose.framework')
|
|
15
|
+
raise "
|
|
16
|
+
|
|
17
|
+
Kotlin framework 'compose' doesn't exist yet, so a proper Xcode project can't be generated.
|
|
18
|
+
'pod install' should be executed after running ':generateDummyFramework' Gradle task:
|
|
19
|
+
|
|
20
|
+
./gradlew :compose:generateDummyFramework
|
|
21
|
+
|
|
22
|
+
Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
spec.xcconfig = {
|
|
26
|
+
'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
spec.pod_target_xcconfig = {
|
|
30
|
+
'KOTLIN_PROJECT_PATH' => ':compose',
|
|
31
|
+
'PRODUCT_MODULE_NAME' => 'compose',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
spec.script_phases = [
|
|
35
|
+
{
|
|
36
|
+
:name => 'Build MoMoComposeKits',
|
|
37
|
+
:execution_position => :before_compile,
|
|
38
|
+
:shell_path => '/bin/sh',
|
|
39
|
+
:script => <<-SCRIPT
|
|
40
|
+
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
|
|
41
|
+
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
set -ev
|
|
45
|
+
REPO_ROOT="$PODS_TARGET_SRCROOT"
|
|
46
|
+
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
|
47
|
+
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
|
48
|
+
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
|
49
|
+
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
|
|
50
|
+
SCRIPT
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
spec.resources = ['build/compose/cocoapods/compose-resources']
|
|
54
|
+
end
|
package/compose/build.gradle.kts
CHANGED
|
@@ -3,13 +3,23 @@ plugins {
|
|
|
3
3
|
alias(libs.plugins.jetbrains.kotlin.multiplatform)
|
|
4
4
|
alias(libs.plugins.compose)
|
|
5
5
|
alias(libs.plugins.kotlin.compose.compiler)
|
|
6
|
+
id(libs.plugins.jetbrains.kotlin.cocoapods.get().pluginId)
|
|
6
7
|
kotlin("plugin.serialization")
|
|
7
8
|
id("maven-publish")
|
|
8
9
|
}
|
|
10
|
+
fun getVersionFromPackageJson(): String {
|
|
11
|
+
val packageJsonFile = file("../package.json")
|
|
12
|
+
val packageJsonText = packageJsonFile.readText()
|
|
13
|
+
val versionRegex = "\"version\"\\s*:\\s*\"([^\"]*)\"".toRegex()
|
|
14
|
+
val matchResult = versionRegex.find(packageJsonText)
|
|
15
|
+
return matchResult?.groupValues?.get(1) ?: throw IllegalStateException("Version not found in package.json")
|
|
16
|
+
}
|
|
9
17
|
|
|
18
|
+
val libName = "MoMoComposeKits"
|
|
10
19
|
val libGroup = "vn.momo.kits"
|
|
11
20
|
val libVersion = "0.0.1"
|
|
12
21
|
|
|
22
|
+
version = getVersionFromPackageJson()
|
|
13
23
|
kotlin {
|
|
14
24
|
androidTarget {
|
|
15
25
|
publishLibraryVariants("release")
|
|
@@ -26,11 +36,30 @@ kotlin {
|
|
|
26
36
|
|
|
27
37
|
iosTargets.forEach { iosTarget ->
|
|
28
38
|
iosTarget.binaries.framework {
|
|
29
|
-
baseName =
|
|
39
|
+
baseName = libName
|
|
30
40
|
isStatic = true
|
|
31
41
|
}
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
cocoapods {
|
|
45
|
+
name = libName
|
|
46
|
+
summary = libName
|
|
47
|
+
homepage = libName
|
|
48
|
+
authors = "M_SERVICE"
|
|
49
|
+
ios.deploymentTarget = "13.0"
|
|
50
|
+
|
|
51
|
+
framework {
|
|
52
|
+
isStatic = true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pod("lottie-ios") {
|
|
56
|
+
moduleName = "Lottie"
|
|
57
|
+
version = "4.4.3"
|
|
58
|
+
extraOpts += listOf("-compiler-option", "-fmodules")
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
34
63
|
sourceSets {
|
|
35
64
|
val commonMain by getting {
|
|
36
65
|
dependencies {
|
|
@@ -47,8 +76,6 @@ kotlin {
|
|
|
47
76
|
implementation(libs.coil.multiplatform.network.ktor)
|
|
48
77
|
implementation(libs.jetbrains.serialization.json)
|
|
49
78
|
implementation(libs.kotlinx.datetime)
|
|
50
|
-
implementation(libs.compottie)
|
|
51
|
-
implementation(libs.compottie.res)
|
|
52
79
|
api(project(":NativeMaxApi"))
|
|
53
80
|
}
|
|
54
81
|
}
|
|
@@ -57,6 +84,7 @@ kotlin {
|
|
|
57
84
|
implementation(libs.ktor.client.okhttp)
|
|
58
85
|
implementation(libs.jetbrains.coroutines.android)
|
|
59
86
|
api("androidx.activity:activity-compose:1.8.2")
|
|
87
|
+
implementation(libs.airbnb.lottie)
|
|
60
88
|
}
|
|
61
89
|
}
|
|
62
90
|
if (isDebug) {
|
|
@@ -4,15 +4,20 @@ import android.annotation.SuppressLint
|
|
|
4
4
|
import android.content.res.Resources
|
|
5
5
|
import android.graphics.BlurMaskFilter
|
|
6
6
|
import android.os.Build
|
|
7
|
+
import androidx.compose.ui.graphics.Color
|
|
7
8
|
import androidx.compose.runtime.Composable
|
|
9
|
+
import androidx.compose.runtime.getValue
|
|
10
|
+
import androidx.compose.ui.Modifier
|
|
8
11
|
import androidx.compose.ui.graphics.NativePaint
|
|
9
12
|
import androidx.compose.ui.platform.LocalConfiguration
|
|
10
|
-
import androidx.compose.ui.platform.LocalView
|
|
11
13
|
import androidx.compose.ui.unit.Dp
|
|
12
14
|
import androidx.compose.ui.unit.dp
|
|
13
|
-
import
|
|
15
|
+
import com.airbnb.lottie.compose.LottieCompositionSpec
|
|
16
|
+
import com.airbnb.lottie.compose.LottieConstants
|
|
17
|
+
import com.airbnb.lottie.compose.rememberLottieComposition
|
|
14
18
|
import vn.momo.kits.const.AppNavigationBar
|
|
15
19
|
import vn.momo.kits.const.AppStatusBar
|
|
20
|
+
import vn.momo.kits.utils.readJson
|
|
16
21
|
import androidx.activity.compose.BackHandler as AndroidBackHandler
|
|
17
22
|
|
|
18
23
|
actual fun getPlatformName(): String = "Android"
|
|
@@ -50,4 +55,19 @@ actual fun getScreenHeight(): Dp {
|
|
|
50
55
|
return getScreenDimensions().height.dp + if (getAndroidBuildVersion() >= 35) 0.dp else AppStatusBar.current + AppNavigationBar.current
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
actual fun getAndroidBuildVersion(): Int = Build.VERSION.SDK_INT
|
|
58
|
+
actual fun getAndroidBuildVersion(): Int = Build.VERSION.SDK_INT
|
|
59
|
+
|
|
60
|
+
@Composable
|
|
61
|
+
actual fun LottieAnimation(
|
|
62
|
+
path: String,
|
|
63
|
+
tintColor: Color?,
|
|
64
|
+
modifier: Modifier
|
|
65
|
+
) {
|
|
66
|
+
val composition by rememberLottieComposition(LottieCompositionSpec.JsonString(readJson(path)))
|
|
67
|
+
|
|
68
|
+
com.airbnb.lottie.compose.LottieAnimation(
|
|
69
|
+
composition = composition,
|
|
70
|
+
modifier = modifier,
|
|
71
|
+
iterations = LottieConstants.IterateForever,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.padding
|
|
|
12
12
|
import androidx.compose.foundation.layout.size
|
|
13
13
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
14
14
|
import androidx.compose.runtime.Composable
|
|
15
|
-
import androidx.compose.runtime.getValue
|
|
16
15
|
import androidx.compose.runtime.remember
|
|
17
16
|
import androidx.compose.ui.Alignment
|
|
18
17
|
import androidx.compose.ui.Modifier
|
|
@@ -23,18 +22,13 @@ import androidx.compose.ui.text.TextStyle
|
|
|
23
22
|
import androidx.compose.ui.text.style.TextOverflow
|
|
24
23
|
import androidx.compose.ui.unit.Dp
|
|
25
24
|
import androidx.compose.ui.unit.dp
|
|
26
|
-
import io.github.alexzhirkevich.compottie.Compottie
|
|
27
|
-
import io.github.alexzhirkevich.compottie.LottieCompositionSpec
|
|
28
|
-
import io.github.alexzhirkevich.compottie.animateLottieCompositionAsState
|
|
29
|
-
import io.github.alexzhirkevich.compottie.rememberLottieComposition
|
|
30
|
-
import io.github.alexzhirkevich.compottie.rememberLottiePainter
|
|
31
25
|
import vn.momo.kits.const.AppTheme
|
|
32
26
|
import vn.momo.kits.const.Colors
|
|
33
27
|
import vn.momo.kits.const.Radius
|
|
34
28
|
import vn.momo.kits.const.Spacing
|
|
35
29
|
import vn.momo.kits.const.Typography
|
|
36
30
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
37
|
-
import vn.momo.
|
|
31
|
+
import vn.momo.kits.platform.LottieAnimation
|
|
38
32
|
|
|
39
33
|
enum class ButtonType {
|
|
40
34
|
PRIMARY,
|
|
@@ -152,23 +146,9 @@ fun RenderLeading(
|
|
|
152
146
|
) {
|
|
153
147
|
if (loading) {
|
|
154
148
|
Box(Modifier.padding(end = marginRight)) {
|
|
155
|
-
|
|
156
|
-
LottieCompositionSpec.JsonString(
|
|
157
|
-
Res.readBytes("files/lottie_circle_loader.json").decodeToString()
|
|
158
|
-
)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
val progress by animateLottieCompositionAsState(
|
|
162
|
-
composition = composition,
|
|
163
|
-
iterations = Compottie.IterateForever
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
Image(
|
|
167
|
-
source = rememberLottiePainter(
|
|
168
|
-
composition = composition,
|
|
169
|
-
progress = { progress }
|
|
170
|
-
),
|
|
149
|
+
LottieAnimation(
|
|
171
150
|
modifier = Modifier.size(iconSize),
|
|
151
|
+
path = "files/lottie_circle_loader"
|
|
172
152
|
)
|
|
173
153
|
}
|
|
174
154
|
} else {
|
package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt
CHANGED
|
@@ -2,6 +2,7 @@ package vn.momo.kits.components.datetimepicker
|
|
|
2
2
|
|
|
3
3
|
import androidx.compose.runtime.Composable
|
|
4
4
|
import androidx.compose.runtime.remember
|
|
5
|
+
import kotlinx.datetime.Clock
|
|
5
6
|
import kotlinx.datetime.DatePeriod
|
|
6
7
|
import kotlinx.datetime.LocalDateTime
|
|
7
8
|
import kotlinx.datetime.TimeZone
|
|
@@ -9,8 +10,6 @@ import kotlinx.datetime.minus
|
|
|
9
10
|
import kotlinx.datetime.number
|
|
10
11
|
import kotlinx.datetime.plus
|
|
11
12
|
import kotlinx.datetime.toLocalDateTime
|
|
12
|
-
import kotlin.time.Clock.System.now
|
|
13
|
-
import kotlin.time.ExperimentalTime
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Format a LocalDateTime object into a string
|
|
@@ -150,9 +149,8 @@ val timeMode = listOf("AM", "PM")
|
|
|
150
149
|
/**
|
|
151
150
|
* Get today's date
|
|
152
151
|
*/
|
|
153
|
-
@OptIn(ExperimentalTime::class)
|
|
154
152
|
fun getCurrentDateTime(): LocalDateTime {
|
|
155
|
-
return now().toLocalDateTime(TimeZone.currentSystemDefault())
|
|
153
|
+
return Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
|
|
156
154
|
}
|
|
157
155
|
|
|
158
156
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package vn.momo.kits.platform
|
|
2
2
|
|
|
3
3
|
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.ui.Modifier
|
|
5
|
+
import androidx.compose.ui.graphics.Color
|
|
4
6
|
import androidx.compose.ui.graphics.NativePaint
|
|
5
7
|
import androidx.compose.ui.unit.Dp
|
|
6
8
|
|
|
@@ -25,4 +27,11 @@ expect fun BackHandler(enabled: Boolean, onBack: () -> Unit)
|
|
|
25
27
|
@Composable
|
|
26
28
|
expect fun getScreenHeight(): Dp
|
|
27
29
|
|
|
28
|
-
expect fun getAndroidBuildVersion(): Int
|
|
30
|
+
expect fun getAndroidBuildVersion(): Int
|
|
31
|
+
|
|
32
|
+
@Composable
|
|
33
|
+
expect fun LottieAnimation(
|
|
34
|
+
path: String,
|
|
35
|
+
tintColor: Color? = null,
|
|
36
|
+
modifier: Modifier = Modifier
|
|
37
|
+
)
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
package vn.momo.kits.utils
|
|
2
2
|
|
|
3
3
|
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
5
|
+
import androidx.compose.runtime.MutableState
|
|
6
|
+
import androidx.compose.runtime.getValue
|
|
7
|
+
import androidx.compose.runtime.mutableStateOf
|
|
8
|
+
import androidx.compose.runtime.remember
|
|
4
9
|
import org.jetbrains.compose.resources.DrawableResource
|
|
5
10
|
import org.jetbrains.compose.resources.InternalResourceApi
|
|
6
11
|
import org.jetbrains.compose.resources.ResourceItem
|
|
12
|
+
import org.jetbrains.compose.resources.readResourceBytes
|
|
7
13
|
|
|
8
14
|
@OptIn(InternalResourceApi::class)
|
|
9
15
|
@Composable
|
|
@@ -12,4 +18,40 @@ fun getResource(name: String): DrawableResource {
|
|
|
12
18
|
"drawable:$name",
|
|
13
19
|
setOf(ResourceItem(setOf(), "drawable/$name", -1, -1))
|
|
14
20
|
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@OptIn(InternalResourceApi::class)
|
|
24
|
+
@Composable
|
|
25
|
+
fun readJson(name: String): String {
|
|
26
|
+
val path = name.plus(".json")
|
|
27
|
+
|
|
28
|
+
val jsonContent by rememberState(path, { "" }) {
|
|
29
|
+
val cached = resourceCache.getOrPut(path) {
|
|
30
|
+
ResourceCache.JSON(
|
|
31
|
+
readResourceBytes(path).decodeToString()
|
|
32
|
+
)
|
|
33
|
+
} as ResourceCache.JSON
|
|
34
|
+
cached.json
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return jsonContent
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private val resourceCache = mutableMapOf<String, ResourceCache>()
|
|
41
|
+
|
|
42
|
+
private sealed interface ResourceCache {
|
|
43
|
+
class JSON(val json: String) : ResourceCache
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Composable
|
|
47
|
+
fun <T> rememberState(
|
|
48
|
+
key: Any,
|
|
49
|
+
getDefault: () -> T,
|
|
50
|
+
block: suspend () -> T
|
|
51
|
+
): MutableState<T> {
|
|
52
|
+
val state = remember(key) { mutableStateOf(getDefault()) }
|
|
53
|
+
LaunchedEffect(key) {
|
|
54
|
+
state.value = block()
|
|
55
|
+
}
|
|
56
|
+
return state
|
|
15
57
|
}
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
package vn.momo.kits.platform
|
|
2
2
|
|
|
3
|
-
import androidx.compose.foundation.
|
|
3
|
+
import androidx.compose.foundation.layout.Box
|
|
4
4
|
import androidx.compose.foundation.layout.WindowInsets
|
|
5
5
|
import androidx.compose.foundation.layout.asPaddingValues
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
6
7
|
import androidx.compose.foundation.layout.systemBars
|
|
7
8
|
import androidx.compose.runtime.Composable
|
|
9
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
10
|
+
import androidx.compose.runtime.getValue
|
|
11
|
+
import androidx.compose.runtime.mutableStateOf
|
|
12
|
+
import androidx.compose.runtime.remember
|
|
13
|
+
import androidx.compose.runtime.setValue
|
|
14
|
+
import androidx.compose.ui.Modifier
|
|
15
|
+
import androidx.compose.ui.graphics.Color
|
|
8
16
|
import androidx.compose.ui.graphics.NativePaint
|
|
17
|
+
import androidx.compose.ui.interop.UIKitView
|
|
9
18
|
import androidx.compose.ui.unit.Dp
|
|
10
19
|
import androidx.compose.ui.unit.dp
|
|
20
|
+
import cocoapods.lottie_ios.CompatibleAnimation
|
|
21
|
+
import cocoapods.lottie_ios.CompatibleAnimationView
|
|
11
22
|
import kotlinx.cinterop.ExperimentalForeignApi
|
|
12
23
|
import kotlinx.cinterop.get
|
|
13
24
|
import kotlinx.cinterop.memScoped
|
|
14
25
|
import org.jetbrains.skia.FilterBlurMode
|
|
15
26
|
import org.jetbrains.skia.MaskFilter
|
|
27
|
+
import platform.Foundation.NSBundle
|
|
28
|
+
import platform.UIKit.UIColor
|
|
16
29
|
import platform.UIKit.UIScreen
|
|
17
30
|
|
|
18
31
|
|
|
@@ -46,4 +59,46 @@ actual fun getScreenHeight(): Dp {
|
|
|
46
59
|
return getScreenDimensions().height.dp
|
|
47
60
|
}
|
|
48
61
|
|
|
49
|
-
actual fun getAndroidBuildVersion(): Int = 999
|
|
62
|
+
actual fun getAndroidBuildVersion(): Int = 999
|
|
63
|
+
|
|
64
|
+
@OptIn(ExperimentalForeignApi::class)
|
|
65
|
+
@Composable
|
|
66
|
+
actual fun LottieAnimation(
|
|
67
|
+
path: String,
|
|
68
|
+
tintColor: Color?,
|
|
69
|
+
modifier: Modifier
|
|
70
|
+
) {
|
|
71
|
+
var animation by remember { mutableStateOf<CompatibleAnimation?>(null) }
|
|
72
|
+
|
|
73
|
+
LaunchedEffect(Unit) {
|
|
74
|
+
animation = CompatibleAnimation(name = "compose-resources/composeResources/vn.momo.compose.resources/".plus(path), subdirectory = null, NSBundle.mainBundle)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
when (val value = animation) {
|
|
78
|
+
null -> {
|
|
79
|
+
Box(modifier)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
else -> {
|
|
83
|
+
val factory = remember {
|
|
84
|
+
{
|
|
85
|
+
CompatibleAnimationView(value).also {
|
|
86
|
+
it.translatesAutoresizingMaskIntoConstraints = true
|
|
87
|
+
it.setBackgroundColor(UIColor.whiteColor)
|
|
88
|
+
it.setLoopAnimationCount(-1.0)
|
|
89
|
+
it.setAnimationSpeed(1.0)
|
|
90
|
+
|
|
91
|
+
it.play()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Box(modifier) {
|
|
97
|
+
UIKitView(
|
|
98
|
+
modifier = Modifier.fillMaxSize(),
|
|
99
|
+
factory = factory,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
package/ios/Button/Button.swift
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import Foundation
|
|
3
2
|
import SwiftUI
|
|
4
3
|
import Lottie
|
|
@@ -23,12 +22,85 @@ public enum ButtonSize {
|
|
|
23
22
|
case small
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
public struct ButtonStyleData {
|
|
26
|
+
let background: Color
|
|
27
|
+
let content: Color
|
|
28
|
+
let border: Color?
|
|
29
|
+
let borderWidth: CGFloat
|
|
30
|
+
|
|
31
|
+
public init(
|
|
32
|
+
background: Color,
|
|
33
|
+
content: Color,
|
|
34
|
+
border: Color? = nil,
|
|
35
|
+
borderWidth: CGFloat = 1
|
|
36
|
+
) {
|
|
37
|
+
self.background = background
|
|
38
|
+
self.content = content
|
|
39
|
+
self.border = border
|
|
40
|
+
self.borderWidth = borderWidth
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// MARK: - ButtonType Extension (Style mapping)
|
|
45
|
+
|
|
46
|
+
public extension ButtonType {
|
|
47
|
+
var style: ButtonStyleData {
|
|
48
|
+
switch self {
|
|
49
|
+
case .primary:
|
|
50
|
+
return ButtonStyleData(
|
|
51
|
+
background: Colors.primary,
|
|
52
|
+
content: Colors.black01
|
|
53
|
+
)
|
|
54
|
+
case .danger:
|
|
55
|
+
return ButtonStyleData(
|
|
56
|
+
background: Colors.error,
|
|
57
|
+
content: Colors.black01
|
|
58
|
+
)
|
|
59
|
+
case .tonal:
|
|
60
|
+
return ButtonStyleData(
|
|
61
|
+
background: Colors.primaryLight,
|
|
62
|
+
content: Colors.primary
|
|
63
|
+
)
|
|
64
|
+
case .text:
|
|
65
|
+
return ButtonStyleData(
|
|
66
|
+
background: .clear,
|
|
67
|
+
content: Colors.primary
|
|
68
|
+
)
|
|
69
|
+
case .secondary:
|
|
70
|
+
return ButtonStyleData(
|
|
71
|
+
background: Colors.black01,
|
|
72
|
+
content: Colors.black17,
|
|
73
|
+
border: Colors.black04
|
|
74
|
+
)
|
|
75
|
+
case .outline:
|
|
76
|
+
return ButtonStyleData(
|
|
77
|
+
background: Colors.card,
|
|
78
|
+
content: Colors.primary,
|
|
79
|
+
border: Colors.primary
|
|
80
|
+
)
|
|
81
|
+
case .disabled:
|
|
82
|
+
return ButtonStyleData(
|
|
83
|
+
background: Colors.black05,
|
|
84
|
+
content: Colors.black06
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// MARK: - Button View
|
|
27
91
|
|
|
28
92
|
public struct Button: View {
|
|
29
93
|
// MARK: Lifecycle
|
|
30
94
|
|
|
31
|
-
public init(
|
|
95
|
+
public init(
|
|
96
|
+
title: String = "",
|
|
97
|
+
action: @escaping () -> Void,
|
|
98
|
+
type: ButtonType = .primary,
|
|
99
|
+
size: ButtonSize = .large,
|
|
100
|
+
iconLeft: AnyView? = nil,
|
|
101
|
+
iconRight: AnyView? = nil,
|
|
102
|
+
loading: Bool = false
|
|
103
|
+
) {
|
|
32
104
|
self.title = title
|
|
33
105
|
self.action = action
|
|
34
106
|
self.type = type
|
|
@@ -38,117 +110,87 @@ public struct Button: View {
|
|
|
38
110
|
self.loading = loading
|
|
39
111
|
}
|
|
40
112
|
|
|
41
|
-
// MARK: Public
|
|
42
|
-
|
|
43
|
-
public var body: some View {
|
|
44
|
-
SwiftUI.Button(action: action) {
|
|
45
|
-
HStack {
|
|
46
|
-
if loading {
|
|
47
|
-
LottieView(name: "lottie_circle_loader", loopMode: .loop)
|
|
48
|
-
.frame(width: iconSize, height: iconSize)
|
|
49
|
-
.colorMultiply(.white)
|
|
50
|
-
} else if let iconLeft = iconLeft {
|
|
51
|
-
iconLeft.frame(width: iconSize, height: iconSize)
|
|
52
|
-
}
|
|
53
|
-
renderTitle(title: title, type: type, size: size).lineLimit(1).truncationMode(.tail)
|
|
54
|
-
if let iconRight = iconRight {
|
|
55
|
-
iconRight.frame(width: iconSize, height: iconSize)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
.buttonSize(size)
|
|
59
|
-
.buttonType(type)
|
|
60
|
-
.opacity(loading ? 0.75 : 1.0)
|
|
61
|
-
}
|
|
62
|
-
.disabled(type == .disabled || loading)
|
|
63
|
-
.clipShape(RoundedRectangle(cornerRadius: Radius.S))
|
|
64
|
-
}
|
|
65
|
-
|
|
66
113
|
// MARK: Internal
|
|
67
114
|
|
|
68
115
|
var title: String
|
|
69
|
-
var action: () -> Void
|
|
70
|
-
var type: ButtonType
|
|
71
|
-
var size: ButtonSize
|
|
116
|
+
var action: () -> Void
|
|
117
|
+
var type: ButtonType
|
|
118
|
+
var size: ButtonSize
|
|
72
119
|
var iconLeft: AnyView?
|
|
73
120
|
var iconRight: AnyView?
|
|
74
121
|
var loading: Bool
|
|
75
|
-
|
|
122
|
+
|
|
123
|
+
// MARK: Private Helpers
|
|
124
|
+
|
|
76
125
|
private var iconSize: CGFloat {
|
|
77
126
|
switch size {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
case .small: return 16
|
|
127
|
+
case .large: return 24
|
|
128
|
+
case .medium, .small: return 16
|
|
81
129
|
}
|
|
82
130
|
}
|
|
83
131
|
|
|
84
|
-
func
|
|
132
|
+
private func height(for size: ButtonSize) -> CGFloat {
|
|
85
133
|
switch size {
|
|
86
|
-
case .large:
|
|
87
|
-
|
|
88
|
-
case .
|
|
89
|
-
return Text(title).font(.system(size: 14, weight: Font.Weight.bold))
|
|
90
|
-
case .small:
|
|
91
|
-
return Text(title).font(.system(size: 12, weight: Font.Weight.bold))
|
|
134
|
+
case .large: return 48
|
|
135
|
+
case .medium: return 36
|
|
136
|
+
case .small: return 28
|
|
92
137
|
}
|
|
93
138
|
}
|
|
94
|
-
}
|
|
95
139
|
|
|
96
|
-
private
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
case .
|
|
100
|
-
|
|
101
|
-
.background(Colors.primary)
|
|
102
|
-
.foregroundColor(Colors.black01)
|
|
103
|
-
)
|
|
104
|
-
case .danger:
|
|
105
|
-
return AnyView(self
|
|
106
|
-
.background(Colors.error)
|
|
107
|
-
.foregroundColor(Colors.black01)
|
|
108
|
-
)
|
|
109
|
-
case .tonal:
|
|
110
|
-
return AnyView(self
|
|
111
|
-
.background(Colors.primaryLight)
|
|
112
|
-
.foregroundColor(Colors.primary)
|
|
113
|
-
)
|
|
114
|
-
case .text:
|
|
115
|
-
return AnyView(self
|
|
116
|
-
.foregroundColor(Colors.primary)
|
|
117
|
-
)
|
|
118
|
-
case .secondary:
|
|
119
|
-
return AnyView(self
|
|
120
|
-
.background(Colors.black01)
|
|
121
|
-
.foregroundColor(Colors.black17)
|
|
122
|
-
.overlay(RoundedRectangle(cornerRadius: Radius.S).stroke(Colors.black04, lineWidth: 1))
|
|
123
|
-
)
|
|
124
|
-
case .outline:
|
|
125
|
-
return AnyView(self
|
|
126
|
-
.background(Colors.card)
|
|
127
|
-
.foregroundColor(Colors.primary)
|
|
128
|
-
.overlay(RoundedRectangle(cornerRadius: Radius.S).stroke(Colors.primary, lineWidth: 1))
|
|
129
|
-
)
|
|
130
|
-
case .disabled:
|
|
131
|
-
return AnyView(self
|
|
132
|
-
.background(Colors.black05)
|
|
133
|
-
.foregroundColor(Colors.black06)
|
|
134
|
-
)
|
|
140
|
+
private func padding(for size: ButtonSize) -> CGFloat {
|
|
141
|
+
switch size {
|
|
142
|
+
case .large: return Spacing.L
|
|
143
|
+
case .medium: return Spacing.M
|
|
144
|
+
case .small: return Spacing.S
|
|
135
145
|
}
|
|
136
146
|
}
|
|
137
147
|
|
|
138
|
-
func
|
|
148
|
+
private func renderTitle(title: String, size: ButtonSize) -> Text {
|
|
139
149
|
switch size {
|
|
140
150
|
case .large:
|
|
141
|
-
return
|
|
142
|
-
.frame(maxWidth: .infinity, minHeight: 48, maxHeight: 48)
|
|
143
|
-
.padding([.leading, .trailing], Spacing.L)
|
|
151
|
+
return Text(title).font(.system(size: 16, weight: .bold))
|
|
144
152
|
case .medium:
|
|
145
|
-
return
|
|
146
|
-
.frame(minWidth: 80, maxWidth: .infinity, minHeight: 36, maxHeight: 36)
|
|
147
|
-
.padding([.leading, .trailing], Spacing.M)
|
|
153
|
+
return Text(title).font(.system(size: 14, weight: .bold))
|
|
148
154
|
case .small:
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
return Text(title).font(.system(size: 12, weight: .bold))
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// MARK: View
|
|
160
|
+
|
|
161
|
+
public var body: some View {
|
|
162
|
+
let style = type.style
|
|
163
|
+
|
|
164
|
+
SwiftUI.Button(action: action) {
|
|
165
|
+
HStack(spacing: 8) {
|
|
166
|
+
if loading {
|
|
167
|
+
LottieView(name: "lottie_circle_loader", loopMode: .loop)
|
|
168
|
+
.frame(width: iconSize, height: iconSize)
|
|
169
|
+
.colorMultiply(style.content)
|
|
170
|
+
} else if let iconLeft = iconLeft {
|
|
171
|
+
iconLeft.frame(width: iconSize, height: iconSize)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
renderTitle(title: title, size: size)
|
|
175
|
+
.lineLimit(1)
|
|
176
|
+
.truncationMode(.tail)
|
|
177
|
+
|
|
178
|
+
if let iconRight = iconRight {
|
|
179
|
+
iconRight.frame(width: iconSize, height: iconSize)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
.frame(maxWidth: .infinity)
|
|
183
|
+
.padding(.horizontal, padding(for: size))
|
|
184
|
+
.frame(height: height(for: size))
|
|
185
|
+
.background(style.background)
|
|
186
|
+
.foregroundColor(style.content)
|
|
187
|
+
.overlay(
|
|
188
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
189
|
+
.stroke(style.border ?? .clear, lineWidth: style.border != nil ? style.borderWidth : 0)
|
|
190
|
+
)
|
|
191
|
+
.clipShape(RoundedRectangle(cornerRadius: Radius.S))
|
|
192
|
+
.opacity(loading ? 0.75 : 1.0)
|
|
152
193
|
}
|
|
194
|
+
.disabled(type == .disabled || loading)
|
|
153
195
|
}
|
|
154
196
|
}
|