diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 317de4523..72ff4f699 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -71,6 +71,7 @@ android {
dependencies {
implementation(projects.core.android)
+ implementation(projects.core.androidx.compose)
implementation(projects.core.androidx.fragment)
implementation(projects.core.androidx.lifecycle)
implementation(projects.core.androidx.navigationCompose)
@@ -78,6 +79,8 @@ dependencies {
implementation(projects.core.androidx.transition)
implementation(projects.core.annotation)
implementation(projects.core.component.tag)
+ implementation(projects.core.component.usermessage)
+ implementation(projects.core.component.usermessageHandle)
implementation(projects.core.dataDi)
implementation(projects.core.data)
implementation(projects.core.designsystem)
@@ -90,7 +93,6 @@ dependencies {
implementation(projects.core.statekit)
kapt(projects.statekit.compiler)
implementation(projects.core.translation)
- implementation(projects.core.uiCompose)
implementation(projects.core.uitext)
implementation(libs.kotlinx.coroutines.android)
diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml
index d16fd18e5..12d59b2a2 100644
--- a/app/src/debug/AndroidManifest.xml
+++ b/app/src/debug/AndroidManifest.xml
@@ -29,10 +29,10 @@
android:exported="false"
tools:node="merge">
diff --git a/app/src/debug/java/com/nlab/reminder/internal/common/android/startup/init/FlipperInitializer.kt b/app/src/debug/kotlin/com/nlab/reminder/apps/startup/init/FlipperInitializer.kt
similarity index 93%
rename from app/src/debug/java/com/nlab/reminder/internal/common/android/startup/init/FlipperInitializer.kt
rename to app/src/debug/kotlin/com/nlab/reminder/apps/startup/init/FlipperInitializer.kt
index e76abc41e..c037402a6 100644
--- a/app/src/debug/java/com/nlab/reminder/internal/common/android/startup/init/FlipperInitializer.kt
+++ b/app/src/debug/kotlin/com/nlab/reminder/apps/startup/init/FlipperInitializer.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.nlab.reminder.internal.common.android.startup.init
+package com.nlab.reminder.apps.startup.init
import android.content.Context
import androidx.startup.Initializer
@@ -27,7 +27,7 @@ import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin
import com.facebook.flipper.plugins.leakcanary2.FlipperLeakEventListener
import com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin
import com.facebook.soloader.SoLoader
-import com.nlab.reminder.internal.common.android.startup.EmptyDependencies
+import com.nlab.reminder.apps.startup.EmptyDependencies
import leakcanary.LeakCanary
/**
diff --git a/app/src/debug/java/com/nlab/reminder/internal/common/android/startup/init/TimberInitializer.kt b/app/src/debug/kotlin/com/nlab/reminder/apps/startup/init/TimberInitializer.kt
similarity index 87%
rename from app/src/debug/java/com/nlab/reminder/internal/common/android/startup/init/TimberInitializer.kt
rename to app/src/debug/kotlin/com/nlab/reminder/apps/startup/init/TimberInitializer.kt
index 26259a9e2..9f1604ffa 100644
--- a/app/src/debug/java/com/nlab/reminder/internal/common/android/startup/init/TimberInitializer.kt
+++ b/app/src/debug/kotlin/com/nlab/reminder/apps/startup/init/TimberInitializer.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.nlab.reminder.internal.common.android.startup.init
+package com.nlab.reminder.apps.startup.init
import android.content.Context
import androidx.startup.Initializer
-import com.nlab.reminder.internal.common.android.startup.EmptyDependencies
+import com.nlab.reminder.apps.startup.EmptyDependencies
import timber.log.Timber
/**
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index af9212e9a..81df6f6b1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -20,7 +20,7 @@
diff --git a/app/src/main/java/com/nlab/reminder/ReminderApplication.kt b/app/src/main/java/com/nlab/reminder/PlaneatApplication.kt
similarity index 95%
rename from app/src/main/java/com/nlab/reminder/ReminderApplication.kt
rename to app/src/main/java/com/nlab/reminder/PlaneatApplication.kt
index e7a99e7ae..35dcc1fbb 100644
--- a/app/src/main/java/com/nlab/reminder/ReminderApplication.kt
+++ b/app/src/main/java/com/nlab/reminder/PlaneatApplication.kt
@@ -25,4 +25,4 @@ import dagger.hilt.android.HiltAndroidApp
*/
@ExcludeFromGeneratedTestReport
@HiltAndroidApp
-class ReminderApplication : Application()
\ No newline at end of file
+class PlaneatApplication : Application()
\ No newline at end of file
diff --git a/app/src/main/java/com/nlab/reminder/internal/common/android/startup/Initializers.kt b/app/src/main/java/com/nlab/reminder/apps/startup/Initializers.kt
similarity index 93%
rename from app/src/main/java/com/nlab/reminder/internal/common/android/startup/Initializers.kt
rename to app/src/main/java/com/nlab/reminder/apps/startup/Initializers.kt
index 3ccfba30e..6309e2be8 100644
--- a/app/src/main/java/com/nlab/reminder/internal/common/android/startup/Initializers.kt
+++ b/app/src/main/java/com/nlab/reminder/apps/startup/Initializers.kt
@@ -16,7 +16,7 @@
@file:Suppress("FunctionName")
-package com.nlab.reminder.internal.common.android.startup
+package com.nlab.reminder.apps.startup
import androidx.startup.Initializer
diff --git a/app/src/main/java/com/nlab/reminder/internal/common/android/startup/init/CoilInitializer.kt b/app/src/main/java/com/nlab/reminder/apps/startup/init/CoilInitializer.kt
similarity index 92%
rename from app/src/main/java/com/nlab/reminder/internal/common/android/startup/init/CoilInitializer.kt
rename to app/src/main/java/com/nlab/reminder/apps/startup/init/CoilInitializer.kt
index befb86250..94587d9cd 100644
--- a/app/src/main/java/com/nlab/reminder/internal/common/android/startup/init/CoilInitializer.kt
+++ b/app/src/main/java/com/nlab/reminder/apps/startup/init/CoilInitializer.kt
@@ -16,7 +16,7 @@
@file:Suppress("unused")
-package com.nlab.reminder.internal.common.android.startup.init
+package com.nlab.reminder.apps.startup.init
import android.content.Context
import androidx.startup.Initializer
@@ -24,7 +24,7 @@ import coil.Coil
import coil.ImageLoader
import coil.disk.DiskCache
import coil.memory.MemoryCache
-import com.nlab.reminder.internal.common.android.startup.EmptyDependencies
+import com.nlab.reminder.apps.startup.EmptyDependencies
/**
* @author Doohyun
diff --git a/app/src/main/java/com/nlab/reminder/internal/common/android/startup/init/StateKitPluginInitializer.kt b/app/src/main/java/com/nlab/reminder/apps/startup/init/StateKitPluginInitializer.kt
similarity index 50%
rename from app/src/main/java/com/nlab/reminder/internal/common/android/startup/init/StateKitPluginInitializer.kt
rename to app/src/main/java/com/nlab/reminder/apps/startup/init/StateKitPluginInitializer.kt
index 901fcf9a4..6cea66b78 100644
--- a/app/src/main/java/com/nlab/reminder/internal/common/android/startup/init/StateKitPluginInitializer.kt
+++ b/app/src/main/java/com/nlab/reminder/apps/startup/init/StateKitPluginInitializer.kt
@@ -15,12 +15,16 @@
*/
@file:Suppress("unused")
-package com.nlab.reminder.internal.common.android.startup.init
+package com.nlab.reminder.apps.startup.init
import android.content.Context
import androidx.startup.Initializer
import com.nlab.reminder.core.statekit.plugins.StateKitPlugin
-import com.nlab.reminder.internal.common.android.startup.EmptyDependencies
+import com.nlab.reminder.apps.startup.EmptyDependencies
+import com.nlab.reminder.core.component.usermessage.UserMessageException
+import com.nlab.reminder.core.component.usermessage.handle.di.getUserMessageBroadcastMonitor
+import com.nlab.reminder.core.component.usermessage.handle.impl.UserMessageBroadcastMonitor
+import kotlinx.coroutines.CancellationException
import timber.log.Timber
/**
@@ -28,7 +32,26 @@ import timber.log.Timber
*/
internal class StateKitPluginInitializer : Initializer {
override fun create(context: Context) {
- StateKitPlugin.addGlobalExceptionHandler { _, throwable -> Timber.tag("StateKitGlobalErr").e(throwable) }
+ val tag = "StateKitGlobalErr"
+ val userMessageBroadcastMonitor: UserMessageBroadcastMonitor =
+ context.getUserMessageBroadcastMonitor()
+
+ StateKitPlugin.addGlobalExceptionHandler { _, throwable ->
+ when (throwable) {
+ is UserMessageException -> {
+ Timber.tag(tag).e(throwable.origin)
+ userMessageBroadcastMonitor.send(userMessage = throwable.userMessage)
+ }
+
+ is CancellationException -> {
+ // do nothing
+ }
+
+ else -> {
+ Timber.tag(tag).e(throwable)
+ }
+ }
+ }
}
override fun dependencies() = EmptyDependencies()
diff --git a/app/src/main/java/com/nlab/reminder/apps/ui/MainActivity.kt b/app/src/main/java/com/nlab/reminder/apps/ui/MainActivity.kt
index 3c9221f00..d22189475 100644
--- a/app/src/main/java/com/nlab/reminder/apps/ui/MainActivity.kt
+++ b/app/src/main/java/com/nlab/reminder/apps/ui/MainActivity.kt
@@ -21,21 +21,27 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import com.nlab.reminder.core.android.widget.Toast
import com.nlab.reminder.core.designsystem.compose.theme.PlaneatTheme
import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
/**
* @author Doohyun
*/
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
+ @Inject
+ lateinit var appToast: Toast
+
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
-
enableEdgeToEdge()
setContent {
- val appState = rememberPlaneatAppState()
+ val appState = rememberPlaneatAppState(
+ appToast = appToast
+ )
PlaneatTheme {
PlaneatApp(appState = appState)
}
diff --git a/app/src/main/java/com/nlab/reminder/apps/ui/PlaneatApp.kt b/app/src/main/java/com/nlab/reminder/apps/ui/PlaneatApp.kt
index f700b9d7e..79e2cdb55 100644
--- a/app/src/main/java/com/nlab/reminder/apps/ui/PlaneatApp.kt
+++ b/app/src/main/java/com/nlab/reminder/apps/ui/PlaneatApp.kt
@@ -19,16 +19,18 @@ package com.nlab.reminder.apps.ui
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import com.nlab.reminder.core.component.usermessage.handle.ui.UserMessageHandler
/**
* @author Thalys
*/
@Composable
-fun PlaneatApp(
- appState: PlaneatAppState
-) {
+fun PlaneatApp(appState: PlaneatAppState) {
PlaneatNavHost(
modifier = Modifier.fillMaxSize(),
appState = appState
)
+ UserMessageHandler(
+ showApplicationToast = { message -> appState.showApplicationToast(message) }
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/nlab/reminder/apps/ui/PlaneatAppState.kt b/app/src/main/java/com/nlab/reminder/apps/ui/PlaneatAppState.kt
index 6e22133b9..9f7a6f25c 100644
--- a/app/src/main/java/com/nlab/reminder/apps/ui/PlaneatAppState.kt
+++ b/app/src/main/java/com/nlab/reminder/apps/ui/PlaneatAppState.kt
@@ -20,18 +20,26 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
+import com.nlab.reminder.core.android.widget.Toast
/**
* @author Thalys
*/
@Stable
class PlaneatAppState(
- val navController: NavHostController
-)
+ val navController: NavHostController,
+ private val appToast: Toast,
+) {
+ fun showApplicationToast(message: String) {
+ appToast.showToast(text = message)
+ }
+}
@Composable
fun rememberPlaneatAppState(
- navController: NavHostController = rememberNavController()
+ navController: NavHostController = rememberNavController(),
+ appToast: Toast,
): PlaneatAppState = PlaneatAppState(
- navController = navController
+ navController = navController,
+ appToast = appToast
)
\ No newline at end of file
diff --git a/app/src/main/java/com/nlab/reminder/domain/common/android/widget/Toasts.kt b/app/src/main/java/com/nlab/reminder/domain/common/android/widget/Toasts.kt
deleted file mode 100644
index b5f098f40..000000000
--- a/app/src/main/java/com/nlab/reminder/domain/common/android/widget/Toasts.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.nlab.reminder.domain.common.android.widget
-
-import android.content.Context
-import androidx.annotation.StringRes
-
-/**
- * @author Doohyun
- */
-fun Context.showToast(@StringRes stringResource: Int) {
- widgetEntryPoint()
- .toastHandle()
- .showToast(stringResource)
-}
-
-fun Context.showToast(text: String) {
- widgetEntryPoint()
- .toastHandle()
- .showToast(text)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/nlab/reminder/domain/common/android/widget/WidgetEntryPoint.kt b/app/src/main/java/com/nlab/reminder/domain/common/android/widget/WidgetEntryPoint.kt
deleted file mode 100644
index 96a729cdc..000000000
--- a/app/src/main/java/com/nlab/reminder/domain/common/android/widget/WidgetEntryPoint.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.nlab.reminder.domain.common.android.widget
-
-import android.content.Context
-import com.nlab.reminder.core.android.widget.ToastHandle
-import dagger.hilt.EntryPoint
-import dagger.hilt.InstallIn
-import dagger.hilt.android.EntryPointAccessors
-import dagger.hilt.components.SingletonComponent
-
-/**
- * @author Doohyun
- */
-@EntryPoint
-@InstallIn(SingletonComponent::class)
-internal interface WidgetEntryPoint {
- fun toastHandle(): ToastHandle
-}
-
-internal fun Context.widgetEntryPoint(): WidgetEntryPoint =
- EntryPointAccessors
- .fromApplication(this, WidgetEntryPoint::class.java)
\ No newline at end of file
diff --git a/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeAction.kt b/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeAction.kt
index 6af39e0b6..cd3b063dc 100644
--- a/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeAction.kt
+++ b/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeAction.kt
@@ -17,7 +17,6 @@
package com.nlab.reminder.domain.feature.home
import com.nlab.reminder.core.component.tag.edit.TagEditState
-import com.nlab.reminder.core.text.UiText
import com.nlab.reminder.core.data.model.Tag
import com.nlab.reminder.core.kotlin.NonNegativeLong
import com.nlab.statekit.annotation.UiAction
@@ -35,11 +34,6 @@ internal sealed class HomeAction private constructor() {
data class TagEditStateSynced(val state: TagEditState?) : HomeAction()
- data class UserMessagePosted(val message: UiText) : HomeAction()
-
- @UiAction
- data class UserMessageShown(val message: UiText) : HomeAction()
-
@UiAction
data object Interacted : HomeAction()
diff --git a/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeReduce.kt b/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeReduce.kt
index 4ee97cf63..68f21f18a 100644
--- a/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeReduce.kt
+++ b/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeReduce.kt
@@ -16,9 +16,7 @@
package com.nlab.reminder.domain.feature.home
-import com.nlab.reminder.core.translation.StringIds
-import com.nlab.reminder.core.text.UiText
-import com.nlab.reminder.core.kotlin.onFailure
+import com.nlab.reminder.core.component.usermessage.getOrThrowMessage
import com.nlab.statekit.dsl.reduce.DslReduce
import com.nlab.statekit.reduce.Reduce
import com.nlab.reminder.domain.feature.home.HomeAction.*
@@ -37,8 +35,7 @@ internal fun HomeReduce(environment: HomeEnvironment): HomeReduce = DslReduce {
timetableScheduleCount = action.timetableSchedulesCount,
allScheduleCount = action.allSchedulesCount,
tags = action.sortedTags,
- interaction = HomeInteraction.Empty,
- userMessages = emptyList()
+ interaction = HomeInteraction.Empty
)
}
transition {
@@ -79,7 +76,7 @@ internal fun HomeReduce(environment: HomeEnvironment): HomeReduce = DslReduce {
suspendEffect {
environment.tagEditDelegate
.startEditing(tag = action.tag)
- .onFailure { dispatch(UserMessagePosted(UiText(StringIds.tag_not_found))) }
+ .getOrThrowMessage()
}
}
scope(isMatch = { current.interaction is HomeInteraction.TagEdit }) {
@@ -89,23 +86,21 @@ internal fun HomeReduce(environment: HomeEnvironment): HomeReduce = DslReduce {
suspendEffect {
environment.tagEditDelegate
.tryUpdateTagName(current.tags)
- .onFailure { dispatch(UserMessagePosted(UiText(StringIds.unknown_error))) }
+ .getOrThrowMessage()
}
suspendEffect {
environment.tagEditDelegate
.mergeTag()
- .onFailure { dispatch(UserMessagePosted(UiText(StringIds.unknown_error))) }
+ .getOrThrowMessage()
}
effect { environment.tagEditDelegate.cancelMergeTag() }
effect { environment.tagEditDelegate.startDelete() }
suspendEffect {
environment.tagEditDelegate
.deleteTag()
- .onFailure { dispatch(UserMessagePosted(UiText(StringIds.unknown_error))) }
+ .getOrThrowMessage()
}
}
- transition { current.copy(userMessages = current.userMessages + action.message) }
- transition { current.copy(userMessages = current.userMessages - action.message) }
transition { current.copy(interaction = HomeInteraction.Empty) }
effect {
if (current.interaction is HomeInteraction.TagEdit) {
diff --git a/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeUiState.kt b/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeUiState.kt
index 1ab447b38..50b508a8f 100644
--- a/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeUiState.kt
+++ b/app/src/main/java/com/nlab/reminder/domain/feature/home/HomeUiState.kt
@@ -18,7 +18,6 @@ package com.nlab.reminder.domain.feature.home
import com.nlab.reminder.core.data.model.Tag
import com.nlab.reminder.core.kotlin.NonNegativeLong
-import com.nlab.reminder.core.text.UiText
/**
* @author Doohyun
@@ -32,6 +31,5 @@ internal sealed class HomeUiState private constructor() {
val allScheduleCount: NonNegativeLong,
val tags: List,
val interaction: HomeInteraction,
- val userMessages: List
) : HomeUiState()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/nlab/reminder/domain/feature/home/ui/HomeScreen.kt b/app/src/main/java/com/nlab/reminder/domain/feature/home/ui/HomeScreen.kt
index 36b9464d4..ed608db0f 100644
--- a/app/src/main/java/com/nlab/reminder/domain/feature/home/ui/HomeScreen.kt
+++ b/app/src/main/java/com/nlab/reminder/domain/feature/home/ui/HomeScreen.kt
@@ -68,8 +68,8 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.nlab.reminder.core.ui.compose.DelayedContent
-import com.nlab.reminder.core.ui.compose.tooling.preview.Previews
+import com.nlab.reminder.core.androidx.compose.ui.DelayedContent
+import com.nlab.reminder.core.androidx.compose.ui.tooling.preview.Previews
import com.nlab.reminder.core.data.model.Tag
import com.nlab.reminder.core.data.model.TagId
import com.nlab.reminder.core.designsystem.compose.theme.PlaneatTheme
@@ -85,8 +85,8 @@ import com.nlab.reminder.core.android.resources.font.CategoryCountFontFamily
import com.nlab.reminder.core.android.resources.icon.IcHomeCategoryAll
import com.nlab.reminder.core.android.resources.icon.IcHomeCategoryTimetable
import com.nlab.reminder.core.android.resources.icon.IcHomeCategoryToday
-import com.nlab.reminder.core.ui.compose.ColorPressButton
-import com.nlab.reminder.core.ui.compose.throttleClick
+import com.nlab.reminder.core.androidx.compose.ui.ColorPressButton
+import com.nlab.reminder.core.androidx.compose.ui.throttleClick
import com.nlab.reminder.core.component.tag.edit.ui.compose.TagEditStateHandler
import com.nlab.reminder.core.component.tag.ui.compose.TagCard
import com.nlab.reminder.core.designsystem.compose.component.PlaneatLoadingContent
@@ -744,7 +744,6 @@ private fun HomeScreenPopulated() {
)
},
interaction = HomeInteraction.Empty,
- userMessages = emptyList()
),
onTodayCategoryClicked = {},
onTimetableCategoryClicked = {},
diff --git a/app/src/test/java/com/nlab/reminder/domain/feature/home/HomeReduceKtTest.kt b/app/src/test/java/com/nlab/reminder/domain/feature/home/HomeReduceKtTest.kt
index 6128c63d2..b2ddc2c36 100644
--- a/app/src/test/java/com/nlab/reminder/domain/feature/home/HomeReduceKtTest.kt
+++ b/app/src/test/java/com/nlab/reminder/domain/feature/home/HomeReduceKtTest.kt
@@ -18,15 +18,12 @@ package com.nlab.reminder.domain.feature.home
import com.nlab.reminder.core.component.tag.edit.TagEditDelegate
import com.nlab.reminder.core.component.tag.edit.genTagEditState
-import com.nlab.reminder.core.text.UiText
-import com.nlab.reminder.core.text.genUiTexts
import com.nlab.reminder.core.data.model.genTag
import com.nlab.reminder.core.kotlin.Result
-import com.nlab.reminder.core.translation.StringIds
import com.nlab.statekit.test.reduce.effectScenario
+import com.nlab.statekit.test.reduce.launchAndJoin
import com.nlab.statekit.test.reduce.transitionScenario
import com.nlab.testkit.faker.genBothify
-import com.nlab.testkit.faker.genInt
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.mockito.kotlin.any
@@ -54,8 +51,7 @@ class HomeReduceKtTest {
timetableScheduleCount = action.timetableSchedulesCount,
allScheduleCount = action.allSchedulesCount,
tags = action.sortedTags,
- interaction = HomeInteraction.Empty,
- userMessages = emptyList()
+ interaction = HomeInteraction.Empty
)
}
.verify()
@@ -74,7 +70,6 @@ class HomeReduceKtTest {
allScheduleCount = action.allSchedulesCount,
tags = action.sortedTags,
interaction = initState.interaction,
- userMessages = initState.userMessages
)
}
.verify()
@@ -165,18 +160,17 @@ class HomeReduceKtTest {
}
@Test
- fun `Given success with no interaction and user messages, When tag long clicked, Then user message filled if TagEditDelegate startEditing result fails`() = runTest {
+ fun `Given success with no interaction, When tag long clicked, Then TagEditDelegate invoke startEditing`() = runTest {
val tagEditDelegate: TagEditDelegate = mock {
- whenever(mock.startEditing(any())) doReturn Result.Failure(IllegalStateException())
+ whenever(mock.startEditing(any())) doReturn Result.Success(Unit)
}
genHomeReduce(environment = genHomeEnvironment(tagEditDelegate))
- .transitionScenario()
- .initState(genHomeUiStateSuccess(interaction = HomeInteraction.Empty, userMessages = emptyList()))
+ .effectScenario()
+ .initState(genHomeUiStateSuccess(interaction = HomeInteraction.Empty))
.action(HomeAction.OnTagLongClicked(genTag()))
- .expectedStateFromInput {
- initState.copy(userMessages = listOf(UiText(StringIds.tag_not_found)))
+ .launchAndJoin {
+ verify(tagEditDelegate, once()).startEditing(action.tag)
}
- .verify(shouldVerifyWithEffect = true)
}
@Test
@@ -204,53 +198,41 @@ class HomeReduceKtTest {
@Test
fun `Given success with tagEdit interaction, When tag rename inputted, Then tagEditDelegate called changeRenameText`() = runTest {
val tagEditDelegate: TagEditDelegate = mock()
- val input = genBothify()
genHomeReduce(environment = genHomeEnvironment(tagEditDelegate))
.effectScenario()
.initState(genHomeUiStateSuccess(interaction = HomeInteraction.TagEdit(genTagEditState())))
- .action(HomeAction.OnTagRenameInputted(input))
- .launchAndJoin()
- verify(tagEditDelegate, once()).changeRenameText(input)
+ .action(HomeAction.OnTagRenameInputted(genBothify()))
+ .launchAndJoin {
+ verify(tagEditDelegate, once()).changeRenameText(action.text)
+ }
}
@Test
- fun `Given success with tagEdit interaction and no user messages, When tag rename confirmed, Then user message filled if TagEditDelegate tryUpdateTagRename result fails`() = runTest {
+ fun `Given success with tagEdit interaction, When tag rename confirmed, Then TagEditDelegate invoke tryUpdateTagRename`() = runTest {
val tagEditDelegate: TagEditDelegate = mock {
- whenever(mock.tryUpdateTagName(any())) doReturn Result.Failure(IllegalStateException())
+ whenever(mock.tryUpdateTagName(any())) doReturn Result.Success(Unit)
}
genHomeReduce(environment = genHomeEnvironment(tagEditDelegate))
- .transitionScenario()
- .initState(
- genHomeUiStateSuccess(
- interaction = HomeInteraction.TagEdit(genTagEditState()),
- userMessages = emptyList()
- )
- )
+ .effectScenario()
+ .initState( genHomeUiStateSuccess(interaction = HomeInteraction.TagEdit(genTagEditState())))
.action(HomeAction.OnTagRenameConfirmClicked)
- .expectedStateFromInput {
- initState.copy(userMessages = listOf(UiText(StringIds.unknown_error)))
+ .launchAndJoin {
+ verify(tagEditDelegate, once()).tryUpdateTagName(initState.tags)
}
- .verify(shouldVerifyWithEffect = true)
}
@Test
- fun `Given success with tagEdit interaction and no user messages, When tag replace confirm clicked, Then user message filled if TagEditDelegate mergeTag result fails`() = runTest {
+ fun `Given success with tagEdit interaction, When tag replace confirm clicked, Then TagEditDelegate invoke mergeTag`() = runTest {
val tagEditDelegate: TagEditDelegate = mock {
- whenever(mock.mergeTag()) doReturn Result.Failure(IllegalStateException())
+ whenever(mock.mergeTag()) doReturn Result.Success(Unit)
}
genHomeReduce(environment = genHomeEnvironment(tagEditDelegate))
- .transitionScenario()
- .initState(
- genHomeUiStateSuccess(
- interaction = HomeInteraction.TagEdit(genTagEditState()),
- userMessages = emptyList()
- )
- )
+ .effectScenario()
+ .initState(genHomeUiStateSuccess(interaction = HomeInteraction.TagEdit(genTagEditState())))
.action(HomeAction.OnTagReplaceConfirmClicked)
- .expectedStateFromInput {
- initState.copy(userMessages = listOf(UiText(StringIds.unknown_error)))
+ .launchAndJoin {
+ verify(tagEditDelegate, once()).mergeTag()
}
- .verify(shouldVerifyWithEffect = true)
}
@Test
@@ -276,53 +258,17 @@ class HomeReduceKtTest {
}
@Test
- fun `Given success with tagEdit interaction and no user messages, When tag delete confirm clicked, Then user message filled if TagEditDelegate deleteTag result fails`() = runTest {
+ fun `Given success with tagEdit interaction, When tag delete confirm clicked, Then TagEditDelegate invoked deleteTag`() = runTest {
val tagEditDelegate: TagEditDelegate = mock {
- whenever(mock.deleteTag()) doReturn Result.Failure(IllegalStateException())
+ whenever(mock.deleteTag()) doReturn Result.Success(Unit)
}
genHomeReduce(environment = genHomeEnvironment(tagEditDelegate))
- .transitionScenario()
- .initState(
- genHomeUiStateSuccess(
- interaction = HomeInteraction.TagEdit(genTagEditState()),
- userMessages = emptyList()
- )
- )
+ .effectScenario()
+ .initState(genHomeUiStateSuccess(interaction = HomeInteraction.TagEdit(genTagEditState())))
.action(HomeAction.OnTagDeleteConfirmClicked)
- .expectedStateFromInput {
- initState.copy(userMessages = listOf(UiText(StringIds.unknown_error)))
- }
- .verify(shouldVerifyWithEffect = true)
- }
-
- @Test
- fun `Given success with empty user messages, When user message posted, Then user message added to state`() = runTest {
- genHomeReduce()
- .transitionScenario()
- .initState(genHomeUiStateSuccess(userMessages = emptyList()))
- .action(HomeAction.UserMessagePosted(UiText(genBothify())))
- .expectedStateFromInput {
- initState.copy(
- userMessages = listOf(action.message)
- )
+ .launchAndJoin {
+ verify(tagEditDelegate, once()).deleteTag()
}
- .verify()
- }
-
- @Test
- fun `Given success with 2 or more user messages, When user message shown, Then user message removed`() = runTest {
- val userMessages = genUiTexts(genInt(min = 2, max = 10))
-
- genHomeReduce()
- .transitionScenario()
- .initState(genHomeUiStateSuccess(userMessages = userMessages))
- .action(HomeAction.UserMessageShown(userMessages[1]))
- .expectedStateFromInput {
- initState.copy(
- userMessages = initState.userMessages - action.message
- )
- }
- .verify()
}
@Test
diff --git a/app/src/test/java/com/nlab/reminder/domain/feature/home/HomeTestGenerator.kt b/app/src/test/java/com/nlab/reminder/domain/feature/home/HomeTestGenerator.kt
index 74e5c8a33..78064d8c5 100644
--- a/app/src/test/java/com/nlab/reminder/domain/feature/home/HomeTestGenerator.kt
+++ b/app/src/test/java/com/nlab/reminder/domain/feature/home/HomeTestGenerator.kt
@@ -18,8 +18,6 @@ package com.nlab.reminder.domain.feature.home
import com.nlab.reminder.core.component.tag.edit.TagEditDelegate
import com.nlab.reminder.core.component.tag.edit.genTagEditState
-import com.nlab.reminder.core.text.UiText
-import com.nlab.reminder.core.text.genUiTexts
import com.nlab.reminder.core.data.model.Tag
import com.nlab.reminder.core.data.model.genTags
import com.nlab.reminder.core.data.repository.ScheduleRepository
@@ -62,14 +60,12 @@ internal fun genHomeUiStateSuccess(
allScheduleCount: NonNegativeLong = genNonNegativeLong(),
tags: List = genTags(),
interaction: HomeInteraction = genHomeInteraction(),
- userMessages: List = genUiTexts()
) = HomeUiState.Success(
todayScheduleCount = todayScheduleCount,
timetableScheduleCount = timetableScheduleCount,
allScheduleCount = allScheduleCount,
tags = tags,
interaction = interaction,
- userMessages = userMessages
)
private val sampleHomeInteractions get() = listOf(
diff --git a/build-logic/convention/src/main/kotlin/com/nlab/reminder/AndroidComposeDependencies.kt b/build-logic/convention/src/main/kotlin/com/nlab/reminder/AndroidComposeDependencies.kt
index bf1131416..ccca9d6a9 100644
--- a/build-logic/convention/src/main/kotlin/com/nlab/reminder/AndroidComposeDependencies.kt
+++ b/build-logic/convention/src/main/kotlin/com/nlab/reminder/AndroidComposeDependencies.kt
@@ -24,8 +24,8 @@ import org.gradle.kotlin.dsl.dependencies
*/
fun Project.configureStdComposeDependencies() {
dependencies {
+ "implementation"(project(":core:androidx:compose"))
"implementation"(project(":core:designsystem"))
- "implementation"(project(":core:ui-compose"))
"implementation"(libs.findLibrary("androidx-compose-foundation").get())
"implementation"(libs.findLibrary("androidx-compose-material3").get())
diff --git a/build-logic/convention/src/main/kotlin/com/nlab/reminder/AndroidJacoco.kt b/build-logic/convention/src/main/kotlin/com/nlab/reminder/AndroidJacoco.kt
index df51361f0..560805e83 100644
--- a/build-logic/convention/src/main/kotlin/com/nlab/reminder/AndroidJacoco.kt
+++ b/build-logic/convention/src/main/kotlin/com/nlab/reminder/AndroidJacoco.kt
@@ -79,11 +79,12 @@ private fun Project.androidJacocoClassDirectories(variant: Variant): Configurabl
"**/BuildConfig.*",
"**/Manifest*.*",
"**/android/**",
- "**/ui/**",
- "**/navigation/**",
- "**/test/**",
"**/di/**",
"**/fake/**",
+ "**/navigation/**",
+ "**/startup/**",
+ "**/test/**",
+ "**/ui/**",
/* filtering unnecessary feature components */
"**/*Action$*.class",
diff --git a/core/android/build.gradle.kts b/core/android/build.gradle.kts
index 3fb2f5ffe..082786c1d 100644
--- a/core/android/build.gradle.kts
+++ b/core/android/build.gradle.kts
@@ -15,6 +15,7 @@
*/
plugins {
alias(libs.plugins.nlab.android.library)
+ alias(libs.plugins.nlab.android.library.di)
}
android {
diff --git a/core/android/src/main/java/com/nlab/reminder/core/android/di/AppScopeAndroidModule.kt b/core/android/src/main/java/com/nlab/reminder/core/android/di/AppScopeAndroidModule.kt
new file mode 100644
index 000000000..59d9a6b15
--- /dev/null
+++ b/core/android/src/main/java/com/nlab/reminder/core/android/di/AppScopeAndroidModule.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The N's lab Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nlab.reminder.core.android.di
+
+import android.content.Context
+import com.nlab.reminder.core.android.widget.Toast
+import com.nlab.reminder.core.inject.qualifiers.coroutine.AppScope
+import com.nlab.reminder.core.inject.qualifiers.coroutine.Dispatcher
+import com.nlab.reminder.core.inject.qualifiers.coroutine.DispatcherOption.Main
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.plus
+import javax.inject.Singleton
+
+/**
+ * @author Thalys
+ */
+@Module
+@InstallIn(SingletonComponent::class)
+internal class AppScopeAndroidModule {
+ @Singleton
+ @Provides
+ fun provideToast(
+ @ApplicationContext context: Context,
+ @AppScope coroutineScope: CoroutineScope,
+ @Dispatcher(Main) dispatcher: CoroutineDispatcher
+ ): Toast = Toast(context = context, coroutineScope = coroutineScope + dispatcher)
+}
\ No newline at end of file
diff --git a/core/android/src/main/java/com/nlab/reminder/core/android/widget/ToastHandle.kt b/core/android/src/main/java/com/nlab/reminder/core/android/widget/Toast.kt
similarity index 60%
rename from core/android/src/main/java/com/nlab/reminder/core/android/widget/ToastHandle.kt
rename to core/android/src/main/java/com/nlab/reminder/core/android/widget/Toast.kt
index 93c9b6b55..83123cfcc 100644
--- a/core/android/src/main/java/com/nlab/reminder/core/android/widget/ToastHandle.kt
+++ b/core/android/src/main/java/com/nlab/reminder/core/android/widget/Toast.kt
@@ -17,29 +17,34 @@
package com.nlab.reminder.core.android.widget
import android.content.Context
-import android.os.Handler
-import android.os.Looper
import android.widget.Toast
import androidx.annotation.StringRes
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import android.widget.Toast as ToastOrigin
+import java.lang.ref.WeakReference
/**
* @author thalys
*/
-class ToastHandle(private val context: Context) {
- private var curToast: Toast? = null
+class Toast(
+ private val context: Context,
+ private val coroutineScope: CoroutineScope,
+) {
+ private var curToastRef: WeakReference? = null
fun showToast(@StringRes resId: Int) {
- showToast { Toast.makeText(context, resId, Toast.LENGTH_SHORT) }
+ showToast { ToastOrigin.makeText(context, resId, ToastOrigin.LENGTH_SHORT) }
}
fun showToast(text: String) {
- showToast { Toast.makeText(context, text, Toast.LENGTH_SHORT) }
+ showToast { ToastOrigin.makeText(context, text, ToastOrigin.LENGTH_SHORT) }
}
private inline fun showToast(crossinline getToast: () -> Toast) {
- Handler(Looper.getMainLooper()).post {
- curToast?.cancel()
- curToast = getToast().also { it.show() }
+ coroutineScope.launch {
+ curToastRef?.get()?.cancel()
+ curToastRef = WeakReference(getToast().also { it.show() })
}
}
}
\ No newline at end of file
diff --git a/core/ui-compose/.gitignore b/core/androidx/compose/.gitignore
similarity index 100%
rename from core/ui-compose/.gitignore
rename to core/androidx/compose/.gitignore
diff --git a/core/ui-compose/build.gradle.kts b/core/androidx/compose/build.gradle.kts
similarity index 87%
rename from core/ui-compose/build.gradle.kts
rename to core/androidx/compose/build.gradle.kts
index 030b9f0e1..2c69e45eb 100644
--- a/core/ui-compose/build.gradle.kts
+++ b/core/androidx/compose/build.gradle.kts
@@ -4,7 +4,7 @@ plugins {
}
android {
- namespace = "com.nlab.reminder.core.ui.compose"
+ namespace = "com.nlab.reminder.core.androidx.compose"
}
dependencies {
diff --git a/core/ui-compose/src/main/AndroidManifest.xml b/core/androidx/compose/src/main/AndroidManifest.xml
similarity index 100%
rename from core/ui-compose/src/main/AndroidManifest.xml
rename to core/androidx/compose/src/main/AndroidManifest.xml
diff --git a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/ColorPressButton.kt b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/ColorPressButton.kt
similarity index 98%
rename from core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/ColorPressButton.kt
rename to core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/ColorPressButton.kt
index 7dc5366eb..cad9ba859 100644
--- a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/ColorPressButton.kt
+++ b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/ColorPressButton.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.nlab.reminder.core.ui.compose
+package com.nlab.reminder.core.androidx.compose.ui
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import androidx.compose.foundation.clickable
diff --git a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/DelayedContent.kt b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/DelayedContent.kt
similarity index 97%
rename from core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/DelayedContent.kt
rename to core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/DelayedContent.kt
index 44ee1f179..0ac2d6122 100644
--- a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/DelayedContent.kt
+++ b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/DelayedContent.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.nlab.reminder.core.ui.compose
+package com.nlab.reminder.core.androidx.compose.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
diff --git a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/TextFieldValues.kt b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/TextFieldValues.kt
similarity index 96%
rename from core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/TextFieldValues.kt
rename to core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/TextFieldValues.kt
index ebd45a68c..1b9ebe4f7 100644
--- a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/TextFieldValues.kt
+++ b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/TextFieldValues.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.nlab.reminder.core.ui.compose
+package com.nlab.reminder.core.androidx.compose.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
diff --git a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/ThrottleClick.kt b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/ThrottleClick.kt
similarity index 96%
rename from core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/ThrottleClick.kt
rename to core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/ThrottleClick.kt
index 6ea6f74a5..713650052 100644
--- a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/ThrottleClick.kt
+++ b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/ThrottleClick.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.nlab.reminder.core.ui.compose
+package com.nlab.reminder.core.androidx.compose.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
diff --git a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/tooling/preview/Previews.kt b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/tooling/preview/Previews.kt
similarity index 94%
rename from core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/tooling/preview/Previews.kt
rename to core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/tooling/preview/Previews.kt
index 0a48ea32d..48d1abc06 100644
--- a/core/ui-compose/src/main/kotlin/com/nlab/reminder/core/ui/compose/tooling/preview/Previews.kt
+++ b/core/androidx/compose/src/main/kotlin/com/nlab/reminder/core/androidx/compose/ui/tooling/preview/Previews.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.nlab.reminder.core.ui.compose.tooling.preview
+package com.nlab.reminder.core.androidx.compose.ui.tooling.preview
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
diff --git a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/edit/TagEditDelegate.kt b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/edit/TagEditDelegate.kt
index 1ea6b2e9d..a2642d0e7 100644
--- a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/edit/TagEditDelegate.kt
+++ b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/edit/TagEditDelegate.kt
@@ -42,10 +42,11 @@ class TagEditDelegate(
private val _state = MutableStateFlow(initialState)
val state: StateFlow = _state.asStateFlow()
- suspend fun startEditing(tag: Tag): Result =
+ suspend fun startEditing(tag: Tag): Result =
tagRepository.getUsageCount(id = tag.id)
.map { usageCount -> TagEditState.Intro(tag, usageCount) }
.onSuccess { intro -> _state.update { current -> current ?: intro } }
+ .map {}
fun startRename() {
_state.updateIfTypeOf { current ->
diff --git a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/edit/ui/compose/TagEditIntroDialog.kt b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/edit/ui/compose/TagEditIntroDialog.kt
index e400ed5d2..8dcb9981b 100644
--- a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/edit/ui/compose/TagEditIntroDialog.kt
+++ b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/edit/ui/compose/TagEditIntroDialog.kt
@@ -41,8 +41,8 @@ import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
-import com.nlab.reminder.core.ui.compose.throttleClick
-import com.nlab.reminder.core.ui.compose.tooling.preview.Previews
+import com.nlab.reminder.core.androidx.compose.ui.throttleClick
+import com.nlab.reminder.core.androidx.compose.ui.tooling.preview.Previews
import com.nlab.reminder.core.designsystem.compose.component.PlaneatDialog
import com.nlab.reminder.core.designsystem.compose.theme.PlaneatTheme
import com.nlab.reminder.core.kotlin.NonBlankString
diff --git a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagCard.kt b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagCard.kt
index 43d83d11d..0847f72af 100644
--- a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagCard.kt
+++ b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagCard.kt
@@ -36,8 +36,8 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
-import com.nlab.reminder.core.ui.compose.tooling.preview.Previews
-import com.nlab.reminder.core.ui.compose.throttleClick
+import com.nlab.reminder.core.androidx.compose.ui.tooling.preview.Previews
+import com.nlab.reminder.core.androidx.compose.ui.throttleClick
import com.nlab.reminder.core.designsystem.compose.theme.PlaneatTheme
import com.nlab.reminder.core.translation.StringIds
diff --git a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagDeleteBottomSheet.kt b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagDeleteBottomSheet.kt
index 286d1a5a6..2b2da2b22 100644
--- a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagDeleteBottomSheet.kt
+++ b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagDeleteBottomSheet.kt
@@ -37,8 +37,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import com.nlab.reminder.core.ui.compose.throttleClick
-import com.nlab.reminder.core.ui.compose.tooling.preview.Previews
+import com.nlab.reminder.core.androidx.compose.ui.throttleClick
+import com.nlab.reminder.core.androidx.compose.ui.tooling.preview.Previews
import com.nlab.reminder.core.component.tag.ui.getUsageCountLabel
import com.nlab.reminder.core.designsystem.compose.component.PlaneatBottomSheet
import com.nlab.reminder.core.designsystem.compose.theme.PlaneatTheme
diff --git a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagDialogButtons.kt b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagDialogButtons.kt
index 8a7135d89..14a33f5ff 100644
--- a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagDialogButtons.kt
+++ b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagDialogButtons.kt
@@ -30,9 +30,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import com.nlab.reminder.core.ui.compose.ColorPressButton
-import com.nlab.reminder.core.ui.compose.throttleClick
-import com.nlab.reminder.core.ui.compose.tooling.preview.Previews
+import com.nlab.reminder.core.androidx.compose.ui.ColorPressButton
+import com.nlab.reminder.core.androidx.compose.ui.throttleClick
+import com.nlab.reminder.core.androidx.compose.ui.tooling.preview.Previews
import com.nlab.reminder.core.designsystem.compose.theme.PlaneatTheme
import com.nlab.reminder.core.translation.StringIds
diff --git a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagMergeDialog.kt b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagMergeDialog.kt
index f56714eb2..68f4bc1ce 100644
--- a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagMergeDialog.kt
+++ b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagMergeDialog.kt
@@ -29,7 +29,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import com.nlab.reminder.core.ui.compose.tooling.preview.Previews
+import com.nlab.reminder.core.androidx.compose.ui.tooling.preview.Previews
import com.nlab.reminder.core.designsystem.compose.component.PlaneatDialog
import com.nlab.reminder.core.designsystem.compose.theme.PlaneatTheme
import com.nlab.reminder.core.kotlin.NonBlankString
diff --git a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagRenameDialog.kt b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagRenameDialog.kt
index 66d238d23..d2d7b66f6 100644
--- a/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagRenameDialog.kt
+++ b/core/component/tag/src/main/kotlin/com/nlab/reminder/core/component/tag/ui/compose/TagRenameDialog.kt
@@ -57,9 +57,9 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import com.nlab.reminder.core.ui.compose.rememberDebouncedTextFieldValueState
-import com.nlab.reminder.core.ui.compose.throttleClick
-import com.nlab.reminder.core.ui.compose.tooling.preview.Previews
+import com.nlab.reminder.core.androidx.compose.ui.rememberDebouncedTextFieldValueState
+import com.nlab.reminder.core.androidx.compose.ui.throttleClick
+import com.nlab.reminder.core.androidx.compose.ui.tooling.preview.Previews
import com.nlab.reminder.core.component.tag.ui.getUsageCountLabel
import com.nlab.reminder.core.designsystem.compose.component.PlaneatDialog
import com.nlab.reminder.core.designsystem.compose.resource.DrawableIds
diff --git a/statekit/runtime/.gitignore b/core/component/usermessage-handle/.gitignore
similarity index 100%
rename from statekit/runtime/.gitignore
rename to core/component/usermessage-handle/.gitignore
diff --git a/core/component/usermessage-handle/build.gradle.kts b/core/component/usermessage-handle/build.gradle.kts
new file mode 100644
index 000000000..428764dcf
--- /dev/null
+++ b/core/component/usermessage-handle/build.gradle.kts
@@ -0,0 +1,27 @@
+plugins {
+ alias(libs.plugins.nlab.android.library)
+ alias(libs.plugins.nlab.android.library.compose.component)
+ alias(libs.plugins.nlab.android.library.di)
+ alias(libs.plugins.nlab.android.library.jacoco)
+ kotlin("kapt")
+}
+
+android {
+ namespace = "com.nlab.reminder.core.component.usermessage.handle"
+}
+
+dependencies {
+ implementation(projects.core.annotation)
+ implementation(projects.core.component.usermessage)
+ implementation(projects.core.kotlinxCoroutine)
+ implementation(projects.core.statekit)
+ kapt(projects.statekit.compiler)
+
+ implementation(libs.androidx.hilt.navigation.compose)
+ implementation(libs.androidx.lifecycle.runtimeCompose)
+ implementation(libs.androidx.navigation.compose)
+
+ testImplementation(projects.core.uitextTest)
+ testImplementation(projects.statekit.test)
+ testImplementation(projects.testkit)
+}
\ No newline at end of file
diff --git a/core/component/usermessage-handle/src/main/AndroidManifest.xml b/core/component/usermessage-handle/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..568741e54
--- /dev/null
+++ b/core/component/usermessage-handle/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/UserMessageHandleFeatures.kt b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/UserMessageHandleFeatures.kt
new file mode 100644
index 000000000..5f4abdb3a
--- /dev/null
+++ b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/UserMessageHandleFeatures.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The N's lab Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nlab.reminder.core.component.usermessage.handle
+
+import com.nlab.reminder.core.annotation.ExcludeFromGeneratedTestReport
+import com.nlab.reminder.core.component.usermessage.UserMessage
+import com.nlab.reminder.core.component.usermessage.handle.UserMessageHandleAction.*
+import com.nlab.reminder.core.kotlinx.coroutine.flow.map
+import com.nlab.reminder.core.statekit.store.androidx.lifecycle.StoreViewModel
+import com.nlab.reminder.core.statekit.store.androidx.lifecycle.createStore
+import com.nlab.statekit.annotation.UiAction
+import com.nlab.statekit.annotation.UiActionMapping
+import com.nlab.statekit.bootstrap.DeliveryStarted
+import com.nlab.statekit.bootstrap.collectAsBootstrap
+import com.nlab.statekit.dsl.reduce.DslReduce
+import com.nlab.statekit.reduce.Reduce
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+typealias UserMessageHandleReduce = Reduce
+
+/**
+ * @author Thalys
+ */
+fun UserMessageHandleReduce(): UserMessageHandleReduce = DslReduce {
+ stateScope {
+ transition {
+ current.copy(userMessages = current.userMessages + action.message)
+ }
+ transition {
+ current.copy(userMessages = current.userMessages - action.message)
+ }
+ }
+}
+
+sealed class UserMessageHandleAction {
+ data class UserMessagePosted(val message: UserMessage) : UserMessageHandleAction()
+
+ @UiAction
+ data class UserMessageShown(val message: UserMessage) : UserMessageHandleAction()
+}
+
+data class UserMessageUiState(
+ val userMessages: List
+)
+
+@ExcludeFromGeneratedTestReport
+@UiActionMapping(UserMessageHandleAction::class)
+@HiltViewModel
+class UserMessageHandleViewModel @Inject constructor(
+ private val userMessageMonitor: UserMessageMonitor
+) : StoreViewModel() {
+ override fun onCreateStore() = createStore(
+ initState = UserMessageUiState(userMessages = emptyList()),
+ reduce = UserMessageHandleReduce(),
+ bootstrap = userMessageMonitor.message
+ .map(::UserMessagePosted)
+ .collectAsBootstrap(DeliveryStarted.Lazily)
+ )
+}
\ No newline at end of file
diff --git a/statekit/runtime/src/main/kotlin/com/nlab/statekit/lifecycle/UiActionDispatchable.kt b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/UserMessageMonitor.kt
similarity index 65%
rename from statekit/runtime/src/main/kotlin/com/nlab/statekit/lifecycle/UiActionDispatchable.kt
rename to core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/UserMessageMonitor.kt
index 79d96c77a..7232f78e1 100644
--- a/statekit/runtime/src/main/kotlin/com/nlab/statekit/lifecycle/UiActionDispatchable.kt
+++ b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/UserMessageMonitor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The N's lab Open Source Project
+ * Copyright (C) 2024 The N's lab Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package com.nlab.statekit.lifecycle
+package com.nlab.reminder.core.component.usermessage.handle
-import kotlinx.coroutines.Job
+import com.nlab.reminder.core.component.usermessage.UserMessage
+import kotlinx.coroutines.flow.Flow
-interface UiActionDispatchable {
- fun dispatch(action: T): Job
+/**
+ * @author Thalys
+ */
+interface UserMessageMonitor {
+ val message: Flow
}
\ No newline at end of file
diff --git a/app/src/main/java/com/nlab/reminder/internal/common/di/UtilityModule.kt b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/di/AppScopeUserMessageHandleModule.kt
similarity index 53%
rename from app/src/main/java/com/nlab/reminder/internal/common/di/UtilityModule.kt
rename to core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/di/AppScopeUserMessageHandleModule.kt
index dfbb117d7..2b923cd13 100644
--- a/app/src/main/java/com/nlab/reminder/internal/common/di/UtilityModule.kt
+++ b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/di/AppScopeUserMessageHandleModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The N's lab Open Source Project
+ * Copyright (C) 2024 The N's lab Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,24 +14,29 @@
* limitations under the License.
*/
-package com.nlab.reminder.internal.common.di
+package com.nlab.reminder.core.component.usermessage.handle.di
-import android.content.Context
-import com.nlab.reminder.core.android.widget.ToastHandle
+import com.nlab.reminder.core.component.usermessage.handle.UserMessageMonitor
+import com.nlab.reminder.core.component.usermessage.handle.impl.UserMessageBroadcastMonitor
+import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
-import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
- * @author Doohyun
+ * @author Thalys
*/
@Module
@InstallIn(SingletonComponent::class)
-class UtilityModule {
- @Singleton
- @Provides
- fun provideToastHandle(@ApplicationContext context: Context): ToastHandle = ToastHandle(context)
+internal abstract class AppScopeUserMessageHandleModule {
+ @Binds
+ abstract fun bindUserMesMessageMonitor(impl: UserMessageBroadcastMonitor): UserMessageMonitor
+
+ companion object {
+ @Singleton
+ @Provides
+ fun provideUserMessageBroadcastMonitor(): UserMessageBroadcastMonitor = UserMessageBroadcastMonitor()
+ }
}
\ No newline at end of file
diff --git a/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/di/UserMessageHandleModuleEntryPoint.kt b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/di/UserMessageHandleModuleEntryPoint.kt
new file mode 100644
index 000000000..326601146
--- /dev/null
+++ b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/di/UserMessageHandleModuleEntryPoint.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The N's lab Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nlab.reminder.core.component.usermessage.handle.di
+
+import android.content.Context
+import com.nlab.reminder.core.component.usermessage.handle.impl.UserMessageBroadcastMonitor
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.android.EntryPointAccessors
+import dagger.hilt.components.SingletonComponent
+
+/**
+ * @author Thalys
+ */
+@EntryPoint
+@InstallIn(SingletonComponent::class)
+internal interface UserMessageHandleModuleEntryPoint {
+ fun userMessageBroadcastMonitor(): UserMessageBroadcastMonitor
+}
+
+fun Context.getUserMessageBroadcastMonitor(): UserMessageBroadcastMonitor =
+ EntryPointAccessors
+ .fromApplication(context = this, UserMessageHandleModuleEntryPoint::class.java)
+ .userMessageBroadcastMonitor()
\ No newline at end of file
diff --git a/statekit/runtime/src/main/kotlin/com/nlab/statekit/lifecycle/viewmodel/ContractUiAction.kt b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/impl/UserMessageBroadcastMonitor.kt
similarity index 50%
rename from statekit/runtime/src/main/kotlin/com/nlab/statekit/lifecycle/viewmodel/ContractUiAction.kt
rename to core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/impl/UserMessageBroadcastMonitor.kt
index 660df10b0..ba8cb948b 100644
--- a/statekit/runtime/src/main/kotlin/com/nlab/statekit/lifecycle/viewmodel/ContractUiAction.kt
+++ b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/impl/UserMessageBroadcastMonitor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The N's lab Open Source Project
+ * Copyright (C) 2024 The N's lab Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,20 +14,22 @@
* limitations under the License.
*/
-package com.nlab.statekit.lifecycle.viewmodel
+package com.nlab.reminder.core.component.usermessage.handle.impl
+
+import com.nlab.reminder.core.component.usermessage.UserMessage
+import com.nlab.reminder.core.component.usermessage.handle.UserMessageMonitor
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.receiveAsFlow
/**
- * Generate UiAction dispatch method without receiver type.
- * The first simple names of annotated class should suffixed with "Action".
- * **SampleUiAction.OnClick**, **SampleUiAction.DialogAction.OnClick**
- *
- * For example, if you have a action like this: **SimpleAction.OnClick**
- * Annotation Processor will be generate method like this:
- *
- * fun SampleViewModel.onClick() {
- * }
- * @author Doohyun
+ * @author Thalys
*/
-@Retention(AnnotationRetention.SOURCE)
-@Target(AnnotationTarget.CLASS)
-annotation class ContractUiAction(val isPublic: Boolean = false)
\ No newline at end of file
+class UserMessageBroadcastMonitor : UserMessageMonitor {
+ private val _message = Channel(Channel.RENDEZVOUS)
+ override val message: Flow = _message.receiveAsFlow()
+
+ fun send(userMessage: UserMessage) {
+ _message.trySend(userMessage)
+ }
+}
\ No newline at end of file
diff --git a/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/ui/UserMessageHandler.kt b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/ui/UserMessageHandler.kt
new file mode 100644
index 000000000..9fa58a0b3
--- /dev/null
+++ b/core/component/usermessage-handle/src/main/kotlin/com/nlab/reminder/core/component/usermessage/handle/ui/UserMessageHandler.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The N's lab Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nlab.reminder.core.component.usermessage.handle.ui
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.nlab.reminder.core.component.usermessage.UserMessage
+import com.nlab.reminder.core.component.usermessage.handle.UserMessageHandleViewModel
+import com.nlab.reminder.core.component.usermessage.handle.UserMessageUiState
+import com.nlab.reminder.core.component.usermessage.handle.userMessageShown
+import com.nlab.reminder.core.text.ui.compose.toText
+
+
+/**
+ * @author Thalys
+ */
+@Composable
+fun UserMessageHandler(
+ showApplicationToast: (String) -> Unit,
+ viewModel: UserMessageHandleViewModel = hiltViewModel()
+) {
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+ UserMessageHandler(
+ uiState = uiState,
+ userMessageShown = { viewModel.userMessageShown(it) },
+ showApplicationToast = showApplicationToast
+ )
+}
+
+@Composable
+private fun UserMessageHandler(
+ uiState: UserMessageUiState,
+ userMessageShown: (UserMessage) -> Unit,
+ showApplicationToast: (String) -> Unit,
+) {
+ val userMessage = uiState.userMessages.firstOrNull() ?: return
+ val messageText = userMessage.message.toText()
+ LaunchedEffect(userMessage) {
+ showApplicationToast(messageText) // TODO implements user message with priority
+ userMessageShown(userMessage)
+ }
+}
\ No newline at end of file
diff --git a/core/component/usermessage-handle/src/test/kotlin/com/nlab/reminder/core/component/usermessage/handle/UserMessageHandleReduceTest.kt b/core/component/usermessage-handle/src/test/kotlin/com/nlab/reminder/core/component/usermessage/handle/UserMessageHandleReduceTest.kt
new file mode 100644
index 000000000..d9270651f
--- /dev/null
+++ b/core/component/usermessage-handle/src/test/kotlin/com/nlab/reminder/core/component/usermessage/handle/UserMessageHandleReduceTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The N's lab Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nlab.reminder.core.component.usermessage.handle
+
+import com.nlab.reminder.core.component.usermessage.FeedbackPriority
+import com.nlab.reminder.core.component.usermessage.UserMessage
+import com.nlab.reminder.core.text.genUiText
+import com.nlab.statekit.test.reduce.transitionScenario
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+/**
+ * @author Thalys
+ */
+class UserMessageHandleReduceTest {
+ @Test
+ fun `Given success with empty userMessage, When user message posted, Then state changed with user message`() = runTest {
+ UserMessageHandleReduce()
+ .transitionScenario()
+ .initState(UserMessageUiState(userMessages = emptyList()))
+ .action(
+ UserMessageHandleAction.UserMessagePosted(
+ UserMessage(
+ message = genUiText(),
+ priority = FeedbackPriority.LOW
+ )
+ )
+ )
+ .expectedStateFromInput {
+ initState.copy(userMessages = listOf(action.message))
+ }
+ .verify()
+ }
+
+ @Test
+ fun `Given success with single user message, When user message shown, Then state changed empty user message`() = runTest {
+ val userMessage = UserMessage(
+ message = genUiText(),
+ priority = FeedbackPriority.URGENT
+ )
+ UserMessageHandleReduce()
+ .transitionScenario()
+ .initState(UserMessageUiState(listOf(userMessage)))
+ .action(UserMessageHandleAction.UserMessageShown(userMessage))
+ .expectedStateFromInput { initState.copy(userMessages = emptyList()) }
+ .verify()
+ }
+}
\ No newline at end of file
diff --git a/core/component/usermessage-handle/src/test/kotlin/com/nlab/reminder/core/component/usermessage/handle/impl/UserMessageBroadcastMonitorTest.kt b/core/component/usermessage-handle/src/test/kotlin/com/nlab/reminder/core/component/usermessage/handle/impl/UserMessageBroadcastMonitorTest.kt
new file mode 100644
index 000000000..bbbb02af0
--- /dev/null
+++ b/core/component/usermessage-handle/src/test/kotlin/com/nlab/reminder/core/component/usermessage/handle/impl/UserMessageBroadcastMonitorTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The N's lab Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nlab.reminder.core.component.usermessage.handle.impl
+
+import com.nlab.reminder.core.component.usermessage.FeedbackPriority
+import com.nlab.reminder.core.component.usermessage.UserMessage
+import com.nlab.reminder.core.text.genUiText
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.assertFlowEmissionsLazy
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+/**
+ * @author Thalys
+ */
+class UserMessageBroadcastMonitorTest {
+ @Test
+ fun `Given user message, When send after subscribe, Then monitor send user message`() = runTest {
+ val userMessage = UserMessage(
+ message = genUiText(),
+ priority = FeedbackPriority.HIGH
+ )
+ val userMessageProvider = UserMessageBroadcastMonitor()
+ val assertion = assertFlowEmissionsLazy(userMessageProvider.message, listOf(userMessage))
+ userMessageProvider.send(userMessage)
+ assertion()
+ }
+
+ @Test
+ fun `Given user message, When send before subscribe, Then monitor not send user message`() = runTest {
+ val userMessage = UserMessage(
+ message = genUiText(),
+ priority = FeedbackPriority.URGENT
+ )
+ val userMessageProvider = UserMessageBroadcastMonitor()
+ userMessageProvider.send(userMessage)
+
+ val assertion = assertFlowEmissionsLazy(userMessageProvider.message, emptyList())
+ advanceUntilIdle()
+ assertion()
+ }
+}
\ No newline at end of file
diff --git a/core/component/usermessage/.gitignore b/core/component/usermessage/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/component/usermessage/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/component/usermessage/build.gradle.kts b/core/component/usermessage/build.gradle.kts
new file mode 100644
index 000000000..ba7f8c065
--- /dev/null
+++ b/core/component/usermessage/build.gradle.kts
@@ -0,0 +1,20 @@
+plugins {
+ alias(libs.plugins.nlab.android.library)
+ alias(libs.plugins.nlab.android.library.compose)
+ alias(libs.plugins.nlab.android.library.di)
+ alias(libs.plugins.nlab.android.library.jacoco)
+}
+
+android {
+ namespace = "com.nlab.reminder.core.component.usermessage"
+}
+
+dependencies {
+ api(projects.core.uitext)
+ implementation(projects.core.annotation)
+ implementation(projects.core.kotlin)
+ implementation(projects.core.translation)
+
+ testImplementation(projects.core.uitextTest)
+ testImplementation(projects.testkit)
+}
\ No newline at end of file
diff --git a/core/component/usermessage/src/main/AndroidManifest.xml b/core/component/usermessage/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..568741e54
--- /dev/null
+++ b/core/component/usermessage/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/statekit/runtime/src/main/kotlin/com/nlab/statekit/lifecycle/UiAction.kt b/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/FeedbackPriority.kt
similarity index 65%
rename from statekit/runtime/src/main/kotlin/com/nlab/statekit/lifecycle/UiAction.kt
rename to core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/FeedbackPriority.kt
index adcdff8f9..70a3f0ae8 100644
--- a/statekit/runtime/src/main/kotlin/com/nlab/statekit/lifecycle/UiAction.kt
+++ b/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/FeedbackPriority.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The N's lab Open Source Project
+ * Copyright (C) 2024 The N's lab Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package com.nlab.statekit.lifecycle
+package com.nlab.reminder.core.component.usermessage
-import kotlin.reflect.KClass
+import com.nlab.reminder.core.annotation.ExcludeFromGeneratedTestReport
/**
* @author Doohyun
*/
-@Retention(AnnotationRetention.SOURCE)
-@Target(AnnotationTarget.CLASS)
-annotation class UiAction(
- val receiverTypes: Array>>,
- val isPublic: Boolean = false,
-)
\ No newline at end of file
+@ExcludeFromGeneratedTestReport
+enum class FeedbackPriority {
+ LOW,
+ MEDIUM,
+ HIGH,
+ URGENT
+}
\ No newline at end of file
diff --git a/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/UserMessage.kt b/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/UserMessage.kt
new file mode 100644
index 000000000..81f3acb9f
--- /dev/null
+++ b/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/UserMessage.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The N's lab Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nlab.reminder.core.component.usermessage
+
+import com.nlab.reminder.core.annotation.ExcludeFromGeneratedTestReport
+import com.nlab.reminder.core.text.UiText
+
+/**
+ * @author Doohyun
+ */
+@ExcludeFromGeneratedTestReport
+data class UserMessage(
+ val message: UiText,
+ val priority: FeedbackPriority
+)
\ No newline at end of file
diff --git a/statekit/runtime/build.gradle.kts b/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/UserMessageException.kt
similarity index 62%
rename from statekit/runtime/build.gradle.kts
rename to core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/UserMessageException.kt
index 69555e4e7..31ababe83 100644
--- a/statekit/runtime/build.gradle.kts
+++ b/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/UserMessageException.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The N's lab Open Source Project
+ * Copyright (C) 2024 The N's lab Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-plugins {
- alias(libs.plugins.nlab.jvm.library)
-}
+package com.nlab.reminder.core.component.usermessage
-dependencies {
- api(projects.statekit.core)
- implementation(libs.kotlinx.coroutines.core)
-}
\ No newline at end of file
+import com.nlab.reminder.core.annotation.ExcludeFromGeneratedTestReport
+
+/**
+ * @author Doohyun
+ */
+@ExcludeFromGeneratedTestReport
+class UserMessageException(
+ val userMessage: UserMessage,
+ val origin: Throwable
+) : RuntimeException()
\ No newline at end of file
diff --git a/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/UserMessageExt.kt b/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/UserMessageExt.kt
new file mode 100644
index 000000000..4a2afd1ab
--- /dev/null
+++ b/core/component/usermessage/src/main/kotlin/com/nlab/reminder/core/component/usermessage/UserMessageExt.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The N's lab Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nlab.reminder.core.component.usermessage
+
+import com.nlab.reminder.core.kotlin.Result
+import com.nlab.reminder.core.kotlin.getOrThrow
+import com.nlab.reminder.core.text.UiText
+import com.nlab.reminder.core.translation.StringIds
+
+/**
+ * @author Doohyun
+ */
+fun Result.getOrThrowMessage(
+ message: UiText? = null,
+ priority: FeedbackPriority? = null
+): T {
+ try {
+ return getOrThrow()
+ } catch (e: UserMessageException) {
+ throw if (message == null && priority == null) e
+ else {
+ val originUserMessage = e.userMessage
+ UserMessageException(
+ userMessage = UserMessage(
+ message = message ?: originUserMessage.message,
+ priority = priority ?: originUserMessage.priority
+ ),
+ origin = e.origin
+ )
+ }
+ } catch (e: Throwable) {
+ throw UserMessageException(
+ userMessage = UserMessage(
+ message = message ?: UiText(StringIds.unknown_error),
+ priority = priority ?: FeedbackPriority.LOW
+ ),
+ origin = e
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/component/usermessage/src/test/kotlin/com/nlab/reminder/core/component/usermessage/UserMessageExtKtTest.kt b/core/component/usermessage/src/test/kotlin/com/nlab/reminder/core/component/usermessage/UserMessageExtKtTest.kt
new file mode 100644
index 000000000..16a0656be
--- /dev/null
+++ b/core/component/usermessage/src/test/kotlin/com/nlab/reminder/core/component/usermessage/UserMessageExtKtTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 The N's lab Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nlab.reminder.core.component.usermessage
+
+import com.nlab.reminder.core.kotlin.Result
+import com.nlab.reminder.core.text.UiText
+import com.nlab.reminder.core.text.genUiText
+import com.nlab.reminder.core.translation.StringIds
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.CoreMatchers.sameInstance
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+
+/**
+ * @author Thalys
+ */
+class UserMessageExtKtTest {
+ @Test
+ fun `Given success result, When getOrThrowMessage, Then result return value`() {
+ val value = Any()
+ val result = Result.Success(value)
+ assertThat(result.getOrThrowMessage(), sameInstance(value))
+ }
+
+ @Test
+ fun `Given failed result with user message, When getOrThrowMessage, Then result throw origin`() {
+ val throwable = UserMessageException(
+ userMessage = UserMessage(message = genUiText(), FeedbackPriority.HIGH),
+ origin = Throwable()
+ )
+ val result = Result.Failure(throwable)
+ try {
+ result.getOrThrowMessage()
+ } catch (e: UserMessageException) {
+ assertThat(e, sameInstance(throwable))
+ }
+ }
+
+ @Test
+ fun `Given message, priority and failed result with user message, When getOrThrowMessage with message and priority, Then result UserMessageException with message and priority `() {
+ val message = genUiText()
+ val priority = FeedbackPriority.URGENT
+ val throwable = Throwable()
+ val result = Result.Failure(
+ UserMessageException(
+ userMessage = UserMessage(message = genUiText(), FeedbackPriority.HIGH),
+ origin = throwable
+ )
+ )
+ try {
+ result.getOrThrowMessage(
+ message = message,
+ priority = priority
+ )
+ } catch (e: UserMessageException) {
+ assertThat(
+ e.userMessage,
+ equalTo(UserMessage(message = message, priority = priority))
+ )
+ assertThat(
+ e.origin,
+ sameInstance(throwable)
+ )
+ }
+ }
+
+ @Test
+ fun `Given message and failed result with user message, When getOrThrowMessage with message, Then result throw UserMessageException with message`() {
+ val message = genUiText()
+ val priority = FeedbackPriority.MEDIUM
+ val throwable = Throwable()
+ val result = Result.Failure(
+ UserMessageException(
+ userMessage = UserMessage(message = genUiText(), priority),
+ origin = throwable
+ )
+ )
+ try {
+ result.getOrThrowMessage(message = message)
+ } catch (e: UserMessageException) {
+ assertThat(
+ e.userMessage,
+ equalTo(UserMessage(message = message, priority = priority))
+ )
+ assertThat(
+ e.origin,
+ sameInstance(throwable)
+ )
+ }
+ }
+
+ @Test
+ fun `Given priority and failed result with user message, When getOrThrowMessage with message, Then result throw UserMessageException with priority`() {
+ val message = genUiText()
+ val priority = FeedbackPriority.MEDIUM
+ val throwable = Throwable()
+ val result = Result.Failure(
+ UserMessageException(
+ userMessage = UserMessage(message = message, FeedbackPriority.HIGH),
+ origin = throwable
+ )
+ )
+ try {
+ result.getOrThrowMessage(priority = priority)
+ } catch (e: UserMessageException) {
+ assertThat(
+ e.userMessage,
+ equalTo(UserMessage(message = message, priority = priority))
+ )
+ assertThat(
+ e.origin,
+ sameInstance(throwable)
+ )
+ }
+ }
+
+ @Test
+ fun `Given message, priority and failed result, When getOrThrowMessage with message and priority, Then result throw UserMessageException`() {
+ val message = genUiText()
+ val priority = FeedbackPriority.MEDIUM
+ val result = Result.Failure(IllegalStateException())
+ try {
+ result.getOrThrowMessage(
+ message = message,
+ priority = priority
+ )
+ } catch (e: UserMessageException) {
+ assertThat(
+ e.userMessage,
+ equalTo(UserMessage(message = message, priority = priority))
+ )
+ }
+ }
+
+ @Test
+ fun `Given failed result, When getOrThrowMessage, Then result throw UserMessageException with default options`() {
+ val result = Result.Failure(IllegalStateException())
+ try {
+ result.getOrThrowMessage()
+ } catch (e: UserMessageException) {
+ assertThat(
+ e.userMessage,
+ equalTo(UserMessage(message = UiText(StringIds.unknown_error), priority = FeedbackPriority.LOW))
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/statekit/build.gradle.kts b/core/statekit/build.gradle.kts
index caabd4283..3b44bbd59 100644
--- a/core/statekit/build.gradle.kts
+++ b/core/statekit/build.gradle.kts
@@ -10,7 +10,6 @@ android {
dependencies {
api(projects.statekit.core)
api(projects.statekit.dsl)
- api(projects.statekit.runtime)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.kotlinx.coroutines.android)
testImplementation(projects.testkit)
diff --git a/core/statekit/src/main/kotlin/com/nlab/reminder/core/statekit/store/androidx/lifecycle/StoreViewModel.kt b/core/statekit/src/main/kotlin/com/nlab/reminder/core/statekit/store/androidx/lifecycle/StoreViewModel.kt
index f75e55176..38eb03875 100644
--- a/core/statekit/src/main/kotlin/com/nlab/reminder/core/statekit/store/androidx/lifecycle/StoreViewModel.kt
+++ b/core/statekit/src/main/kotlin/com/nlab/reminder/core/statekit/store/androidx/lifecycle/StoreViewModel.kt
@@ -17,7 +17,6 @@
package com.nlab.reminder.core.statekit.store.androidx.lifecycle
import androidx.lifecycle.ViewModel
-import com.nlab.statekit.lifecycle.UiActionDispatchable
import com.nlab.statekit.store.Store
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.StateFlow
@@ -25,10 +24,10 @@ import kotlinx.coroutines.flow.StateFlow
/**
* @author Doohyun
*/
-abstract class StoreViewModel : ViewModel(), UiActionDispatchable {
+abstract class StoreViewModel : ViewModel() {
private val store: Store by lazy(LazyThreadSafetyMode.NONE) { onCreateStore() }
val uiState: StateFlow get() = store.state
- final override fun dispatch(action: A): Job = store.dispatch(action)
+ fun dispatch(action: A): Job = store.dispatch(action)
protected abstract fun onCreateStore(): Store
}
\ No newline at end of file
diff --git a/core/translation/src/main/res/values-en/strings.xml b/core/translation/src/main/res/values-en/strings.xml
index 127300198..a2782a190 100644
--- a/core/translation/src/main/res/values-en/strings.xml
+++ b/core/translation/src/main/res/values-en/strings.xml
@@ -75,7 +75,6 @@
- In addition to #%1$s, #%2$d tags are used in %3$d(+) notifications.
%1$s is being used in %2$d(+) notifications.
- Tag not found.
Tag Already Exists
Do you want to replace #%1$s with #%2$s in all of your schedules?
diff --git a/core/translation/src/main/res/values/strings.xml b/core/translation/src/main/res/values/strings.xml
index 3d24367f2..d459b3878 100644
--- a/core/translation/src/main/res/values/strings.xml
+++ b/core/translation/src/main/res/values/strings.xml
@@ -74,7 +74,6 @@
- #%1$s 외 #%2$d개의 태그가 %3$d(+)개의 알림에서 사용되고 있습니다.
#%1$s이(가) %2$d(+)개의 알림에서 사용되고 있습니다.
- 태그를 찾을 수 없습니다.
태그가 이미 존재함
사용자의 모든 스케줄에서 #%1$s을(를) #%2$s(으)로 대치하겠습니까?
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 0a02843b6..f959702fb 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -40,7 +40,6 @@ include(
":statekit:compiler",
":statekit:core",
":statekit:dsl",
- ":statekit:runtime",
":statekit:test",
":testkit"
)
@@ -50,6 +49,7 @@ include(
)
include(
":core:android",
+ ":core:androidx:compose",
":core:androidx:fragment",
":core:androidx:lifecycle",
":core:androidx:navigation-compose",
@@ -58,6 +58,8 @@ include(
":core:annotation",
":core:component:tag",
":core:component:tag-test",
+ ":core:component:usermessage",
+ ":core:component:usermessage-handle",
":core:data",
":core:data-di",
":core:data-impl",
@@ -78,7 +80,6 @@ include(
":core:schedule-test",
":core:statekit",
":core:translation",
- ":core:ui-compose",
":core:uitext",
":core:uitext-test",
)
\ No newline at end of file
diff --git a/statekit/test/src/main/kotlin/com/nlab/statekit/test/reduce/EffectScenarios.kt b/statekit/test/src/main/kotlin/com/nlab/statekit/test/reduce/EffectScenarios.kt
index 868dcfaec..bafbaa64a 100644
--- a/statekit/test/src/main/kotlin/com/nlab/statekit/test/reduce/EffectScenarios.kt
+++ b/statekit/test/src/main/kotlin/com/nlab/statekit/test/reduce/EffectScenarios.kt
@@ -21,6 +21,7 @@ import com.nlab.statekit.reduce.Reduce
import com.nlab.statekit.store.createStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.currentCoroutineContext
/**
@@ -55,7 +56,7 @@ class EffectScenario internal constructor(
fun launchIn(
coroutineScope: CoroutineScope,
shouldLaunchWithTransition: Boolean = false
- ): Job {
+ ): EffectScenarioTestJob {
val reduce = if (shouldLaunchWithTransition) reduce else Reduce(effect = reduce.effect)
val store = createStore(
coroutineScope = coroutineScope,
@@ -64,11 +65,10 @@ class EffectScenario internal constructor(
additionalEffects.fold(baseEffect) { acc, effect -> Effect.Composite(effect, acc) }
})
)
- return store.dispatch(input.action)
- }
-
- suspend fun launchAndJoin(shouldLaunchWithTransition: Boolean = false) {
- launchIn(CoroutineScope(currentCoroutineContext()), shouldLaunchWithTransition).join()
+ return EffectScenarioTestJob(
+ input = input,
+ job = store.dispatch(input.action)
+ )
}
}
@@ -79,4 +79,26 @@ class TestEffectScope internal constructor(
) {
val inputIAction: IA get() = input.action
val inputState: IS get() = input.initState
+}
+
+class EffectScenarioTestJob internal constructor(
+ val input: ScenarioInput,
+ val job: Job
+) {
+ suspend fun join() {
+ job.join()
+ }
+
+ suspend fun cancelAndJoin() {
+ job.cancelAndJoin()
+ }
+}
+
+suspend inline fun EffectScenario.launchAndJoin(
+ shouldLaunchWithTransition: Boolean = false,
+ verifyBlock: ScenarioInput.() -> Unit = {}
+) {
+ val job = launchIn(CoroutineScope(currentCoroutineContext()), shouldLaunchWithTransition)
+ job.join()
+ verifyBlock(job.input)
}
\ No newline at end of file