Skip to content

Commit 8817d69

Browse files
Merge pull request #399 from wisemen-digital/core-v2
Core v2
2 parents 3e3b741 + 7360592 commit 8817d69

29 files changed

+494
-84
lines changed

build.gradle.kts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,8 @@
1-
// Top-level build file where you can add configuration options common to all sub-projects/modules.
2-
buildscript {
3-
dependencies {
4-
classpath(libs.gradle)
5-
classpath(kotlin("gradle-plugin", version = libs.versions.kotlin.get()))
6-
7-
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:${libs.versions.navigation.get()}")
8-
9-
// NOTE: Do not place your application dependencies here; they belong
10-
// in the individual module build.gradle files
11-
}
12-
}
13-
141
plugins {
15-
alias(libs.plugins.android.library) apply false
162
alias(libs.plugins.kotlin.android) apply false
173
alias(libs.plugins.android.application) apply false
184
alias(libs.plugins.ksp) apply false
5+
alias(libs.plugins.kotlin.compose) apply false
196
}
207

218
project.tasks.register("clean", Delete::class) {

core-compose/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

core-compose/build.gradle.kts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
plugins {
2+
id("com.android.library")
3+
id("org.jetbrains.kotlin.android")
4+
alias(libs.plugins.kotlin.compose)
5+
}
6+
7+
android {
8+
namespace = "be.appwise.compose.core"
9+
compileSdk = 35
10+
11+
defaultConfig {
12+
minSdk = 26
13+
14+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15+
consumerProguardFiles("consumer-rules.pro")
16+
}
17+
18+
buildTypes {
19+
release {
20+
isMinifyEnabled = false
21+
proguardFiles(
22+
getDefaultProguardFile("proguard-android-optimize.txt"),
23+
"proguard-rules.pro"
24+
)
25+
}
26+
}
27+
compileOptions {
28+
sourceCompatibility = JavaVersion.VERSION_17
29+
targetCompatibility = JavaVersion.VERSION_17
30+
}
31+
kotlinOptions {
32+
jvmTarget = "17"
33+
}
34+
buildFeatures {
35+
compose = true
36+
}
37+
}
38+
39+
dependencies {
40+
implementation(project(":core"))
41+
42+
implementation(libs.core.ktx)
43+
implementation(libs.appcompat)
44+
implementation(libs.material)
45+
46+
implementation(libs.material3)
47+
48+
implementation(platform(libs.compose.bom))
49+
implementation(libs.ui)
50+
implementation(libs.ui.graphics)
51+
implementation(libs.ui.tooling)
52+
implementation(libs.ui.tooling.preview)
53+
debugImplementation(libs.ui.test.manifest)
54+
implementation(libs.ui.test.junit4)
55+
}

core-compose/consumer-rules.pro

Whitespace-only changes.

core-compose/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
</manifest>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package be.appwise.compose.core.ui.base
2+
3+
import kotlinx.coroutines.channels.Channel
4+
import kotlinx.coroutines.flow.MutableStateFlow
5+
import kotlinx.coroutines.flow.asStateFlow
6+
import kotlinx.coroutines.flow.receiveAsFlow
7+
8+
open class BaseComposeStateViewModel<UIState, NavigationType>(initialState: UIState): BaseComposeViewModel() {
9+
private val _uiState = MutableStateFlow(initialState)
10+
val uiState = _uiState.asStateFlow()
11+
12+
private val _navigation = Channel<NavigationType>()
13+
val navigation = _navigation.receiveAsFlow()
14+
15+
fun updateUIState(state: UIState) {
16+
_uiState.tryEmit(state)
17+
}
18+
19+
fun navigate(navigation: NavigationType) {
20+
_navigation.trySend(navigation)
21+
}
22+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package be.appwise.compose.core.ui.base
2+
3+
import android.util.Log
4+
import androidx.compose.animation.AnimatedVisibility
5+
import androidx.compose.material3.SnackbarHost
6+
import androidx.compose.material3.SnackbarHostState
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.LaunchedEffect
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.runtime.mutableStateOf
11+
import androidx.compose.runtime.remember
12+
import androidx.compose.runtime.setValue
13+
import androidx.compose.ui.graphics.Color
14+
import androidx.compose.ui.graphics.vector.ImageVector
15+
import androidx.compose.ui.platform.LocalContext
16+
import be.appwise.compose.core.ui.base.snackbar.BaseSnackbar
17+
import be.appwise.compose.core.ui.base.snackbar.Empty
18+
import be.appwise.compose.core.ui.base.snackbar.Error
19+
import be.appwise.compose.core.ui.base.snackbar.SnackbarMessage
20+
import be.appwise.core.ui.base.BaseViewModel
21+
import kotlinx.coroutines.CoroutineScope
22+
import kotlinx.coroutines.launch
23+
24+
open class BaseComposeViewModel: BaseViewModel() {
25+
// Snackbar Handling
26+
private var snackbarMessageState by mutableStateOf<SnackbarMessage>(Empty)
27+
28+
override fun vmScopeWithCustomExceptionHandler(onError: (error: Throwable) -> Unit): CoroutineScope {
29+
return super.vmScopeWithCustomExceptionHandler {
30+
Log.w(this::class.simpleName, "Exception caught: ", it)
31+
snackbarMessageState = Error(it.message)
32+
}
33+
}
34+
35+
@Composable
36+
fun createSnackBarHost(
37+
snackBar: (@Composable (message: String, icon: ImageVector?, containerColor: Color) -> Unit)? = null
38+
) {
39+
val snackBarHostState = remember {
40+
SnackbarHostState()
41+
}
42+
43+
val context = LocalContext.current
44+
45+
LaunchedEffect(key1 = snackbarMessageState) {
46+
if (snackbarMessageState !is Empty) {
47+
snackBarHostState.showSnackbar(snackbarMessageState.getMessage(context) ?: "Unknown error")
48+
setCoroutineException(null)
49+
snackbarMessageState = Empty
50+
}
51+
}
52+
53+
SnackbarHost(hostState = snackBarHostState) { snackBarData ->
54+
AnimatedVisibility(visible = snackbarMessageState.isNotEmpty()) {
55+
val icon = snackbarMessageState.icon
56+
val message = snackBarData.visuals.message
57+
val containerColor = snackbarMessageState.containerColor ?: Color.Transparent
58+
59+
snackBar?.invoke(message, icon, containerColor)
60+
?: BaseSnackbar(
61+
icon = icon,
62+
message = message,
63+
containerColor = containerColor
64+
)
65+
}
66+
}
67+
}
68+
69+
70+
fun showSnackbar(snackbarMessage: SnackbarMessage) = vmScope.launch {
71+
snackbarMessageState = snackbarMessage
72+
}
73+
74+
fun showSnackbar(message: String?) = vmScope.launch {
75+
snackbarMessageState = Error(message = message ?: return@launch)
76+
}
77+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package be.appwise.compose.core.ui.base.snackbar
2+
3+
import androidx.compose.foundation.layout.Row
4+
import androidx.compose.foundation.layout.padding
5+
import androidx.compose.foundation.shape.RoundedCornerShape
6+
import androidx.compose.material3.Icon
7+
import androidx.compose.material3.Snackbar
8+
import androidx.compose.material3.Text
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.ui.Alignment
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.graphics.Color
13+
import androidx.compose.ui.graphics.vector.ImageVector
14+
import androidx.compose.ui.unit.dp
15+
import androidx.compose.ui.zIndex
16+
17+
@Composable
18+
fun BaseSnackbar(
19+
message: String,
20+
containerColor: Color,
21+
icon: ImageVector? = null,
22+
) {
23+
Snackbar(
24+
containerColor = containerColor,
25+
modifier = Modifier.padding(8.dp).zIndex(100f),
26+
shape = RoundedCornerShape(0.dp)
27+
) {
28+
Row(
29+
verticalAlignment = Alignment.CenterVertically
30+
) {
31+
if(icon != null) {
32+
Icon(
33+
icon,
34+
tint = Color.White,
35+
contentDescription = null
36+
)
37+
}
38+
Text(
39+
message,
40+
color = Color.White,
41+
modifier = Modifier
42+
.padding(start = 8.dp)
43+
.weight(1f)
44+
)
45+
}
46+
}
47+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package be.appwise.compose.core.ui.base.snackbar
2+
3+
import android.content.Context
4+
import androidx.compose.material.icons.Icons
5+
import androidx.compose.material.icons.filled.Check
6+
import androidx.compose.material.icons.filled.Warning
7+
import androidx.compose.ui.graphics.Color
8+
import androidx.compose.ui.graphics.vector.ImageVector
9+
import java.io.Serializable
10+
11+
12+
open class SnackbarMessage(
13+
open val message: String? = null,
14+
open val icon: ImageVector? = null,
15+
open val containerColor: Color? = Color.Transparent,
16+
open vararg val formatParams: Any?
17+
) : Serializable {
18+
19+
fun getMessage(context: Context) = when {
20+
message != null -> message
21+
else -> null
22+
}
23+
24+
fun isNotEmpty() = message != null
25+
}
26+
27+
data object Empty : SnackbarMessage()
28+
29+
class Success(
30+
override val message: String? = null,
31+
override val icon: ImageVector = Icons.Default.Check,
32+
override val containerColor: Color = Color.Green,
33+
override vararg val formatParams: Any?
34+
) : SnackbarMessage(
35+
message = message,
36+
icon = icon,
37+
containerColor = containerColor,
38+
*formatParams
39+
)
40+
41+
class Error(
42+
override val message: String? = null,
43+
override val icon: ImageVector = Icons.Default.Warning,
44+
override val containerColor: Color = Color.Red,
45+
override vararg val formatParams: Any?
46+
) : SnackbarMessage(
47+
message = message,
48+
icon = icon,
49+
containerColor = containerColor,
50+
*formatParams
51+
)

0 commit comments

Comments
 (0)