From 81376952982c6bb3eeefbf95bc51f5c76bd019c4 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 17:15:29 +0200 Subject: [PATCH 01/73] Init CategoryViewModel and make old things Deprecated --- .../commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt | 1 + .../viewmodel/category/CategorySummaryEvent.kt | 6 ++++++ .../viewmodel/{ => category}/CategoryViewModel.kt | 13 +++++++++++-- .../compose/category/CategoryComponent.kt | 7 +------ 4 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/{ => category}/CategoryViewModel.kt (89%) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index 7b371fe8..efa41423 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -19,6 +19,7 @@ import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel import de.hsfl.budgetBinder.presentation.viewmodel.* +import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditServerUrlViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditUserViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsViewModel diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt new file mode 100644 index 00000000..87afc891 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt @@ -0,0 +1,6 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category + +sealed class CategorySummaryEvent { + data class OnCategory(val id: Int): CategorySummaryEvent() + object OnCategoryCreate: CategorySummaryEvent() +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt similarity index 89% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/CategoryViewModel.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt index 10e8a5e3..e530700e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt @@ -1,4 +1,4 @@ -package de.hsfl.budgetBinder.presentation.viewmodel +package de.hsfl.budgetBinder.presentation.viewmodel.category import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.DataResponse @@ -13,9 +13,13 @@ class CategoryViewModel( private val categoryUseCases: CategoriesUseCases, private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) ) { + // OLD private val _state = MutableStateFlow(UiState.Empty) + + @Deprecated(message = "Use new StateFlow") val state: StateFlow = _state + @Deprecated(message = "Use StateFlow") fun getAllCategories() { categoryUseCases.getAllCategoriesUseCase.categories().onEach { when (it) { @@ -27,6 +31,7 @@ class CategoryViewModel( }.launchIn(scope) } + @Deprecated(message = "Use StateFlow") fun getCategoryById(id: Int) { categoryUseCases.getCategoryByIdUseCase(id).onEach { when (it) { @@ -38,6 +43,7 @@ class CategoryViewModel( }.launchIn(scope) } + @Deprecated(message = "Use StateFlow") fun createCategory(category: Category.In) { categoryUseCases.createCategoryUseCase(category).onEach { when (it) { @@ -49,7 +55,8 @@ class CategoryViewModel( }.launchIn(scope) } - fun changeCategory(category: Category.Patch,id: Int) { + @Deprecated(message = "Use StateFlow") + fun changeCategory(category: Category.Patch, id: Int) { categoryUseCases.changeCategoryByIdUseCase(category, id).onEach { when (it) { is DataResponse.Success -> _state.value = UiState.Success(it.data) @@ -60,6 +67,7 @@ class CategoryViewModel( }.launchIn(scope) } + @Deprecated(message = "Use StateFlow") fun removeCategory(id: Int) { categoryUseCases.deleteCategoryByIdUseCase(id).onEach { when (it) { @@ -71,6 +79,7 @@ class CategoryViewModel( }.launchIn(scope) } + @Deprecated(message = "Use StateFlow") fun getEntriesByCategory(id: Int) { categoryUseCases.getAllEntriesByCategoryUseCase(id).onEach { when (it) { diff --git a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/category/CategoryComponent.kt b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/category/CategoryComponent.kt index 5df06015..983695f3 100644 --- a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/category/CategoryComponent.kt +++ b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/category/CategoryComponent.kt @@ -4,19 +4,14 @@ import androidx.compose.runtime.* import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Constants.DEFAULT_CATEGORY import de.hsfl.budgetBinder.common.Entry -import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.viewmodel.CategoryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel import di -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import org.jetbrains.compose.web.ExperimentalComposeWebSvgApi import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.svg.Rect import org.jetbrains.compose.web.svg.Svg -import org.kodein.di.compose.localDI import org.kodein.di.instance @Composable From 03e466f8c73f4af0144cd9ff1e4fa6c7ecd086e7 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 18:07:35 +0200 Subject: [PATCH 02/73] Implement CategorySummaryViewModel.kt --- .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 2 + .../hsfl/budgetBinder/presentation/Screen.kt | 1 + .../category/CategorySummaryEvent.kt | 1 + .../category/CategorySummaryViewModel.kt | 55 +++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index efa41423..fd603737 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -19,6 +19,7 @@ import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel import de.hsfl.budgetBinder.presentation.viewmodel.* +import de.hsfl.budgetBinder.presentation.viewmodel.category.CategorySummaryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditServerUrlViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditUserViewModel @@ -93,6 +94,7 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { SettingsEditUserViewModel(instance(), instance(), instance(), instance()) } bindSingleton { SettingsEditServerUrlViewModel(instance(), instance(), instance()) } bindSingleton { CategoryViewModel(instance(), instance()) } + bindSingleton { CategorySummaryViewModel(instance(), instance(), instance()) } bindSingleton { EntryViewModel(instance(), instance()) } bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance(), instance(), instance()) } bindSingleton { NavDrawerViewModel(instance(), instance()) } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt index e6517a0a..f4e8970d 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt @@ -12,6 +12,7 @@ sealed class Screen { object Server: Settings() } sealed class Category: Screen() { + data class Detail(val id: Int): Category() object Summary: Category() object Edit: Category() object Create: Category() diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt index 87afc891..3f146ea2 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt @@ -3,4 +3,5 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category sealed class CategorySummaryEvent { data class OnCategory(val id: Int): CategorySummaryEvent() object OnCategoryCreate: CategorySummaryEvent() + object OnRefresh: CategorySummaryEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt new file mode 100644 index 00000000..6b296300 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt @@ -0,0 +1,55 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category + +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.DataResponse +import de.hsfl.budgetBinder.domain.usecase.GetAllCategoriesUseCase +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class CategorySummaryViewModel( + private val getAllCategoriesUseCase: GetAllCategoriesUseCase, + private val routerFlow: RouterFlow, + private val scope: CoroutineScope +) { + + private val _categoryList = MutableStateFlow>(emptyList()) + val categoryList: StateFlow> = _categoryList + + private val _eventFlow = UiEventSharedFlow.mutableEventFlow + val eventFlow = UiEventSharedFlow.eventFlow + + init { + getAllCategories() + } + + fun onEvent(event: CategorySummaryEvent) { + when (event) { + is CategorySummaryEvent.OnCategory -> routerFlow.navigateTo(Screen.Category.Detail(event.id)) + is CategorySummaryEvent.OnCategoryCreate -> routerFlow.navigateTo(Screen.Category.Summary) + is CategorySummaryEvent.OnRefresh -> getAllCategories() + } + } + + private fun getAllCategories() = scope.launch { + getAllCategoriesUseCase.categories().collect { response -> + when (response) { + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + is DataResponse.Unauthorized -> { + _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + routerFlow.navigateTo(Screen.Login) + } + is DataResponse.Success -> { + _categoryList.value = response.data!! + _eventFlow.emit(UiEvent.ShowSuccess("TODO: Change to HideSuccess")) + } + } + } + } +} From 68451ad833fa23c9b72ab3a0408dab60b3ce5793 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 19:14:08 +0200 Subject: [PATCH 03/73] Implement CategorySummary View --- .../kotlin/de/hsfl/budgetBinder/Router.kt | 2 + .../screens/category/CategoryComponent.kt | 8 +++ .../screens/category/CategorySummary.kt | 69 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt create mode 100644 budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt index f8d2d400..2752b219 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt @@ -8,6 +8,7 @@ import de.hsfl.budgetBinder.screens.login.LoginComponent import de.hsfl.budgetBinder.screens.register.RegisterComponent import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.screens.category.CategoryComponent import de.hsfl.budgetBinder.screens.settings.SettingsView import org.kodein.di.instance @@ -21,6 +22,7 @@ fun Router() { is Screen.Login -> LoginComponent() is Screen.Dashboard -> DashboardComponent() is Screen.Settings.Menu, Screen.Settings.User, Screen.Settings.Server -> SettingsView() + is Screen.Category.Summary -> CategoryComponent() else -> {} } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt new file mode 100644 index 00000000..16f52d3d --- /dev/null +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt @@ -0,0 +1,8 @@ +package de.hsfl.budgetBinder.screens.category + +import androidx.compose.runtime.Composable + +@Composable +fun CategoryComponent() { + CategorySummary() +} diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt new file mode 100644 index 00000000..29b9545f --- /dev/null +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt @@ -0,0 +1,69 @@ +package de.hsfl.budgetBinder.screens.category + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import de.hsfl.budgetBinder.di +import de.hsfl.budgetBinder.presentation.CategoryImageToIcon +import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.viewmodel.category.CategorySummaryEvent +import de.hsfl.budgetBinder.presentation.viewmodel.category.CategorySummaryViewModel +import kotlinx.coroutines.flow.collectLatest +import org.kodein.di.instance + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun CategorySummary() { + val viewModel: CategorySummaryViewModel by di.instance() + val categoryList = viewModel.categoryList.collectAsState() + val scaffoldState = rememberScaffoldState() + val loadingState = remember { mutableStateOf(false) } + + LaunchedEffect(key1 = true) { + viewModel.eventFlow.collectLatest { event -> + when (event) { + is UiEvent.ShowLoading -> loadingState.value = true + else -> loadingState.value = false + } + } + } + + Scaffold(scaffoldState = scaffoldState, + floatingActionButtonPosition = FabPosition.End, + floatingActionButton = { + FloatingActionButton(onClick = { viewModel.onEvent(CategorySummaryEvent.OnCategoryCreate) }) { + Icon(Icons.Default.Add, contentDescription = null) + } + } + ) { + if (loadingState.value) LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + LazyColumn { + items(categoryList.value) { category -> + ListItem( + modifier = Modifier.clickable(onClick = { viewModel.onEvent(CategorySummaryEvent.OnCategory(category.id)) }), + text = { Text("Budget: ${category.name}") }, + secondaryText = { Text(category.budget.toString()) }, + icon = { + Box(modifier = Modifier.clip(CircleShape).background(Color("af${category.color}".toLong(16)))) { + Box(modifier = Modifier.align(Alignment.Center).padding(12.dp)) { + CategoryImageToIcon(category.image) + } + } + }, + ) + } + } + } +} From 057f0a25f7d4a4074e9d76d7b258d06fa6e4c18b Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 19:57:55 +0200 Subject: [PATCH 04/73] Implement CategoryDetailViewModel.kt --- .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 6 +- .../viewmodel/category/CategoryViewModel.kt | 91 +----------------- .../viewmodel/category/_CategoryViewModel.kt | 93 +++++++++++++++++++ .../category/detail/CategoryDetailEvent.kt | 7 ++ .../detail/CategoryDetailViewModel.kt | 41 ++++++++ .../{ => summary}/CategorySummaryEvent.kt | 3 +- .../{ => summary}/CategorySummaryViewModel.kt | 2 +- .../compose/category/CategoryComponent.kt | 4 +- .../screens/category/CategorySummary.kt | 4 +- 9 files changed, 153 insertions(+), 98 deletions(-) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/_CategoryViewModel.kt create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/{ => summary}/CategorySummaryEvent.kt (67%) rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/{ => summary}/CategorySummaryViewModel.kt (96%) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index fd603737..ba8fbf4c 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -19,8 +19,8 @@ import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel import de.hsfl.budgetBinder.presentation.viewmodel.* -import de.hsfl.budgetBinder.presentation.viewmodel.category.CategorySummaryViewModel -import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditServerUrlViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditUserViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsViewModel @@ -93,7 +93,7 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { SettingsViewModel(instance(), instance(), instance(), instance()) } bindSingleton { SettingsEditUserViewModel(instance(), instance(), instance(), instance()) } bindSingleton { SettingsEditServerUrlViewModel(instance(), instance(), instance()) } - bindSingleton { CategoryViewModel(instance(), instance()) } + bindSingleton { _CategoryViewModel(instance(), instance()) } bindSingleton { CategorySummaryViewModel(instance(), instance(), instance()) } bindSingleton { EntryViewModel(instance(), instance()) } bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance(), instance(), instance()) } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt index e530700e..218dd618 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt @@ -1,93 +1,6 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category -import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.DataResponse -import de.hsfl.budgetBinder.domain.usecase.* -import de.hsfl.budgetBinder.presentation.UiState -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.* +import de.hsfl.budgetBinder.presentation.flow.RouterFlow -class CategoryViewModel( - private val categoryUseCases: CategoriesUseCases, - private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) -) { - // OLD - private val _state = MutableStateFlow(UiState.Empty) - - @Deprecated(message = "Use new StateFlow") - val state: StateFlow = _state - - @Deprecated(message = "Use StateFlow") - fun getAllCategories() { - categoryUseCases.getAllCategoriesUseCase.categories().onEach { - when (it) { - is DataResponse.Success -> _state.value = UiState.Success(it.data) - is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) - is DataResponse.Loading -> _state.value = UiState.Loading - is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized - } - }.launchIn(scope) - } - - @Deprecated(message = "Use StateFlow") - fun getCategoryById(id: Int) { - categoryUseCases.getCategoryByIdUseCase(id).onEach { - when (it) { - is DataResponse.Success -> _state.value = UiState.Success(it.data) - is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) - is DataResponse.Loading -> _state.value = UiState.Loading - is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized - } - }.launchIn(scope) - } - - @Deprecated(message = "Use StateFlow") - fun createCategory(category: Category.In) { - categoryUseCases.createCategoryUseCase(category).onEach { - when (it) { - is DataResponse.Success -> _state.value = UiState.Success(it.data) - is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) - is DataResponse.Loading -> _state.value = UiState.Loading - is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized - } - }.launchIn(scope) - } - - @Deprecated(message = "Use StateFlow") - fun changeCategory(category: Category.Patch, id: Int) { - categoryUseCases.changeCategoryByIdUseCase(category, id).onEach { - when (it) { - is DataResponse.Success -> _state.value = UiState.Success(it.data) - is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) - is DataResponse.Loading -> _state.value = UiState.Loading - is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized - } - }.launchIn(scope) - } - - @Deprecated(message = "Use StateFlow") - fun removeCategory(id: Int) { - categoryUseCases.deleteCategoryByIdUseCase(id).onEach { - when (it) { - is DataResponse.Success -> _state.value = UiState.Success(it.data) - is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) - is DataResponse.Loading -> _state.value = UiState.Loading - is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized - } - }.launchIn(scope) - } - - @Deprecated(message = "Use StateFlow") - fun getEntriesByCategory(id: Int) { - categoryUseCases.getAllEntriesByCategoryUseCase(id).onEach { - when (it) { - is DataResponse.Success -> _state.value = UiState.Success(it.data) - is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) - is DataResponse.Loading -> _state.value = UiState.Loading - is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized - } - }.launchIn(scope) - } +class CategoryViewModel() { } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/_CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/_CategoryViewModel.kt new file mode 100644 index 00000000..1c0cc181 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/_CategoryViewModel.kt @@ -0,0 +1,93 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category + +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.DataResponse +import de.hsfl.budgetBinder.domain.usecase.* +import de.hsfl.budgetBinder.presentation.UiState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.* + +class _CategoryViewModel( + private val categoryUseCases: CategoriesUseCases, + private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) +) { + // OLD + private val _state = MutableStateFlow(UiState.Empty) + + @Deprecated(message = "Use new StateFlow") + val state: StateFlow = _state + + @Deprecated(message = "Use StateFlow") + fun getAllCategories() { + categoryUseCases.getAllCategoriesUseCase.categories().onEach { + when (it) { + is DataResponse.Success -> _state.value = UiState.Success(it.data) + is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) + is DataResponse.Loading -> _state.value = UiState.Loading + is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized + } + }.launchIn(scope) + } + + @Deprecated(message = "Use StateFlow") + fun getCategoryById(id: Int) { + categoryUseCases.getCategoryByIdUseCase(id).onEach { + when (it) { + is DataResponse.Success -> _state.value = UiState.Success(it.data) + is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) + is DataResponse.Loading -> _state.value = UiState.Loading + is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized + } + }.launchIn(scope) + } + + @Deprecated(message = "Use StateFlow") + fun createCategory(category: Category.In) { + categoryUseCases.createCategoryUseCase(category).onEach { + when (it) { + is DataResponse.Success -> _state.value = UiState.Success(it.data) + is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) + is DataResponse.Loading -> _state.value = UiState.Loading + is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized + } + }.launchIn(scope) + } + + @Deprecated(message = "Use StateFlow") + fun changeCategory(category: Category.Patch, id: Int) { + categoryUseCases.changeCategoryByIdUseCase(category, id).onEach { + when (it) { + is DataResponse.Success -> _state.value = UiState.Success(it.data) + is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) + is DataResponse.Loading -> _state.value = UiState.Loading + is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized + } + }.launchIn(scope) + } + + @Deprecated(message = "Use StateFlow") + fun removeCategory(id: Int) { + categoryUseCases.deleteCategoryByIdUseCase(id).onEach { + when (it) { + is DataResponse.Success -> _state.value = UiState.Success(it.data) + is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) + is DataResponse.Loading -> _state.value = UiState.Loading + is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized + } + }.launchIn(scope) + } + + @Deprecated(message = "Use StateFlow") + fun getEntriesByCategory(id: Int) { + categoryUseCases.getAllEntriesByCategoryUseCase(id).onEach { + when (it) { + is DataResponse.Success -> _state.value = UiState.Success(it.data) + is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) + is DataResponse.Loading -> _state.value = UiState.Loading + is DataResponse.Unauthorized -> _state.value = UiState.Unauthorized + } + }.launchIn(scope) + } +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt new file mode 100644 index 00000000..29e75295 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt @@ -0,0 +1,7 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category.detail + +sealed class CategoryDetailEvent { + object OnEdit: CategoryDetailEvent() + object OnDelete: CategoryDetailEvent() + data class OnEntry(val id: Int): CategoryDetailEvent() +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt new file mode 100644 index 00000000..23c4f266 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt @@ -0,0 +1,41 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category.detail + +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.domain.usecase.GetAllEntriesByCategoryUseCase +import de.hsfl.budgetBinder.domain.usecase.GetCategoryByIdUseCase +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class CategoryDetailViewModel( + private val getCategoryByIdUseCase: GetCategoryByIdUseCase, + private val getAllEntriesByCategoryUseCase: GetAllEntriesByCategoryUseCase, + private val routerFlow: RouterFlow, + private val scope: CoroutineScope +) { + private val _categoryState = + MutableStateFlow(Category(id = -1, name = "0", color = "111111", image = Category.Image.DEFAULT, budget = 0f)) + val categoryState: StateFlow = _categoryState + + private val _entryList = MutableStateFlow>(emptyList()) + val entryList: StateFlow> = _entryList + + + fun onEvent(event: CategoryDetailEvent) { + when (event) { + is CategoryDetailEvent.OnEdit -> routerFlow.navigateTo(Screen.Category.Edit) + is CategoryDetailEvent.OnDelete -> {} + is CategoryDetailEvent.OnEntry -> { /* TODO: Navigate to Entry detail */ + } + } + } + + private fun deleteCurrentEntry() { + + } + + +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt similarity index 67% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt index 3f146ea2..d5cef432 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt @@ -1,7 +1,8 @@ -package de.hsfl.budgetBinder.presentation.viewmodel.category +package de.hsfl.budgetBinder.presentation.viewmodel.category.summary sealed class CategorySummaryEvent { data class OnCategory(val id: Int): CategorySummaryEvent() object OnCategoryCreate: CategorySummaryEvent() object OnRefresh: CategorySummaryEvent() + // TODO: Maybe delete? } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt similarity index 96% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt index 6b296300..2216e4ba 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt @@ -1,4 +1,4 @@ -package de.hsfl.budgetBinder.presentation.viewmodel.category +package de.hsfl.budgetBinder.presentation.viewmodel.category.summary import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.DataResponse diff --git a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/category/CategoryComponent.kt b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/category/CategoryComponent.kt index 983695f3..8d483556 100644 --- a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/category/CategoryComponent.kt +++ b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/category/CategoryComponent.kt @@ -5,7 +5,7 @@ import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Constants.DEFAULT_CATEGORY import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel import di import org.jetbrains.compose.web.ExperimentalComposeWebSvgApi import org.jetbrains.compose.web.css.* @@ -31,7 +31,7 @@ fun CategoryComponent(screenState: MutableState) { val categoryViewModel = CategoryViewModel(getAllCategoriesUseCase, getCategoryByIdUseCase,createCategoryUseCase, changeCategoryByIdUseCase, deleteCategoryByIdUseCase, getAllEntriesByCategoryUseCase, scope) val viewState = categoryViewModel.state.collectAsState(scope)*/ - val viewModel: CategoryViewModel by di.instance() + val viewModel: _CategoryViewModel by di.instance() val viewState = viewModel.state.collectAsState(scope.coroutineContext) when (screenState.value) { diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt index 29b9545f..50998478 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt @@ -18,8 +18,8 @@ import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon import de.hsfl.budgetBinder.presentation.UiEvent -import de.hsfl.budgetBinder.presentation.viewmodel.category.CategorySummaryEvent -import de.hsfl.budgetBinder.presentation.viewmodel.category.CategorySummaryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryEvent +import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel import kotlinx.coroutines.flow.collectLatest import org.kodein.di.instance From bf89345bf56c857fbd8535ce64baf98a184f326d Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Thu, 16 Jun 2022 23:36:29 +0200 Subject: [PATCH 05/73] Implement DashboardViewModel --- .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 1 + .../viewmodel/dashboard/DashboardEvent.kt | 7 ++ .../viewmodel/dashboard/DashboardState.kt | 10 +++ .../{ => dashboard}/DashboardViewModel.kt | 82 +++++++++++++++++-- 4 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/{ => dashboard}/DashboardViewModel.kt (56%) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index ba8fbf4c..c50f45ff 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -19,6 +19,7 @@ import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel import de.hsfl.budgetBinder.presentation.viewmodel.* +import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditServerUrlViewModel diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt new file mode 100644 index 00000000..0c3f3c15 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt @@ -0,0 +1,7 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.dashboard + +sealed class DashboardEvent { + object OnCategoryChanged : DashboardEvent() + data class OnEntry(val id: Int) : DashboardEvent() + object OnCategoryCreate : DashboardEvent() +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt new file mode 100644 index 00000000..b561a7ff --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt @@ -0,0 +1,10 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.dashboard + +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.Entry + +data class DashboardState( + val categoryList: List = emptyList(), + val entryList: List = emptyList(), + val focusedCategory: Category = Category(0, "Allgemein", "FFFFFF", Category.Image.DEFAULT, 0f) +) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt similarity index 56% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/DashboardViewModel.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index d6d7a971..d2aee8bd 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -1,15 +1,18 @@ -package de.hsfl.budgetBinder.presentation.viewmodel +package de.hsfl.budgetBinder.presentation.viewmodel.dashboard import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch class DashboardViewModel( private val dashboardUseCases: DashboardUseCases, @@ -20,19 +23,82 @@ class DashboardViewModel( private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) ) { - private val _categoriesState = MutableStateFlow(UiState.Empty) - val categoriesState: StateFlow = _categoriesState - private val _entriesState = MutableStateFlow(UiState.Empty) - val entriesState: StateFlow = _entriesState + private val _categoryListState = MutableStateFlow(DashboardState()) + val categoryListSate: StateFlow = _categoryListState + + private val _entryListState = MutableStateFlow(DashboardState()) + val entryListState: StateFlow = _entryListState + + private val _focusedCategoryState = MutableStateFlow(DashboardState()) + val focusedCategoryState: StateFlow = _focusedCategoryState + + private val _eventFlow = UiEventSharedFlow.mutableEventFlow + val eventFlow = _eventFlow.asSharedFlow() // Everytime the View is open or only once? init { + _getAllEntries() + _getAllCategories() + getAllEntries() getAllCategories() } + fun onEvent(event: DashboardEvent) { + when (event) { + is DashboardEvent.OnCategoryChanged -> { /* TODO: On Category Changed */ + } + is DashboardEvent.OnEntry -> { /* TODO: On Entry Clicked */ + } + is DashboardEvent.OnCategoryCreate -> {/* TODO: On Category Create */ + } + } + } + + private fun getAllEntries() { + scope.launch { + dashboardUseCases.getAllEntriesUseCase.entries().collect { + when (it) { + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Success -> { + _entryListState.value = entryListState.value.copy(entryList = it.data!!) + } + } + } + } + } + + private fun getAllCategories() { + scope.launch { + dashboardUseCases.getAllCategoriesUseCase.categories().collect { + when (it) { + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Success -> { + _categoryListState.value = categoryListSate.value.copy(categoryList = it.data!!) + } + } + } + } + } + + + // Old + private val _categoriesState = MutableStateFlow(UiState.Empty) + + @Deprecated(message = "Use new StateFlow") + val categoriesState: StateFlow = _categoriesState + + private val _entriesState = MutableStateFlow(UiState.Empty) + + @Deprecated(message = "Use new StateFlow") + val entriesState: StateFlow = _entriesState + private val _state = MutableStateFlow(UiState.Empty) + + @Deprecated(message = "Use new StateFlow") val state: StateFlow = _state fun logOut(onAllDevices: Boolean) { @@ -58,7 +124,8 @@ class DashboardViewModel( } - private fun getAllCategories() { + @Deprecated(message = "Use new StateFlow") + private fun _getAllCategories() { dashboardUseCases.getAllCategoriesUseCase.categories().onEach { when (it) { is DataResponse.Success -> _categoriesState.value = UiState.Success(it.data) @@ -69,7 +136,8 @@ class DashboardViewModel( }.launchIn(scope) } - private fun getAllEntries() { + @Deprecated(message = "Use new StateFlow") + private fun _getAllEntries() { dashboardUseCases.getAllEntriesUseCase.entries().onEach { when (it) { is DataResponse.Success -> _entriesState.value = UiState.Success(it.data) From 34158e67501a2aa366dccf8bf82f4b99ea9a800d Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Fri, 17 Jun 2022 00:15:00 +0200 Subject: [PATCH 06/73] Implement DashboardView --- .../viewmodel/dashboard/DashboardEvent.kt | 2 +- .../viewmodel/dashboard/DashboardViewModel.kt | 2 +- .../viewmodel/login/LoginViewModel.kt | 2 +- .../compose/dashboard/DashboardComponent.kt | 9 +- .../screens/dashboard/DashboardComponent.kt | 77 ++++++++++++---- .../screens/login/LoginComponent.kt | 90 +++++++++---------- 6 files changed, 105 insertions(+), 77 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt index 0c3f3c15..6b8b00c4 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt @@ -3,5 +3,5 @@ package de.hsfl.budgetBinder.presentation.viewmodel.dashboard sealed class DashboardEvent { object OnCategoryChanged : DashboardEvent() data class OnEntry(val id: Int) : DashboardEvent() - object OnCategoryCreate : DashboardEvent() + object OnEntryCreate : DashboardEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index d2aee8bd..25e1f55c 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -51,7 +51,7 @@ class DashboardViewModel( } is DashboardEvent.OnEntry -> { /* TODO: On Entry Clicked */ } - is DashboardEvent.OnCategoryCreate -> {/* TODO: On Category Create */ + is DashboardEvent.OnEntryCreate -> {/* TODO: On Category Create */ } } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt index e2cdbc82..9f414177 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt @@ -46,7 +46,7 @@ class LoginViewModel( _eventFlow.emit(UiEvent.ShowLoading) dataFlow.storeUserState(it.data!!) delay(1000L) - routerFlow.navigateTo(Screen.Settings.Menu) + routerFlow.navigateTo(Screen.Dashboard) } else -> { // If the request failed diff --git a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/dashboard/DashboardComponent.kt index c123f872..c7237976 100644 --- a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/dashboard/DashboardComponent.kt @@ -2,13 +2,8 @@ package de.hsfl.budgetBinder.compose.dashboard import androidx.compose.runtime.* import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.domain.usecase.* -import de.hsfl.budgetBinder.presentation.viewmodel.DashboardViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import di -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import org.kodein.di.compose.localDI import org.kodein.di.instance @Composable @@ -34,4 +29,4 @@ fun DashboardComponent(screenState: MutableState) { onEntryCreateButton = {screenState.value = Screen.EntryCreate}, onEntryEditButton = {screenState.value = Screen.EntryEdit} ) -} \ No newline at end of file +} diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index 784f903c..bec5f3eb 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -1,34 +1,77 @@ package de.hsfl.budgetBinder.screens.dashboard +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ShoppingCart +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.di +import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow -import de.hsfl.budgetBinder.presentation.viewmodel.DashboardViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEvent +import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel +import kotlinx.coroutines.flow.collectLatest import org.kodein.di.instance @Composable fun DashboardComponent() { - val scope = rememberCoroutineScope() - val dataFlow: DataFlow by di.instance() - val userState = dataFlow.userState.collectAsState(scope.coroutineContext) val viewModel: DashboardViewModel by di.instance() - val logoutState = viewModel.state.collectAsState(scope.coroutineContext) + val categoryList = viewModel.categoryListSate.collectAsState() + val entryList = viewModel.entryListState.collectAsState() + val focusedCategory = viewModel.entryListState.collectAsState() + val scaffoldState = rememberScaffoldState() + val loadingState = remember { mutableStateOf(false) } - Column { - Text(userState.value.toString()) - Row { - Button(onClick = { viewModel.logOut(true) }) { - Text("Logout") + LaunchedEffect(key1 = true) { + viewModel.eventFlow.collectLatest { event -> + when (event) { + UiEvent.ShowLoading -> loadingState.value = true + else -> loadingState.value = false } - Button(onClick = { viewModel.getMyUser() }) { - Text("Update") + } + } + + if(loadingState.value) { + LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + } + Scaffold( + scaffoldState = scaffoldState, + floatingActionButton = { + FloatingActionButton(onClick = { viewModel.onEvent(DashboardEvent.OnEntryCreate) }) { + Icon(Icons.Default.Add, contentDescription = null) } + }, + floatingActionButtonPosition = FabPosition.End, + isFloatingActionButtonDocked = true + ) { + Column { + EntryList(entryList = entryList.value.entryList) + } + } +} + + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun EntryList(entryList: List) { + when { + entryList.isEmpty() -> Text("No entries here, you can create an new entry.") + } + LazyColumn { + items(entryList) { entry -> + ListItem( + text = { Text(entry.name) }, + icon = { Icon(Icons.Default.ShoppingCart, contentDescription = null) }, + trailing = { Text("${entry.amount} €") } + ) } } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt index 24b75fd7..011ef49a 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt @@ -24,74 +24,64 @@ import org.kodein.di.instance fun LoginComponent() { val scope = rememberCoroutineScope() val viewModel: LoginViewModel by di.instance() - val emailTextState = viewModel.emailText.collectAsState(scope.coroutineContext) val passwordTextState = viewModel.passwordText.collectAsState(scope.coroutineContext) val serverUrlState = viewModel.serverUrlText.collectAsState(scope.coroutineContext) val openDialog = viewModel.dialogState.collectAsState(scope.coroutineContext) val localFocusManager = LocalFocusManager.current - val scaffoldState = rememberScaffoldState() val loadingState = remember { mutableStateOf(false) } LaunchedEffect(key1 = true) { viewModel.eventFlow.collectLatest { event -> when (event) { - is UiEvent.ShowLoading -> { - // TODO: Refactor this, it's working but ahh - loadingState.value = true - } - is UiEvent.ShowError -> { - loadingState.value = false - scaffoldState.snackbarHostState.showSnackbar(message = event.msg, actionLabel = "Dissmiss") - } + is UiEvent.ShowLoading -> loadingState.value = true + else -> loadingState.value = false } } } - Scaffold(scaffoldState = scaffoldState) { - if (loadingState.value) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) - } - ServerUrlDialog( - value = serverUrlState.value.serverAddress, - onValueChange = { viewModel.onEvent(LoginEvent.EnteredServerUrl(it)) }, - openDialog = openDialog.value, - onConfirm = { viewModel.onEvent(LoginEvent.OnServerUrlDialogConfirm) }, - onDismiss = { viewModel.onEvent(LoginEvent.OnServerUrlDialogDismiss) } + if (loadingState.value) { + LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + } + ServerUrlDialog( + value = serverUrlState.value.serverAddress, + onValueChange = { viewModel.onEvent(LoginEvent.EnteredServerUrl(it)) }, + openDialog = openDialog.value, + onConfirm = { viewModel.onEvent(LoginEvent.OnServerUrlDialogConfirm) }, + onDismiss = { viewModel.onEvent(LoginEvent.OnServerUrlDialogDismiss) } + ) + Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { + AppIcon(modifier = Modifier.size(128.dp).padding(8.dp)) + Text(text = "Welcome back to Budget Binder 💸", style = MaterialTheme.typography.h5) + Spacer(modifier = Modifier.height(8.dp)) + EmailTextField( + value = emailTextState.value.email, + onValueChange = { viewModel.onEvent(LoginEvent.EnteredEmail(it)) }, + label = { Text("Email") }, + isError = !emailTextState.value.emailValid, + enabled = !loadingState.value ) - Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { - AppIcon(modifier = Modifier.size(128.dp).padding(8.dp)) - Text(text = "Welcome back to Budget Binder 💸", style = MaterialTheme.typography.h5) - Spacer(modifier = Modifier.height(8.dp)) - EmailTextField( - value = emailTextState.value.email, - onValueChange = { viewModel.onEvent(LoginEvent.EnteredEmail(it)) }, - label = { Text("Email") }, - isError = !emailTextState.value.emailValid, - enabled = !loadingState.value - ) - OutlinedTextField( - value = passwordTextState.value.password, - onValueChange = { viewModel.onEvent(LoginEvent.EnteredPassword(it)) }, - label = { Text("Password") }, - visualTransformation = PasswordVisualTransformation(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), - singleLine = true - ) - Button(onClick = { + OutlinedTextField( + value = passwordTextState.value.password, + onValueChange = { viewModel.onEvent(LoginEvent.EnteredPassword(it)) }, + label = { Text("Password") }, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + singleLine = true + ) + Button(onClick = { + localFocusManager.clearFocus() + viewModel.onEvent(LoginEvent.OnLogin) + }) { + Text("Login") + } + Box(modifier = Modifier.fillMaxSize()) { + TextButton(modifier = Modifier.align(Alignment.BottomCenter), onClick = { localFocusManager.clearFocus() - viewModel.onEvent(LoginEvent.OnLogin) + viewModel.onEvent(LoginEvent.OnRegisterScreen) }) { - Text("Login") - } - Box(modifier = Modifier.fillMaxSize()) { - TextButton(modifier = Modifier.align(Alignment.BottomCenter), onClick = { - localFocusManager.clearFocus() - viewModel.onEvent(LoginEvent.OnRegisterScreen) - }) { - Text("Or Register your Account here") - } + Text("Or Register your Account here") } } } From 5b6f7631bbcc72484a6f028a485ab487d102d680 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Fri, 17 Jun 2022 11:03:53 +0200 Subject: [PATCH 07/73] Make id from getEntriesFromCategory nullable --- .../kotlin/de/hsfl/budgetBinder/data/client/Client.kt | 6 +++--- .../budgetBinder/data/repository/CategoryRepositoryImpl.kt | 4 ++-- .../budgetBinder/domain/repository/CategoryRepository.kt | 4 ++-- .../hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt index 8e810b0c..a8ef4087 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt @@ -104,7 +104,7 @@ interface ApiClient { * Get Entries from a Category ID * @param id ID from category to get the Entries */ - suspend fun getEntriesFromCategory(id: Int): APIResponse> + suspend fun getEntriesFromCategory(id: Int?): APIResponse> /** * Get All Entries from current month. The request has a query in the link ?current @@ -261,7 +261,7 @@ class Client( engine: HttpClientEngine) : ApiClient { }.body() } - override suspend fun getEntriesFromCategory(id: Int): APIResponse> { + override suspend fun getEntriesFromCategory(id: Int?): APIResponse> { return client.get(urlString = "/categories/$id/entries").body() } @@ -300,4 +300,4 @@ class Client( engine: HttpClientEngine) : ApiClient { contentType(ContentType.Application.Json) }.body() } -} \ No newline at end of file +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/repository/CategoryRepositoryImpl.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/repository/CategoryRepositoryImpl.kt index 1265ee2d..1625ede0 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/repository/CategoryRepositoryImpl.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/repository/CategoryRepositoryImpl.kt @@ -33,7 +33,7 @@ class CategoryRepositoryImpl( return client.deleteCategoryById(id) } - override suspend fun getEntriesFromCategory(id: Int): APIResponse> { + override suspend fun getEntriesFromCategory(id: Int?): APIResponse> { return client.getEntriesFromCategory(id) } -} \ No newline at end of file +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/repository/CategoryRepository.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/repository/CategoryRepository.kt index c92c44e1..faab5b34 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/repository/CategoryRepository.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/repository/CategoryRepository.kt @@ -52,5 +52,5 @@ interface CategoryRepository { * @param id ID from Category to get all Entries from this * @author Cedrik Hoffmann */ - suspend fun getEntriesFromCategory(id: Int): APIResponse> -} \ No newline at end of file + suspend fun getEntriesFromCategory(id: Int?): APIResponse> +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt index 957479b8..9cd8f144 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt @@ -147,7 +147,7 @@ class DeleteCategoryByIdUseCase(private val repository: CategoryRepository) { } class GetAllEntriesByCategoryUseCase(private val repository: CategoryRepository) { - operator fun invoke(id: Int): Flow>> = flow { + operator fun invoke(id: Int?): Flow>> = flow { try { emit(DataResponse.Loading()) repository.getEntriesFromCategory(id).let { response -> From 9de061dfb1084be86cab5cb3ad23d1d0f8cdbe8f Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Fri, 17 Jun 2022 11:42:37 +0200 Subject: [PATCH 08/73] Implement change category logic --- .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 4 +- .../domain/usecase/DashboardUseCases.kt | 3 +- .../viewmodel/dashboard/DashboardEvent.kt | 3 +- .../viewmodel/dashboard/DashboardState.kt | 7 +- .../viewmodel/dashboard/DashboardViewModel.kt | 98 ++++++++++++++----- 5 files changed, 84 insertions(+), 31 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index c50f45ff..08c93161 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -79,7 +79,7 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { CategoriesUseCases(instance(), instance(), instance(), instance(), instance(), instance()) } bindSingleton { SettingsUseCases(instance(), instance(), instance()) } bindSingleton { LoginUseCases(instance(), instance()) } - bindSingleton { DashboardUseCases(instance(), instance()) } + bindSingleton { DashboardUseCases(instance(), instance(), instance()) } bindSingleton { RegisterUseCases(instance(), instance(), instance()) } bindSingleton { DataFlowUseCases(instance(), instance(), instance()) } @@ -97,6 +97,6 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { _CategoryViewModel(instance(), instance()) } bindSingleton { CategorySummaryViewModel(instance(), instance(), instance()) } bindSingleton { EntryViewModel(instance(), instance()) } - bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance(), instance(), instance()) } + bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance(), instance()) } bindSingleton { NavDrawerViewModel(instance(), instance()) } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/DashboardUseCases.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/DashboardUseCases.kt index c2f5d485..3c1c857f 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/DashboardUseCases.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/DashboardUseCases.kt @@ -2,5 +2,6 @@ package de.hsfl.budgetBinder.domain.usecase data class DashboardUseCases( val getAllEntriesUseCase: GetAllEntriesUseCase, - val getAllCategoriesUseCase: GetAllCategoriesUseCase + val getAllCategoriesUseCase: GetAllCategoriesUseCase, + val getAllEntriesByCategoryUseCase: GetAllEntriesByCategoryUseCase, ) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt index 6b8b00c4..d8dc872f 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt @@ -1,7 +1,8 @@ package de.hsfl.budgetBinder.presentation.viewmodel.dashboard sealed class DashboardEvent { - object OnCategoryChanged : DashboardEvent() + object OnPrevCategory : DashboardEvent() + object OnNextCategory : DashboardEvent() data class OnEntry(val id: Int) : DashboardEvent() object OnEntryCreate : DashboardEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt index b561a7ff..2b7f5fe7 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt @@ -4,7 +4,8 @@ import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Entry data class DashboardState( - val categoryList: List = emptyList(), - val entryList: List = emptyList(), - val focusedCategory: Category = Category(0, "Allgemein", "FFFFFF", Category.Image.DEFAULT, 0f) + val hasPrev: Boolean = false, + val hasNext: Boolean = true, + val category: Category = Category(0, "Overall", "111111", Category.Image.DEFAULT, 0f), + val entryList: List = emptyList() ) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 25e1f55c..0a3d300e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -1,6 +1,8 @@ package de.hsfl.budgetBinder.presentation.viewmodel.dashboard +import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.DataResponse +import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.UiEvent @@ -19,13 +21,11 @@ class DashboardViewModel( private val logoutUseCase: LogoutUseCase, private val routerFlow: RouterFlow, private val dataFlow: DataFlow, - private val getMyUserUseCase: GetMyUserUseCase, private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) ) { - - private val _categoryListState = MutableStateFlow(DashboardState()) - val categoryListSate: StateFlow = _categoryListState + private var internalCategoryId = -1 + private val _categoryListState = MutableStateFlow>(emptyList()) private val _entryListState = MutableStateFlow(DashboardState()) val entryListState: StateFlow = _entryListState @@ -36,23 +36,21 @@ class DashboardViewModel( private val _eventFlow = UiEventSharedFlow.mutableEventFlow val eventFlow = _eventFlow.asSharedFlow() - // Everytime the View is open or only once? init { _getAllEntries() _getAllCategories() getAllEntries() getAllCategories() + } fun onEvent(event: DashboardEvent) { when (event) { - is DashboardEvent.OnCategoryChanged -> { /* TODO: On Category Changed */ - } - is DashboardEvent.OnEntry -> { /* TODO: On Entry Clicked */ - } - is DashboardEvent.OnEntryCreate -> {/* TODO: On Category Create */ - } + is DashboardEvent.OnNextCategory -> changedFocusedCategory(increase = true) + is DashboardEvent.OnPrevCategory -> changedFocusedCategory(increase = false) + is DashboardEvent.OnEntry -> {} + is DashboardEvent.OnEntryCreate -> {} } } @@ -77,13 +75,77 @@ class DashboardViewModel( is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { - _categoryListState.value = categoryListSate.value.copy(categoryList = it.data!!) + _categoryListState.value = it.data!! + getEntriesByCategory() } } } } } + private fun getEntriesByCategory(id: Int? = null) { + scope.launch { + dashboardUseCases.getAllEntriesByCategoryUseCase(id).collect { + when (it) { + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Success -> { + _entryListState.value = entryListState.value.copy(entryList = it.data!!) + } + } + } + } + } + + private fun changedFocusedCategory(increase: Boolean) { + changeInternalCategoryId(increase) + when (internalCategoryId) { + -1 -> setOverallCategoryState() + in _categoryListState.value.indices -> setCategoryState() + _categoryListState.value.size -> setCategoryWithNoCategory() + } + } + + private fun changeInternalCategoryId(increase: Boolean) { + var newFocusedCategory = internalCategoryId + if (increase) + newFocusedCategory++ + else + newFocusedCategory-- + internalCategoryId = + when { + newFocusedCategory > -1 -> -1 + newFocusedCategory > _categoryListState.value.size -> _categoryListState.value.size + else -> newFocusedCategory + } + } + + private fun setOverallCategoryState() { + _focusedCategoryState.value = focusedCategoryState.value.copy( + hasPrev = false, + hasNext = true, + category = Category(0, "Overall", "111111", Category.Image.DEFAULT, 0f) + ) + getAllCategories() + } + + private fun setCategoryState() { + _focusedCategoryState.value = focusedCategoryState.value.copy( + hasPrev = true, + hasNext = true, + category = _categoryListState.value[internalCategoryId] + ) + getEntriesByCategory(id = focusedCategoryState.value.category.id) + } + + private fun setCategoryWithNoCategory() { + _focusedCategoryState.value = focusedCategoryState.value.copy( + hasPrev = true, + hasNext = false, + category = Category(0, "No Category", "111111", Category.Image.DEFAULT, 0f) + ) + getEntriesByCategory(id = null) + } // Old private val _categoriesState = MutableStateFlow(UiState.Empty) @@ -112,18 +174,6 @@ class DashboardViewModel( }.launchIn(scope) } - fun getMyUser() { - getMyUserUseCase().onEach { - when (it) { - is DataResponse.Loading -> _state.value = UiState.Loading - is DataResponse.Success<*> -> dataFlow.storeUserState(it.data!!) - is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) - is DataResponse.Unauthorized -> routerFlow.navigateTo(Screen.Login) - } - }.launchIn(scope) - - } - @Deprecated(message = "Use new StateFlow") private fun _getAllCategories() { dashboardUseCases.getAllCategoriesUseCase.categories().onEach { From d17567bbd947d8987444e104d528ce15034d5a5b Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Fri, 17 Jun 2022 12:39:38 +0200 Subject: [PATCH 09/73] Fix some logic bugs --- .../viewmodel/dashboard/DashboardViewModel.kt | 15 ++++---- .../screens/dashboard/DashboardComponent.kt | 36 +++++++++++++++---- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 0a3d300e..c5e54c87 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -37,8 +37,8 @@ class DashboardViewModel( val eventFlow = _eventFlow.asSharedFlow() init { - _getAllEntries() - _getAllCategories() + //_getAllEntries() + //_getAllCategories() getAllEntries() getAllCategories() @@ -50,7 +50,9 @@ class DashboardViewModel( is DashboardEvent.OnNextCategory -> changedFocusedCategory(increase = true) is DashboardEvent.OnPrevCategory -> changedFocusedCategory(increase = false) is DashboardEvent.OnEntry -> {} - is DashboardEvent.OnEntryCreate -> {} + is DashboardEvent.OnEntryCreate -> scope.launch { + _eventFlow.emit(UiEvent.ShowError("OnEntryCreate Clicked")) + } } } @@ -76,7 +78,6 @@ class DashboardViewModel( is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { _categoryListState.value = it.data!! - getEntriesByCategory() } } } @@ -98,12 +99,14 @@ class DashboardViewModel( } private fun changedFocusedCategory(increase: Boolean) { + println("DashboardViewModel::Category before, ${focusedCategoryState.value.category}") changeInternalCategoryId(increase) when (internalCategoryId) { -1 -> setOverallCategoryState() in _categoryListState.value.indices -> setCategoryState() _categoryListState.value.size -> setCategoryWithNoCategory() } + println("DashboardViewModel::Category after, ${focusedCategoryState.value.category}") } private fun changeInternalCategoryId(increase: Boolean) { @@ -114,7 +117,7 @@ class DashboardViewModel( newFocusedCategory-- internalCategoryId = when { - newFocusedCategory > -1 -> -1 + newFocusedCategory < -1 -> -1 newFocusedCategory > _categoryListState.value.size -> _categoryListState.value.size else -> newFocusedCategory } @@ -126,7 +129,7 @@ class DashboardViewModel( hasNext = true, category = Category(0, "Overall", "111111", Category.Image.DEFAULT, 0f) ) - getAllCategories() + getAllEntries() } private fun setCategoryState() { diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index bec5f3eb..74122551 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -9,11 +9,15 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowForward import androidx.compose.material.icons.filled.ShoppingCart import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.di +import de.hsfl.budgetBinder.presentation.CategoryImageToIcon import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEvent @@ -24,22 +28,22 @@ import org.kodein.di.instance @Composable fun DashboardComponent() { val viewModel: DashboardViewModel by di.instance() - val categoryList = viewModel.categoryListSate.collectAsState() + //val categoryList = viewModel.categoryListSate.collectAsState() val entryList = viewModel.entryListState.collectAsState() - val focusedCategory = viewModel.entryListState.collectAsState() + val focusedCategory = viewModel.focusedCategoryState.collectAsState() val scaffoldState = rememberScaffoldState() val loadingState = remember { mutableStateOf(false) } LaunchedEffect(key1 = true) { viewModel.eventFlow.collectLatest { event -> when (event) { - UiEvent.ShowLoading -> loadingState.value = true + is UiEvent.ShowLoading -> loadingState.value = true else -> loadingState.value = false } } } - if(loadingState.value) { + if (loadingState.value) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } Scaffold( @@ -53,23 +57,41 @@ fun DashboardComponent() { isFloatingActionButtonDocked = true ) { Column { + Row { + if (focusedCategory.value.hasPrev) { + IconButton(onClick = { viewModel.onEvent(DashboardEvent.OnPrevCategory) }) { + Icon(Icons.Default.ArrowBack, contentDescription = null) + } + } + Text(focusedCategory.value.category.name) + if (focusedCategory.value.hasNext) { + IconButton(onClick = { viewModel.onEvent(DashboardEvent.OnNextCategory) }) { + Icon(Icons.Default.ArrowForward, contentDescription = null) + } + } + } EntryList(entryList = entryList.value.entryList) } } } +@Composable +fun FocusedCategory(category: Category) { + Column { Text(category.name) } +} + @OptIn(ExperimentalMaterialApi::class) @Composable -private fun EntryList(entryList: List) { +private fun EntryList(entryList: List, categoryIcon: Category.Image = Category.Image.DEFAULT) { when { - entryList.isEmpty() -> Text("No entries here, you can create an new entry.") + entryList.isEmpty() -> Text("This category has no entries. You can create an new entry.") } LazyColumn { items(entryList) { entry -> ListItem( text = { Text(entry.name) }, - icon = { Icon(Icons.Default.ShoppingCart, contentDescription = null) }, + icon = { CategoryImageToIcon(icon = categoryIcon) }, trailing = { Text("${entry.amount} €") } ) } From 372d52dd497ebb7e2e0bacf2c2589ecd1b44cc97 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Fri, 17 Jun 2022 15:17:18 +0200 Subject: [PATCH 10/73] Implement new UiEvent HideSuccess and new DashboardEvent onRefresh --- .../hsfl/budgetBinder/presentation/UiEvent.kt | 3 ++ .../viewmodel/dashboard/DashboardEvent.kt | 2 ++ .../viewmodel/dashboard/DashboardState.kt | 3 +- .../viewmodel/dashboard/DashboardViewModel.kt | 29 ++++++++++++++++--- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt index d0838dc7..642caed7 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt @@ -9,4 +9,7 @@ sealed class UiEvent { // Show Success data class ShowSuccess(val msg: String) : UiEvent() + + // Call on Success, with could reset the loading state + object HideSuccess : UiEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt index d8dc872f..48f82a4b 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt @@ -1,8 +1,10 @@ package de.hsfl.budgetBinder.presentation.viewmodel.dashboard + sealed class DashboardEvent { object OnPrevCategory : DashboardEvent() object OnNextCategory : DashboardEvent() + object OnRefresh: DashboardEvent() data class OnEntry(val id: Int) : DashboardEvent() object OnEntryCreate : DashboardEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt index 2b7f5fe7..6022890f 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt @@ -7,5 +7,6 @@ data class DashboardState( val hasPrev: Boolean = false, val hasNext: Boolean = true, val category: Category = Category(0, "Overall", "111111", Category.Image.DEFAULT, 0f), - val entryList: List = emptyList() + val entryList: List = emptyList(), + val spendBudgetOnCurrentCategory: Float = 0f ) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index c5e54c87..721e7fe9 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -33,12 +33,16 @@ class DashboardViewModel( private val _focusedCategoryState = MutableStateFlow(DashboardState()) val focusedCategoryState: StateFlow = _focusedCategoryState + private val _spendBudgetOnCurrentCategory = MutableStateFlow(DashboardState()) + val spendBudgetOnCurrentCategory: StateFlow = _spendBudgetOnCurrentCategory + private val _eventFlow = UiEventSharedFlow.mutableEventFlow val eventFlow = _eventFlow.asSharedFlow() init { - //_getAllEntries() - //_getAllCategories() + // Throws nullPointerException? + _getAllEntries() + _getAllCategories() getAllEntries() getAllCategories() @@ -53,6 +57,13 @@ class DashboardViewModel( is DashboardEvent.OnEntryCreate -> scope.launch { _eventFlow.emit(UiEvent.ShowError("OnEntryCreate Clicked")) } + is DashboardEvent.OnRefresh -> { + when (internalCategoryId) { + -1 -> getAllEntries() + in _categoryListState.value.indices -> getEntriesByCategory(id = focusedCategoryState.value.category.id) + _categoryListState.value.size -> getEntriesByCategory(id = null) + } + } } } @@ -63,7 +74,9 @@ class DashboardViewModel( is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { + calcSpendBudgetOnCategory() _entryListState.value = entryListState.value.copy(entryList = it.data!!) + _eventFlow.emit(UiEvent.HideSuccess) } } } @@ -78,6 +91,7 @@ class DashboardViewModel( is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { _categoryListState.value = it.data!! + _eventFlow.emit(UiEvent.HideSuccess) } } } @@ -91,7 +105,9 @@ class DashboardViewModel( is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { + calcSpendBudgetOnCategory() _entryListState.value = entryListState.value.copy(entryList = it.data!!) + _eventFlow.emit(UiEvent.HideSuccess) } } } @@ -99,14 +115,12 @@ class DashboardViewModel( } private fun changedFocusedCategory(increase: Boolean) { - println("DashboardViewModel::Category before, ${focusedCategoryState.value.category}") changeInternalCategoryId(increase) when (internalCategoryId) { -1 -> setOverallCategoryState() in _categoryListState.value.indices -> setCategoryState() _categoryListState.value.size -> setCategoryWithNoCategory() } - println("DashboardViewModel::Category after, ${focusedCategoryState.value.category}") } private fun changeInternalCategoryId(increase: Boolean) { @@ -150,6 +164,13 @@ class DashboardViewModel( getEntriesByCategory(id = null) } + private fun calcSpendBudgetOnCategory() { + var spendMoney = 0F + entryListState.value.entryList.onEach { spendMoney += it.amount } + _spendBudgetOnCurrentCategory.value = + spendBudgetOnCurrentCategory.value.copy(spendBudgetOnCurrentCategory = spendMoney) + } + // Old private val _categoriesState = MutableStateFlow(UiState.Empty) From 52b94fa5c56b81251f6a79d2072321fb8b9bbab8 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Fri, 17 Jun 2022 18:43:19 +0200 Subject: [PATCH 11/73] Fix calcSpendBudgetOnCategory --- .../viewmodel/dashboard/DashboardViewModel.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 721e7fe9..d690ced3 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -21,7 +21,7 @@ class DashboardViewModel( private val logoutUseCase: LogoutUseCase, private val routerFlow: RouterFlow, private val dataFlow: DataFlow, - private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) + private val scope: CoroutineScope ) { private var internalCategoryId = -1 @@ -40,9 +40,9 @@ class DashboardViewModel( val eventFlow = _eventFlow.asSharedFlow() init { - // Throws nullPointerException? - _getAllEntries() - _getAllCategories() + // Throws nullPointerException, crash on Android? + //_getAllEntries() + //_getAllCategories() getAllEntries() getAllCategories() @@ -58,6 +58,7 @@ class DashboardViewModel( _eventFlow.emit(UiEvent.ShowError("OnEntryCreate Clicked")) } is DashboardEvent.OnRefresh -> { + getAllCategories() when (internalCategoryId) { -1 -> getAllEntries() in _categoryListState.value.indices -> getEntriesByCategory(id = focusedCategoryState.value.category.id) @@ -74,9 +75,9 @@ class DashboardViewModel( is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { - calcSpendBudgetOnCategory() _entryListState.value = entryListState.value.copy(entryList = it.data!!) _eventFlow.emit(UiEvent.HideSuccess) + calcSpendBudgetOnCategory() } } } @@ -105,9 +106,9 @@ class DashboardViewModel( is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { - calcSpendBudgetOnCategory() _entryListState.value = entryListState.value.copy(entryList = it.data!!) _eventFlow.emit(UiEvent.HideSuccess) + calcSpendBudgetOnCategory() } } } @@ -166,7 +167,13 @@ class DashboardViewModel( private fun calcSpendBudgetOnCategory() { var spendMoney = 0F - entryListState.value.entryList.onEach { spendMoney += it.amount } + entryListState.value.entryList.onEach { + if (it.amount > 0) { + spendMoney -= it.amount + } else { + spendMoney += (it.amount * -1) + } + } _spendBudgetOnCurrentCategory.value = spendBudgetOnCurrentCategory.value.copy(spendBudgetOnCurrentCategory = spendMoney) } From 24aae8730afddbf4f07697e606cc9cb4efea420e Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Fri, 17 Jun 2022 19:04:55 +0200 Subject: [PATCH 12/73] Map Entry with Category Icon --- .../dashboard/DashboardEntryState.kt | 9 +++++++ .../viewmodel/dashboard/DashboardState.kt | 2 +- .../viewmodel/dashboard/DashboardViewModel.kt | 27 +++++++++++++++---- 3 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEntryState.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEntryState.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEntryState.kt new file mode 100644 index 00000000..b0b944a0 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEntryState.kt @@ -0,0 +1,9 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.dashboard + +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.Entry + +data class DashboardEntryState( + val entry: Entry, + val categoryImage: Category.Image = Category.Image.DEFAULT +) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt index 6022890f..8b820753 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardState.kt @@ -7,6 +7,6 @@ data class DashboardState( val hasPrev: Boolean = false, val hasNext: Boolean = true, val category: Category = Category(0, "Overall", "111111", Category.Image.DEFAULT, 0f), - val entryList: List = emptyList(), + val entryList: List = emptyList(), val spendBudgetOnCurrentCategory: Float = 0f ) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index d690ced3..f95e759b 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -75,7 +75,12 @@ class DashboardViewModel( is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { - _entryListState.value = entryListState.value.copy(entryList = it.data!!) + _entryListState.value = entryListState.value.copy(entryList = it.data!!.map { entry -> + DashboardEntryState( + entry, + categoryImage = getCategoryByEntry(entry)?.image ?: Category.Image.DEFAULT + ) + }) _eventFlow.emit(UiEvent.HideSuccess) calcSpendBudgetOnCategory() } @@ -106,7 +111,12 @@ class DashboardViewModel( is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { - _entryListState.value = entryListState.value.copy(entryList = it.data!!) + _entryListState.value = entryListState.value.copy(entryList = it.data!!.map { entry -> + DashboardEntryState( + entry, + categoryImage = getCategoryByEntry(entry)?.image ?: Category.Image.DEFAULT + ) + }) _eventFlow.emit(UiEvent.HideSuccess) calcSpendBudgetOnCategory() } @@ -115,6 +125,13 @@ class DashboardViewModel( } } + private fun getCategoryByEntry(entry: Entry): Category? { + _categoryListState.value.forEach { category -> + if (category.id == entry.category_id) return category + } + return null + } + private fun changedFocusedCategory(increase: Boolean) { changeInternalCategoryId(increase) when (internalCategoryId) { @@ -168,10 +185,10 @@ class DashboardViewModel( private fun calcSpendBudgetOnCategory() { var spendMoney = 0F entryListState.value.entryList.onEach { - if (it.amount > 0) { - spendMoney -= it.amount + if (it.entry.amount > 0) { + spendMoney -= it.entry.amount } else { - spendMoney += (it.amount * -1) + spendMoney += (it.entry.amount * -1) } } _spendBudgetOnCurrentCategory.value = From cecc02af2d17c7ca3e6153404a3c51f09313654d Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 00:53:34 +0200 Subject: [PATCH 13/73] Fix wrong API Call --- .../kotlin/de/hsfl/budgetBinder/data/client/Client.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt index a8ef4087..a72440e2 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt @@ -272,7 +272,7 @@ class Client( engine: HttpClientEngine) : ApiClient { } override suspend fun getAllEntries(period: String): APIResponse> { - return client.submitForm(url = "/categories", formParameters = Parameters.build { + return client.submitForm(url = "/entries", formParameters = Parameters.build { append("period", period) }, encodeInQuery = true).body() } From 9ee807cfa6a2f4caa2baa94ca159e29ee1097d5c Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 00:56:27 +0200 Subject: [PATCH 14/73] Implement older entries on request --- .../viewmodel/dashboard/DashboardEvent.kt | 2 + .../viewmodel/dashboard/DashboardViewModel.kt | 84 +++++++++++++++++-- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt index 48f82a4b..2aa782fc 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt @@ -5,6 +5,8 @@ sealed class DashboardEvent { object OnPrevCategory : DashboardEvent() object OnNextCategory : DashboardEvent() object OnRefresh: DashboardEvent() + object OnLoadMore: DashboardEvent() data class OnEntry(val id: Int) : DashboardEvent() + data class OnEntryDelete(val id: Int): DashboardEvent() object OnEntryCreate : DashboardEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index f95e759b..64d12984 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -10,9 +10,8 @@ import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow +import io.ktor.util.date.* import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -25,11 +24,16 @@ class DashboardViewModel( ) { private var internalCategoryId = -1 - private val _categoryListState = MutableStateFlow>(emptyList()) + private val currentMonth: Month = GMTDate().month + private var lastRequestedMonth = currentMonth + private val _categoryListState = MutableStateFlow>(emptyList()) private val _entryListState = MutableStateFlow(DashboardState()) val entryListState: StateFlow = _entryListState + private val _oldEntriesMapState = MutableStateFlow>>(mutableMapOf()) + val oldEntriesMapState: StateFlow>> = _oldEntriesMapState + private val _focusedCategoryState = MutableStateFlow(DashboardState()) val focusedCategoryState: StateFlow = _focusedCategoryState @@ -44,9 +48,8 @@ class DashboardViewModel( //_getAllEntries() //_getAllCategories() - getAllEntries() getAllCategories() - + getAllEntries() } fun onEvent(event: DashboardEvent) { @@ -55,7 +58,7 @@ class DashboardViewModel( is DashboardEvent.OnPrevCategory -> changedFocusedCategory(increase = false) is DashboardEvent.OnEntry -> {} is DashboardEvent.OnEntryCreate -> scope.launch { - _eventFlow.emit(UiEvent.ShowError("OnEntryCreate Clicked")) + _eventFlow.emit(UiEvent.ShowError(currentMonth.toMonthString())) } is DashboardEvent.OnRefresh -> { getAllCategories() @@ -65,6 +68,8 @@ class DashboardViewModel( _categoryListState.value.size -> getEntriesByCategory(id = null) } } + is DashboardEvent.OnLoadMore -> loadMoreEntries() + is DashboardEvent.OnEntryDelete -> {} } } @@ -125,6 +130,31 @@ class DashboardViewModel( } } + private fun getAllEntriesFromMonth(month: Month) = scope.launch { + // TODO: Year had also be to check + dashboardUseCases.getAllEntriesUseCase.entries("${month.toMonthString()}-${GMTDate().year}").collect { + when (it) { + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Success -> { + _eventFlow.emit(UiEvent.HideSuccess) + _oldEntriesMapState.value.putAll( + mapOf( + Pair( + month.toMonthString(), + it.data!! + ) + ) + ) + } + is DataResponse.Unauthorized -> { + _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + routerFlow.navigateTo(Screen.Login) + } + } + } + } + private fun getCategoryByEntry(entry: Entry): Category? { _categoryListState.value.forEach { category -> if (category.id == entry.category_id) return category @@ -141,6 +171,11 @@ class DashboardViewModel( } } + /** + * Change the internal Id to swipe between the categories + * @param increase if the next or prev button is pressed + * @author Cedrik Hoffmann + */ private fun changeInternalCategoryId(increase: Boolean) { var newFocusedCategory = internalCategoryId if (increase) @@ -155,15 +190,23 @@ class DashboardViewModel( } } + /** + * Set the first category, all entries are here + */ private fun setOverallCategoryState() { + var totalBudget = 0f + _categoryListState.value.forEach { totalBudget += it.budget } _focusedCategoryState.value = focusedCategoryState.value.copy( hasPrev = false, hasNext = true, - category = Category(0, "Overall", "111111", Category.Image.DEFAULT, 0f) + category = Category(0, "Overall", "111111", Category.Image.DEFAULT, totalBudget) ) getAllEntries() } + /** + * Set the category state for the current category, wich the user moved to + */ private fun setCategoryState() { _focusedCategoryState.value = focusedCategoryState.value.copy( hasPrev = true, @@ -173,6 +216,9 @@ class DashboardViewModel( getEntriesByCategory(id = focusedCategoryState.value.category.id) } + /** + * All entries with no category are shown in this category + */ private fun setCategoryWithNoCategory() { _focusedCategoryState.value = focusedCategoryState.value.copy( hasPrev = true, @@ -182,6 +228,9 @@ class DashboardViewModel( getEntriesByCategory(id = null) } + /** + * Calculate the spend money for a category + */ private fun calcSpendBudgetOnCategory() { var spendMoney = 0F entryListState.value.entryList.onEach { @@ -195,6 +244,27 @@ class DashboardViewModel( spendBudgetOnCurrentCategory.value.copy(spendBudgetOnCurrentCategory = spendMoney) } + private fun loadMoreEntries() { + getAllEntriesFromMonth( + month = Month.from(lastRequestedMonth.ordinal - 1) + ) + lastRequestedMonth = Month.from(lastRequestedMonth.ordinal - 1) + } + + + /** + * Helper Function to convert object in String for ApiRequest + * Month.ordinal starts at 0, so we have to add by 1 every time + * if (Month.ordinal + 1) < 10 then add a 0 at the front + * else return the ordinal + */ + private fun Month.toMonthString(): String { + return when { + this.ordinal + 1 < 10 -> "0${this.ordinal + 1}" + else -> "${this.ordinal + 1}" + } + } + // Old private val _categoriesState = MutableStateFlow(UiState.Empty) From 02d962915f7c796f297582bfeb9b03e10072478f Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 01:01:35 +0200 Subject: [PATCH 15/73] Implement remove entries --- .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 2 +- .../domain/usecase/DashboardUseCases.kt | 1 + .../screens/dashboard/DashboardComponent.kt | 176 ++++++++++++++++-- 3 files changed, 160 insertions(+), 19 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index 08c93161..517bca6a 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -79,7 +79,7 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { CategoriesUseCases(instance(), instance(), instance(), instance(), instance(), instance()) } bindSingleton { SettingsUseCases(instance(), instance(), instance()) } bindSingleton { LoginUseCases(instance(), instance()) } - bindSingleton { DashboardUseCases(instance(), instance(), instance()) } + bindSingleton { DashboardUseCases(instance(), instance(), instance(), instance()) } bindSingleton { RegisterUseCases(instance(), instance(), instance()) } bindSingleton { DataFlowUseCases(instance(), instance(), instance()) } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/DashboardUseCases.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/DashboardUseCases.kt index 3c1c857f..758fb310 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/DashboardUseCases.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/DashboardUseCases.kt @@ -4,4 +4,5 @@ data class DashboardUseCases( val getAllEntriesUseCase: GetAllEntriesUseCase, val getAllCategoriesUseCase: GetAllCategoriesUseCase, val getAllEntriesByCategoryUseCase: GetAllEntriesByCategoryUseCase, + val deleteEntryByIdUseCase: DeleteEntryByIdUseCase ) diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index 74122551..19de8cd5 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -1,17 +1,16 @@ package de.hsfl.budgetBinder.screens.dashboard -import androidx.compose.foundation.background +import androidx.compose.foundation.* import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.ArrowForward -import androidx.compose.material.icons.filled.ShoppingCart +import androidx.compose.material.icons.Icons.Default +import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import de.hsfl.budgetBinder.common.Category @@ -19,7 +18,7 @@ import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon import de.hsfl.budgetBinder.presentation.UiEvent -import de.hsfl.budgetBinder.presentation.flow.DataFlow +import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEntryState import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import kotlinx.coroutines.flow.collectLatest @@ -31,6 +30,8 @@ fun DashboardComponent() { //val categoryList = viewModel.categoryListSate.collectAsState() val entryList = viewModel.entryListState.collectAsState() val focusedCategory = viewModel.focusedCategoryState.collectAsState() + val totalSpendBudget = viewModel.spendBudgetOnCurrentCategory.collectAsState() + val olderEntries = viewModel.oldEntriesMapState.collectAsState() val scaffoldState = rememberScaffoldState() val loadingState = remember { mutableStateOf(false) } @@ -42,35 +43,45 @@ fun DashboardComponent() { } } } - - if (loadingState.value) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) - } Scaffold( scaffoldState = scaffoldState, floatingActionButton = { FloatingActionButton(onClick = { viewModel.onEvent(DashboardEvent.OnEntryCreate) }) { - Icon(Icons.Default.Add, contentDescription = null) + Icon(Default.Add, contentDescription = null) } }, floatingActionButtonPosition = FabPosition.End, isFloatingActionButtonDocked = true ) { + if (loadingState.value) { + LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + } Column { Row { if (focusedCategory.value.hasPrev) { IconButton(onClick = { viewModel.onEvent(DashboardEvent.OnPrevCategory) }) { - Icon(Icons.Default.ArrowBack, contentDescription = null) + Icon(Default.ArrowBack, contentDescription = null) } } Text(focusedCategory.value.category.name) if (focusedCategory.value.hasNext) { IconButton(onClick = { viewModel.onEvent(DashboardEvent.OnNextCategory) }) { - Icon(Icons.Default.ArrowForward, contentDescription = null) + Icon(Default.ArrowForward, contentDescription = null) } } } + Text("Spend: ${totalSpendBudget.value.spendBudgetOnCurrentCategory}") + Text("Total: ${focusedCategory.value.category.budget}") + Button(onClick = { viewModel.onEvent(DashboardEvent.OnRefresh) }) { + Text("Update") + } EntryList(entryList = entryList.value.entryList) + OlderEntryList(olderEntries.value) + Button(onClick = { viewModel.onEvent(DashboardEvent.OnLoadMore) }) { + Text("Load more") + } + //EntryList() + } } } @@ -83,17 +94,146 @@ fun FocusedCategory(category: Category) { @OptIn(ExperimentalMaterialApi::class) @Composable -private fun EntryList(entryList: List, categoryIcon: Category.Image = Category.Image.DEFAULT) { +private fun EntryList(entryList: List) { + when { entryList.isEmpty() -> Text("This category has no entries. You can create an new entry.") } LazyColumn { - items(entryList) { entry -> + items(entryList) { state -> + Divider() ListItem( - text = { Text(entry.name) }, - icon = { CategoryImageToIcon(icon = categoryIcon) }, - trailing = { Text("${entry.amount} €") } + text = { Text(state.entry.name) }, + icon = { CategoryImageToIcon(icon = state.categoryImage) }, + trailing = { Text("${state.entry.amount} €") } ) } } } + +@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) +@Composable +private fun OlderEntryList(entryMap: Map>) { + + when { + entryMap.isEmpty() -> Text("There are not more entries") + } + LazyColumn { + entryMap.forEach { (date, entries) -> + stickyHeader { + Text(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxWidth(), text = date) + } + + items(entries) { entry -> + Divider() + ListItem( + text = { Text(entry.name) }, + icon = { Icon(Icons.Default.List, contentDescription = null) }, + trailing = { Text("${entry.amount} €") } + ) + } + } + } +} + + +@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) +@Composable +private fun EntryList() { + data class _DashboardEntryState( + val entry: Entry, + val categoryImage: Category.Image = Category.Image.DEFAULT, + val date: String = "02-2022" + ) + + val dateEntryList = listOf<_DashboardEntryState>( + _DashboardEntryState( + entry = Entry(0, "Entry 1", -20.51F, false, null), + date = "06-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 2", -20.51F, false, null), + date = "06-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 3", -20.51F, false, null), + date = "06-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 4", -20.51F, false, null), + date = "06-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 5", -20.51F, false, null), + date = "05-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 6", -20.51F, false, null), + date = "05-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 7", -20.51F, false, null), + date = "04-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 8", -20.51F, false, null), + date = "04-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 9", -20.51F, false, null), + date = "04-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 10", -20.51F, false, null), + date = "04-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 11", -20.51F, false, null), + date = "04-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 12", -20.51F, false, null), + date = "04-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 13", -20.51F, false, null), + date = "03-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 14", -20.51F, false, null), + date = "03-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 15", -20.51F, false, null), + date = "03-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 16", -20.51F, false, null), + date = "03-2022" + ), + _DashboardEntryState( + entry = Entry(0, "Entry 17", -20.51F, false, null), + date = "03-2022" + ) + ) + + val groupList = dateEntryList.groupBy { it.date } + + val listState = rememberLazyListState() + LazyColumn(state = listState) { + groupList.forEach { (date, states) -> + stickyHeader { + Text(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxWidth(), text = date) + } + + items(states) { state -> + Divider() + ListItem( + text = { Text(state.entry.name) }, + icon = { CategoryImageToIcon(icon = state.categoryImage) }, + trailing = { Text("${state.entry.amount} €") } + ) + } + } + } +} From 05c80ad87ffe2b8a4a0f220a3a02082678378f32 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 01:01:59 +0200 Subject: [PATCH 16/73] Implement remove entries --- .../viewmodel/dashboard/DashboardViewModel.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 64d12984..f569e92d 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -69,7 +69,7 @@ class DashboardViewModel( } } is DashboardEvent.OnLoadMore -> loadMoreEntries() - is DashboardEvent.OnEntryDelete -> {} + is DashboardEvent.OnEntryDelete -> deleteEntry(id = event.id) } } @@ -155,6 +155,19 @@ class DashboardViewModel( } } + private fun deleteEntry(id: Int) = scope.launch { + dashboardUseCases.deleteEntryByIdUseCase(id).collect { + when (it) { + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Success -> { + _eventFlow.emit(UiEvent.ShowSuccess("Removed Category")) + onEvent(DashboardEvent.OnRefresh) + } + } + } + } + private fun getCategoryByEntry(entry: Entry): Category? { _categoryListState.value.forEach { category -> if (category.id == entry.category_id) return category From b3b76266e760288c0ee9101d3f00f0224437b90d Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 01:44:44 +0200 Subject: [PATCH 17/73] Implement CategoryIcons --- .../budgetBinder/presentation/CategoryIcon.kt | 76 +++++++++---------- .../budgetBinder/presentation/CategoryIcon.kt | 74 +++++++++--------- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt index 4c4329bc..a2366958 100644 --- a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt +++ b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt @@ -16,45 +16,45 @@ actual fun CategoryImageToIcon(icon: Category.Image) { Category.Image.WRONG -> Icon(imageVector = Icons.Default.Dangerous, contentDescription = null) Category.Image.HOME -> Icon(imageVector = Icons.Default.Home, contentDescription = null) Category.Image.FOOD -> Icon(imageVector = Icons.Default.BakeryDining, contentDescription = null) - Category.Image.FASTFOOD -> Icon(imageVector = Icons.Default.BakeryDining, contentDescription = null) + Category.Image.FASTFOOD -> Icon(imageVector = Icons.Default.Fastfood, contentDescription = null) + Category.Image.RESTAURANT -> Icon(imageVector = Icons.Default.Restaurant, contentDescription = null) + Category.Image.FAMILY -> Icon(imageVector = Icons.Default.People, contentDescription = null) + Category.Image.MONEY -> Icon(imageVector = Icons.Default.Payments, contentDescription = null) + Category.Image.HEALTH -> Icon(imageVector = Icons.Default.HealthAndSafety, contentDescription = null) + Category.Image.MEDICATION -> Icon(imageVector = Icons.Default.Medication, contentDescription = null) + Category.Image.INVEST -> Icon(imageVector = Icons.Default.QueryStats, contentDescription = null) + Category.Image.SPORT -> Icon(imageVector = Icons.Default.SportsSoccer, contentDescription = null) + Category.Image.CLOTH -> Icon(imageVector = Icons.Default.Checkroom, contentDescription = null) + Category.Image.GIFT -> Icon(imageVector = Icons.Default.Redeem, contentDescription = null) + Category.Image.WEALTH -> Icon(imageVector = Icons.Default.MonetizationOn, contentDescription = null) + Category.Image.FLOWER -> Icon(imageVector = Icons.Default.LocalFlorist, contentDescription = null) + Category.Image.PET -> Icon(imageVector = Icons.Default.Pets, contentDescription = null) + Category.Image.BILLS -> Icon(imageVector = Icons.Default.Receipt, contentDescription = null) + Category.Image.KEYBOARD -> Icon(imageVector = Icons.Default.Redeem, contentDescription = null) + Category.Image.PRINTER -> Icon(imageVector = Icons.Default.Print, contentDescription = null) + Category.Image.WATER -> Icon(imageVector = Icons.Default.WaterDrop, contentDescription = null) + Category.Image.FIRE -> Icon(imageVector = Icons.Default.LocalFireDepartment, contentDescription = null) + Category.Image.STAR -> Icon(imageVector = Icons.Default.Grade, contentDescription = null) + Category.Image.SAVINGS -> Icon(imageVector = Icons.Default.Savings, contentDescription = null) + Category.Image.CAR -> Icon(imageVector = Icons.Default.CarRepair, contentDescription = null) + Category.Image.BIKE -> Icon(imageVector = Icons.Default.PedalBike, contentDescription = null) + Category.Image.TRAIN -> Icon(imageVector = Icons.Default.DirectionsTransit, contentDescription = null) + Category.Image.MOPED -> Icon(imageVector = Icons.Default.Moped, contentDescription = null) + Category.Image.MOTORCYCLE -> Icon(imageVector = Icons.Default.TwoWheeler, contentDescription = null) + Category.Image.ELECTRONICS -> Icon(imageVector = Icons.Default.ElectricalServices, contentDescription = null) + Category.Image.BOOK -> Icon(imageVector = Icons.Default.Book, contentDescription = null) + Category.Image.FLIGHT -> Icon(imageVector = Icons.Default.FlightTakeoff, contentDescription = null) + Category.Image.WORK -> Icon(imageVector = Icons.Default.Work, contentDescription = null) + Category.Image.MOON -> Icon(imageVector = Icons.Default.NightlightRound, contentDescription = null) + Category.Image.LOCK -> Icon(imageVector = Icons.Default.Https, contentDescription = null) + Category.Image.PHONE -> Icon(imageVector = Icons.Default.PermPhoneMsg, contentDescription = null) + Category.Image.STORE -> Icon(imageVector = Icons.Default.StoreMallDirectory, contentDescription = null) + Category.Image.BAR -> Icon(imageVector = Icons.Default.Nightlife, contentDescription = null) + Category.Image.FOREST -> Icon(imageVector = Icons.Default.Redeem, contentDescription = null) + Category.Image.HARDWARE -> Icon(imageVector = Icons.Default.Hardware, contentDescription = null) + Category.Image.PEST -> Icon(imageVector = Icons.Default.PestControl, contentDescription = null) else -> { - Icon(imageVector = Icons.Default.QuestionAnswer, contentDescription = null) + Icon(imageVector = Icons.Default.ShoppingCart, contentDescription = null) } - /*Category.Image.RESTAURANT -> "restaurant" - Category.Image.FAMILY -> "people" - Category.Image.MONEY -> "payments" - Category.Image.HEALTH -> "health_and_safety" - Category.Image.MEDICATION -> "medication" - Category.Image.INVEST -> "query_stats" - Category.Image.SPORT -> "sports_soccer" - Category.Image.CLOTH -> "checkroom" - Category.Image.GIFT -> "redeem" - Category.Image.WEALTH -> "monetization_on" - Category.Image.FLOWER -> "local_florist" - Category.Image.PET -> "pets" - Category.Image.BILLS -> "receipt" - Category.Image.KEYBOARD-> "redeem" - Category.Image.PRINTER-> "print" - Category.Image.WATER -> "water_drop" - Category.Image.FIRE -> "fire" - Category.Image.STAR -> "grade" - Category.Image.SAVINGS -> "savings" - Category.Image.CAR -> "minor_crash" - Category.Image.BIKE -> "pedal_bike" - Category.Image.TRAIN -> "directions_transit" - Category.Image.MOPED-> "moped" - Category.Image.MOTORCYCLE -> "two_wheeler" - Category.Image.ELECTRONICS -> "electrical_services" - Category.Image.BOOK -> "book" - Category.Image.FLIGHT -> "flight_takeoff" - Category.Image.WORK -> "work" - Category.Image.MOON -> "nightlight_round" - Category.Image.LOCK -> "https" - Category.Image.PHONE -> "perm_phone_msg" - Category.Image.STORE -> "store_mall_directory" - Category.Image.BAR -> "nightlife" - Category.Image.FOREST -> "forest" - Category.Image.HARDWARE -> "hardware" - Category.Image.PEST -> "pest_control"*/ } } diff --git a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt index 4c4329bc..b46b6cfa 100644 --- a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt +++ b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt @@ -17,44 +17,44 @@ actual fun CategoryImageToIcon(icon: Category.Image) { Category.Image.HOME -> Icon(imageVector = Icons.Default.Home, contentDescription = null) Category.Image.FOOD -> Icon(imageVector = Icons.Default.BakeryDining, contentDescription = null) Category.Image.FASTFOOD -> Icon(imageVector = Icons.Default.BakeryDining, contentDescription = null) + Category.Image.RESTAURANT -> Icon(imageVector = Icons.Default.Restaurant, contentDescription = null) + Category.Image.FAMILY -> Icon(imageVector = Icons.Default.People, contentDescription = null) + Category.Image.MONEY -> Icon(imageVector = Icons.Default.Payments, contentDescription = null) + Category.Image.HEALTH -> Icon(imageVector = Icons.Default.HealthAndSafety, contentDescription = null) + Category.Image.MEDICATION -> Icon(imageVector = Icons.Default.Medication, contentDescription = null) + Category.Image.INVEST -> Icon(imageVector = Icons.Default.QueryStats, contentDescription = null) + Category.Image.SPORT -> Icon(imageVector = Icons.Default.SportsSoccer, contentDescription = null) + Category.Image.CLOTH -> Icon(imageVector = Icons.Default.Checkroom, contentDescription = null) + Category.Image.GIFT -> Icon(imageVector = Icons.Default.Redeem, contentDescription = null) + Category.Image.WEALTH -> Icon(imageVector = Icons.Default.MonetizationOn, contentDescription = null) + Category.Image.FLOWER -> Icon(imageVector = Icons.Default.LocalFlorist, contentDescription = null) + Category.Image.PET -> Icon(imageVector = Icons.Default.Pets, contentDescription = null) + Category.Image.BILLS -> Icon(imageVector = Icons.Default.Receipt, contentDescription = null) + Category.Image.KEYBOARD -> Icon(imageVector = Icons.Default.Redeem, contentDescription = null) + Category.Image.PRINTER -> Icon(imageVector = Icons.Default.Print, contentDescription = null) + Category.Image.WATER -> Icon(imageVector = Icons.Default.WaterDrop, contentDescription = null) + Category.Image.FIRE -> Icon(imageVector = Icons.Default.FireExtinguisher, contentDescription = null) + Category.Image.STAR -> Icon(imageVector = Icons.Default.Grade, contentDescription = null) + Category.Image.SAVINGS -> Icon(imageVector = Icons.Default.Savings, contentDescription = null) + Category.Image.CAR -> Icon(imageVector = Icons.Default.MinorCrash, contentDescription = null) + Category.Image.BIKE -> Icon(imageVector = Icons.Default.PedalBike, contentDescription = null) + Category.Image.TRAIN -> Icon(imageVector = Icons.Default.DirectionsTransit, contentDescription = null) + Category.Image.MOPED -> Icon(imageVector = Icons.Default.Moped, contentDescription = null) + Category.Image.MOTORCYCLE -> Icon(imageVector = Icons.Default.TwoWheeler, contentDescription = null) + Category.Image.ELECTRONICS -> Icon(imageVector = Icons.Default.ElectricalServices, contentDescription = null) + Category.Image.BOOK -> Icon(imageVector = Icons.Default.Book, contentDescription = null) + Category.Image.FLIGHT -> Icon(imageVector = Icons.Default.FlightTakeoff, contentDescription = null) + Category.Image.WORK -> Icon(imageVector = Icons.Default.Work, contentDescription = null) + Category.Image.MOON -> Icon(imageVector = Icons.Default.NightlightRound, contentDescription = null) + Category.Image.LOCK -> Icon(imageVector = Icons.Default.Https, contentDescription = null) + Category.Image.PHONE -> Icon(imageVector = Icons.Default.PermPhoneMsg, contentDescription = null) + Category.Image.STORE -> Icon(imageVector = Icons.Default.StoreMallDirectory, contentDescription = null) + Category.Image.BAR -> Icon(imageVector = Icons.Default.Nightlife, contentDescription = null) + Category.Image.FOREST -> Icon(imageVector = Icons.Default.Redeem, contentDescription = null) + Category.Image.HARDWARE -> Icon(imageVector = Icons.Default.Hardware, contentDescription = null) + Category.Image.PEST -> Icon(imageVector = Icons.Default.PestControl, contentDescription = null) else -> { - Icon(imageVector = Icons.Default.QuestionAnswer, contentDescription = null) + Icon(imageVector = Icons.Default.ShoppingCart, contentDescription = null) } - /*Category.Image.RESTAURANT -> "restaurant" - Category.Image.FAMILY -> "people" - Category.Image.MONEY -> "payments" - Category.Image.HEALTH -> "health_and_safety" - Category.Image.MEDICATION -> "medication" - Category.Image.INVEST -> "query_stats" - Category.Image.SPORT -> "sports_soccer" - Category.Image.CLOTH -> "checkroom" - Category.Image.GIFT -> "redeem" - Category.Image.WEALTH -> "monetization_on" - Category.Image.FLOWER -> "local_florist" - Category.Image.PET -> "pets" - Category.Image.BILLS -> "receipt" - Category.Image.KEYBOARD-> "redeem" - Category.Image.PRINTER-> "print" - Category.Image.WATER -> "water_drop" - Category.Image.FIRE -> "fire" - Category.Image.STAR -> "grade" - Category.Image.SAVINGS -> "savings" - Category.Image.CAR -> "minor_crash" - Category.Image.BIKE -> "pedal_bike" - Category.Image.TRAIN -> "directions_transit" - Category.Image.MOPED-> "moped" - Category.Image.MOTORCYCLE -> "two_wheeler" - Category.Image.ELECTRONICS -> "electrical_services" - Category.Image.BOOK -> "book" - Category.Image.FLIGHT -> "flight_takeoff" - Category.Image.WORK -> "work" - Category.Image.MOON -> "nightlight_round" - Category.Image.LOCK -> "https" - Category.Image.PHONE -> "perm_phone_msg" - Category.Image.STORE -> "store_mall_directory" - Category.Image.BAR -> "nightlife" - Category.Image.FOREST -> "forest" - Category.Image.HARDWARE -> "hardware" - Category.Image.PEST -> "pest_control"*/ } } From 679a9bc95d2af068a4aaf8e739f22dbbcfffc84b Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 12:41:18 +0200 Subject: [PATCH 18/73] Implement CategoryIcons --- .../kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt | 2 +- .../kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt index a2366958..c5a033a0 100644 --- a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt +++ b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt @@ -30,7 +30,7 @@ actual fun CategoryImageToIcon(icon: Category.Image) { Category.Image.FLOWER -> Icon(imageVector = Icons.Default.LocalFlorist, contentDescription = null) Category.Image.PET -> Icon(imageVector = Icons.Default.Pets, contentDescription = null) Category.Image.BILLS -> Icon(imageVector = Icons.Default.Receipt, contentDescription = null) - Category.Image.KEYBOARD -> Icon(imageVector = Icons.Default.Redeem, contentDescription = null) + Category.Image.KEYBOARD -> Icon(imageVector = Icons.Default.Keyboard, contentDescription = null) Category.Image.PRINTER -> Icon(imageVector = Icons.Default.Print, contentDescription = null) Category.Image.WATER -> Icon(imageVector = Icons.Default.WaterDrop, contentDescription = null) Category.Image.FIRE -> Icon(imageVector = Icons.Default.LocalFireDepartment, contentDescription = null) diff --git a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt index b46b6cfa..849d3d47 100644 --- a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt +++ b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/presentation/CategoryIcon.kt @@ -30,7 +30,7 @@ actual fun CategoryImageToIcon(icon: Category.Image) { Category.Image.FLOWER -> Icon(imageVector = Icons.Default.LocalFlorist, contentDescription = null) Category.Image.PET -> Icon(imageVector = Icons.Default.Pets, contentDescription = null) Category.Image.BILLS -> Icon(imageVector = Icons.Default.Receipt, contentDescription = null) - Category.Image.KEYBOARD -> Icon(imageVector = Icons.Default.Redeem, contentDescription = null) + Category.Image.KEYBOARD -> Icon(imageVector = Icons.Default.Keyboard, contentDescription = null) Category.Image.PRINTER -> Icon(imageVector = Icons.Default.Print, contentDescription = null) Category.Image.WATER -> Icon(imageVector = Icons.Default.WaterDrop, contentDescription = null) Category.Image.FIRE -> Icon(imageVector = Icons.Default.FireExtinguisher, contentDescription = null) From ed03fa0d660c3204cec4a1b93f6591e30e401319 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 12:43:12 +0200 Subject: [PATCH 19/73] Redefine DashboardView --- .../screens/dashboard/DashboardComponent.kt | 247 ++++++------------ 1 file changed, 85 insertions(+), 162 deletions(-) diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index 19de8cd5..49af1f2d 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -1,100 +1,127 @@ package de.hsfl.budgetBinder.screens.dashboard +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.* -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.Icons.Default -import androidx.compose.material.icons.filled.* -import androidx.compose.runtime.* +import androidx.compose.material.icons.filled.KeyboardArrowLeft +import androidx.compose.material.icons.filled.KeyboardArrowRight +import androidx.compose.material.icons.filled.List +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.unit.dp import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon -import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEntryState import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel -import kotlinx.coroutines.flow.collectLatest import org.kodein.di.instance @Composable fun DashboardComponent() { val viewModel: DashboardViewModel by di.instance() - //val categoryList = viewModel.categoryListSate.collectAsState() val entryList = viewModel.entryListState.collectAsState() val focusedCategory = viewModel.focusedCategoryState.collectAsState() val totalSpendBudget = viewModel.spendBudgetOnCurrentCategory.collectAsState() val olderEntries = viewModel.oldEntriesMapState.collectAsState() - val scaffoldState = rememberScaffoldState() val loadingState = remember { mutableStateOf(false) } - LaunchedEffect(key1 = true) { - viewModel.eventFlow.collectLatest { event -> - when (event) { - is UiEvent.ShowLoading -> loadingState.value = true - else -> loadingState.value = false - } - } - } - Scaffold( - scaffoldState = scaffoldState, - floatingActionButton = { - FloatingActionButton(onClick = { viewModel.onEvent(DashboardEvent.OnEntryCreate) }) { - Icon(Default.Add, contentDescription = null) - } - }, - floatingActionButtonPosition = FabPosition.End, - isFloatingActionButtonDocked = true - ) { - if (loadingState.value) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) - } + Column { + TopDashboardSection( + focusedCategory = focusedCategory.value.category, + totalSpendBudget = totalSpendBudget.value.spendBudgetOnCurrentCategory, + totalBudget = focusedCategory.value.category.budget, + hasPrev = focusedCategory.value.hasPrev, + hasNext = focusedCategory.value.hasNext, + onPrevClicked = { viewModel.onEvent(DashboardEvent.OnPrevCategory) }, + onNextClicked = { viewModel.onEvent(DashboardEvent.OnNextCategory) } + ) Column { - Row { - if (focusedCategory.value.hasPrev) { - IconButton(onClick = { viewModel.onEvent(DashboardEvent.OnPrevCategory) }) { - Icon(Default.ArrowBack, contentDescription = null) - } - } - Text(focusedCategory.value.category.name) - if (focusedCategory.value.hasNext) { - IconButton(onClick = { viewModel.onEvent(DashboardEvent.OnNextCategory) }) { - Icon(Default.ArrowForward, contentDescription = null) - } + EntryList(entryList = entryList.value.entryList, onItemClicked = {}) + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "Older entries...", style = MaterialTheme.typography.caption) + Spacer(modifier = Modifier.height(8.dp)) + OlderEntryList(entryMap = olderEntries.value) + Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + OutlinedButton(onClick = { viewModel.onEvent(DashboardEvent.OnLoadMore) }) { + Text("Load More") } } - Text("Spend: ${totalSpendBudget.value.spendBudgetOnCurrentCategory}") - Text("Total: ${focusedCategory.value.category.budget}") - Button(onClick = { viewModel.onEvent(DashboardEvent.OnRefresh) }) { - Text("Update") - } - EntryList(entryList = entryList.value.entryList) - OlderEntryList(olderEntries.value) - Button(onClick = { viewModel.onEvent(DashboardEvent.OnLoadMore) }) { - Text("Load more") - } - //EntryList() } } } @Composable -fun FocusedCategory(category: Category) { - Column { Text(category.name) } +fun BudgetBar(modifier: Modifier = Modifier, progress: Float) { + var _progress = progress + if (progress > 1f) _progress = 1f + if (progress < 0f) _progress = 0f + val animatedProgress = animateFloatAsState( + targetValue = _progress, + animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec + ).value + LinearProgressIndicator(modifier = modifier, progress = animatedProgress) } +@Composable +private fun TopDashboardSection( + focusedCategory: Category, + totalSpendBudget: Float, + totalBudget: Float, + hasPrev: Boolean, + hasNext: Boolean, + onPrevClicked: () -> Unit, + onNextClicked: () -> Unit +) { + Surface(elevation = 8.dp) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Spacer(modifier = Modifier.height(8.dp)) + Text(text = focusedCategory.name) + Spacer(modifier = Modifier.height(8.dp)) + Row(horizontalArrangement = Arrangement.Center) { + IconButton( + modifier = Modifier.weight(1F), + onClick = onPrevClicked, + enabled = hasPrev + ) { + Icon(Icons.Default.KeyboardArrowLeft, contentDescription = null) + } + BudgetBar( + modifier = Modifier.weight(4F).height(32.dp).clip(RoundedCornerShape(8.dp)), + progress = totalSpendBudget / totalBudget + ) + IconButton( + modifier = Modifier.weight(1F), + onClick = onNextClicked, + enabled = hasNext + ) { + Icon(Icons.Default.KeyboardArrowRight, contentDescription = null) + } + } + Text("Spend: $totalSpendBudget") + Text("Total: $totalBudget") + Spacer(modifier = Modifier.height(8.dp)) + } + } +} @OptIn(ExperimentalMaterialApi::class) @Composable -private fun EntryList(entryList: List) { +private fun EntryList(entryList: List, onItemClicked: (Int) -> Unit) { when { entryList.isEmpty() -> Text("This category has no entries. You can create an new entry.") @@ -103,6 +130,7 @@ private fun EntryList(entryList: List) { items(entryList) { state -> Divider() ListItem( + modifier = Modifier.clickable(onClick = { onItemClicked(state.entry.id) }), text = { Text(state.entry.name) }, icon = { CategoryImageToIcon(icon = state.categoryImage) }, trailing = { Text("${state.entry.amount} €") } @@ -114,10 +142,6 @@ private fun EntryList(entryList: List) { @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable private fun OlderEntryList(entryMap: Map>) { - - when { - entryMap.isEmpty() -> Text("There are not more entries") - } LazyColumn { entryMap.forEach { (date, entries) -> stickyHeader { @@ -136,104 +160,3 @@ private fun OlderEntryList(entryMap: Map>) { } } - -@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) -@Composable -private fun EntryList() { - data class _DashboardEntryState( - val entry: Entry, - val categoryImage: Category.Image = Category.Image.DEFAULT, - val date: String = "02-2022" - ) - - val dateEntryList = listOf<_DashboardEntryState>( - _DashboardEntryState( - entry = Entry(0, "Entry 1", -20.51F, false, null), - date = "06-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 2", -20.51F, false, null), - date = "06-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 3", -20.51F, false, null), - date = "06-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 4", -20.51F, false, null), - date = "06-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 5", -20.51F, false, null), - date = "05-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 6", -20.51F, false, null), - date = "05-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 7", -20.51F, false, null), - date = "04-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 8", -20.51F, false, null), - date = "04-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 9", -20.51F, false, null), - date = "04-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 10", -20.51F, false, null), - date = "04-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 11", -20.51F, false, null), - date = "04-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 12", -20.51F, false, null), - date = "04-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 13", -20.51F, false, null), - date = "03-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 14", -20.51F, false, null), - date = "03-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 15", -20.51F, false, null), - date = "03-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 16", -20.51F, false, null), - date = "03-2022" - ), - _DashboardEntryState( - entry = Entry(0, "Entry 17", -20.51F, false, null), - date = "03-2022" - ) - ) - - val groupList = dateEntryList.groupBy { it.date } - - val listState = rememberLazyListState() - LazyColumn(state = listState) { - groupList.forEach { (date, states) -> - stickyHeader { - Text(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxWidth(), text = date) - } - - items(states) { state -> - Divider() - ListItem( - text = { Text(state.entry.name) }, - icon = { CategoryImageToIcon(icon = state.categoryImage) }, - trailing = { Text("${state.entry.amount} €") } - ) - } - } - } -} From b1c44634feae780e908400e1e884ff9cd6ae7843 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 15:35:18 +0200 Subject: [PATCH 20/73] Refactor MonthToString and Unauthorized --- .../viewmodel/dashboard/DashboardViewModel.kt | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index f569e92d..94badb75 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -78,7 +78,7 @@ class DashboardViewModel( dashboardUseCases.getAllEntriesUseCase.entries().collect { when (it) { is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { _entryListState.value = entryListState.value.copy(entryList = it.data!!.map { entry -> DashboardEntryState( @@ -89,6 +89,10 @@ class DashboardViewModel( _eventFlow.emit(UiEvent.HideSuccess) calcSpendBudgetOnCategory() } + is DataResponse.Unauthorized -> { + _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + routerFlow.navigateTo(Screen.Login) + } } } } @@ -99,11 +103,15 @@ class DashboardViewModel( dashboardUseCases.getAllCategoriesUseCase.categories().collect { when (it) { is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Error-> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { _categoryListState.value = it.data!! _eventFlow.emit(UiEvent.HideSuccess) } + is DataResponse.Unauthorized -> { + _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + routerFlow.navigateTo(Screen.Login) + } } } } @@ -114,7 +122,7 @@ class DashboardViewModel( dashboardUseCases.getAllEntriesByCategoryUseCase(id).collect { when (it) { is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { _entryListState.value = entryListState.value.copy(entryList = it.data!!.map { entry -> DashboardEntryState( @@ -125,6 +133,10 @@ class DashboardViewModel( _eventFlow.emit(UiEvent.HideSuccess) calcSpendBudgetOnCategory() } + is DataResponse.Unauthorized -> { + _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + routerFlow.navigateTo(Screen.Login) + } } } } @@ -141,7 +153,7 @@ class DashboardViewModel( _oldEntriesMapState.value.putAll( mapOf( Pair( - month.toMonthString(), + "${month.toMonthString()}-${GMTDate().year}", it.data!! ) ) @@ -258,10 +270,14 @@ class DashboardViewModel( } private fun loadMoreEntries() { + val nextMonth = when { + lastRequestedMonth.ordinal - 1 < 0 -> 11 + else -> lastRequestedMonth.ordinal - 1 + } getAllEntriesFromMonth( - month = Month.from(lastRequestedMonth.ordinal - 1) + month = Month.from(nextMonth) ) - lastRequestedMonth = Month.from(lastRequestedMonth.ordinal - 1) + lastRequestedMonth = Month.from(nextMonth) } From fa72d1964261e0400de2553748d6149f2b49d009 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 15:35:49 +0200 Subject: [PATCH 21/73] To one single LazyColumn --- .../screens/dashboard/DashboardComponent.kt | 103 ++++++++++++------ 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index 49af1f2d..ec4e269b 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -8,13 +8,11 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.KeyboardArrowLeft import androidx.compose.material.icons.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.List -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.unit.dp import androidx.compose.ui.Modifier @@ -25,9 +23,11 @@ import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon +import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEntryState import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel +import kotlinx.coroutines.flow.collectLatest import org.kodein.di.instance @Composable @@ -38,29 +38,50 @@ fun DashboardComponent() { val totalSpendBudget = viewModel.spendBudgetOnCurrentCategory.collectAsState() val olderEntries = viewModel.oldEntriesMapState.collectAsState() val loadingState = remember { mutableStateOf(false) } + val scaffoldState = rememberScaffoldState() - Column { - TopDashboardSection( - focusedCategory = focusedCategory.value.category, - totalSpendBudget = totalSpendBudget.value.spendBudgetOnCurrentCategory, - totalBudget = focusedCategory.value.category.budget, - hasPrev = focusedCategory.value.hasPrev, - hasNext = focusedCategory.value.hasNext, - onPrevClicked = { viewModel.onEvent(DashboardEvent.OnPrevCategory) }, - onNextClicked = { viewModel.onEvent(DashboardEvent.OnNextCategory) } - ) - Column { - EntryList(entryList = entryList.value.entryList, onItemClicked = {}) - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Older entries...", style = MaterialTheme.typography.caption) - Spacer(modifier = Modifier.height(8.dp)) - OlderEntryList(entryMap = olderEntries.value) - Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - OutlinedButton(onClick = { viewModel.onEvent(DashboardEvent.OnLoadMore) }) { - Text("Load More") - } + LaunchedEffect(key1 = true) { + viewModel.eventFlow.collectLatest { event -> + when (event) { + is UiEvent.ShowLoading -> loadingState.value = true + is UiEvent.HideSuccess -> loadingState.value = false + else -> loadingState.value = false } + } + } + + Scaffold( + scaffoldState = scaffoldState, + floatingActionButtonPosition = FabPosition.End, + floatingActionButton = { + FloatingActionButton(onClick = { viewModel.onEvent(DashboardEvent.OnEntryCreate) }) { + Icon(Icons.Default.Add, contentDescription = null) + } + } + ) { + Column { + if (loadingState.value) { + LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + } + TopDashboardSection( + focusedCategory = focusedCategory.value.category, + totalSpendBudget = totalSpendBudget.value.spendBudgetOnCurrentCategory, + totalBudget = focusedCategory.value.category.budget, + hasPrev = focusedCategory.value.hasPrev, + hasNext = focusedCategory.value.hasNext, + onPrevClicked = { viewModel.onEvent(DashboardEvent.OnPrevCategory) }, + onNextClicked = { viewModel.onEvent(DashboardEvent.OnNextCategory) } + ) + Column { + EntryList( + entryList = entryList.value.entryList, + oldEntries = olderEntries.value, + onItemClicked = {}, + onLoadMore = { viewModel.onEvent(DashboardEvent.OnLoadMore) }) + Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(8.dp)) + } } } } @@ -119,9 +140,14 @@ private fun TopDashboardSection( } } -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable -private fun EntryList(entryList: List, onItemClicked: (Int) -> Unit) { +private fun EntryList( + entryList: List, + oldEntries: Map>, + onItemClicked: (Int) -> Unit, + onLoadMore: () -> Unit +) { when { entryList.isEmpty() -> Text("This category has no entries. You can create an new entry.") @@ -136,14 +162,14 @@ private fun EntryList(entryList: List, onItemClicked: (Int) trailing = { Text("${state.entry.amount} €") } ) } - } -} - -@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) -@Composable -private fun OlderEntryList(entryMap: Map>) { - LazyColumn { - entryMap.forEach { (date, entries) -> + stickyHeader { + Spacer(modifier = Modifier.height(8.dp)) + Text( + modifier = Modifier.background(MaterialTheme.colors.background).fillMaxWidth(), + text = "Older entries..." + ) + } + oldEntries.forEach { (date, entries) -> stickyHeader { Text(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxWidth(), text = date) } @@ -157,6 +183,15 @@ private fun OlderEntryList(entryMap: Map>) { ) } } + + item { + Spacer(modifier = Modifier.height(8.dp)) + Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + OutlinedButton(onClick = onLoadMore) { + Text("Load More") + } + } + } } } From e21f8e248fefab65de55e07b3efcdce38e6f8534 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 16:55:18 +0200 Subject: [PATCH 22/73] Implement SwipeToDelete --- .../screens/dashboard/DashboardComponent.kt | 118 ++++++++++++------ 1 file changed, 77 insertions(+), 41 deletions(-) diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index ec4e269b..c2533643 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -1,22 +1,22 @@ package de.hsfl.budgetBinder.screens.dashboard +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.KeyboardArrowLeft -import androidx.compose.material.icons.filled.KeyboardArrowRight -import androidx.compose.material.icons.filled.List +import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.unit.dp import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import de.hsfl.budgetBinder.common.Category @@ -51,36 +51,29 @@ fun DashboardComponent() { } - Scaffold( - scaffoldState = scaffoldState, - floatingActionButtonPosition = FabPosition.End, - floatingActionButton = { - FloatingActionButton(onClick = { viewModel.onEvent(DashboardEvent.OnEntryCreate) }) { - Icon(Icons.Default.Add, contentDescription = null) - } + Scaffold(scaffoldState = scaffoldState, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { + FloatingActionButton(onClick = { viewModel.onEvent(DashboardEvent.OnEntryCreate) }) { + Icon(Icons.Default.Add, contentDescription = null) } - ) { + }) { Column { if (loadingState.value) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } - TopDashboardSection( - focusedCategory = focusedCategory.value.category, + TopDashboardSection(focusedCategory = focusedCategory.value.category, totalSpendBudget = totalSpendBudget.value.spendBudgetOnCurrentCategory, totalBudget = focusedCategory.value.category.budget, hasPrev = focusedCategory.value.hasPrev, hasNext = focusedCategory.value.hasNext, onPrevClicked = { viewModel.onEvent(DashboardEvent.OnPrevCategory) }, - onNextClicked = { viewModel.onEvent(DashboardEvent.OnNextCategory) } - ) + onNextClicked = { viewModel.onEvent(DashboardEvent.OnNextCategory) }) Column { - EntryList( - entryList = entryList.value.entryList, + EntryList(entryList = entryList.value.entryList, oldEntries = olderEntries.value, onItemClicked = {}, - onLoadMore = { viewModel.onEvent(DashboardEvent.OnLoadMore) }) - Spacer(modifier = Modifier.height(8.dp)) - Spacer(modifier = Modifier.height(8.dp)) + onLoadMore = { viewModel.onEvent(DashboardEvent.OnLoadMore) }, + onEntryDelete = { viewModel.onEvent(DashboardEvent.OnEntryDelete(it)) } + ) } } } @@ -92,8 +85,7 @@ fun BudgetBar(modifier: Modifier = Modifier, progress: Float) { if (progress > 1f) _progress = 1f if (progress < 0f) _progress = 0f val animatedProgress = animateFloatAsState( - targetValue = _progress, - animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec + targetValue = _progress, animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec ).value LinearProgressIndicator(modifier = modifier, progress = animatedProgress) } @@ -115,9 +107,7 @@ private fun TopDashboardSection( Spacer(modifier = Modifier.height(8.dp)) Row(horizontalArrangement = Arrangement.Center) { IconButton( - modifier = Modifier.weight(1F), - onClick = onPrevClicked, - enabled = hasPrev + modifier = Modifier.weight(1F), onClick = onPrevClicked, enabled = hasPrev ) { Icon(Icons.Default.KeyboardArrowLeft, contentDescription = null) } @@ -126,9 +116,7 @@ private fun TopDashboardSection( progress = totalSpendBudget / totalBudget ) IconButton( - modifier = Modifier.weight(1F), - onClick = onNextClicked, - enabled = hasNext + modifier = Modifier.weight(1F), onClick = onNextClicked, enabled = hasNext ) { Icon(Icons.Default.KeyboardArrowRight, contentDescription = null) } @@ -146,7 +134,8 @@ private fun EntryList( entryList: List, oldEntries: Map>, onItemClicked: (Int) -> Unit, - onLoadMore: () -> Unit + onLoadMore: () -> Unit, + onEntryDelete: (Int) -> Unit ) { when { @@ -154,13 +143,17 @@ private fun EntryList( } LazyColumn { items(entryList) { state -> + val swipeState = rememberDismissState() + if (swipeState.isDismissed(DismissDirection.EndToStart)) { + onEntryDelete(state.entry.id) + } Divider() - ListItem( - modifier = Modifier.clickable(onClick = { onItemClicked(state.entry.id) }), - text = { Text(state.entry.name) }, - icon = { CategoryImageToIcon(icon = state.categoryImage) }, - trailing = { Text("${state.entry.amount} €") } - ) + SwipeToDelete(dismissState = swipeState, content = { + ListItem(modifier = Modifier.clickable(onClick = { onItemClicked(state.entry.id) }), + text = { Text(state.entry.name) }, + icon = { CategoryImageToIcon(icon = state.categoryImage) }, + trailing = { Text("${state.entry.amount} €") }) + }) } stickyHeader { Spacer(modifier = Modifier.height(8.dp)) @@ -175,12 +168,18 @@ private fun EntryList( } items(entries) { entry -> + val swipeState = rememberDismissState(confirmStateChange = { + if (it == DismissValue.DismissedToEnd) { + onEntryDelete(entry.id) + } + true + }) Divider() - ListItem( - text = { Text(entry.name) }, - icon = { Icon(Icons.Default.List, contentDescription = null) }, - trailing = { Text("${entry.amount} €") } - ) + SwipeToDelete(dismissState = swipeState) { + ListItem(text = { Text(entry.name) }, + icon = { Icon(Icons.Default.List, contentDescription = null) }, + trailing = { Text("${entry.amount} €") }) + } } } @@ -195,3 +194,40 @@ private fun EntryList( } } +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun SwipeToDelete(dismissState: DismissState, content: @Composable RowScope.() -> Unit) { + SwipeToDismiss( + state = dismissState, + directions = setOf(DismissDirection.EndToStart), + dismissThresholds = { FractionalThreshold(0.2f) }, + background = { + val direction = dismissState.dismissDirection ?: return@SwipeToDismiss + val color by animateColorAsState( + targetValue = when (dismissState.targetValue) { + DismissValue.Default -> MaterialTheme.colors.secondary + DismissValue.DismissedToStart -> Color.Red + else -> MaterialTheme.colors.background + } + ) + val alignment = when (direction) { + DismissDirection.StartToEnd -> Alignment.CenterStart + DismissDirection.EndToStart -> Alignment.CenterEnd + } + val scale by animateFloatAsState(targetValue = if (dismissState.targetValue == DismissValue.Default) 0.75f else 1f) + Box( + Modifier + .fillMaxSize() + .background(color) + .padding(horizontal = 20.dp), + contentAlignment = alignment + ) { + if (direction == DismissDirection.EndToStart) { + Icon(Icons.Default.Delete, contentDescription = null, modifier = Modifier.scale(scale)) + } + } + }, + dismissContent = content + ) +} + From 1a8898a2121830f548195eb94335058a6ceff7b5 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sat, 18 Jun 2022 18:36:26 +0200 Subject: [PATCH 23/73] Manage async stuff --- .../viewmodel/dashboard/DashboardViewModel.kt | 128 +++++++++++------- 1 file changed, 82 insertions(+), 46 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 94badb75..7af54b01 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -48,8 +48,11 @@ class DashboardViewModel( //_getAllEntries() //_getAllCategories() - getAllCategories() - getAllEntries() + getAllCategories(onSuccess = { categories -> + _categoryListState.value = categories + setOverallCategoryState() + }) + } fun onEvent(event: DashboardEvent) { @@ -61,11 +64,23 @@ class DashboardViewModel( _eventFlow.emit(UiEvent.ShowError(currentMonth.toMonthString())) } is DashboardEvent.OnRefresh -> { - getAllCategories() when (internalCategoryId) { - -1 -> getAllEntries() - in _categoryListState.value.indices -> getEntriesByCategory(id = focusedCategoryState.value.category.id) - _categoryListState.value.size -> getEntriesByCategory(id = null) + -1 -> getAllEntries(onSuccess = { + _entryListState.value = + entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + }) + in _categoryListState.value.indices -> getEntriesByCategory( + id = focusedCategoryState.value.category.id, + onSuccess = { + _entryListState.value = + entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + }) + _categoryListState.value.size -> getEntriesByCategory( + id = null, + onSuccess = { + _entryListState.value = + entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + }) } } is DashboardEvent.OnLoadMore -> loadMoreEntries() @@ -73,21 +88,25 @@ class DashboardViewModel( } } - private fun getAllEntries() { + private fun mapEntryListToDashboardEntryState(entryList: List): List { + return entryList.map { entry -> + DashboardEntryState( + entry, + categoryImage = getCategoryByEntry(entry)?.image ?: Category.Image.DEFAULT + ) + } + } + + private fun getAllEntries(onSuccess: (List) -> Unit) { scope.launch { dashboardUseCases.getAllEntriesUseCase.entries().collect { when (it) { is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { - _entryListState.value = entryListState.value.copy(entryList = it.data!!.map { entry -> - DashboardEntryState( - entry, - categoryImage = getCategoryByEntry(entry)?.image ?: Category.Image.DEFAULT - ) - }) + onSuccess(it.data!!) _eventFlow.emit(UiEvent.HideSuccess) - calcSpendBudgetOnCategory() + //calcSpendBudgetOnCategory() } is DataResponse.Unauthorized -> { _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) @@ -98,14 +117,15 @@ class DashboardViewModel( } } - private fun getAllCategories() { + private fun getAllCategories(onSuccess: (List) -> Unit) { scope.launch { dashboardUseCases.getAllCategoriesUseCase.categories().collect { when (it) { is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error-> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { - _categoryListState.value = it.data!! + onSuccess(it.data!!) + //_categoryListState.value = it.data!! _eventFlow.emit(UiEvent.HideSuccess) } is DataResponse.Unauthorized -> { @@ -117,21 +137,22 @@ class DashboardViewModel( } } - private fun getEntriesByCategory(id: Int? = null) { + private fun getEntriesByCategory(id: Int? = null, onSuccess: (List) -> Unit) { scope.launch { dashboardUseCases.getAllEntriesByCategoryUseCase(id).collect { when (it) { is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { - _entryListState.value = entryListState.value.copy(entryList = it.data!!.map { entry -> + onSuccess(it.data!!) + /*_entryListState.value = entryListState.value.copy(entryList = it.data!!.map { entry -> DashboardEntryState( entry, categoryImage = getCategoryByEntry(entry)?.image ?: Category.Image.DEFAULT ) - }) + })*/ _eventFlow.emit(UiEvent.HideSuccess) - calcSpendBudgetOnCategory() + //calcSpendBudgetOnCategory() } is DataResponse.Unauthorized -> { _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) @@ -194,6 +215,12 @@ class DashboardViewModel( in _categoryListState.value.indices -> setCategoryState() _categoryListState.value.size -> setCategoryWithNoCategory() } + resetOldEntries() + } + + private fun resetOldEntries() { + _oldEntriesMapState.value = mutableMapOf() + lastRequestedMonth = currentMonth } /** @@ -219,50 +246,59 @@ class DashboardViewModel( * Set the first category, all entries are here */ private fun setOverallCategoryState() { - var totalBudget = 0f - _categoryListState.value.forEach { totalBudget += it.budget } - _focusedCategoryState.value = focusedCategoryState.value.copy( - hasPrev = false, - hasNext = true, - category = Category(0, "Overall", "111111", Category.Image.DEFAULT, totalBudget) - ) - getAllEntries() + getAllEntries(onSuccess = { + var totalBudget = 0f + _categoryListState.value.forEach { totalBudget += it.budget } + _focusedCategoryState.value = focusedCategoryState.value.copy( + hasPrev = false, + hasNext = true, + category = Category(0, "Overall", "111111", Category.Image.DEFAULT, totalBudget) + ) + calcSpendBudgetOnCategory(it) + _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + }) } /** * Set the category state for the current category, wich the user moved to */ private fun setCategoryState() { - _focusedCategoryState.value = focusedCategoryState.value.copy( - hasPrev = true, - hasNext = true, - category = _categoryListState.value[internalCategoryId] - ) - getEntriesByCategory(id = focusedCategoryState.value.category.id) + getEntriesByCategory(id = _categoryListState.value[internalCategoryId].id, onSuccess = { + calcSpendBudgetOnCategory(it) + _focusedCategoryState.value = focusedCategoryState.value.copy( + hasPrev = true, + hasNext = true, + category = _categoryListState.value[internalCategoryId] + ) + _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + + }) } /** * All entries with no category are shown in this category */ private fun setCategoryWithNoCategory() { - _focusedCategoryState.value = focusedCategoryState.value.copy( - hasPrev = true, - hasNext = false, - category = Category(0, "No Category", "111111", Category.Image.DEFAULT, 0f) - ) - getEntriesByCategory(id = null) + getEntriesByCategory(id = null, onSuccess = { + _focusedCategoryState.value = focusedCategoryState.value.copy( + hasPrev = true, + hasNext = false, + category = Category(0, "No Category", "111111", Category.Image.DEFAULT, 0f) + ) + _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + }) } /** * Calculate the spend money for a category */ - private fun calcSpendBudgetOnCategory() { + private fun calcSpendBudgetOnCategory(entryList: List) { var spendMoney = 0F - entryListState.value.entryList.onEach { - if (it.entry.amount > 0) { - spendMoney -= it.entry.amount + entryList.onEach { + if (it.amount > 0) { + spendMoney -= it.amount } else { - spendMoney += (it.entry.amount * -1) + spendMoney += (it.amount * -1) } } _spendBudgetOnCurrentCategory.value = From d17973980e7570b804623df7abf6e08a8c988116 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 10:49:50 +0200 Subject: [PATCH 24/73] Implement new Api Request --- .../hsfl/budgetBinder/data/client/Client.kt | 21 ++++++++++++++++--- .../data/repository/CategoryRepositoryImpl.kt | 4 ++++ .../domain/repository/CategoryRepository.kt | 10 ++++++++- .../domain/usecase/CategoriesUseCase.kt | 15 +++++++++++-- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt index a72440e2..8e2b49df 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt @@ -101,11 +101,18 @@ interface ApiClient { suspend fun deleteCategoryById(id: Int): APIResponse /** - * Get Entries from a Category ID + * Get Entries from a Category ID. The request has a query in the link ?current * @param id ID from category to get the Entries */ suspend fun getEntriesFromCategory(id: Int?): APIResponse> + /** + * Get Entries from a Category ID on period + * @param id ID from category to get the Entries + * @param period Period definition in format MM-YYYY (03-2022) + */ + suspend fun getEntriesFromCategory(id: Int?, period: String): APIResponse> + /** * Get All Entries from current month. The request has a query in the link ?current * @author Cedrik Hoffmann @@ -160,7 +167,7 @@ expect fun HttpClientConfig<*>.specificClientConfig() * @author Cedrik Hoffmann * @see de.hsfl.budgetBinder.data.client.ApiClient */ -class Client( engine: HttpClientEngine) : ApiClient { +class Client(engine: HttpClientEngine) : ApiClient { private val client = HttpClient(engine) { install(ContentNegotiation) { json() @@ -262,7 +269,15 @@ class Client( engine: HttpClientEngine) : ApiClient { } override suspend fun getEntriesFromCategory(id: Int?): APIResponse> { - return client.get(urlString = "/categories/$id/entries").body() + return client.submitForm(url = "/categories/$id/entries", formParameters = Parameters.build { + append("current", "true") + }, encodeInQuery = true).body() + } + + override suspend fun getEntriesFromCategory(id: Int?, period: String): APIResponse> { + return client.submitForm(url = "/categories/$id/entries", formParameters = Parameters.build { + append("period", period) + }, encodeInQuery = true).body() } override suspend fun getAllEntries(): APIResponse> { diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/repository/CategoryRepositoryImpl.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/repository/CategoryRepositoryImpl.kt index 1625ede0..83d75488 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/repository/CategoryRepositoryImpl.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/repository/CategoryRepositoryImpl.kt @@ -36,4 +36,8 @@ class CategoryRepositoryImpl( override suspend fun getEntriesFromCategory(id: Int?): APIResponse> { return client.getEntriesFromCategory(id) } + + override suspend fun getEntriesFromCategory(id: Int?, period: String): APIResponse> { + return client.getEntriesFromCategory(id, period) + } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/repository/CategoryRepository.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/repository/CategoryRepository.kt index faab5b34..6500a521 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/repository/CategoryRepository.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/repository/CategoryRepository.kt @@ -48,9 +48,17 @@ interface CategoryRepository { suspend fun deleteCategoryById(id: Int): APIResponse /** - * Get all entries from a category + * Get all entries from a category of current month * @param id ID from Category to get all Entries from this * @author Cedrik Hoffmann */ suspend fun getEntriesFromCategory(id: Int?): APIResponse> + + /** + * Get all entries from a category on period of time + * @param id ID from Category to get all Entries from this + * @param period Time period in format MM-YYYY (03-2022) + * @author Cedrik Hoffmann + */ + suspend fun getEntriesFromCategory(id: Int?, period: String): APIResponse> } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt index 9cd8f144..9b67d13d 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt @@ -147,10 +147,21 @@ class DeleteCategoryByIdUseCase(private val repository: CategoryRepository) { } class GetAllEntriesByCategoryUseCase(private val repository: CategoryRepository) { - operator fun invoke(id: Int?): Flow>> = flow { + operator fun invoke(id: Int?, period: String? = null): Flow>> = flow { try { emit(DataResponse.Loading()) - repository.getEntriesFromCategory(id).let { response -> + period?.let { period -> + repository.getEntriesFromCategory(id, period).let { response -> + response.data?.let { + emit(DataResponse.Success(it)) + } ?: response.error!!.let { error -> + when (error.code) { + HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) + else -> emit(DataResponse.Error(error)) + } + } + } + } ?: repository.getEntriesFromCategory(id).let { response -> response.data?.let { emit(DataResponse.Success(it)) } ?: response.error!!.let { error -> From e418672750f4dbe0347320f01a7c0d693476d72a Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 10:51:03 +0200 Subject: [PATCH 25/73] Fix the 'load more' button from a specific category --- .../viewmodel/dashboard/DashboardViewModel.kt | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 7af54b01..5c9b8a85 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -97,26 +97,25 @@ class DashboardViewModel( } } - private fun getAllEntries(onSuccess: (List) -> Unit) { - scope.launch { - dashboardUseCases.getAllEntriesUseCase.entries().collect { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Success -> { - onSuccess(it.data!!) - _eventFlow.emit(UiEvent.HideSuccess) - //calcSpendBudgetOnCategory() - } - is DataResponse.Unauthorized -> { - _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - routerFlow.navigateTo(Screen.Login) - } + private fun getAllEntries(onSuccess: (List) -> Unit) = scope.launch { + dashboardUseCases.getAllEntriesUseCase.entries().collect { + when (it) { + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Success -> { + onSuccess(it.data!!) + _eventFlow.emit(UiEvent.HideSuccess) + //calcSpendBudgetOnCategory() + } + is DataResponse.Unauthorized -> { + _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + routerFlow.navigateTo(Screen.Login) } } } } + private fun getAllCategories(onSuccess: (List) -> Unit) { scope.launch { dashboardUseCases.getAllCategoriesUseCase.categories().collect { @@ -137,22 +136,15 @@ class DashboardViewModel( } } - private fun getEntriesByCategory(id: Int? = null, onSuccess: (List) -> Unit) { + private fun getEntriesByCategory(id: Int? = null, period: String? = null, onSuccess: (List) -> Unit) { scope.launch { - dashboardUseCases.getAllEntriesByCategoryUseCase(id).collect { + dashboardUseCases.getAllEntriesByCategoryUseCase(id, period).collect { when (it) { is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { onSuccess(it.data!!) - /*_entryListState.value = entryListState.value.copy(entryList = it.data!!.map { entry -> - DashboardEntryState( - entry, - categoryImage = getCategoryByEntry(entry)?.image ?: Category.Image.DEFAULT - ) - })*/ _eventFlow.emit(UiEvent.HideSuccess) - //calcSpendBudgetOnCategory() } is DataResponse.Unauthorized -> { _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) @@ -310,9 +302,20 @@ class DashboardViewModel( lastRequestedMonth.ordinal - 1 < 0 -> 11 else -> lastRequestedMonth.ordinal - 1 } - getAllEntriesFromMonth( - month = Month.from(nextMonth) - ) + val monthString = "${Month.from(nextMonth).toMonthString()}-${GMTDate().year}" + when (internalCategoryId) { + -1 -> getAllEntriesFromMonth(month = Month.from(nextMonth)) + in _categoryListState.value.indices -> getEntriesByCategory( + id = focusedCategoryState.value.category.id, period = monthString + ) { + _oldEntriesMapState.value.putAll(mapOf(Pair(monthString, it))) + } + _categoryListState.value.size -> getEntriesByCategory( + id = null, period = monthString + ) { + _oldEntriesMapState.value.putAll(mapOf(Pair(monthString, it))) + } + } lastRequestedMonth = Month.from(nextMonth) } From 0b5143616e1a33e134b2f63cd3bad82c7aeaa593 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 11:02:00 +0200 Subject: [PATCH 26/73] Calculate the Year correctly --- .../viewmodel/dashboard/DashboardViewModel.kt | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 5c9b8a85..64978262 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -25,7 +25,9 @@ class DashboardViewModel( private var internalCategoryId = -1 private val currentMonth: Month = GMTDate().month + private val currentYear: Int = GMTDate().year private var lastRequestedMonth = currentMonth + private var lastRequestedYear = currentYear private val _categoryListState = MutableStateFlow>(emptyList()) private val _entryListState = MutableStateFlow(DashboardState()) @@ -155,22 +157,15 @@ class DashboardViewModel( } } - private fun getAllEntriesFromMonth(month: Month) = scope.launch { + private fun getAllEntriesFromMonth(period: String) = scope.launch { // TODO: Year had also be to check - dashboardUseCases.getAllEntriesUseCase.entries("${month.toMonthString()}-${GMTDate().year}").collect { + dashboardUseCases.getAllEntriesUseCase.entries(period).collect { when (it) { is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { _eventFlow.emit(UiEvent.HideSuccess) - _oldEntriesMapState.value.putAll( - mapOf( - Pair( - "${month.toMonthString()}-${GMTDate().year}", - it.data!! - ) - ) - ) + _oldEntriesMapState.value.putAll(mapOf(Pair(period, it.data!!))) } is DataResponse.Unauthorized -> { _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) @@ -213,6 +208,7 @@ class DashboardViewModel( private fun resetOldEntries() { _oldEntriesMapState.value = mutableMapOf() lastRequestedMonth = currentMonth + lastRequestedYear = currentYear } /** @@ -299,21 +295,25 @@ class DashboardViewModel( private fun loadMoreEntries() { val nextMonth = when { - lastRequestedMonth.ordinal - 1 < 0 -> 11 + // When the month goes from 01 to 12, the year has to be changed + lastRequestedMonth.ordinal - 1 < 0 -> { + lastRequestedYear -= 1 + 11 + } else -> lastRequestedMonth.ordinal - 1 } - val monthString = "${Month.from(nextMonth).toMonthString()}-${GMTDate().year}" + val periodString = "${Month.from(nextMonth).toMonthString()}-${lastRequestedYear}" when (internalCategoryId) { - -1 -> getAllEntriesFromMonth(month = Month.from(nextMonth)) + -1 -> getAllEntriesFromMonth(periodString) in _categoryListState.value.indices -> getEntriesByCategory( - id = focusedCategoryState.value.category.id, period = monthString + id = focusedCategoryState.value.category.id, period = periodString ) { - _oldEntriesMapState.value.putAll(mapOf(Pair(monthString, it))) + _oldEntriesMapState.value.putAll(mapOf(Pair(periodString, it))) } _categoryListState.value.size -> getEntriesByCategory( - id = null, period = monthString + id = null, period = periodString ) { - _oldEntriesMapState.value.putAll(mapOf(Pair(monthString, it))) + _oldEntriesMapState.value.putAll(mapOf(Pair(periodString, it))) } } lastRequestedMonth = Month.from(nextMonth) From ebd5e7f7c4497ff14e0dee82887530f1d41f5856 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 11:13:19 +0200 Subject: [PATCH 27/73] Implement Icon on old entries --- .../viewmodel/dashboard/DashboardViewModel.kt | 37 +++++++++++++++---- .../screens/dashboard/DashboardComponent.kt | 15 ++++---- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 64978262..68d34503 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -33,8 +33,8 @@ class DashboardViewModel( private val _entryListState = MutableStateFlow(DashboardState()) val entryListState: StateFlow = _entryListState - private val _oldEntriesMapState = MutableStateFlow>>(mutableMapOf()) - val oldEntriesMapState: StateFlow>> = _oldEntriesMapState + private val _oldEntriesMapState = MutableStateFlow>(mutableMapOf()) + val oldEntriesMapState: StateFlow> = _oldEntriesMapState private val _focusedCategoryState = MutableStateFlow(DashboardState()) val focusedCategoryState: StateFlow = _focusedCategoryState @@ -157,7 +157,7 @@ class DashboardViewModel( } } - private fun getAllEntriesFromMonth(period: String) = scope.launch { + private fun getAllEntriesFromMonth(period: String, onSuccess: (List) -> Unit) = scope.launch { // TODO: Year had also be to check dashboardUseCases.getAllEntriesUseCase.entries(period).collect { when (it) { @@ -165,7 +165,7 @@ class DashboardViewModel( is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Success -> { _eventFlow.emit(UiEvent.HideSuccess) - _oldEntriesMapState.value.putAll(mapOf(Pair(period, it.data!!))) + onSuccess(it.data!!) } is DataResponse.Unauthorized -> { _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) @@ -304,16 +304,39 @@ class DashboardViewModel( } val periodString = "${Month.from(nextMonth).toMonthString()}-${lastRequestedYear}" when (internalCategoryId) { - -1 -> getAllEntriesFromMonth(periodString) + -1 -> getAllEntriesFromMonth(period = periodString) { + _oldEntriesMapState.value.putAll( + mapOf( + Pair( + periodString, + DashboardState(entryList = mapEntryListToDashboardEntryState(it)) + ) + ) + ) + } in _categoryListState.value.indices -> getEntriesByCategory( id = focusedCategoryState.value.category.id, period = periodString ) { - _oldEntriesMapState.value.putAll(mapOf(Pair(periodString, it))) + _oldEntriesMapState.value.putAll( + mapOf( + Pair( + periodString, + DashboardState(entryList = mapEntryListToDashboardEntryState(it)) + ) + ) + ) } _categoryListState.value.size -> getEntriesByCategory( id = null, period = periodString ) { - _oldEntriesMapState.value.putAll(mapOf(Pair(periodString, it))) + _oldEntriesMapState.value.putAll( + mapOf( + Pair( + periodString, + DashboardState(entryList = mapEntryListToDashboardEntryState(it)) + ) + ) + ) } } lastRequestedMonth = Month.from(nextMonth) diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index c2533643..ba199985 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -26,6 +26,7 @@ import de.hsfl.budgetBinder.presentation.CategoryImageToIcon import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEntryState import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEvent +import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardState import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import kotlinx.coroutines.flow.collectLatest import org.kodein.di.instance @@ -132,7 +133,7 @@ private fun TopDashboardSection( @Composable private fun EntryList( entryList: List, - oldEntries: Map>, + oldEntries: Map, onItemClicked: (Int) -> Unit, onLoadMore: () -> Unit, onEntryDelete: (Int) -> Unit @@ -162,23 +163,23 @@ private fun EntryList( text = "Older entries..." ) } - oldEntries.forEach { (date, entries) -> + oldEntries.forEach { (date, dashboardState) -> stickyHeader { Text(modifier = Modifier.background(MaterialTheme.colors.background).fillMaxWidth(), text = date) } - items(entries) { entry -> + items(dashboardState.entryList) { entryState -> val swipeState = rememberDismissState(confirmStateChange = { if (it == DismissValue.DismissedToEnd) { - onEntryDelete(entry.id) + onEntryDelete(entryState.entry.id) } true }) Divider() SwipeToDelete(dismissState = swipeState) { - ListItem(text = { Text(entry.name) }, - icon = { Icon(Icons.Default.List, contentDescription = null) }, - trailing = { Text("${entry.amount} €") }) + ListItem(text = { Text(entryState.entry.name) }, + icon = { CategoryImageToIcon(icon = entryState.categoryImage) }, + trailing = { Text("${entryState.entry.amount} €") }) } } } From ac1dca8fb04ec855e178e972d16078edf79fefdd Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 12:12:37 +0200 Subject: [PATCH 28/73] Comment nullable id in Client --- .../kotlin/de/hsfl/budgetBinder/data/client/Client.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt index 8e2b49df..8652d1f9 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt @@ -102,7 +102,7 @@ interface ApiClient { /** * Get Entries from a Category ID. The request has a query in the link ?current - * @param id ID from category to get the Entries + * @param id ID from category to get the Entries. When ID null, the Api returns all entries with no category */ suspend fun getEntriesFromCategory(id: Int?): APIResponse> From f4ce224c8d3d6869188a435027e9c7b6523b6cf7 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 12:22:50 +0200 Subject: [PATCH 29/73] Implement OnEntry and OnEntryCreate --- .../kotlin/de/hsfl/budgetBinder/presentation/Screen.kt | 7 +++++-- .../presentation/viewmodel/dashboard/DashboardViewModel.kt | 6 ++---- .../src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt | 3 +++ .../budgetBinder/screens/dashboard/DashboardComponent.kt | 5 +++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt index f4e8970d..499c2b5b 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt @@ -1,5 +1,7 @@ package de.hsfl.budgetBinder.presentation +import de.hsfl.budgetBinder.domain.usecase.DeleteEntryByIdUseCase + sealed class Screen { sealed class Welcome: Screen() { object Screen1: Welcome() @@ -19,8 +21,9 @@ sealed class Screen { object CreateOnRegister: Category() } sealed class Entry: Screen() { - object Edit: Category() - object Create: Category() + data class Overview(val id: Int): Entry() + object Edit: Entry() + object Create: Entry() } object Login : Screen() object Register : Screen() diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 68d34503..b44006ac 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -61,10 +61,8 @@ class DashboardViewModel( when (event) { is DashboardEvent.OnNextCategory -> changedFocusedCategory(increase = true) is DashboardEvent.OnPrevCategory -> changedFocusedCategory(increase = false) - is DashboardEvent.OnEntry -> {} - is DashboardEvent.OnEntryCreate -> scope.launch { - _eventFlow.emit(UiEvent.ShowError(currentMonth.toMonthString())) - } + is DashboardEvent.OnEntry -> routerFlow.navigateTo(Screen.Entry.Overview(event.id)) + is DashboardEvent.OnEntryCreate -> routerFlow.navigateTo(Screen.Entry.Create) is DashboardEvent.OnRefresh -> { when (internalCategoryId) { -1 -> getAllEntries(onSuccess = { diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt index 2752b219..a629280f 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt @@ -1,5 +1,6 @@ package de.hsfl.budgetBinder +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope @@ -22,6 +23,8 @@ fun Router() { is Screen.Login -> LoginComponent() is Screen.Dashboard -> DashboardComponent() is Screen.Settings.Menu, Screen.Settings.User, Screen.Settings.Server -> SettingsView() + is Screen.Entry.Overview -> Text(text = "Entry Click with id: ${(screenState.value as Screen.Entry.Overview).id}") + is Screen.Entry.Create -> Text(text = "Entry Create") is Screen.Category.Summary -> CategoryComponent() else -> {} } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index ba199985..218fcba0 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -71,7 +71,7 @@ fun DashboardComponent() { Column { EntryList(entryList = entryList.value.entryList, oldEntries = olderEntries.value, - onItemClicked = {}, + onItemClicked = { viewModel.onEvent(DashboardEvent.OnEntry(it)) }, onLoadMore = { viewModel.onEvent(DashboardEvent.OnLoadMore) }, onEntryDelete = { viewModel.onEvent(DashboardEvent.OnEntryDelete(it)) } ) @@ -177,7 +177,8 @@ private fun EntryList( }) Divider() SwipeToDelete(dismissState = swipeState) { - ListItem(text = { Text(entryState.entry.name) }, + ListItem(modifier = Modifier.clickable(onClick = { onItemClicked(entryState.entry.id) }), + text = { Text(entryState.entry.name) }, icon = { CategoryImageToIcon(icon = entryState.categoryImage) }, trailing = { Text("${entryState.entry.amount} €") }) } From 8b472065397356216103ac33cb93657aa2744ff2 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 15:48:29 +0200 Subject: [PATCH 30/73] Refactor DashboardViewModel.kt --- .../viewmodel/dashboard/DashboardViewModel.kt | 167 +++++++----------- 1 file changed, 66 insertions(+), 101 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index b44006ac..3ffa7f19 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -7,7 +7,6 @@ import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.UiState -import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import io.ktor.util.date.* @@ -19,7 +18,6 @@ class DashboardViewModel( private val dashboardUseCases: DashboardUseCases, private val logoutUseCase: LogoutUseCase, private val routerFlow: RouterFlow, - private val dataFlow: DataFlow, private val scope: CoroutineScope ) { @@ -63,129 +61,92 @@ class DashboardViewModel( is DashboardEvent.OnPrevCategory -> changedFocusedCategory(increase = false) is DashboardEvent.OnEntry -> routerFlow.navigateTo(Screen.Entry.Overview(event.id)) is DashboardEvent.OnEntryCreate -> routerFlow.navigateTo(Screen.Entry.Create) - is DashboardEvent.OnRefresh -> { - when (internalCategoryId) { - -1 -> getAllEntries(onSuccess = { - _entryListState.value = - entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) - }) - in _categoryListState.value.indices -> getEntriesByCategory( - id = focusedCategoryState.value.category.id, - onSuccess = { - _entryListState.value = - entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) - }) - _categoryListState.value.size -> getEntriesByCategory( - id = null, - onSuccess = { - _entryListState.value = - entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) - }) - } - } + is DashboardEvent.OnRefresh -> refresh() is DashboardEvent.OnLoadMore -> loadMoreEntries() is DashboardEvent.OnEntryDelete -> deleteEntry(id = event.id) } } - private fun mapEntryListToDashboardEntryState(entryList: List): List { - return entryList.map { entry -> - DashboardEntryState( - entry, - categoryImage = getCategoryByEntry(entry)?.image ?: Category.Image.DEFAULT - ) + private fun refresh() { + when (internalCategoryId) { + -1 -> getAllEntries(onSuccess = { + _entryListState.value = + entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + }) + in _categoryListState.value.indices -> getEntriesByCategory( + id = focusedCategoryState.value.category.id, + onSuccess = { + _entryListState.value = + entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + }) + _categoryListState.value.size -> getEntriesByCategory( + id = null, + onSuccess = { + _entryListState.value = + entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + }) } } private fun getAllEntries(onSuccess: (List) -> Unit) = scope.launch { - dashboardUseCases.getAllEntriesUseCase.entries().collect { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Success -> { - onSuccess(it.data!!) - _eventFlow.emit(UiEvent.HideSuccess) - //calcSpendBudgetOnCategory() - } - is DataResponse.Unauthorized -> { - _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - routerFlow.navigateTo(Screen.Login) - } - } - } + dashboardUseCases.getAllEntriesUseCase.entries() + .collect { handleDataResponse(response = it, onSuccess = onSuccess) } } - private fun getAllCategories(onSuccess: (List) -> Unit) { - scope.launch { - dashboardUseCases.getAllCategoriesUseCase.categories().collect { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Success -> { - onSuccess(it.data!!) - //_categoryListState.value = it.data!! - _eventFlow.emit(UiEvent.HideSuccess) - } - is DataResponse.Unauthorized -> { - _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - routerFlow.navigateTo(Screen.Login) - } - } - } - } + private fun getAllCategories(onSuccess: (List) -> Unit) = scope.launch { + dashboardUseCases.getAllCategoriesUseCase.categories() + .collect { handleDataResponse(response = it, onSuccess = onSuccess) } } - private fun getEntriesByCategory(id: Int? = null, period: String? = null, onSuccess: (List) -> Unit) { + + private fun getEntriesByCategory(id: Int? = null, period: String? = null, onSuccess: (List) -> Unit) = scope.launch { - dashboardUseCases.getAllEntriesByCategoryUseCase(id, period).collect { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Success -> { - onSuccess(it.data!!) - _eventFlow.emit(UiEvent.HideSuccess) - } - is DataResponse.Unauthorized -> { - _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - routerFlow.navigateTo(Screen.Login) - } - } - } + dashboardUseCases.getAllEntriesByCategoryUseCase(id, period) + .collect { handleDataResponse(response = it, onSuccess = onSuccess) } } - } + private fun getAllEntriesFromMonth(period: String, onSuccess: (List) -> Unit) = scope.launch { - // TODO: Year had also be to check - dashboardUseCases.getAllEntriesUseCase.entries(period).collect { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Success -> { - _eventFlow.emit(UiEvent.HideSuccess) - onSuccess(it.data!!) - } - is DataResponse.Unauthorized -> { - _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - routerFlow.navigateTo(Screen.Login) - } - } - } + dashboardUseCases.getAllEntriesUseCase.entries(period) + .collect { handleDataResponse(response = it, onSuccess = onSuccess) } } private fun deleteEntry(id: Int) = scope.launch { dashboardUseCases.deleteEntryByIdUseCase(id).collect { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error, is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Success -> { + handleDataResponse(response = it, onSuccess = { + scope.launch { _eventFlow.emit(UiEvent.ShowSuccess("Removed Category")) - onEvent(DashboardEvent.OnRefresh) } + onEvent(DashboardEvent.OnRefresh) + }) + } + } + + private suspend fun handleDataResponse(response: DataResponse, onSuccess: (T) -> Unit) { + when (response) { + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + is DataResponse.Success -> { + _eventFlow.emit(UiEvent.HideSuccess) + onSuccess(response.data!!) + } + is DataResponse.Unauthorized -> { + _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + routerFlow.navigateTo(Screen.Login) } } } + private fun mapEntryListToDashboardEntryState(entryList: List): List { + return entryList.map { entry -> + DashboardEntryState( + entry, + categoryImage = getCategoryByEntry(entry)?.image ?: Category.Image.DEFAULT + ) + } + } + private fun getCategoryByEntry(entry: Entry): Category? { _categoryListState.value.forEach { category -> if (category.id == entry.category_id) return category @@ -210,7 +171,7 @@ class DashboardViewModel( } /** - * Change the internal Id to swipe between the categories + * Change the internal id to swipe between the categories * @param increase if the next or prev button is pressed * @author Cedrik Hoffmann */ @@ -232,7 +193,7 @@ class DashboardViewModel( * Set the first category, all entries are here */ private fun setOverallCategoryState() { - getAllEntries(onSuccess = { + getAllEntries(onSuccess = { entryList -> var totalBudget = 0f _categoryListState.value.forEach { totalBudget += it.budget } _focusedCategoryState.value = focusedCategoryState.value.copy( @@ -240,8 +201,8 @@ class DashboardViewModel( hasNext = true, category = Category(0, "Overall", "111111", Category.Image.DEFAULT, totalBudget) ) - calcSpendBudgetOnCategory(it) - _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + calcSpendBudgetOnCategory(entryList) + _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(entryList)) }) } @@ -291,6 +252,10 @@ class DashboardViewModel( spendBudgetOnCurrentCategory.value.copy(spendBudgetOnCurrentCategory = spendMoney) } + /** + * First, calculate the next Month and Year to request + * + */ private fun loadMoreEntries() { val nextMonth = when { // When the month goes from 01 to 12, the year has to be changed From 2057851ddd5e7080dda67d021cee86d39d69e201 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 16:01:39 +0200 Subject: [PATCH 31/73] Refactor more on DashboardViewModel.kt --- .../viewmodel/dashboard/DashboardViewModel.kt | 71 +++++++------------ 1 file changed, 24 insertions(+), 47 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 3ffa7f19..2a215b30 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -67,24 +67,25 @@ class DashboardViewModel( } } + private fun fillEntryListStateWithResult(entryList: List) { + _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(entryList)) + } + + private fun fillOldEntriesMapState(period: String, entryList: List) { + _oldEntriesMapState.value.putAll( + mapOf(Pair(period, DashboardState(entryList = mapEntryListToDashboardEntryState(entryList)))) + ) + } + private fun refresh() { when (internalCategoryId) { - -1 -> getAllEntries(onSuccess = { - _entryListState.value = - entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) - }) + -1 -> getAllEntries(onSuccess = { fillEntryListStateWithResult(it) }) in _categoryListState.value.indices -> getEntriesByCategory( id = focusedCategoryState.value.category.id, - onSuccess = { - _entryListState.value = - entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) - }) + onSuccess = { fillEntryListStateWithResult(it) }) _categoryListState.value.size -> getEntriesByCategory( id = null, - onSuccess = { - _entryListState.value = - entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) - }) + onSuccess = { fillEntryListStateWithResult(it) }) } } @@ -202,7 +203,7 @@ class DashboardViewModel( category = Category(0, "Overall", "111111", Category.Image.DEFAULT, totalBudget) ) calcSpendBudgetOnCategory(entryList) - _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(entryList)) + fillEntryListStateWithResult(entryList) }) } @@ -217,8 +218,7 @@ class DashboardViewModel( hasNext = true, category = _categoryListState.value[internalCategoryId] ) - _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) - + fillEntryListStateWithResult(it) }) } @@ -232,7 +232,7 @@ class DashboardViewModel( hasNext = false, category = Category(0, "No Category", "111111", Category.Image.DEFAULT, 0f) ) - _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(it)) + fillEntryListStateWithResult(it) }) } @@ -254,7 +254,7 @@ class DashboardViewModel( /** * First, calculate the next Month and Year to request - * + * After that, show on wich category we are at the moment and fetch old entries */ private fun loadMoreEntries() { val nextMonth = when { @@ -268,39 +268,16 @@ class DashboardViewModel( val periodString = "${Month.from(nextMonth).toMonthString()}-${lastRequestedYear}" when (internalCategoryId) { -1 -> getAllEntriesFromMonth(period = periodString) { - _oldEntriesMapState.value.putAll( - mapOf( - Pair( - periodString, - DashboardState(entryList = mapEntryListToDashboardEntryState(it)) - ) - ) - ) + fillOldEntriesMapState(periodString, it) } in _categoryListState.value.indices -> getEntriesByCategory( - id = focusedCategoryState.value.category.id, period = periodString - ) { - _oldEntriesMapState.value.putAll( - mapOf( - Pair( - periodString, - DashboardState(entryList = mapEntryListToDashboardEntryState(it)) - ) - ) - ) - } + id = focusedCategoryState.value.category.id, period = periodString, + onSuccess = { fillOldEntriesMapState(periodString, it) } + ) _categoryListState.value.size -> getEntriesByCategory( - id = null, period = periodString - ) { - _oldEntriesMapState.value.putAll( - mapOf( - Pair( - periodString, - DashboardState(entryList = mapEntryListToDashboardEntryState(it)) - ) - ) - ) - } + id = null, period = periodString, + onSuccess = { fillOldEntriesMapState(periodString, it) } + ) } lastRequestedMonth = Month.from(nextMonth) } From dc426175b87d8bdcf3d61ab1958b985b77c82e3f Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 12:41:26 +0200 Subject: [PATCH 32/73] Add new viewmodel folder --- .../commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt | 3 ++- .../viewmodel/{ => navdrawer}/NavDrawerViewModel.kt | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/{ => navdrawer}/NavDrawerViewModel.kt (82%) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index 517bca6a..ba7f238e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -19,6 +19,7 @@ import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel import de.hsfl.budgetBinder.presentation.viewmodel.* +import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerViewModel import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel @@ -98,5 +99,5 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { CategorySummaryViewModel(instance(), instance(), instance()) } bindSingleton { EntryViewModel(instance(), instance()) } bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance(), instance()) } - bindSingleton { NavDrawerViewModel(instance(), instance()) } + bindSingleton { NavDrawerViewModel(instance(), instance(), instance()) } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/NavDrawerViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt similarity index 82% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/NavDrawerViewModel.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt index e1004d5a..7da2531a 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/NavDrawerViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt @@ -1,8 +1,10 @@ -package de.hsfl.budgetBinder.presentation.viewmodel +package de.hsfl.budgetBinder.presentation.viewmodel.navdrawer import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.domain.usecase.LogoutUseCase import de.hsfl.budgetBinder.presentation.UiState +import de.hsfl.budgetBinder.presentation.flow.DataFlow +import de.hsfl.budgetBinder.presentation.flow.RouterFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -13,8 +15,14 @@ import kotlinx.coroutines.flow.onEach class NavDrawerViewModel( private val logoutUseCase: LogoutUseCase, + private val dataFlow: DataFlow, + private val routerFlow: RouterFlow, private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()), ) { + + + + // Old private val _state = MutableStateFlow(UiState.Empty) val state: StateFlow = _state From 9475ec10365b26d1dd56ec2aa05081834dc4215a Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 14:46:48 +0200 Subject: [PATCH 33/73] Implement NavDrawerIcon --- .../viewmodel/navdrawer/NavDrawerEvent.kt | 9 ++++++ .../viewmodel/navdrawer/NavDrawerViewModel.kt | 29 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerEvent.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerEvent.kt new file mode 100644 index 00000000..ff09084c --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerEvent.kt @@ -0,0 +1,9 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.navdrawer + +sealed class NavDrawerEvent { + object OnDashboard: NavDrawerEvent() + object OnCreateEntry: NavDrawerEvent() + object OnCategory: NavDrawerEvent() + object OnSettings: NavDrawerEvent() + object OnLogout: NavDrawerEvent() +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt index 7da2531a..6e554e3e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt @@ -2,9 +2,12 @@ package de.hsfl.budgetBinder.presentation.viewmodel.navdrawer import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.domain.usecase.LogoutUseCase +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -12,14 +15,38 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch class NavDrawerViewModel( private val logoutUseCase: LogoutUseCase, - private val dataFlow: DataFlow, private val routerFlow: RouterFlow, private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()), ) { + private val _eventFlow = UiEventSharedFlow.mutableEventFlow + val eventFlow = UiEventSharedFlow.eventFlow + + fun onEvent(event: NavDrawerEvent) { + when (event) { + is NavDrawerEvent.OnDashboard -> routerFlow.navigateTo(Screen.Dashboard) + is NavDrawerEvent.OnCreateEntry -> routerFlow.navigateTo(Screen.Entry.Create) + is NavDrawerEvent.OnCategory -> routerFlow.navigateTo(Screen.Category.Summary) + is NavDrawerEvent.OnSettings -> routerFlow.navigateTo(Screen.Settings.Menu) + is NavDrawerEvent.OnLogout -> scope.launch { + logoutUseCase(onAllDevices = false).collect { + when (it) { + is DataResponse.Success -> _eventFlow.emit(UiEvent.ShowSuccess("Your are logged out")) + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Unauthorized -> { + routerFlow.navigateTo(Screen.Login) + _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + } + } + } + } + } + } // Old From c9c2decef3e1fef4d317384c67d0afd857b5ac22 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 14:47:00 +0200 Subject: [PATCH 34/73] Add more Icons --- .../hsfl/budgetBinder/compose/icon/AppIcon.kt | 43 +++++++++++++++++++ .../hsfl/budgetBinder/compose/icon/AppIcon.kt | 43 +++++++++++++++++++ .../budgetBinder/compose/icon/AppIcons.kt | 24 +++++++++++ 3 files changed, 110 insertions(+) diff --git a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt index d28786f1..544ca4ad 100644 --- a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt +++ b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt @@ -1,6 +1,9 @@ package de.hsfl.budgetBinder.compose.icon import androidx.compose.foundation.Image +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector @@ -16,3 +19,43 @@ actual fun AppIcon(modifier: Modifier) { actual fun AvatarImage(modifier: Modifier) { Image(modifier = modifier, imageVector = ImageVector.vectorResource(id = R.drawable.ic_avataricon), contentDescription = null) } + +@Composable +actual fun DashboardIcon() { + Icon(imageVector = Icons.Default.Dashboard, contentDescription = null) +} + +@Composable +actual fun CategoryIcon() { + Icon(imageVector = Icons.Default.Category, contentDescription = null) +} + +@Composable +actual fun SettingsIcon() { + Icon(imageVector = Icons.Default.Settings, contentDescription = null) +} + +@Composable +actual fun LogoutIcon() { + Icon(imageVector = Icons.Default.Logout, contentDescription = null) +} + +@Composable +actual fun DarkModeIcon() { + Icon(imageVector = Icons.Default.DarkMode, contentDescription = null) +} + +@Composable +actual fun ServerIcon() { + Icon(imageVector = Icons.Default.Dns, contentDescription = null) +} + +@Composable +actual fun AccountIcon() { + Icon(imageVector = Icons.Default.ManageAccounts, contentDescription = null) +} + +@Composable +actual fun DeleteForeverIcon() { + Icon(imageVector = Icons.Default.DeleteForever, contentDescription = null) +} diff --git a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt index 21393e61..011a3444 100644 --- a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt +++ b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt @@ -1,6 +1,9 @@ package de.hsfl.budgetBinder.compose.icon import androidx.compose.foundation.Image +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -14,3 +17,43 @@ actual fun AppIcon(modifier: Modifier) { actual fun AvatarImage(modifier: Modifier) { Image(modifier = modifier, painter = painterResource("undraw_profile_pic_ic-5-t.svg"), contentDescription = null) } + +@Composable +actual fun DashboardIcon() { + Icon(imageVector = Icons.Default.Dashboard, contentDescription = null) +} + +@Composable +actual fun CategoryIcon() { + Icon(imageVector = Icons.Default.Category, contentDescription = null) +} + +@Composable +actual fun SettingsIcon() { + Icon(imageVector = Icons.Default.Settings, contentDescription = null) +} + +@Composable +actual fun LogoutIcon() { + Icon(imageVector = Icons.Default.Logout, contentDescription = null) +} + +@Composable +actual fun DarkModeIcon() { + Icon(imageVector = Icons.Default.DarkMode, contentDescription = null) +} + +@Composable +actual fun ServerIcon() { + Icon(imageVector = Icons.Default.Dns, contentDescription = null) +} + +@Composable +actual fun AccountIcon() { + Icon(imageVector = Icons.Default.ManageAccounts, contentDescription = null) +} + +@Composable +actual fun DeleteForeverIcon() { + Icon(imageVector = Icons.Default.DeleteForever, contentDescription = null) +} diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcons.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcons.kt index d738e4bf..e70fe7bc 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcons.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcons.kt @@ -8,3 +8,27 @@ expect fun AppIcon(modifier: Modifier = Modifier) @Composable expect fun AvatarImage(modifier: Modifier = Modifier) + +@Composable +expect fun DashboardIcon() + +@Composable +expect fun CategoryIcon() + +@Composable +expect fun SettingsIcon() + +@Composable +expect fun LogoutIcon() + +@Composable +expect fun DarkModeIcon() + +@Composable +expect fun ServerIcon() + +@Composable +expect fun AccountIcon() + +@Composable +expect fun DeleteForeverIcon() From 15b296a6e660ebda3df3275edd89cb0745f45df5 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 14:47:13 +0200 Subject: [PATCH 35/73] Implement NavDrawer View --- .../kotlin/de/hsfl/budgetBinder/NavDrawer.kt | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt new file mode 100644 index 00000000..46d41940 --- /dev/null +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt @@ -0,0 +1,97 @@ +package de.hsfl.budgetBinder + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import de.hsfl.budgetBinder.compose.icon.* +import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.flow.DataFlow +import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerEvent +import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerViewModel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import org.kodein.di.instance + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun NavDrawer( + drawerState: DrawerState, + gesturesEnabled: Boolean = true, + content: @Composable () -> Unit +) { + val scope = rememberCoroutineScope() + val viewModel: NavDrawerViewModel by di.instance() + ModalDrawer( + drawerState = drawerState, + gesturesEnabled = gesturesEnabled, + content = content, + drawerContent = { + UserData() + ListItem( + icon = { DashboardIcon() }, + text = { Text("Dashboard") }, + modifier = Modifier.clickable(onClick = { + scope.launch { + drawerState.close() + viewModel.onEvent(NavDrawerEvent.OnDashboard) + } + }) + ) + ListItem( + icon = { CategoryIcon() }, + text = { Text("Categories") }, + modifier = Modifier.clickable(onClick = { + scope.launch { + drawerState.close() + viewModel.onEvent(NavDrawerEvent.OnCategory) + } + }) + ) + ListItem( + icon = { SettingsIcon() }, + text = { Text("Settings") }, + modifier = Modifier.clickable(onClick = { + scope.launch { + drawerState.close() + viewModel.onEvent(NavDrawerEvent.OnSettings) + } + }) + ) + ListItem( + icon = { LogoutIcon() }, + text = { Text("Logout") }, + modifier = Modifier.clickable(onClick = { + scope.launch { + drawerState.close() + viewModel.onEvent(NavDrawerEvent.OnLogout) + } + }) + ) + } + ) +} + +@Composable +fun UserData() { + val dataFlow: DataFlow by di.instance() + val userData = dataFlow.userState.collectAsState() + + Column { + Row(verticalAlignment = Alignment.CenterVertically) { + AvatarImage(modifier = Modifier.size(64.dp)) + Spacer(modifier = Modifier.width(16.dp)) + Column(verticalArrangement = Arrangement.Center) { + Text(text = "${userData.value.firstName} ${userData.value.name}", style = MaterialTheme.typography.h5) + Text(text = userData.value.email, style = MaterialTheme.typography.body1) + } + } + Spacer(modifier = Modifier.width(16.dp)) + Divider() + } +} From 2721348bc524694cf89f25f970c1b76584574f11 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 14:47:18 +0200 Subject: [PATCH 36/73] Implement NavDrawer View --- .../src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt index dd202171..64546390 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt @@ -7,12 +7,14 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.di.kodein import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import io.ktor.client.engine.cio.* import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import org.kodein.di.compose.withDI import org.kodein.di.instance @@ -26,6 +28,7 @@ fun App() = withDI(di) { val darkTheme = dataFlow.darkModeState.collectAsState(scope.coroutineContext) val scaffoldState = rememberScaffoldState() val loadingState = remember { mutableStateOf(false) } + val drawerState = rememberDrawerState(DrawerValue.Closed) LaunchedEffect(key1 = true) { uiEventFlow.eventFlow.collectLatest { event -> @@ -55,14 +58,19 @@ fun App() = withDI(di) { // TODO: Show NavDrawer not on Login und Register TopAppBar(title = { Text("Budget Binder") }, navigationIcon = { IconButton(onClick = { - // TODO: Implement NavBar + scope.launch { + if (drawerState.isOpen) drawerState.close() + else drawerState.open() + } }) { Icon(Icons.Filled.Menu, contentDescription = null) } }) } ) { - Router() + NavDrawer(drawerState = drawerState) { + Router() + } } } } From bcf4ef01185302c02151c01382665dbb326dfed0 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 14:50:09 +0200 Subject: [PATCH 37/73] Change Icons in SettingsMenu --- .../budgetBinder/screens/settings/SettingsMenuView.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsMenuView.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsMenuView.kt index 3f8e7a41..22c1429f 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsMenuView.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsMenuView.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.dialog.DeleteUserDialog +import de.hsfl.budgetBinder.compose.icon.* import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEvent @@ -34,7 +35,7 @@ fun SettingsMenuView(modifier: Modifier = Modifier) { Divider() ListItem(text = { Text("Dark Mode") }, modifier = Modifier.clickable(onClick = { viewModel.onEvent(SettingsEvent.OnToggleDarkMode) }), - icon = { Icon(Icons.Filled.Build, contentDescription = null) }, + icon = { DarkModeIcon() }, trailing = { Switch(modifier = Modifier.padding(start = 8.dp), checked = darkMode.value, @@ -43,17 +44,17 @@ fun SettingsMenuView(modifier: Modifier = Modifier) { Divider() ListItem(modifier = Modifier.clickable(onClick = { viewModel.onEvent(SettingsEvent.OnChangeToSettingsUserEdit) }), text = { Text("Account") }, - icon = { Icon(Icons.Filled.AccountCircle, contentDescription = null) }, + icon = { AccountIcon() }, trailing = { Icon(Icons.Filled.KeyboardArrowRight, contentDescription = null) }) Divider() ListItem(modifier = Modifier.clickable(onClick = { viewModel.onEvent(SettingsEvent.OnChangeToSettingsServerUrlEdit) }), text = { Text("Server") }, - icon = { Icon(Icons.Filled.Edit, contentDescription = null) }, + icon = { ServerIcon() }, trailing = { Icon(Icons.Filled.KeyboardArrowRight, contentDescription = null) }) Divider() ListItem(modifier = Modifier.clickable(onClick = { viewModel.onEvent(SettingsEvent.OnLogoutAllDevices) }), text = { Text("Logout on all device") }, - icon = { Icon(Icons.Filled.ExitToApp, contentDescription = null) }) + icon = { LogoutIcon() }) Spacer(modifier = Modifier.height(4.dp)) Text( modifier = Modifier.padding(start = 8.dp), @@ -64,7 +65,7 @@ fun SettingsMenuView(modifier: Modifier = Modifier) { Divider() ListItem(modifier = Modifier.clickable(onClick = { viewModel.onEvent(SettingsEvent.OnDeleteUser) }), text = { Text("Delete my User") }, - icon = { Icon(Icons.Filled.Delete, contentDescription = null) }) + icon = { DeleteForeverIcon() }) } DeleteUserDialog( openDialog = dialogState.value, From 965912b6a1b52d3e87278741d6f4f06bc851f32b Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 14:56:06 +0200 Subject: [PATCH 38/73] Add padding --- .../src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt index 46d41940..a3df0d4d 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt @@ -83,7 +83,7 @@ fun UserData() { val userData = dataFlow.userState.collectAsState() Column { - Row(verticalAlignment = Alignment.CenterVertically) { + Row(modifier = Modifier.padding(8.dp),verticalAlignment = Alignment.CenterVertically) { AvatarImage(modifier = Modifier.size(64.dp)) Spacer(modifier = Modifier.width(16.dp)) Column(verticalArrangement = Arrangement.Center) { @@ -91,7 +91,6 @@ fun UserData() { Text(text = userData.value.email, style = MaterialTheme.typography.body1) } } - Spacer(modifier = Modifier.width(16.dp)) Divider() } } From a8b4465d8e700454abfae25bbcdb086b1047130d Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 15:29:27 +0200 Subject: [PATCH 39/73] Send user to login screen after logout --- .../presentation/viewmodel/navdrawer/NavDrawerViewModel.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt index 6e554e3e..1d414b97 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt @@ -35,7 +35,10 @@ class NavDrawerViewModel( is NavDrawerEvent.OnLogout -> scope.launch { logoutUseCase(onAllDevices = false).collect { when (it) { - is DataResponse.Success -> _eventFlow.emit(UiEvent.ShowSuccess("Your are logged out")) + is DataResponse.Success -> { + routerFlow.navigateTo(Screen.Login) + _eventFlow.emit(UiEvent.ShowSuccess("Your are logged out")) + } is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Unauthorized -> { From e39c9b6a5c26c36d8ff05b436186485c4c063a7f Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 20:01:37 +0200 Subject: [PATCH 40/73] Fix merge conflict --- .../src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index ba7f238e..19102351 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -98,6 +98,6 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { _CategoryViewModel(instance(), instance()) } bindSingleton { CategorySummaryViewModel(instance(), instance(), instance()) } bindSingleton { EntryViewModel(instance(), instance()) } - bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance(), instance()) } + bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance()) } bindSingleton { NavDrawerViewModel(instance(), instance(), instance()) } } From 1d53ff369cb8c4ca2cdd1bfef6fdd12015d58d3c Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 20:03:12 +0200 Subject: [PATCH 41/73] Fix typo --- .../kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt | 2 +- .../presentation/viewmodel/dashboard/DashboardViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt index 642caed7..d32a5745 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt @@ -10,6 +10,6 @@ sealed class UiEvent { // Show Success data class ShowSuccess(val msg: String) : UiEvent() - // Call on Success, with could reset the loading state + // Call on Success, which could reset the loading state object HideSuccess : UiEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 2a215b30..2c28da1b 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -254,7 +254,7 @@ class DashboardViewModel( /** * First, calculate the next Month and Year to request - * After that, show on wich category we are at the moment and fetch old entries + * After that, show on which category we are at the moment and fetch old entries */ private fun loadMoreEntries() { val nextMonth = when { From d322762902654a6c47986ade68e1275abf60684d Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 20:14:24 +0200 Subject: [PATCH 42/73] Fix another wich typo --- .../presentation/viewmodel/dashboard/DashboardViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 2c28da1b..7ea57038 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -208,7 +208,7 @@ class DashboardViewModel( } /** - * Set the category state for the current category, wich the user moved to + * Set the category state for the current category, which the user moved to */ private fun setCategoryState() { getEntriesByCategory(id = _categoryListState.value[internalCategoryId].id, onSuccess = { From 14d00ee24c5dac29393135bac763e08361c388cd Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 18:07:35 +0200 Subject: [PATCH 43/73] Implement CategorySummaryViewModel.kt --- .../category/CategorySummaryViewModel.kt | 55 +++++++++++++++++++ .../category/summary/CategorySummaryEvent.kt | 1 + 2 files changed, 56 insertions(+) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt new file mode 100644 index 00000000..6b296300 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt @@ -0,0 +1,55 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category + +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.DataResponse +import de.hsfl.budgetBinder.domain.usecase.GetAllCategoriesUseCase +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class CategorySummaryViewModel( + private val getAllCategoriesUseCase: GetAllCategoriesUseCase, + private val routerFlow: RouterFlow, + private val scope: CoroutineScope +) { + + private val _categoryList = MutableStateFlow>(emptyList()) + val categoryList: StateFlow> = _categoryList + + private val _eventFlow = UiEventSharedFlow.mutableEventFlow + val eventFlow = UiEventSharedFlow.eventFlow + + init { + getAllCategories() + } + + fun onEvent(event: CategorySummaryEvent) { + when (event) { + is CategorySummaryEvent.OnCategory -> routerFlow.navigateTo(Screen.Category.Detail(event.id)) + is CategorySummaryEvent.OnCategoryCreate -> routerFlow.navigateTo(Screen.Category.Summary) + is CategorySummaryEvent.OnRefresh -> getAllCategories() + } + } + + private fun getAllCategories() = scope.launch { + getAllCategoriesUseCase.categories().collect { response -> + when (response) { + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + is DataResponse.Unauthorized -> { + _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + routerFlow.navigateTo(Screen.Login) + } + is DataResponse.Success -> { + _categoryList.value = response.data!! + _eventFlow.emit(UiEvent.ShowSuccess("TODO: Change to HideSuccess")) + } + } + } + } +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt index d5cef432..aa007741 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt @@ -4,5 +4,6 @@ sealed class CategorySummaryEvent { data class OnCategory(val id: Int): CategorySummaryEvent() object OnCategoryCreate: CategorySummaryEvent() object OnRefresh: CategorySummaryEvent() + object OnRefresh: CategorySummaryEvent() // TODO: Maybe delete? } From 1e4956c59c3059b5d5c6746b4d0eb2f4e919c2fc Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Sun, 19 Jun 2022 19:57:55 +0200 Subject: [PATCH 44/73] Implement CategoryDetailViewModel.kt --- .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 3 + .../category/CategorySummaryViewModel.kt | 55 ------------------- 2 files changed, 3 insertions(+), 55 deletions(-) delete mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index 19102351..0bc90715 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -19,6 +19,9 @@ import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel import de.hsfl.budgetBinder.presentation.viewmodel.* +import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerViewModel import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt deleted file mode 100644 index 6b296300..00000000 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategorySummaryViewModel.kt +++ /dev/null @@ -1,55 +0,0 @@ -package de.hsfl.budgetBinder.presentation.viewmodel.category - -import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.DataResponse -import de.hsfl.budgetBinder.domain.usecase.GetAllCategoriesUseCase -import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent -import de.hsfl.budgetBinder.presentation.flow.RouterFlow -import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch - -class CategorySummaryViewModel( - private val getAllCategoriesUseCase: GetAllCategoriesUseCase, - private val routerFlow: RouterFlow, - private val scope: CoroutineScope -) { - - private val _categoryList = MutableStateFlow>(emptyList()) - val categoryList: StateFlow> = _categoryList - - private val _eventFlow = UiEventSharedFlow.mutableEventFlow - val eventFlow = UiEventSharedFlow.eventFlow - - init { - getAllCategories() - } - - fun onEvent(event: CategorySummaryEvent) { - when (event) { - is CategorySummaryEvent.OnCategory -> routerFlow.navigateTo(Screen.Category.Detail(event.id)) - is CategorySummaryEvent.OnCategoryCreate -> routerFlow.navigateTo(Screen.Category.Summary) - is CategorySummaryEvent.OnRefresh -> getAllCategories() - } - } - - private fun getAllCategories() = scope.launch { - getAllCategoriesUseCase.categories().collect { response -> - when (response) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) - is DataResponse.Unauthorized -> { - _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) - routerFlow.navigateTo(Screen.Login) - } - is DataResponse.Success -> { - _categoryList.value = response.data!! - _eventFlow.emit(UiEvent.ShowSuccess("TODO: Change to HideSuccess")) - } - } - } - } -} From 7cdbfd215093387be4f21acb602d2110f05f6a3b Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 08:17:40 +0200 Subject: [PATCH 45/73] Fix rebase stuff --- .../src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt | 3 --- .../viewmodel/category/summary/CategorySummaryEvent.kt | 1 - 2 files changed, 4 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index 0bc90715..527ad339 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -23,9 +23,6 @@ import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySumm import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerViewModel -import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel -import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel -import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditServerUrlViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditUserViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsViewModel diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt index aa007741..d5cef432 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt @@ -4,6 +4,5 @@ sealed class CategorySummaryEvent { data class OnCategory(val id: Int): CategorySummaryEvent() object OnCategoryCreate: CategorySummaryEvent() object OnRefresh: CategorySummaryEvent() - object OnRefresh: CategorySummaryEvent() // TODO: Maybe delete? } From 5727d079886768c271833dab5d52598db77544ba Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 09:50:53 +0200 Subject: [PATCH 46/73] Restructure Category ViewModel and implement CategoryDetailViewModel.kt --- .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 6 +- .../presentation/flow/RouterFlow.kt | 10 +-- .../viewmodel/category/CategoryViewModel.kt | 72 ++++++++++++++++++- .../category/detail/CategoryDetailEvent.kt | 1 + .../detail/CategoryDetailViewModel.kt | 31 ++++---- .../summary/CategorySummaryViewModel.kt | 36 ++++------ 6 files changed, 105 insertions(+), 51 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index 527ad339..97089e1c 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -14,13 +14,13 @@ import de.hsfl.budgetBinder.domain.usecase.storage.StoreDarkModeUseCase import de.hsfl.budgetBinder.domain.usecase.storage.StoreServerUrlUseCase import de.hsfl.budgetBinder.domain.usecase.storage.StoreUserStateUseCase import de.hsfl.budgetBinder.presentation.flow.DataFlow -import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel import de.hsfl.budgetBinder.presentation.viewmodel.* import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailViewModel import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditServerUrlViewModel @@ -85,7 +85,6 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { DataFlowUseCases(instance(), instance(), instance()) } // Flows - bindSingleton { RouterFlow(instance(), instance()) } bindSingleton { DataFlow(instance(), instance()) } bindSingleton { UiEventSharedFlow } @@ -96,7 +95,8 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { SettingsEditUserViewModel(instance(), instance(), instance(), instance()) } bindSingleton { SettingsEditServerUrlViewModel(instance(), instance(), instance()) } bindSingleton { _CategoryViewModel(instance(), instance()) } - bindSingleton { CategorySummaryViewModel(instance(), instance(), instance()) } + bindSingleton { CategorySummaryViewModel(instance(), instance()) } + bindSingleton { CategoryDetailViewModel(instance(), instance()) } bindSingleton { EntryViewModel(instance(), instance()) } bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance()) } bindSingleton { NavDrawerViewModel(instance(), instance(), instance()) } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/RouterFlow.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/RouterFlow.kt index 2d612263..3d2515b1 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/RouterFlow.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/RouterFlow.kt @@ -3,14 +3,16 @@ package de.hsfl.budgetBinder.presentation.flow import de.hsfl.budgetBinder.domain.usecase.NavigateToScreenUseCase import de.hsfl.budgetBinder.presentation.Screen import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -class RouterFlow( - private val navigateToScreenUseCase: NavigateToScreenUseCase, - private val scope: CoroutineScope -) { +object RouterFlow{ + private val navigateToScreenUseCase: NavigateToScreenUseCase = NavigateToScreenUseCase() + private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) + private val _state = MutableStateFlow(Screen.Login) val state: StateFlow = _state diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt index 218dd618..79a25975 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt @@ -1,6 +1,76 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.DataResponse +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch -class CategoryViewModel() { +open class CategoryViewModel( + _categoriesUseCases: CategoriesUseCases, + _scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) +) { + private val scope: CoroutineScope = _scope + private val categoriesUseCases: CategoriesUseCases = _categoriesUseCases + + private val _eventFlow = UiEventSharedFlow.mutableEventFlow + val eventFlow = UiEventSharedFlow.eventFlow + + protected fun getAll(onSuccess: (List) -> Unit) = scope.launch { + categoriesUseCases.getAllCategoriesUseCase.categories().collect { response -> + handleDataResponse(response = response, onSuccess = onSuccess) + } + } + + protected fun getById(id: Int, onSuccess: (Category) -> Unit) = scope.launch { + categoriesUseCases.getCategoryByIdUseCase(id = id).collect { response -> + handleDataResponse(response = response, onSuccess = onSuccess) + } + } + + protected fun create(category: Category.In, onSuccess: (Category) -> Unit) = scope.launch { + categoriesUseCases.createCategoryUseCase(category = category).collect { response -> + handleDataResponse(response = response, onSuccess = onSuccess) + } + } + + protected fun change(id: Int, category: Category.Patch, onSuccess: (Category) -> Unit) = scope.launch { + categoriesUseCases.changeCategoryByIdUseCase(id = id, category = category).collect { response -> + handleDataResponse(response = response, onSuccess = onSuccess) + } + } + + protected fun delete(id: Int, onSuccess: (Category) -> Unit) = scope.launch { + categoriesUseCases.deleteCategoryByIdUseCase(id = id).collect { response -> + handleDataResponse(response = response, onSuccess = onSuccess) + } + } + + protected fun entries(id: Int, onSuccess: (List) -> Unit) = scope.launch { + categoriesUseCases.getAllEntriesByCategoryUseCase(id = id).collect { response -> + handleDataResponse(response = response, onSuccess = onSuccess) + } + } + + private fun handleDataResponse(response: DataResponse, onSuccess: (T) -> Unit) = scope.launch { + when (response) { + is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Success -> { + _eventFlow.emit(UiEvent.HideSuccess) + onSuccess(response.data!!) + } + is DataResponse.Unauthorized -> { + RouterFlow.navigateTo(Screen.Login) + _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + } + } + } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt index 29e75295..ac5af2de 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt @@ -3,5 +3,6 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.detail sealed class CategoryDetailEvent { object OnEdit: CategoryDetailEvent() object OnDelete: CategoryDetailEvent() + object OnBack: CategoryDetailEvent() data class OnEntry(val id: Int): CategoryDetailEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt index 23c4f266..48a2a7d3 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt @@ -1,41 +1,34 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.detail import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.Entry -import de.hsfl.budgetBinder.domain.usecase.GetAllEntriesByCategoryUseCase -import de.hsfl.budgetBinder.domain.usecase.GetCategoryByIdUseCase +import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow class CategoryDetailViewModel( - private val getCategoryByIdUseCase: GetCategoryByIdUseCase, - private val getAllEntriesByCategoryUseCase: GetAllEntriesByCategoryUseCase, - private val routerFlow: RouterFlow, - private val scope: CoroutineScope + categoriesUseCases: CategoriesUseCases, + scope: CoroutineScope +) : CategoryViewModel( + _categoriesUseCases = categoriesUseCases, + _scope = scope ) { private val _categoryState = MutableStateFlow(Category(id = -1, name = "0", color = "111111", image = Category.Image.DEFAULT, budget = 0f)) val categoryState: StateFlow = _categoryState - private val _entryList = MutableStateFlow>(emptyList()) - val entryList: StateFlow> = _entryList - - fun onEvent(event: CategoryDetailEvent) { when (event) { - is CategoryDetailEvent.OnEdit -> routerFlow.navigateTo(Screen.Category.Edit) - is CategoryDetailEvent.OnDelete -> {} + is CategoryDetailEvent.OnEdit -> RouterFlow.navigateTo(Screen.Category.Edit) + is CategoryDetailEvent.OnDelete -> super.delete( + id = categoryState.value.id, + onSuccess = { RouterFlow.navigateTo(Screen.Category.Summary) }) + is CategoryDetailEvent.OnBack -> RouterFlow.navigateTo(Screen.Category.Summary) is CategoryDetailEvent.OnEntry -> { /* TODO: Navigate to Entry detail */ } } } - - private fun deleteCurrentEntry() { - - } - - } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt index 2216e4ba..d25ffcd7 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt @@ -2,54 +2,42 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.summary import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.DataResponse +import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases +import de.hsfl.budgetBinder.domain.usecase.DeleteCategoryByIdUseCase import de.hsfl.budgetBinder.domain.usecase.GetAllCategoriesUseCase import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow +import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch class CategorySummaryViewModel( - private val getAllCategoriesUseCase: GetAllCategoriesUseCase, - private val routerFlow: RouterFlow, - private val scope: CoroutineScope + categoriesUseCases: CategoriesUseCases, + scope: CoroutineScope +): CategoryViewModel( + _categoriesUseCases = categoriesUseCases, + _scope = scope ) { - private val _categoryList = MutableStateFlow>(emptyList()) val categoryList: StateFlow> = _categoryList - private val _eventFlow = UiEventSharedFlow.mutableEventFlow - val eventFlow = UiEventSharedFlow.eventFlow - init { getAllCategories() } fun onEvent(event: CategorySummaryEvent) { when (event) { - is CategorySummaryEvent.OnCategory -> routerFlow.navigateTo(Screen.Category.Detail(event.id)) - is CategorySummaryEvent.OnCategoryCreate -> routerFlow.navigateTo(Screen.Category.Summary) + is CategorySummaryEvent.OnCategory -> RouterFlow.navigateTo(Screen.Category.Detail(event.id)) + is CategorySummaryEvent.OnCategoryCreate -> RouterFlow.navigateTo(Screen.Category.Summary) is CategorySummaryEvent.OnRefresh -> getAllCategories() } } - private fun getAllCategories() = scope.launch { - getAllCategoriesUseCase.categories().collect { response -> - when (response) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) - is DataResponse.Unauthorized -> { - _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) - routerFlow.navigateTo(Screen.Login) - } - is DataResponse.Success -> { - _categoryList.value = response.data!! - _eventFlow.emit(UiEvent.ShowSuccess("TODO: Change to HideSuccess")) - } - } - } + private fun getAllCategories() { + super.getAll(onSuccess = {_categoryList.value = it}) } } From 6ffa84d59cbd870233c3f4740645fc0f2a47befe Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 10:17:14 +0200 Subject: [PATCH 47/73] Implement View and more ViewModel functions --- .../category/detail/CategoryDetailEvent.kt | 1 + .../detail/CategoryDetailViewModel.kt | 29 +++++++++++++++++- .../screens/category/CategoryComponent.kt | 8 ++++- .../screens/category/CategoryDetail.kt | 30 +++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt index ac5af2de..bc18a565 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt @@ -4,5 +4,6 @@ sealed class CategoryDetailEvent { object OnEdit: CategoryDetailEvent() object OnDelete: CategoryDetailEvent() object OnBack: CategoryDetailEvent() + object OnRefresh: CategoryDetailEvent() data class OnEntry(val id: Int): CategoryDetailEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt index 48a2a7d3..4bdb741f 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt @@ -1,6 +1,7 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.detail import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.flow.RouterFlow @@ -20,15 +21,41 @@ class CategoryDetailViewModel( MutableStateFlow(Category(id = -1, name = "0", color = "111111", image = Category.Image.DEFAULT, budget = 0f)) val categoryState: StateFlow = _categoryState + private val _entryList = MutableStateFlow>(emptyList()) + val entryList: StateFlow> = _entryList + + private val currentCategoryId: Int = when (RouterFlow.state.value) { + is Screen.Category.Detail -> (RouterFlow.state.value as Screen.Category.Detail).id + else -> -1 + } + + init { + getCategoryByCurrentId() + setEntryList() + } + fun onEvent(event: CategoryDetailEvent) { when (event) { is CategoryDetailEvent.OnEdit -> RouterFlow.navigateTo(Screen.Category.Edit) is CategoryDetailEvent.OnDelete -> super.delete( - id = categoryState.value.id, + id = currentCategoryId, onSuccess = { RouterFlow.navigateTo(Screen.Category.Summary) }) is CategoryDetailEvent.OnBack -> RouterFlow.navigateTo(Screen.Category.Summary) + is CategoryDetailEvent.OnRefresh -> getCategoryByCurrentId() is CategoryDetailEvent.OnEntry -> { /* TODO: Navigate to Entry detail */ } } } + + private fun getCategoryByCurrentId() { + if (currentCategoryId != -1) { + super.getById(id = currentCategoryId, onSuccess = { _categoryState.value = it }) + } + } + + private fun setEntryList() { + if (currentCategoryId != -1) { + super.entries(id = currentCategoryId, onSuccess = { _entryList.value = it }) + } + } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt index 16f52d3d..36f7bb85 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt @@ -1,8 +1,14 @@ package de.hsfl.budgetBinder.screens.category import androidx.compose.runtime.Composable +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.flow.RouterFlow @Composable fun CategoryComponent() { - CategorySummary() + when (RouterFlow.state.value) { + is Screen.Category.Summary -> CategorySummary() + is Screen.Category.Detail -> CategoryDetailView() + else -> {} + } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt new file mode 100644 index 00000000..62e40f63 --- /dev/null +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt @@ -0,0 +1,30 @@ +package de.hsfl.budgetBinder.screens.category + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ListItem +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import de.hsfl.budgetBinder.di +import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailViewModel +import org.kodein.di.instance + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun CategoryDetailView() { + val viewModel: CategoryDetailViewModel by di.instance() + val categoryState = viewModel.categoryState.collectAsState() + val entryListState = viewModel.entryList.collectAsState() + + Column { + Text(categoryState.value.toString()) + LazyColumn { + items(entryListState.value) { entry -> + ListItem(text = { Text(entry.name) }, trailing = { Text(entry.amount.toString()) }) + } + } + } +} From b25de32feb86706705d6a01c7287d4eedfbe4cf8 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 12:00:04 +0200 Subject: [PATCH 48/73] Implement Lifecycle --- .../hsfl/budgetBinder/data/client/Client.kt | 4 +- .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 6 ++- .../presentation/flow/RouterFlow.kt | 10 ++-- .../viewmodel/category/CategoryViewModel.kt | 8 ++-- .../category/detail/CategoryDetailEvent.kt | 2 + .../detail/CategoryDetailViewModel.kt | 48 +++++++++++-------- .../summary/CategorySummaryViewModel.kt | 14 +++--- .../kotlin/de/hsfl/budgetBinder/Router.kt | 2 + .../screens/category/CategoryComponent.kt | 6 ++- .../screens/category/CategoryDetail.kt | 15 ++++++ 10 files changed, 73 insertions(+), 42 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt index 8652d1f9..c757ed5f 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt @@ -179,10 +179,10 @@ class Client(engine: HttpClientEngine) : ApiClient { refreshPath = "/refresh_token" } - install(Logging) { + /*install(Logging) { logger = Logger.DEFAULT level = LogLevel.HEADERS - } + }*/ defaultRequest { url(BASE_URL) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index 97089e1c..f7c6f2bc 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -14,6 +14,7 @@ import de.hsfl.budgetBinder.domain.usecase.storage.StoreDarkModeUseCase import de.hsfl.budgetBinder.domain.usecase.storage.StoreServerUrlUseCase import de.hsfl.budgetBinder.domain.usecase.storage.StoreUserStateUseCase import de.hsfl.budgetBinder.presentation.flow.DataFlow +import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel @@ -86,6 +87,7 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { // Flows bindSingleton { DataFlow(instance(), instance()) } + bindSingleton { RouterFlow(instance(), instance()) } bindSingleton { UiEventSharedFlow } // ViewModels @@ -95,8 +97,8 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { SettingsEditUserViewModel(instance(), instance(), instance(), instance()) } bindSingleton { SettingsEditServerUrlViewModel(instance(), instance(), instance()) } bindSingleton { _CategoryViewModel(instance(), instance()) } - bindSingleton { CategorySummaryViewModel(instance(), instance()) } - bindSingleton { CategoryDetailViewModel(instance(), instance()) } + bindSingleton { CategorySummaryViewModel(instance(), instance(), instance()) } + bindSingleton { CategoryDetailViewModel(instance(), instance(), instance()) } bindSingleton { EntryViewModel(instance(), instance()) } bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance()) } bindSingleton { NavDrawerViewModel(instance(), instance(), instance()) } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/RouterFlow.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/RouterFlow.kt index 3d2515b1..2d612263 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/RouterFlow.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/RouterFlow.kt @@ -3,16 +3,14 @@ package de.hsfl.budgetBinder.presentation.flow import de.hsfl.budgetBinder.domain.usecase.NavigateToScreenUseCase import de.hsfl.budgetBinder.presentation.Screen import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -object RouterFlow{ - private val navigateToScreenUseCase: NavigateToScreenUseCase = NavigateToScreenUseCase() - private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) - +class RouterFlow( + private val navigateToScreenUseCase: NavigateToScreenUseCase, + private val scope: CoroutineScope +) { private val _state = MutableStateFlow(Screen.Login) val state: StateFlow = _state diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt index 79a25975..334fd7f7 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt @@ -15,12 +15,14 @@ import kotlinx.coroutines.launch open class CategoryViewModel( _categoriesUseCases: CategoriesUseCases, - _scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) + _scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()), + _routerFlow: RouterFlow ) { private val scope: CoroutineScope = _scope private val categoriesUseCases: CategoriesUseCases = _categoriesUseCases + private val routerFlow: RouterFlow = _routerFlow - private val _eventFlow = UiEventSharedFlow.mutableEventFlow + protected val _eventFlow = UiEventSharedFlow.mutableEventFlow val eventFlow = UiEventSharedFlow.eventFlow protected fun getAll(onSuccess: (List) -> Unit) = scope.launch { @@ -68,7 +70,7 @@ open class CategoryViewModel( onSuccess(response.data!!) } is DataResponse.Unauthorized -> { - RouterFlow.navigateTo(Screen.Login) + routerFlow.navigateTo(Screen.Login) _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt index bc18a565..5628e39f 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt @@ -5,5 +5,7 @@ sealed class CategoryDetailEvent { object OnDelete: CategoryDetailEvent() object OnBack: CategoryDetailEvent() object OnRefresh: CategoryDetailEvent() + object OnLaunch: CategoryDetailEvent() + object OnDispose: CategoryDetailEvent() data class OnEntry(val id: Int): CategoryDetailEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt index 4bdb741f..eed06b9d 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt @@ -4,18 +4,22 @@ import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch class CategoryDetailViewModel( categoriesUseCases: CategoriesUseCases, - scope: CoroutineScope + private val scope: CoroutineScope, + private val routerFlow: RouterFlow ) : CategoryViewModel( _categoriesUseCases = categoriesUseCases, - _scope = scope + _scope = scope, + _routerFlow = routerFlow ) { private val _categoryState = MutableStateFlow(Category(id = -1, name = "0", color = "111111", image = Category.Image.DEFAULT, budget = 0f)) @@ -23,39 +27,41 @@ class CategoryDetailViewModel( private val _entryList = MutableStateFlow>(emptyList()) val entryList: StateFlow> = _entryList - - private val currentCategoryId: Int = when (RouterFlow.state.value) { - is Screen.Category.Detail -> (RouterFlow.state.value as Screen.Category.Detail).id - else -> -1 - } - - init { - getCategoryByCurrentId() - setEntryList() - } + private var currentCategoryId = -1 fun onEvent(event: CategoryDetailEvent) { when (event) { - is CategoryDetailEvent.OnEdit -> RouterFlow.navigateTo(Screen.Category.Edit) + is CategoryDetailEvent.OnEdit -> routerFlow.navigateTo(Screen.Category.Edit) is CategoryDetailEvent.OnDelete -> super.delete( id = currentCategoryId, - onSuccess = { RouterFlow.navigateTo(Screen.Category.Summary) }) - is CategoryDetailEvent.OnBack -> RouterFlow.navigateTo(Screen.Category.Summary) - is CategoryDetailEvent.OnRefresh -> getCategoryByCurrentId() + onSuccess = { routerFlow.navigateTo(Screen.Category.Summary) }) + is CategoryDetailEvent.OnBack -> routerFlow.navigateTo(Screen.Category.Summary) + is CategoryDetailEvent.OnRefresh -> initStateFlows() is CategoryDetailEvent.OnEntry -> { /* TODO: Navigate to Entry detail */ } + is CategoryDetailEvent.OnLaunch -> initStateFlows() } } + private fun initStateFlows() { + updateCurrentCategoryId() + getCategoryByCurrentId() + setEntryList() + } + private fun getCategoryByCurrentId() { - if (currentCategoryId != -1) { - super.getById(id = currentCategoryId, onSuccess = { _categoryState.value = it }) - } + super.getById(id = currentCategoryId, onSuccess = { _categoryState.value = it }) } private fun setEntryList() { - if (currentCategoryId != -1) { - super.entries(id = currentCategoryId, onSuccess = { _entryList.value = it }) + super.entries(id = currentCategoryId, onSuccess = { _entryList.value = it }) + + } + + private fun updateCurrentCategoryId() { + currentCategoryId = when (routerFlow.state.value) { + is Screen.Category.Detail -> (routerFlow.state.value as Screen.Category.Detail).id + else -> -1 } } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt index d25ffcd7..385f2c83 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt @@ -17,9 +17,11 @@ import kotlinx.coroutines.launch class CategorySummaryViewModel( categoriesUseCases: CategoriesUseCases, - scope: CoroutineScope -): CategoryViewModel( - _categoriesUseCases = categoriesUseCases, + scope: CoroutineScope, + private val routerFlow: RouterFlow +) : CategoryViewModel( + _routerFlow = routerFlow, + _categoriesUseCases = categoriesUseCases, _scope = scope ) { private val _categoryList = MutableStateFlow>(emptyList()) @@ -31,13 +33,13 @@ class CategorySummaryViewModel( fun onEvent(event: CategorySummaryEvent) { when (event) { - is CategorySummaryEvent.OnCategory -> RouterFlow.navigateTo(Screen.Category.Detail(event.id)) - is CategorySummaryEvent.OnCategoryCreate -> RouterFlow.navigateTo(Screen.Category.Summary) + is CategorySummaryEvent.OnCategory -> routerFlow.navigateTo(Screen.Category.Detail(event.id)) + is CategorySummaryEvent.OnCategoryCreate -> routerFlow.navigateTo(Screen.Category.Summary) is CategorySummaryEvent.OnRefresh -> getAllCategories() } } private fun getAllCategories() { - super.getAll(onSuccess = {_categoryList.value = it}) + super.getAll(onSuccess = { _categoryList.value = it }) } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt index a629280f..6014343c 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt @@ -10,6 +10,7 @@ import de.hsfl.budgetBinder.screens.register.RegisterComponent import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.screens.category.CategoryComponent +import de.hsfl.budgetBinder.screens.category.CategoryDetailView import de.hsfl.budgetBinder.screens.settings.SettingsView import org.kodein.di.instance @@ -26,6 +27,7 @@ fun Router() { is Screen.Entry.Overview -> Text(text = "Entry Click with id: ${(screenState.value as Screen.Entry.Overview).id}") is Screen.Entry.Create -> Text(text = "Entry Create") is Screen.Category.Summary -> CategoryComponent() + is Screen.Category.Detail -> CategoryDetailView() else -> {} } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt index 36f7bb85..4297208b 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt @@ -1,14 +1,16 @@ package de.hsfl.budgetBinder.screens.category import androidx.compose.runtime.Composable +import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import org.kodein.di.instance @Composable fun CategoryComponent() { - when (RouterFlow.state.value) { + val routerFlow: RouterFlow by di.instance() + when (routerFlow.state.value) { is Screen.Category.Summary -> CategorySummary() is Screen.Category.Detail -> CategoryDetailView() - else -> {} } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt index 62e40f63..067101ca 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt @@ -7,8 +7,13 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ListItem import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import de.hsfl.budgetBinder.di +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailEvent import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailViewModel import org.kodein.di.instance @@ -16,9 +21,19 @@ import org.kodein.di.instance @Composable fun CategoryDetailView() { val viewModel: CategoryDetailViewModel by di.instance() + val routerFlow: RouterFlow by di.instance() val categoryState = viewModel.categoryState.collectAsState() val entryListState = viewModel.entryList.collectAsState() + LaunchedEffect(Unit) { + viewModel.onEvent(CategoryDetailEvent.OnLaunch) + } + DisposableEffect(Unit) { + onDispose { + println("CategoryDetailView::Dispos") + } + } + Column { Text(categoryState.value.toString()) LazyColumn { From 926b6d3a202069c077726389b15d792e85facdf9 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 12:02:40 +0200 Subject: [PATCH 49/73] Rollback Client.kt --- .../kotlin/de/hsfl/budgetBinder/data/client/Client.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt index c757ed5f..8652d1f9 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt @@ -179,10 +179,10 @@ class Client(engine: HttpClientEngine) : ApiClient { refreshPath = "/refresh_token" } - /*install(Logging) { + install(Logging) { logger = Logger.DEFAULT level = LogLevel.HEADERS - }*/ + } defaultRequest { url(BASE_URL) From 8677b3e02bb1ba4ea459dcd9e4b262beb24a5a4e Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 12:15:01 +0200 Subject: [PATCH 50/73] Implement global LifecycleEvent.kt --- .../presentation/event/LifecycleEvent.kt | 13 +++++++++++++ .../presentation/{ => event}/UiEvent.kt | 2 +- .../presentation/flow/UiEventSharedFlow.kt | 2 +- .../viewmodel/category/CategoryViewModel.kt | 2 +- .../category/detail/CategoryDetailEvent.kt | 5 +++-- .../category/detail/CategoryDetailViewModel.kt | 10 +++++++--- .../viewmodel/category/edit/CategoryEditEvent.kt | 9 +++++++++ .../category/edit/CategoryEditViewModel.kt | 4 ++++ .../category/summary/CategorySummaryViewModel.kt | 6 ------ .../viewmodel/dashboard/DashboardViewModel.kt | 2 +- .../presentation/viewmodel/login/LoginViewModel.kt | 2 +- .../viewmodel/navdrawer/NavDrawerViewModel.kt | 3 +-- .../viewmodel/register/RegisterViewModel.kt | 3 +-- .../viewmodel/settings/SettingsEditUserViewModel.kt | 5 +---- .../viewmodel/settings/SettingsViewModel.kt | 2 +- .../kotlin/de/hsfl/budgetBinder/NavDrawer.kt | 4 ---- .../src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt | 3 +-- .../screens/category/CategorySummary.kt | 2 +- .../screens/dashboard/DashboardComponent.kt | 5 +---- .../budgetBinder/screens/login/LoginComponent.kt | 2 +- .../screens/register/RegisterComponent.kt | 2 +- .../hsfl/budgetBinder/screens/settings/Settings.kt | 2 +- .../screens/settings/SettingsEditUserView.kt | 8 -------- 23 files changed, 51 insertions(+), 47 deletions(-) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/{ => event}/UiEvent.kt (88%) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt new file mode 100644 index 00000000..038ff966 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt @@ -0,0 +1,13 @@ +package de.hsfl.budgetBinder.presentation.event + +sealed class LifecycleEvent { + object OnLaunch: LifecycleEvent() + object OnDispose: LifecycleEvent() +} + +fun LifecycleEvent.handleLifeCycle(onLaunch: () -> Unit, onDispose: () -> Unit) { + when (this) { + is LifecycleEvent.OnLaunch -> onLaunch() + is LifecycleEvent.OnDispose -> onDispose() + } +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/UiEvent.kt similarity index 88% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/UiEvent.kt index d32a5745..36bd1312 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/UiEvent.kt @@ -1,4 +1,4 @@ -package de.hsfl.budgetBinder.presentation +package de.hsfl.budgetBinder.presentation.event sealed class UiEvent { // Show Loading State in Ui diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/UiEventSharedFlow.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/UiEventSharedFlow.kt index 43261012..3111073a 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/UiEventSharedFlow.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/UiEventSharedFlow.kt @@ -1,6 +1,6 @@ package de.hsfl.budgetBinder.presentation.flow -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt index 334fd7f7..f8e863be 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt @@ -5,7 +5,7 @@ import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt index 5628e39f..6fcf9adb 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt @@ -1,11 +1,12 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.detail +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent + sealed class CategoryDetailEvent { object OnEdit: CategoryDetailEvent() object OnDelete: CategoryDetailEvent() object OnBack: CategoryDetailEvent() object OnRefresh: CategoryDetailEvent() - object OnLaunch: CategoryDetailEvent() - object OnDispose: CategoryDetailEvent() + data class LifeCycle(val value: LifecycleEvent): CategoryDetailEvent() data class OnEntry(val id: Int): CategoryDetailEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt index eed06b9d..ef51e57e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt @@ -4,13 +4,14 @@ import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent +import de.hsfl.budgetBinder.presentation.event.handleLifeCycle import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch class CategoryDetailViewModel( categoriesUseCases: CategoriesUseCases, @@ -39,7 +40,10 @@ class CategoryDetailViewModel( is CategoryDetailEvent.OnRefresh -> initStateFlows() is CategoryDetailEvent.OnEntry -> { /* TODO: Navigate to Entry detail */ } - is CategoryDetailEvent.OnLaunch -> initStateFlows() + is CategoryDetailEvent.LifeCycle -> event.value.handleLifeCycle( + onLaunch = { initStateFlows() }, + onDispose = { /* Nothing to do on dispose */ } + ) } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt new file mode 100644 index 00000000..b38a7205 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt @@ -0,0 +1,9 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category.edit + +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent + +sealed class CategoryEditEvent { + data class LifeCycle(val value: LifecycleEvent): CategoryEditEvent() + object OnSave: CategoryEditEvent() + object OnDelete: CategoryEditEvent() +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt new file mode 100644 index 00000000..6edd13c3 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt @@ -0,0 +1,4 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category.edit + +class CategoryEditViewModel { +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt index 385f2c83..1a2bf94f 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt @@ -1,19 +1,13 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.summary import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases -import de.hsfl.budgetBinder.domain.usecase.DeleteCategoryByIdUseCase -import de.hsfl.budgetBinder.domain.usecase.GetAllCategoriesUseCase import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow -import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch class CategorySummaryViewModel( categoriesUseCases: CategoriesUseCases, diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 7ea57038..39e8013e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -5,7 +5,7 @@ import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt index 9f414177..8f60d5d8 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt @@ -5,7 +5,7 @@ import de.hsfl.budgetBinder.common.utils.validateEmail import de.hsfl.budgetBinder.domain.usecase.LoginUseCases import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt index 1d414b97..578fe861 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt @@ -3,9 +3,8 @@ package de.hsfl.budgetBinder.presentation.viewmodel.navdrawer import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.domain.usecase.LogoutUseCase import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState -import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt index f84eb0cf..520c1f13 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt @@ -5,14 +5,13 @@ import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.common.utils.validateEmail import de.hsfl.budgetBinder.domain.usecase.RegisterUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch class RegisterViewModel( private val registerUseCases: RegisterUseCases, diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsEditUserViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsEditUserViewModel.kt index 5199e26c..05640bd8 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsEditUserViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsEditUserViewModel.kt @@ -2,18 +2,15 @@ package de.hsfl.budgetBinder.presentation.viewmodel.settings import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.User -import de.hsfl.budgetBinder.domain.usecase.ChangeMyUserUseCase import de.hsfl.budgetBinder.domain.usecase.SettingsUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch class SettingsEditUserViewModel( diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsViewModel.kt index 30eeb413..4a8e41ac 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsViewModel.kt @@ -4,7 +4,7 @@ import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt index a3df0d4d..0a822e58 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt @@ -1,20 +1,16 @@ package de.hsfl.budgetBinder -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.icon.* -import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerEvent import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerViewModel -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.kodein.di.instance diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt index 64546390..58734ffb 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt @@ -7,9 +7,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.di.kodein -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import io.ktor.client.engine.cio.* diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt index 50998478..6d2e9a99 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryEvent import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel import kotlinx.coroutines.flow.collectLatest diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index 218fcba0..51d12af5 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -3,7 +3,6 @@ package de.hsfl.budgetBinder.screens.dashboard import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.* -import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -17,13 +16,11 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEntryState import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardState diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt index 011ef49a..9f49cffb 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt @@ -14,7 +14,7 @@ import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.compose.dialog.ServerUrlDialog import de.hsfl.budgetBinder.compose.icon.AppIcon import de.hsfl.budgetBinder.compose.textfield.EmailTextField -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginEvent import kotlinx.coroutines.flow.collectLatest diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt index 0c9d3fc5..ce5f3eb6 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.icon.AppIcon import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.compose.textfield.EmailTextField -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterEvent import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel import kotlinx.coroutines.flow.collectLatest diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/Settings.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/Settings.kt index f86ba63a..cd19bfd9 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/Settings.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/Settings.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.icon.AvatarImage import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsViewModel diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsEditUserView.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsEditUserView.kt index 577cae6f..814613b8 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsEditUserView.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsEditUserView.kt @@ -1,26 +1,18 @@ package de.hsfl.budgetBinder.screens.settings -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Info import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.textfield.SettingsPasswordTextField import de.hsfl.budgetBinder.compose.textfield.SettingsTextField import de.hsfl.budgetBinder.di -import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.settings.EditUserEvent import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditUserViewModel -import kotlinx.coroutines.flow.collectLatest import org.kodein.di.instance @Composable From 2145b62d1047047f2bcdfe30be8f9356f00ab274 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 12:15:01 +0200 Subject: [PATCH 51/73] Implement global LifecycleEvent.kt --- .../presentation/event/LifecycleEvent.kt | 13 +++++++++++++ .../presentation/{ => event}/UiEvent.kt | 2 +- .../presentation/flow/UiEventSharedFlow.kt | 2 +- .../viewmodel/category/CategoryViewModel.kt | 2 +- .../category/detail/CategoryDetailEvent.kt | 5 +++-- .../category/detail/CategoryDetailViewModel.kt | 10 +++++++--- .../viewmodel/category/edit/CategoryEditEvent.kt | 9 +++++++++ .../category/edit/CategoryEditViewModel.kt | 4 ++++ .../category/summary/CategorySummaryViewModel.kt | 6 ------ .../viewmodel/dashboard/DashboardViewModel.kt | 2 +- .../presentation/viewmodel/login/LoginViewModel.kt | 2 +- .../viewmodel/navdrawer/NavDrawerViewModel.kt | 3 +-- .../viewmodel/register/RegisterViewModel.kt | 3 +-- .../viewmodel/settings/SettingsEditUserViewModel.kt | 5 +---- .../viewmodel/settings/SettingsViewModel.kt | 2 +- .../kotlin/de/hsfl/budgetBinder/NavDrawer.kt | 4 ---- .../src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt | 3 +-- .../budgetBinder/screens/category/CategoryDetail.kt | 3 ++- .../screens/category/CategorySummary.kt | 2 +- .../screens/dashboard/DashboardComponent.kt | 5 +---- .../budgetBinder/screens/login/LoginComponent.kt | 2 +- .../screens/register/RegisterComponent.kt | 2 +- .../hsfl/budgetBinder/screens/settings/Settings.kt | 2 +- .../screens/settings/SettingsEditUserView.kt | 8 -------- 24 files changed, 53 insertions(+), 48 deletions(-) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/{ => event}/UiEvent.kt (88%) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt new file mode 100644 index 00000000..038ff966 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt @@ -0,0 +1,13 @@ +package de.hsfl.budgetBinder.presentation.event + +sealed class LifecycleEvent { + object OnLaunch: LifecycleEvent() + object OnDispose: LifecycleEvent() +} + +fun LifecycleEvent.handleLifeCycle(onLaunch: () -> Unit, onDispose: () -> Unit) { + when (this) { + is LifecycleEvent.OnLaunch -> onLaunch() + is LifecycleEvent.OnDispose -> onDispose() + } +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/UiEvent.kt similarity index 88% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/UiEvent.kt index d32a5745..36bd1312 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/UiEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/UiEvent.kt @@ -1,4 +1,4 @@ -package de.hsfl.budgetBinder.presentation +package de.hsfl.budgetBinder.presentation.event sealed class UiEvent { // Show Loading State in Ui diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/UiEventSharedFlow.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/UiEventSharedFlow.kt index 43261012..3111073a 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/UiEventSharedFlow.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/UiEventSharedFlow.kt @@ -1,6 +1,6 @@ package de.hsfl.budgetBinder.presentation.flow -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt index 334fd7f7..f8e863be 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt @@ -5,7 +5,7 @@ import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt index 5628e39f..6fcf9adb 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailEvent.kt @@ -1,11 +1,12 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.detail +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent + sealed class CategoryDetailEvent { object OnEdit: CategoryDetailEvent() object OnDelete: CategoryDetailEvent() object OnBack: CategoryDetailEvent() object OnRefresh: CategoryDetailEvent() - object OnLaunch: CategoryDetailEvent() - object OnDispose: CategoryDetailEvent() + data class LifeCycle(val value: LifecycleEvent): CategoryDetailEvent() data class OnEntry(val id: Int): CategoryDetailEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt index eed06b9d..ef51e57e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt @@ -4,13 +4,14 @@ import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent +import de.hsfl.budgetBinder.presentation.event.handleLifeCycle import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch class CategoryDetailViewModel( categoriesUseCases: CategoriesUseCases, @@ -39,7 +40,10 @@ class CategoryDetailViewModel( is CategoryDetailEvent.OnRefresh -> initStateFlows() is CategoryDetailEvent.OnEntry -> { /* TODO: Navigate to Entry detail */ } - is CategoryDetailEvent.OnLaunch -> initStateFlows() + is CategoryDetailEvent.LifeCycle -> event.value.handleLifeCycle( + onLaunch = { initStateFlows() }, + onDispose = { /* Nothing to do on dispose */ } + ) } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt new file mode 100644 index 00000000..b38a7205 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt @@ -0,0 +1,9 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category.edit + +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent + +sealed class CategoryEditEvent { + data class LifeCycle(val value: LifecycleEvent): CategoryEditEvent() + object OnSave: CategoryEditEvent() + object OnDelete: CategoryEditEvent() +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt new file mode 100644 index 00000000..6edd13c3 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt @@ -0,0 +1,4 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category.edit + +class CategoryEditViewModel { +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt index 385f2c83..1a2bf94f 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt @@ -1,19 +1,13 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.summary import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases -import de.hsfl.budgetBinder.domain.usecase.DeleteCategoryByIdUseCase -import de.hsfl.budgetBinder.domain.usecase.GetAllCategoriesUseCase import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow -import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch class CategorySummaryViewModel( categoriesUseCases: CategoriesUseCases, diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 7ea57038..39e8013e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -5,7 +5,7 @@ import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt index 9f414177..8f60d5d8 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt @@ -5,7 +5,7 @@ import de.hsfl.budgetBinder.common.utils.validateEmail import de.hsfl.budgetBinder.domain.usecase.LoginUseCases import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt index 1d414b97..578fe861 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt @@ -3,9 +3,8 @@ package de.hsfl.budgetBinder.presentation.viewmodel.navdrawer import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.domain.usecase.LogoutUseCase import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState -import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt index f84eb0cf..520c1f13 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt @@ -5,14 +5,13 @@ import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.common.utils.validateEmail import de.hsfl.budgetBinder.domain.usecase.RegisterUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch class RegisterViewModel( private val registerUseCases: RegisterUseCases, diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsEditUserViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsEditUserViewModel.kt index 5199e26c..05640bd8 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsEditUserViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsEditUserViewModel.kt @@ -2,18 +2,15 @@ package de.hsfl.budgetBinder.presentation.viewmodel.settings import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.User -import de.hsfl.budgetBinder.domain.usecase.ChangeMyUserUseCase import de.hsfl.budgetBinder.domain.usecase.SettingsUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch class SettingsEditUserViewModel( diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsViewModel.kt index 30eeb413..4a8e41ac 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/settings/SettingsViewModel.kt @@ -4,7 +4,7 @@ import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt index a3df0d4d..0a822e58 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/NavDrawer.kt @@ -1,20 +1,16 @@ package de.hsfl.budgetBinder -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.icon.* -import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerEvent import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerViewModel -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.kodein.di.instance diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt index 64546390..58734ffb 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Root.kt @@ -7,9 +7,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.di.kodein -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import io.ktor.client.engine.cio.* diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt index 067101ca..78f62939 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailEvent import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailViewModel @@ -26,7 +27,7 @@ fun CategoryDetailView() { val entryListState = viewModel.entryList.collectAsState() LaunchedEffect(Unit) { - viewModel.onEvent(CategoryDetailEvent.OnLaunch) + viewModel.onEvent(CategoryDetailEvent.LifeCycle(LifecycleEvent.OnLaunch)) } DisposableEffect(Unit) { onDispose { diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt index 50998478..6d2e9a99 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryEvent import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel import kotlinx.coroutines.flow.collectLatest diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index 218fcba0..51d12af5 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -3,7 +3,6 @@ package de.hsfl.budgetBinder.screens.dashboard import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.* -import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -17,13 +16,11 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEntryState import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardState diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt index 011ef49a..9f49cffb 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt @@ -14,7 +14,7 @@ import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.compose.dialog.ServerUrlDialog import de.hsfl.budgetBinder.compose.icon.AppIcon import de.hsfl.budgetBinder.compose.textfield.EmailTextField -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginEvent import kotlinx.coroutines.flow.collectLatest diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt index 0c9d3fc5..ce5f3eb6 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.icon.AppIcon import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.compose.textfield.EmailTextField -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterEvent import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel import kotlinx.coroutines.flow.collectLatest diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/Settings.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/Settings.kt index f86ba63a..cd19bfd9 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/Settings.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/Settings.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.icon.AvatarImage import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.UiEvent +import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsViewModel diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsEditUserView.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsEditUserView.kt index 577cae6f..814613b8 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsEditUserView.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/settings/SettingsEditUserView.kt @@ -1,26 +1,18 @@ package de.hsfl.budgetBinder.screens.settings -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Info import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.textfield.SettingsPasswordTextField import de.hsfl.budgetBinder.compose.textfield.SettingsTextField import de.hsfl.budgetBinder.di -import de.hsfl.budgetBinder.presentation.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.settings.EditUserEvent import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditUserViewModel -import kotlinx.coroutines.flow.collectLatest import org.kodein.di.instance @Composable From bb362698d16390aa890ef068958ab8b6010b5322 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 12:25:52 +0200 Subject: [PATCH 52/73] Add Comments --- .../budgetBinder/presentation/event/LifecycleEvent.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt index 038ff966..2a7b4948 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/event/LifecycleEvent.kt @@ -1,10 +1,19 @@ package de.hsfl.budgetBinder.presentation.event sealed class LifecycleEvent { + // Call on LaunchEffect object OnLaunch: LifecycleEvent() + + // Call on DisposableEffect object OnDispose: LifecycleEvent() } + +/** + * Callback on launch or on dispose + * @param onLaunch Call on Launch + * @param onDispose Call on Dispose + */ fun LifecycleEvent.handleLifeCycle(onLaunch: () -> Unit, onDispose: () -> Unit) { when (this) { is LifecycleEvent.OnLaunch -> onLaunch() From 2b753adc5a5c11872abdd330c35f49ea71f36392 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 14:31:36 +0200 Subject: [PATCH 53/73] Implement CategoryEditViewModel and View --- .../hsfl/budgetBinder/compose/icon/AppIcon.kt | 5 ++ .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 2 + .../hsfl/budgetBinder/presentation/Screen.kt | 3 +- .../viewmodel/category/CategoryViewModel.kt | 11 ++- .../detail/CategoryDetailViewModel.kt | 56 +++++++++---- .../category/edit/CategoryEditEvent.kt | 7 ++ .../category/edit/CategoryEditViewModel.kt | 84 ++++++++++++++++++- .../hsfl/budgetBinder/compose/icon/AppIcon.kt | 5 ++ .../kotlin/de/hsfl/budgetBinder/Router.kt | 3 +- .../budgetBinder/compose/icon/AppIcons.kt | 3 + .../screens/category/CategoryComponent.kt | 1 + .../screens/category/CategoryDetail.kt | 28 +++++-- .../screens/category/CategoryEdit.kt | 64 ++++++++++++++ 13 files changed, 239 insertions(+), 33 deletions(-) create mode 100644 budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryEdit.kt diff --git a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt index 544ca4ad..0632b8ea 100644 --- a/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt +++ b/budget-binder-multiplatform-app/src/androidMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt @@ -59,3 +59,8 @@ actual fun AccountIcon() { actual fun DeleteForeverIcon() { Icon(imageVector = Icons.Default.DeleteForever, contentDescription = null) } + +@Composable +actual fun SaveIcon() { + Icon(imageVector = Icons.Default.Save, contentDescription = null) +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index f7c6f2bc..2f718a32 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -22,6 +22,7 @@ import de.hsfl.budgetBinder.presentation.viewmodel.* import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditViewModel import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel import de.hsfl.budgetBinder.presentation.viewmodel.navdrawer.NavDrawerViewModel import de.hsfl.budgetBinder.presentation.viewmodel.settings.SettingsEditServerUrlViewModel @@ -99,6 +100,7 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { _CategoryViewModel(instance(), instance()) } bindSingleton { CategorySummaryViewModel(instance(), instance(), instance()) } bindSingleton { CategoryDetailViewModel(instance(), instance(), instance()) } + bindSingleton { CategoryEditViewModel(instance(), instance(), instance()) } bindSingleton { EntryViewModel(instance(), instance()) } bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance()) } bindSingleton { NavDrawerViewModel(instance(), instance(), instance()) } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt index 499c2b5b..c4a63e2b 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/Screen.kt @@ -1,6 +1,5 @@ package de.hsfl.budgetBinder.presentation -import de.hsfl.budgetBinder.domain.usecase.DeleteEntryByIdUseCase sealed class Screen { sealed class Welcome: Screen() { @@ -16,7 +15,7 @@ sealed class Screen { sealed class Category: Screen() { data class Detail(val id: Int): Category() object Summary: Category() - object Edit: Category() + data class Edit(val id: Int): Category() object Create: Category() object CreateOnRegister: Category() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt index f8e863be..abb26dad 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt @@ -22,8 +22,7 @@ open class CategoryViewModel( private val categoriesUseCases: CategoriesUseCases = _categoriesUseCases private val routerFlow: RouterFlow = _routerFlow - protected val _eventFlow = UiEventSharedFlow.mutableEventFlow - val eventFlow = UiEventSharedFlow.eventFlow + val eventFlow = UiEventSharedFlow.mutableEventFlow protected fun getAll(onSuccess: (List) -> Unit) = scope.launch { categoriesUseCases.getAllCategoriesUseCase.categories().collect { response -> @@ -63,15 +62,15 @@ open class CategoryViewModel( private fun handleDataResponse(response: DataResponse, onSuccess: (T) -> Unit) = scope.launch { when (response) { - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error -> eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + is DataResponse.Loading -> eventFlow.emit(UiEvent.ShowLoading) is DataResponse.Success -> { - _eventFlow.emit(UiEvent.HideSuccess) + eventFlow.emit(UiEvent.HideSuccess) onSuccess(response.data!!) } is DataResponse.Unauthorized -> { routerFlow.navigateTo(Screen.Login) - _eventFlow.emit(UiEvent.ShowError(response.error!!.message)) + eventFlow.emit(UiEvent.ShowError(response.error!!.message)) } } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt index ef51e57e..011b5f22 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt @@ -4,35 +4,48 @@ import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.Entry import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.event.LifecycleEvent import de.hsfl.budgetBinder.presentation.event.handleLifeCycle import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel -import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -class CategoryDetailViewModel( - categoriesUseCases: CategoriesUseCases, - private val scope: CoroutineScope, +open class CategoryDetailViewModel( + _categoriesUseCases: CategoriesUseCases, + _scope: CoroutineScope, private val routerFlow: RouterFlow ) : CategoryViewModel( - _categoriesUseCases = categoriesUseCases, - _scope = scope, + _categoriesUseCases = _categoriesUseCases, + _scope = _scope, _routerFlow = routerFlow ) { - private val _categoryState = - MutableStateFlow(Category(id = -1, name = "0", color = "111111", image = Category.Image.DEFAULT, budget = 0f)) + protected var currentCategoryId = -1 + protected val initCategory = + Category(id = -1, name = "init", color = "111111", image = Category.Image.DEFAULT, budget = 0f) + protected val _categoryState = MutableStateFlow(initCategory) val categoryState: StateFlow = _categoryState private val _entryList = MutableStateFlow>(emptyList()) val entryList: StateFlow> = _entryList - private var currentCategoryId = -1 + + /** + * OnEdit => Navigate to edit category screen + * + * OnDelete => Delete the current category + * + * OnBack => Navigate to category summary screen + * + * OnRefresh => Update the Flows, fetch data from api and emit into flows + * + * OnEntry => Navigate to Entry Detail Screen from this Entry + * + * Lifecycle => On Launch, fetch data from api and emit into flows + */ fun onEvent(event: CategoryDetailEvent) { when (event) { - is CategoryDetailEvent.OnEdit -> routerFlow.navigateTo(Screen.Category.Edit) + is CategoryDetailEvent.OnEdit -> routerFlow.navigateTo(Screen.Category.Edit(currentCategoryId)) is CategoryDetailEvent.OnDelete -> super.delete( id = currentCategoryId, onSuccess = { routerFlow.navigateTo(Screen.Category.Summary) }) @@ -42,29 +55,44 @@ class CategoryDetailViewModel( } is CategoryDetailEvent.LifeCycle -> event.value.handleLifeCycle( onLaunch = { initStateFlows() }, - onDispose = { /* Nothing to do on dispose */ } + onDispose = { } ) } } + /** + * Init state flow + * 1. Update the current category id + * 2. Emit the category callback into the category state + * 3. Emit the entry callback into the entryList state + */ private fun initStateFlows() { updateCurrentCategoryId() getCategoryByCurrentId() setEntryList() } + private fun resetStateFlows() { + _categoryState.value = initCategory + _entryList.value = emptyList() + } + private fun getCategoryByCurrentId() { super.getById(id = currentCategoryId, onSuccess = { _categoryState.value = it }) } private fun setEntryList() { super.entries(id = currentCategoryId, onSuccess = { _entryList.value = it }) - } - private fun updateCurrentCategoryId() { + /** + * Update internal category id, which was set on the router screen state + * to fetch data from backend with this category id + */ + protected fun updateCurrentCategoryId() { currentCategoryId = when (routerFlow.state.value) { is Screen.Category.Detail -> (routerFlow.state.value as Screen.Category.Detail).id + is Screen.Category.Edit -> (routerFlow.state.value as Screen.Category.Edit).id else -> -1 } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt index b38a7205..2a06d902 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditEvent.kt @@ -1,9 +1,16 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.edit +import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.presentation.event.LifecycleEvent +import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailEvent sealed class CategoryEditEvent { + data class EnteredCategoryName(val value: String): CategoryEditEvent() + data class EnteredCategoryColor(val value: String): CategoryEditEvent() + data class EnteredCategoryImage(val value: Category.Image): CategoryEditEvent() + data class EnteredCategoryBudget(val value: Float): CategoryEditEvent() data class LifeCycle(val value: LifecycleEvent): CategoryEditEvent() object OnSave: CategoryEditEvent() + object OnCancel: CategoryEditEvent() object OnDelete: CategoryEditEvent() } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt index 6edd13c3..c45fd9e4 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt @@ -1,4 +1,86 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.edit -class CategoryEditViewModel { +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.event.handleLifeCycle +import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class CategoryEditViewModel( + _categoriesUseCases: CategoriesUseCases, + _scope: CoroutineScope, + private val routerFlow: RouterFlow +) : CategoryDetailViewModel( + _categoriesUseCases = _categoriesUseCases, + _scope = _scope, + routerFlow = routerFlow +) { + private val _categoryNameState = MutableStateFlow("") + val categoryNameState: StateFlow = _categoryNameState + + private val _categoryColorState = MutableStateFlow("") + val categoryColorState: StateFlow = _categoryColorState + + private val _categoryImageState = MutableStateFlow(Category.Image.DEFAULT) + val categoryImageState: StateFlow = _categoryImageState + + private val _categoryBudgetState = MutableStateFlow(0f) + val categoryBudgetState: StateFlow = _categoryBudgetState + + fun onEvent(event: CategoryEditEvent) { + when (event) { + is CategoryEditEvent.EnteredCategoryName -> _categoryNameState.value = event.value + is CategoryEditEvent.EnteredCategoryColor -> _categoryColorState.value = event.value + is CategoryEditEvent.EnteredCategoryImage -> _categoryImageState.value = event.value + is CategoryEditEvent.EnteredCategoryBudget -> _categoryBudgetState.value = event.value + is CategoryEditEvent.OnSave -> super.change( + id = currentCategoryId, + category = createCategoryPathFromState(), + onSuccess = { routerFlow.navigateTo(Screen.Category.Detail(it.id)) } + ) + is CategoryEditEvent.OnCancel -> routerFlow.navigateTo(Screen.Category.Summary) + is CategoryEditEvent.OnDelete -> super.delete( + id = currentCategoryId, + onSuccess = { routerFlow.navigateTo(Screen.Category.Summary) } + ) + is CategoryEditEvent.LifeCycle -> event.value.handleLifeCycle( + onLaunch = { initSateFlows() }, + onDispose = { resetStateFlows() } + ) + } + } + + private fun initSateFlows() { + super.updateCurrentCategoryId() + super.getById(id = currentCategoryId, onSuccess = { category -> + _categoryState.value = category + _categoryNameState.value = _categoryState.value.name + _categoryColorState.value = _categoryState.value.color + _categoryImageState.value = _categoryState.value.image + _categoryBudgetState.value = _categoryState.value.budget + }) + } + + private fun resetStateFlows() { + _categoryState.value = initCategory + _categoryNameState.value = "" + _categoryColorState.value = "" + _categoryImageState.value = Category.Image.DEFAULT + _categoryBudgetState.value = 0f + } + + private fun createCategoryPathFromState(): Category.Patch { + return Category.Patch( + name = categoryNameState.value, + color = categoryColorState.value, + image = categoryImageState.value, + budget = categoryBudgetState.value + ) + } } diff --git a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt index 011a3444..c48cfc15 100644 --- a/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt +++ b/budget-binder-multiplatform-app/src/desktopMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcon.kt @@ -57,3 +57,8 @@ actual fun AccountIcon() { actual fun DeleteForeverIcon() { Icon(imageVector = Icons.Default.DeleteForever, contentDescription = null) } + +@Composable +actual fun SaveIcon() { + Icon(imageVector = Icons.Default.Save, contentDescription = null) +} diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt index 6014343c..0b1fbd30 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/Router.kt @@ -27,7 +27,8 @@ fun Router() { is Screen.Entry.Overview -> Text(text = "Entry Click with id: ${(screenState.value as Screen.Entry.Overview).id}") is Screen.Entry.Create -> Text(text = "Entry Create") is Screen.Category.Summary -> CategoryComponent() - is Screen.Category.Detail -> CategoryDetailView() + is Screen.Category.Detail -> CategoryComponent() + is Screen.Category.Edit -> CategoryComponent() else -> {} } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcons.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcons.kt index e70fe7bc..32ef4fb1 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcons.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/compose/icon/AppIcons.kt @@ -32,3 +32,6 @@ expect fun AccountIcon() @Composable expect fun DeleteForeverIcon() + +@Composable +expect fun SaveIcon() diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt index 4297208b..de800f58 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryComponent.kt @@ -12,5 +12,6 @@ fun CategoryComponent() { when (routerFlow.state.value) { is Screen.Category.Summary -> CategorySummary() is Screen.Category.Detail -> CategoryDetailView() + is Screen.Category.Edit -> CategoryEditView() } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt index 78f62939..978878a9 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt @@ -3,9 +3,9 @@ package de.hsfl.budgetBinder.screens.category import androidx.compose.foundation.layout.Column import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ListItem -import androidx.compose.material.Text +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -22,9 +22,9 @@ import org.kodein.di.instance @Composable fun CategoryDetailView() { val viewModel: CategoryDetailViewModel by di.instance() - val routerFlow: RouterFlow by di.instance() val categoryState = viewModel.categoryState.collectAsState() val entryListState = viewModel.entryList.collectAsState() + val scaffoldState = rememberScaffoldState() LaunchedEffect(Unit) { viewModel.onEvent(CategoryDetailEvent.LifeCycle(LifecycleEvent.OnLaunch)) @@ -35,12 +35,22 @@ fun CategoryDetailView() { } } - Column { - Text(categoryState.value.toString()) - LazyColumn { - items(entryListState.value) { entry -> - ListItem(text = { Text(entry.name) }, trailing = { Text(entry.amount.toString()) }) + Scaffold(scaffoldState = scaffoldState, + floatingActionButtonPosition = FabPosition.End, + floatingActionButton = { + FloatingActionButton(onClick = { viewModel.onEvent(CategoryDetailEvent.OnEdit) }) { + Icon(Icons.Default.Edit, contentDescription = null) + } + } + ) { + Column { + Text(categoryState.value.toString()) + LazyColumn { + items(entryListState.value) { entry -> + ListItem(text = { Text(entry.name) }, trailing = { Text(entry.amount.toString()) }) + } } } } + } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryEdit.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryEdit.kt new file mode 100644 index 00000000..846eda4c --- /dev/null +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryEdit.kt @@ -0,0 +1,64 @@ +package de.hsfl.budgetBinder.screens.category + +import androidx.compose.foundation.layout.Column +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Send +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import de.hsfl.budgetBinder.compose.icon.SaveIcon +import de.hsfl.budgetBinder.di +import de.hsfl.budgetBinder.presentation.CategoryImageToIcon +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditViewModel +import org.kodein.di.instance + +@Composable +fun CategoryEditView() { + val viewModel: CategoryEditViewModel by di.instance() + val categoryNameState = viewModel.categoryNameState.collectAsState() + val categoryColorState = viewModel.categoryColorState.collectAsState() + val categoryImageState = viewModel.categoryImageState.collectAsState() + val categoryBudgetState = viewModel.categoryBudgetState.collectAsState() + val scaffoldState = rememberScaffoldState() + + LaunchedEffect(Unit) { + viewModel.onEvent(CategoryEditEvent.LifeCycle(LifecycleEvent.OnLaunch)) + } + DisposableEffect(Unit) { + onDispose { + viewModel.onEvent(CategoryEditEvent.LifeCycle(LifecycleEvent.OnDispose)) + } + } + + Scaffold(scaffoldState = scaffoldState, + floatingActionButtonPosition = FabPosition.End, + floatingActionButton = { + FloatingActionButton(onClick = {viewModel.onEvent(CategoryEditEvent.OnSave)}) { + SaveIcon() + } + } + ) { + Column { + OutlinedTextField( + value = categoryNameState.value, + onValueChange = { viewModel.onEvent(CategoryEditEvent.EnteredCategoryName(it)) }, + label = { Text("Category Name") } + ) + OutlinedTextField( + value = categoryColorState.value, + onValueChange = { viewModel.onEvent(CategoryEditEvent.EnteredCategoryColor(it)) }, + label = { Text("Category Color") } + ) + OutlinedTextField( + value = categoryBudgetState.value.toString(), + onValueChange = { viewModel.onEvent(CategoryEditEvent.EnteredCategoryBudget(it.toFloat())) }, + label = { Text("Category Budget") } + ) + CategoryImageToIcon(categoryImageState.value) + } + } +} From bc24a6835c3a335d50e131f2bc6fbe347d10df60 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 14:38:51 +0200 Subject: [PATCH 54/73] Implement Dashboard Lifecycle --- .../viewmodel/dashboard/DashboardEvent.kt | 2 ++ .../viewmodel/dashboard/DashboardViewModel.kt | 32 ++++++++++++------- .../screens/dashboard/DashboardComponent.kt | 9 ++++++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt index 2aa782fc..6a91cbb3 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardEvent.kt @@ -1,7 +1,9 @@ package de.hsfl.budgetBinder.presentation.viewmodel.dashboard +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent sealed class DashboardEvent { + data class LifeCycle(val value: LifecycleEvent): DashboardEvent() object OnPrevCategory : DashboardEvent() object OnNextCategory : DashboardEvent() object OnRefresh: DashboardEvent() diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 39e8013e..5042f455 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -7,6 +7,7 @@ import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState +import de.hsfl.budgetBinder.presentation.event.handleLifeCycle import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import io.ktor.util.date.* @@ -43,18 +44,6 @@ class DashboardViewModel( private val _eventFlow = UiEventSharedFlow.mutableEventFlow val eventFlow = _eventFlow.asSharedFlow() - init { - // Throws nullPointerException, crash on Android? - //_getAllEntries() - //_getAllCategories() - - getAllCategories(onSuccess = { categories -> - _categoryListState.value = categories - setOverallCategoryState() - }) - - } - fun onEvent(event: DashboardEvent) { when (event) { is DashboardEvent.OnNextCategory -> changedFocusedCategory(increase = true) @@ -64,9 +53,28 @@ class DashboardViewModel( is DashboardEvent.OnRefresh -> refresh() is DashboardEvent.OnLoadMore -> loadMoreEntries() is DashboardEvent.OnEntryDelete -> deleteEntry(id = event.id) + is DashboardEvent.LifeCycle -> event.value.handleLifeCycle( + onLaunch = { initStateFlows() }, + onDispose = { resetStateFlows() } + ) } } + private fun initStateFlows() { + getAllCategories(onSuccess = { categories -> + _categoryListState.value = categories + setOverallCategoryState() + }) + } + + private fun resetStateFlows() { + resetOldEntries() + _categoryListState.value = emptyList() + _entryListState.value = DashboardState() + _focusedCategoryState.value = DashboardState() + _spendBudgetOnCurrentCategory.value = DashboardState() + } + private fun fillEntryListStateWithResult(entryList: List) { _entryListState.value = entryListState.value.copy(entryList = mapEntryListToDashboardEntryState(entryList)) } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt index 51d12af5..e1870294 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/dashboard/DashboardComponent.kt @@ -20,7 +20,9 @@ import androidx.compose.ui.graphics.Color import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent import de.hsfl.budgetBinder.presentation.event.UiEvent +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEntryState import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardEvent import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardState @@ -39,6 +41,7 @@ fun DashboardComponent() { val scaffoldState = rememberScaffoldState() LaunchedEffect(key1 = true) { + viewModel.onEvent(DashboardEvent.LifeCycle(LifecycleEvent.OnLaunch)) viewModel.eventFlow.collectLatest { event -> when (event) { is UiEvent.ShowLoading -> loadingState.value = true @@ -47,6 +50,12 @@ fun DashboardComponent() { } } } + DisposableEffect(Unit) { + onDispose { + viewModel.onEvent(DashboardEvent.LifeCycle(LifecycleEvent.OnDispose)) + } + } + Scaffold(scaffoldState = scaffoldState, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { From 914a95d64b1a1bc1de41b6b18dd90acf0990142d Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 14:42:14 +0200 Subject: [PATCH 55/73] Implement CategorySummary Lifecycle --- .../viewmodel/category/summary/CategorySummaryEvent.kt | 3 +++ .../category/summary/CategorySummaryViewModel.kt | 9 +++++---- .../budgetBinder/screens/category/CategorySummary.kt | 2 ++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt index d5cef432..f92aa276 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryEvent.kt @@ -1,6 +1,9 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.summary +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent + sealed class CategorySummaryEvent { + data class LifeCycle(val value: LifecycleEvent): CategorySummaryEvent() data class OnCategory(val id: Int): CategorySummaryEvent() object OnCategoryCreate: CategorySummaryEvent() object OnRefresh: CategorySummaryEvent() diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt index 1a2bf94f..3a9312ab 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt @@ -3,6 +3,7 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category.summary import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.event.handleLifeCycle import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.category.CategoryViewModel import kotlinx.coroutines.CoroutineScope @@ -21,15 +22,15 @@ class CategorySummaryViewModel( private val _categoryList = MutableStateFlow>(emptyList()) val categoryList: StateFlow> = _categoryList - init { - getAllCategories() - } - fun onEvent(event: CategorySummaryEvent) { when (event) { is CategorySummaryEvent.OnCategory -> routerFlow.navigateTo(Screen.Category.Detail(event.id)) is CategorySummaryEvent.OnCategoryCreate -> routerFlow.navigateTo(Screen.Category.Summary) is CategorySummaryEvent.OnRefresh -> getAllCategories() + is CategorySummaryEvent.LifeCycle -> event.value.handleLifeCycle( + onLaunch = { getAllCategories() }, + onDispose = {} + ) } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt index 6d2e9a99..69b1199b 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategorySummary.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.presentation.CategoryImageToIcon +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryEvent import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel @@ -32,6 +33,7 @@ fun CategorySummary() { val loadingState = remember { mutableStateOf(false) } LaunchedEffect(key1 = true) { + viewModel.onEvent(CategorySummaryEvent.LifeCycle(LifecycleEvent.OnLaunch)) viewModel.eventFlow.collectLatest { event -> when (event) { is UiEvent.ShowLoading -> loadingState.value = true From 698b850a4fdf50a309513b0d929a5abe762a3012 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 15:25:01 +0200 Subject: [PATCH 56/73] Implement Back and Cancel Buttons --- .../viewmodel/category/detail/CategoryDetailViewModel.kt | 2 +- .../viewmodel/category/edit/CategoryEditViewModel.kt | 2 +- .../de/hsfl/budgetBinder/screens/category/CategoryDetail.kt | 6 +++++- .../de/hsfl/budgetBinder/screens/category/CategoryEdit.kt | 3 +++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt index 011b5f22..757dbd26 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt @@ -55,7 +55,7 @@ open class CategoryDetailViewModel( } is CategoryDetailEvent.LifeCycle -> event.value.handleLifeCycle( onLaunch = { initStateFlows() }, - onDispose = { } + onDispose = { resetStateFlows() } ) } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt index c45fd9e4..5327dc0a 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/edit/CategoryEditViewModel.kt @@ -44,7 +44,7 @@ class CategoryEditViewModel( category = createCategoryPathFromState(), onSuccess = { routerFlow.navigateTo(Screen.Category.Detail(it.id)) } ) - is CategoryEditEvent.OnCancel -> routerFlow.navigateTo(Screen.Category.Summary) + is CategoryEditEvent.OnCancel -> routerFlow.navigateTo(Screen.Category.Detail(currentCategoryId)) is CategoryEditEvent.OnDelete -> super.delete( id = currentCategoryId, onSuccess = { routerFlow.navigateTo(Screen.Category.Summary) } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt index 978878a9..5caa959e 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryDetail.kt @@ -16,6 +16,7 @@ import de.hsfl.budgetBinder.presentation.event.LifecycleEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailEvent import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent import org.kodein.di.instance @OptIn(ExperimentalMaterialApi::class) @@ -31,7 +32,7 @@ fun CategoryDetailView() { } DisposableEffect(Unit) { onDispose { - println("CategoryDetailView::Dispos") + viewModel.onEvent(CategoryDetailEvent.LifeCycle(LifecycleEvent.OnDispose)) } } @@ -50,6 +51,9 @@ fun CategoryDetailView() { ListItem(text = { Text(entry.name) }, trailing = { Text(entry.amount.toString()) }) } } + Button(onClick = { viewModel.onEvent(CategoryDetailEvent.OnBack) }) { + Text("Back") + } } } diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryEdit.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryEdit.kt index 846eda4c..04e6eb95 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryEdit.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/category/CategoryEdit.kt @@ -59,6 +59,9 @@ fun CategoryEditView() { label = { Text("Category Budget") } ) CategoryImageToIcon(categoryImageState.value) + Button(onClick = {viewModel.onEvent(CategoryEditEvent.OnCancel)}) { + Text("Cancel") + } } } } From fca0b42be73371b64c2a7e9ce19b72e53d5596f2 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 15:39:13 +0200 Subject: [PATCH 57/73] Refactor HandleDataResponse --- .../hsfl/budgetBinder/common/DataResponse.kt | 29 +++++++++++ .../viewmodel/category/CategoryViewModel.kt | 49 +++++-------------- .../viewmodel/dashboard/DashboardViewModel.kt | 9 ++-- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/common/DataResponse.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/common/DataResponse.kt index 3f6b9fc7..d8cdec62 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/common/DataResponse.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/common/DataResponse.kt @@ -1,8 +1,37 @@ package de.hsfl.budgetBinder.common +import de.hsfl.budgetBinder.domain.usecase.NavigateToScreenUseCase +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.event.UiEvent +import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + sealed class DataResponse(val data: T? = null, val error: ErrorModel? = null) { class Success(data: T) : DataResponse(data) class Error(error: ErrorModel?, data: T? = null) : DataResponse(data, error) class Loading(data: T? = null) : DataResponse(data) class Unauthorized(error: ErrorModel? = null, data: T? = null) : DataResponse(data, error) } + +fun DataResponse.handleDataResponse( + scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()), + routerFlow: RouterFlow = RouterFlow(NavigateToScreenUseCase(), scope), + onSuccess: (T) -> Unit +) = scope.launch { + when (this@handleDataResponse) { + is DataResponse.Error -> UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowError(this@handleDataResponse.error!!.message)) + is DataResponse.Loading -> UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Success -> { + UiEventSharedFlow.mutableEventFlow.emit(UiEvent.HideSuccess) + onSuccess(this@handleDataResponse.data!!) + } + is DataResponse.Unauthorized -> { + routerFlow.navigateTo(Screen.Login) + UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowError(this@handleDataResponse.error!!.message)) + } + } +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt index abb26dad..f5770024 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt @@ -1,11 +1,9 @@ package de.hsfl.budgetBinder.presentation.viewmodel.category import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.common.handleDataResponse import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases -import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope @@ -25,53 +23,32 @@ open class CategoryViewModel( val eventFlow = UiEventSharedFlow.mutableEventFlow protected fun getAll(onSuccess: (List) -> Unit) = scope.launch { - categoriesUseCases.getAllCategoriesUseCase.categories().collect { response -> - handleDataResponse(response = response, onSuccess = onSuccess) - } + categoriesUseCases.getAllCategoriesUseCase.categories() + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } protected fun getById(id: Int, onSuccess: (Category) -> Unit) = scope.launch { - categoriesUseCases.getCategoryByIdUseCase(id = id).collect { response -> - handleDataResponse(response = response, onSuccess = onSuccess) - } + categoriesUseCases.getCategoryByIdUseCase(id = id) + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } protected fun create(category: Category.In, onSuccess: (Category) -> Unit) = scope.launch { - categoriesUseCases.createCategoryUseCase(category = category).collect { response -> - handleDataResponse(response = response, onSuccess = onSuccess) - } + categoriesUseCases.createCategoryUseCase(category = category) + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } protected fun change(id: Int, category: Category.Patch, onSuccess: (Category) -> Unit) = scope.launch { - categoriesUseCases.changeCategoryByIdUseCase(id = id, category = category).collect { response -> - handleDataResponse(response = response, onSuccess = onSuccess) - } + categoriesUseCases.changeCategoryByIdUseCase(id = id, category = category) + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } protected fun delete(id: Int, onSuccess: (Category) -> Unit) = scope.launch { - categoriesUseCases.deleteCategoryByIdUseCase(id = id).collect { response -> - handleDataResponse(response = response, onSuccess = onSuccess) - } + categoriesUseCases.deleteCategoryByIdUseCase(id = id) + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } protected fun entries(id: Int, onSuccess: (List) -> Unit) = scope.launch { - categoriesUseCases.getAllEntriesByCategoryUseCase(id = id).collect { response -> - handleDataResponse(response = response, onSuccess = onSuccess) - } - } - - private fun handleDataResponse(response: DataResponse, onSuccess: (T) -> Unit) = scope.launch { - when (response) { - is DataResponse.Error -> eventFlow.emit(UiEvent.ShowError(response.error!!.message)) - is DataResponse.Loading -> eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Success -> { - eventFlow.emit(UiEvent.HideSuccess) - onSuccess(response.data!!) - } - is DataResponse.Unauthorized -> { - routerFlow.navigateTo(Screen.Login) - eventFlow.emit(UiEvent.ShowError(response.error!!.message)) - } - } + categoriesUseCases.getAllEntriesByCategoryUseCase(id = id) + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index 5042f455..ca8d6969 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -3,6 +3,7 @@ package de.hsfl.budgetBinder.presentation.viewmodel.dashboard import de.hsfl.budgetBinder.common.Category import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.Entry +import de.hsfl.budgetBinder.common.handleDataResponse import de.hsfl.budgetBinder.domain.usecase.* import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.event.UiEvent @@ -99,26 +100,26 @@ class DashboardViewModel( private fun getAllEntries(onSuccess: (List) -> Unit) = scope.launch { dashboardUseCases.getAllEntriesUseCase.entries() - .collect { handleDataResponse(response = it, onSuccess = onSuccess) } + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } private fun getAllCategories(onSuccess: (List) -> Unit) = scope.launch { dashboardUseCases.getAllCategoriesUseCase.categories() - .collect { handleDataResponse(response = it, onSuccess = onSuccess) } + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } private fun getEntriesByCategory(id: Int? = null, period: String? = null, onSuccess: (List) -> Unit) = scope.launch { dashboardUseCases.getAllEntriesByCategoryUseCase(id, period) - .collect { handleDataResponse(response = it, onSuccess = onSuccess) } + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } private fun getAllEntriesFromMonth(period: String, onSuccess: (List) -> Unit) = scope.launch { dashboardUseCases.getAllEntriesUseCase.entries(period) - .collect { handleDataResponse(response = it, onSuccess = onSuccess) } + .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } private fun deleteEntry(id: Int) = scope.launch { From f9b64004e59d6847c06499790373896b0d2e2289 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 15:44:21 +0200 Subject: [PATCH 58/73] Refactor DataFlow.kt --- .../presentation/flow/DataFlow.kt | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/DataFlow.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/DataFlow.kt index c1cc5969..40b4b286 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/DataFlow.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/DataFlow.kt @@ -2,12 +2,10 @@ package de.hsfl.budgetBinder.presentation.flow import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.domain.usecase.DataFlowUseCases -import de.hsfl.budgetBinder.domain.usecase.storage.StoreUserStateUseCase import io.ktor.http.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch class DataFlow( @@ -18,7 +16,7 @@ class DataFlow( private val _userState = MutableStateFlow(User(0, "", "", "")) val userState: StateFlow = _userState - suspend fun storeUserState(user: User) { + suspend fun storeUserState(user: User) = scope.launch { dataFlowUseCases.storeUserStateUseCase(user).collect { _userState.value = it } @@ -28,23 +26,21 @@ class DataFlow( private val _serverUrlState = MutableStateFlow(Url("http://localhost:8080")) val serverUrlState: StateFlow = _serverUrlState - fun storeServerUrl(serverUrl: Url) { - scope.launch { - dataFlowUseCases.storeServerUrlUseCase(serverUrl).collect { - _serverUrlState.value = it - } + fun storeServerUrl(serverUrl: Url) = scope.launch { + dataFlowUseCases.storeServerUrlUseCase(serverUrl).collect { + _serverUrlState.value = it } } + // Dark Mode private val _darkModeState = MutableStateFlow(false) val darkModeState: StateFlow = _darkModeState - fun toggleDarkMode() { - scope.launch { - dataFlowUseCases.storeDarkModeUseCase(!darkModeState.value).collect { - _darkModeState.value = it - } + fun toggleDarkMode() = scope.launch { + dataFlowUseCases.storeDarkModeUseCase(!darkModeState.value).collect { + _darkModeState.value = it } } + } From 844cb8265e79cbdd25b841dcd7be5a08b832c416 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 16:30:43 +0200 Subject: [PATCH 59/73] Add more Callbacks on handleDataResponse --- .../hsfl/budgetBinder/common/DataResponse.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/common/DataResponse.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/common/DataResponse.kt index d8cdec62..07d43c41 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/common/DataResponse.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/common/DataResponse.kt @@ -20,18 +20,33 @@ sealed class DataResponse(val data: T? = null, val error: ErrorModel? = null) fun DataResponse.handleDataResponse( scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()), routerFlow: RouterFlow = RouterFlow(NavigateToScreenUseCase(), scope), - onSuccess: (T) -> Unit + onSuccess: (T) -> Unit, + onError: ((ErrorModel) -> Unit)? = null, + onLoading: (() -> Unit)? = null, + onUnauthorized: (() -> Unit)? = null ) = scope.launch { when (this@handleDataResponse) { - is DataResponse.Error -> UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowError(this@handleDataResponse.error!!.message)) - is DataResponse.Loading -> UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowLoading) + is DataResponse.Error -> { + onError?.let { + onError(this@handleDataResponse.error!!) + } ?: UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowError(this@handleDataResponse.error!!.message)) + } + is DataResponse.Loading -> { + onLoading?.let { + onLoading() + } ?: UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowLoading) + } is DataResponse.Success -> { UiEventSharedFlow.mutableEventFlow.emit(UiEvent.HideSuccess) onSuccess(this@handleDataResponse.data!!) } is DataResponse.Unauthorized -> { - routerFlow.navigateTo(Screen.Login) - UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowError(this@handleDataResponse.error!!.message)) + onUnauthorized?.let { + onUnauthorized() + } ?: run { + routerFlow.navigateTo(Screen.Login) + UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowError(this@handleDataResponse.error!!.message)) + } } } } From 566d6f618c32d7e7d6b90d16675ce42bd3b055d3 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 16:41:43 +0200 Subject: [PATCH 60/73] Refactor LoginViewModel --- .../hsfl/budgetBinder/data/client/Client.kt | 4 +- .../presentation/flow/DataFlow.kt | 2 +- .../viewmodel/login/LoginEvent.kt | 5 + .../viewmodel/login/LoginViewModel.kt | 111 +++++++++--------- .../screens/login/LoginComponent.kt | 2 + 5 files changed, 66 insertions(+), 58 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt index 8652d1f9..c757ed5f 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/data/client/Client.kt @@ -179,10 +179,10 @@ class Client(engine: HttpClientEngine) : ApiClient { refreshPath = "/refresh_token" } - install(Logging) { + /*install(Logging) { logger = Logger.DEFAULT level = LogLevel.HEADERS - } + }*/ defaultRequest { url(BASE_URL) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/DataFlow.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/DataFlow.kt index 40b4b286..78b0c53e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/DataFlow.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/flow/DataFlow.kt @@ -16,7 +16,7 @@ class DataFlow( private val _userState = MutableStateFlow(User(0, "", "", "")) val userState: StateFlow = _userState - suspend fun storeUserState(user: User) = scope.launch { + fun storeUserState(user: User) = scope.launch { dataFlowUseCases.storeUserStateUseCase(user).collect { _userState.value = it } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginEvent.kt index dd6b105b..15eca332 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginEvent.kt @@ -1,9 +1,14 @@ package de.hsfl.budgetBinder.presentation.viewmodel.login +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent + sealed class LoginEvent { data class EnteredEmail(val value: String) : LoginEvent() data class EnteredPassword(val value: String) : LoginEvent() data class EnteredServerUrl(val value: String) : LoginEvent() + data class LifeCycle(val value: LifecycleEvent): LoginEvent() object OnLogin : LoginEvent() object OnServerUrlDialogConfirm : LoginEvent() object OnServerUrlDialogDismiss : LoginEvent() diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt index 8f60d5d8..cffffed4 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt @@ -1,12 +1,15 @@ package de.hsfl.budgetBinder.presentation.viewmodel.login import de.hsfl.budgetBinder.common.DataResponse +import de.hsfl.budgetBinder.common.User +import de.hsfl.budgetBinder.common.handleDataResponse import de.hsfl.budgetBinder.common.utils.validateEmail import de.hsfl.budgetBinder.domain.usecase.LoginUseCases import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState +import de.hsfl.budgetBinder.presentation.event.handleLifeCycle import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import io.ktor.http.* @@ -21,6 +24,7 @@ class LoginViewModel( private val dataFlow: DataFlow, private val scope: CoroutineScope ) { + private val screenAfterSuccess = Screen.Dashboard private val _emailText = MutableStateFlow(LoginTextFieldState()) val emailText: StateFlow = _emailText @@ -34,31 +38,8 @@ class LoginViewModel( private val _dialogState = MutableStateFlow(false) val dialogState: StateFlow = _dialogState - private val _eventFlow = UiEventSharedFlow.mutableEventFlow val eventFlow = UiEventSharedFlow.eventFlow - init { - // try to fetch user '/me' on start. If successful, the user is already authorized - scope.launch { - loginUseCases.getMyUserUseCase().collect { - when (it) { - is DataResponse.Success -> { - _eventFlow.emit(UiEvent.ShowLoading) - dataFlow.storeUserState(it.data!!) - delay(1000L) - routerFlow.navigateTo(Screen.Dashboard) - } - else -> { - // If the request failed - - // Debug: - // _eventFlow.emit(UiEvent.ShowError("init: user is nor authorized")) - } - } - } - } - } - fun onEvent(event: LoginEvent) { when (event) { is LoginEvent.EnteredEmail -> _emailText.value = @@ -67,51 +48,69 @@ class LoginViewModel( passwordText.value.copy(password = event.value) is LoginEvent.EnteredServerUrl -> _serverUrlText.value = serverUrlText.value.copy(serverAddress = event.value) - is LoginEvent.OnLogin -> { - if (validateEmail(email = emailText.value.email)) { - toggleDialog() - } else { - _emailText.value = emailText.value.copy(emailValid = false) - } - } + is LoginEvent.OnLogin -> onLogin() is LoginEvent.OnRegisterScreen -> routerFlow.navigateTo(Screen.Register) - is LoginEvent.OnServerUrlDialogConfirm -> { - toggleDialog() - dataFlow.storeServerUrl(Url(urlString = serverUrlText.value.serverAddress)) - auth(email = emailText.value.email, password = passwordText.value.password) - } + is LoginEvent.OnServerUrlDialogConfirm -> onServerUrlDialogConfirm() is LoginEvent.OnServerUrlDialogDismiss -> toggleDialog() + is LoginEvent.LifeCycle -> event.value.handleLifeCycle( + onLaunch = { tryToLoginUserOnStart() }, + onDispose = { clearStateFlows() } + ) } } + private fun onLogin() { + if (validateEmail(email = emailText.value.email)) { + toggleDialog() + } else { + _emailText.value = emailText.value.copy(emailValid = false) + } + } + + private fun onServerUrlDialogConfirm() { + toggleDialog() + dataFlow.storeServerUrl(Url(urlString = serverUrlText.value.serverAddress)) + login() + } + + private fun tryToLoginUserOnStart() = scope.launch { + loginUseCases.getMyUserUseCase() + .collect { + it.handleDataResponse( + onSuccess = { user -> + storeUser(user) + routerFlow.navigateTo(screenAfterSuccess) + }, + onUnauthorized = { /* Don't show an error message on unauthorized */ } + ) + } + } + + private fun storeUser(user: User) { + dataFlow.storeUserState(user) + } + private fun toggleDialog() { _dialogState.value = !dialogState.value } - private fun auth(email: String, password: String) { - loginUseCases.loginUseCase(email, password).onEach { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Success<*> -> getMyUser() - is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - } - }.launchIn(scope) + private fun login() = scope.launch { + loginUseCases.loginUseCase( + email = emailText.value.email, + password = passwordText.value.password + ).collect { + it.handleDataResponse(onSuccess = { getMyUser() }) + } } - private fun getMyUser() { - loginUseCases.getMyUserUseCase().onEach { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Success<*> -> { - dataFlow.storeUserState(it.data!!) + private fun getMyUser() = scope.launch { + loginUseCases.getMyUserUseCase() + .collect { + it.handleDataResponse(onSuccess = { user -> + dataFlow.storeUserState(user) routerFlow.navigateTo(Screen.Dashboard) - clearStateFlows() - } - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - else -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) + }) } - }.launchIn(scope) } private fun clearStateFlows() { @@ -119,6 +118,8 @@ class LoginViewModel( _passwordText.value = passwordText.value.copy(password = "") } + + // Old private val _state = MutableStateFlow(UiState.Empty) @Deprecated(message = "Old ViewModel, use the new State") diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt index 9f49cffb..e48a5513 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt @@ -14,6 +14,7 @@ import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.compose.dialog.ServerUrlDialog import de.hsfl.budgetBinder.compose.icon.AppIcon import de.hsfl.budgetBinder.compose.textfield.EmailTextField +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginEvent @@ -33,6 +34,7 @@ fun LoginComponent() { LaunchedEffect(key1 = true) { + viewModel.onEvent(LoginEvent.LifeCycle(LifecycleEvent.OnLaunch)) viewModel.eventFlow.collectLatest { event -> when (event) { is UiEvent.ShowLoading -> loadingState.value = true From 6810c95e4675dee222638721aaa4b37e52dc3b0e Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 16:45:45 +0200 Subject: [PATCH 61/73] Refactor NavDrawerViewModel.kt --- .../viewmodel/navdrawer/NavDrawerViewModel.kt | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt index 578fe861..9ab4c62a 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/navdrawer/NavDrawerViewModel.kt @@ -1,19 +1,16 @@ package de.hsfl.budgetBinder.presentation.viewmodel.navdrawer import de.hsfl.budgetBinder.common.DataResponse +import de.hsfl.budgetBinder.common.handleDataResponse import de.hsfl.budgetBinder.domain.usecase.LogoutUseCase import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch class NavDrawerViewModel( @@ -22,7 +19,6 @@ class NavDrawerViewModel( private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()), ) { - private val _eventFlow = UiEventSharedFlow.mutableEventFlow val eventFlow = UiEventSharedFlow.eventFlow fun onEvent(event: NavDrawerEvent) { @@ -31,25 +27,14 @@ class NavDrawerViewModel( is NavDrawerEvent.OnCreateEntry -> routerFlow.navigateTo(Screen.Entry.Create) is NavDrawerEvent.OnCategory -> routerFlow.navigateTo(Screen.Category.Summary) is NavDrawerEvent.OnSettings -> routerFlow.navigateTo(Screen.Settings.Menu) - is NavDrawerEvent.OnLogout -> scope.launch { - logoutUseCase(onAllDevices = false).collect { - when (it) { - is DataResponse.Success -> { - routerFlow.navigateTo(Screen.Login) - _eventFlow.emit(UiEvent.ShowSuccess("Your are logged out")) - } - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Unauthorized -> { - routerFlow.navigateTo(Screen.Login) - _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - } - } - } - } + is NavDrawerEvent.OnLogout -> logout() } } + private fun logout() = scope.launch { + logoutUseCase(onAllDevices = false) + .collect { it.handleDataResponse(onSuccess = { routerFlow.navigateTo(Screen.Login) }) } + } // Old private val _state = MutableStateFlow(UiState.Empty) From fdfccdf2f5ce18cd27a9321f57c38674c8610271 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 19:42:45 +0200 Subject: [PATCH 62/73] Refactor Login and Register with AuthViewModel.kt --- .../kotlin/de/hsfl/budgetBinder/di/DI.kt | 7 +- .../domain/usecase/AuthUseCases.kt | 7 ++ .../domain/usecase/LoginUseCases.kt | 6 -- .../domain/usecase/RegisterUseCases.kt | 7 -- .../viewmodel/auth/AuthViewModel.kt | 47 +++++++++++++ .../viewmodel/{ => auth}/login/LoginEvent.kt | 4 +- .../{ => auth}/login/LoginTextFieldState.kt | 2 +- .../{ => auth}/login/LoginViewModel.kt | 50 +++++--------- .../{ => auth}/register/RegisterEvent.kt | 2 +- .../register/RegisterTextFieldState.kt | 2 +- .../{ => auth}/register/RegisterViewModel.kt | 66 ++++--------------- .../compose/login/LoginComponent.kt | 6 +- .../compose/register/RegisterComponent.kt | 4 +- .../screens/login/LoginComponent.kt | 4 +- .../screens/register/RegisterComponent.kt | 4 +- 15 files changed, 100 insertions(+), 118 deletions(-) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/AuthUseCases.kt delete mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/LoginUseCases.kt delete mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/RegisterUseCases.kt create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/AuthViewModel.kt rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/{ => auth}/login/LoginEvent.kt (73%) rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/{ => auth}/login/LoginTextFieldState.kt (80%) rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/{ => auth}/login/LoginViewModel.kt (78%) rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/{ => auth}/register/RegisterEvent.kt (87%) rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/{ => auth}/register/RegisterTextFieldState.kt (81%) rename budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/{ => auth}/register/RegisterViewModel.kt (68%) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index 2f718a32..bc63832e 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -16,8 +16,8 @@ import de.hsfl.budgetBinder.domain.usecase.storage.StoreUserStateUseCase import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow -import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel -import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.auth.login.LoginViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.auth.register.RegisterViewModel import de.hsfl.budgetBinder.presentation.viewmodel.* import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel @@ -81,9 +81,8 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { EntriesUseCases(instance(), instance(), instance(), instance(), instance()) } bindSingleton { CategoriesUseCases(instance(), instance(), instance(), instance(), instance(), instance()) } bindSingleton { SettingsUseCases(instance(), instance(), instance()) } - bindSingleton { LoginUseCases(instance(), instance()) } + bindSingleton { AuthUseCases(instance(), instance(), instance()) } bindSingleton { DashboardUseCases(instance(), instance(), instance(), instance()) } - bindSingleton { RegisterUseCases(instance(), instance(), instance()) } bindSingleton { DataFlowUseCases(instance(), instance(), instance()) } // Flows diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/AuthUseCases.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/AuthUseCases.kt new file mode 100644 index 00000000..04d657ad --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/AuthUseCases.kt @@ -0,0 +1,7 @@ +package de.hsfl.budgetBinder.domain.usecase + +data class AuthUseCases( + val loginUseCase: LoginUseCase, + val getMyUserUseCase: GetMyUserUseCase, + val registerUseCase: RegisterUseCase +) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/LoginUseCases.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/LoginUseCases.kt deleted file mode 100644 index 69549efe..00000000 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/LoginUseCases.kt +++ /dev/null @@ -1,6 +0,0 @@ -package de.hsfl.budgetBinder.domain.usecase - -data class LoginUseCases( - val loginUseCase: LoginUseCase, - val getMyUserUseCase: GetMyUserUseCase -) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/RegisterUseCases.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/RegisterUseCases.kt deleted file mode 100644 index c5d8a0e5..00000000 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/RegisterUseCases.kt +++ /dev/null @@ -1,7 +0,0 @@ -package de.hsfl.budgetBinder.domain.usecase - -data class RegisterUseCases( - val registerUseCase: RegisterUseCase, - val loginUseCase: LoginUseCase, - val getMyUserUseCase: GetMyUserUseCase -) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/AuthViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/AuthViewModel.kt new file mode 100644 index 00000000..ac621be2 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/AuthViewModel.kt @@ -0,0 +1,47 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.auth + +import de.hsfl.budgetBinder.common.User +import de.hsfl.budgetBinder.common.handleDataResponse +import de.hsfl.budgetBinder.domain.usecase.AuthUseCases +import de.hsfl.budgetBinder.domain.usecase.NavigateToScreenUseCase +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.flow.DataFlow +import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +open class AuthViewModel( + _scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()), + _routerFlow: RouterFlow = RouterFlow(NavigateToScreenUseCase(), _scope), + _dataFlow: DataFlow, + _authUseCases: AuthUseCases +) { + private val scope = _scope + private val routerFlow = _routerFlow + private val dataFlow = _dataFlow + private val authUseCases = _authUseCases + + val eventFlow = UiEventSharedFlow.eventFlow + + protected fun register(user: User.In) = scope.launch { + authUseCases.registerUseCase(user) + .collect { it.handleDataResponse(onSuccess = { login(email = user.email, password = user.password) }) } + } + + protected fun login(email: String, password: String) = scope.launch { + authUseCases.loginUseCase(email = email, password = password) + .collect { it.handleDataResponse(onSuccess = { getMyUser() }) } + } + + private fun getMyUser() = scope.launch { + authUseCases.getMyUserUseCase().collect { + it.handleDataResponse(onSuccess = { user -> + dataFlow.storeUserState(user) + routerFlow.navigateTo(Screen.Dashboard) + }) + } + } +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/login/LoginEvent.kt similarity index 73% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginEvent.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/login/LoginEvent.kt index 15eca332..11e95ab1 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/login/LoginEvent.kt @@ -1,8 +1,6 @@ -package de.hsfl.budgetBinder.presentation.viewmodel.login +package de.hsfl.budgetBinder.presentation.viewmodel.auth.login -import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.event.LifecycleEvent -import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent sealed class LoginEvent { data class EnteredEmail(val value: String) : LoginEvent() diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginTextFieldState.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/login/LoginTextFieldState.kt similarity index 80% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginTextFieldState.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/login/LoginTextFieldState.kt index ceac2886..50f0b906 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginTextFieldState.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/login/LoginTextFieldState.kt @@ -1,4 +1,4 @@ -package de.hsfl.budgetBinder.presentation.viewmodel.login +package de.hsfl.budgetBinder.presentation.viewmodel.auth.login data class LoginTextFieldState( val email: String = "root@budget-binder.com", diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/login/LoginViewModel.kt similarity index 78% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/login/LoginViewModel.kt index cffffed4..9fde059c 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/login/LoginViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/login/LoginViewModel.kt @@ -1,28 +1,31 @@ -package de.hsfl.budgetBinder.presentation.viewmodel.login +package de.hsfl.budgetBinder.presentation.viewmodel.auth.login import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.common.handleDataResponse import de.hsfl.budgetBinder.common.utils.validateEmail -import de.hsfl.budgetBinder.domain.usecase.LoginUseCases +import de.hsfl.budgetBinder.domain.usecase.AuthUseCases import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.event.handleLifeCycle import de.hsfl.budgetBinder.presentation.flow.DataFlow -import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow +import de.hsfl.budgetBinder.presentation.viewmodel.auth.AuthViewModel import io.ktor.http.* import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch class LoginViewModel( - private val loginUseCases: LoginUseCases, + private val authUseCases: AuthUseCases, private val routerFlow: RouterFlow, private val dataFlow: DataFlow, private val scope: CoroutineScope +) : AuthViewModel( + _scope = scope, + _routerFlow = routerFlow, + _dataFlow = dataFlow, + _authUseCases = authUseCases ) { private val screenAfterSuccess = Screen.Dashboard @@ -38,8 +41,6 @@ class LoginViewModel( private val _dialogState = MutableStateFlow(false) val dialogState: StateFlow = _dialogState - val eventFlow = UiEventSharedFlow.eventFlow - fun onEvent(event: LoginEvent) { when (event) { is LoginEvent.EnteredEmail -> _emailText.value = @@ -48,7 +49,7 @@ class LoginViewModel( passwordText.value.copy(password = event.value) is LoginEvent.EnteredServerUrl -> _serverUrlText.value = serverUrlText.value.copy(serverAddress = event.value) - is LoginEvent.OnLogin -> onLogin() + is LoginEvent.OnLogin -> validateInput() is LoginEvent.OnRegisterScreen -> routerFlow.navigateTo(Screen.Register) is LoginEvent.OnServerUrlDialogConfirm -> onServerUrlDialogConfirm() is LoginEvent.OnServerUrlDialogDismiss -> toggleDialog() @@ -59,7 +60,7 @@ class LoginViewModel( } } - private fun onLogin() { + private fun validateInput() { if (validateEmail(email = emailText.value.email)) { toggleDialog() } else { @@ -70,11 +71,11 @@ class LoginViewModel( private fun onServerUrlDialogConfirm() { toggleDialog() dataFlow.storeServerUrl(Url(urlString = serverUrlText.value.serverAddress)) - login() + super.login(email = emailText.value.email, password = passwordText.value.password) } private fun tryToLoginUserOnStart() = scope.launch { - loginUseCases.getMyUserUseCase() + authUseCases.getMyUserUseCase() .collect { it.handleDataResponse( onSuccess = { user -> @@ -94,25 +95,6 @@ class LoginViewModel( _dialogState.value = !dialogState.value } - private fun login() = scope.launch { - loginUseCases.loginUseCase( - email = emailText.value.email, - password = passwordText.value.password - ).collect { - it.handleDataResponse(onSuccess = { getMyUser() }) - } - } - - private fun getMyUser() = scope.launch { - loginUseCases.getMyUserUseCase() - .collect { - it.handleDataResponse(onSuccess = { user -> - dataFlow.storeUserState(user) - routerFlow.navigateTo(Screen.Dashboard) - }) - } - } - private fun clearStateFlows() { _emailText.value = emailText.value.copy(email = "") _passwordText.value = passwordText.value.copy(password = "") @@ -126,8 +108,8 @@ class LoginViewModel( val state: StateFlow = _state @Deprecated(message = "Use ViewModel function onEvent()") - fun login(email: String, password: String) { - loginUseCases.loginUseCase(email, password).onEach { + fun _login(email: String, password: String) { + authUseCases.loginUseCase(email, password).onEach { when (it) { is DataResponse.Loading -> _state.value = UiState.Loading is DataResponse.Success<*> -> getMyUserDeprecated() @@ -139,7 +121,7 @@ class LoginViewModel( @Deprecated(message = "Use new getMyUser function and SharedFlow UiEvents") private fun getMyUserDeprecated() { - loginUseCases.getMyUserUseCase().onEach { + authUseCases.getMyUserUseCase().onEach { when (it) { is DataResponse.Loading -> _state.value = UiState.Loading is DataResponse.Success<*> -> _state.value = UiState.Success(it.data) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterEvent.kt similarity index 87% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterEvent.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterEvent.kt index 19b849e0..050b3ee5 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterEvent.kt @@ -1,4 +1,4 @@ -package de.hsfl.budgetBinder.presentation.viewmodel.register +package de.hsfl.budgetBinder.presentation.viewmodel.auth.register sealed class RegisterEvent { data class EnteredFirstname(val value: String): RegisterEvent() diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterTextFieldState.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterTextFieldState.kt similarity index 81% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterTextFieldState.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterTextFieldState.kt index d6d19ff7..90543b9b 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterTextFieldState.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterTextFieldState.kt @@ -1,4 +1,4 @@ -package de.hsfl.budgetBinder.presentation.viewmodel.register +package de.hsfl.budgetBinder.presentation.viewmodel.auth.register data class RegisterTextFieldState( val firstName: String = "", diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterViewModel.kt similarity index 68% rename from budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt rename to budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterViewModel.kt index 520c1f13..42e5c3a2 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/register/RegisterViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterViewModel.kt @@ -1,23 +1,27 @@ -package de.hsfl.budgetBinder.presentation.viewmodel.register +package de.hsfl.budgetBinder.presentation.viewmodel.auth.register import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.common.utils.validateEmail -import de.hsfl.budgetBinder.domain.usecase.RegisterUseCases +import de.hsfl.budgetBinder.domain.usecase.AuthUseCases import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.UiState import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow -import de.hsfl.budgetBinder.presentation.flow.UiEventSharedFlow +import de.hsfl.budgetBinder.presentation.viewmodel.auth.AuthViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* class RegisterViewModel( - private val registerUseCases: RegisterUseCases, + private val authUseCases: AuthUseCases, private val routerFlow: RouterFlow, - private val dataFlow: DataFlow, + dataFlow: DataFlow, private val scope: CoroutineScope +): AuthViewModel( + _scope = scope, + _authUseCases = authUseCases, + _dataFlow = dataFlow, + _routerFlow = routerFlow ) { private val _firstNameText = MutableStateFlow(RegisterTextFieldState()) val firstNameText: StateFlow = _firstNameText @@ -34,9 +38,6 @@ class RegisterViewModel( private val _confirmedPasswordText = MutableStateFlow(RegisterTextFieldState()) val confirmedPasswordText: StateFlow = _confirmedPasswordText - private val _eventFlow = UiEventSharedFlow.mutableEventFlow - val eventFlow = UiEventSharedFlow.eventFlow - fun onEvent(event: RegisterEvent) { when (event) { is RegisterEvent.EnteredFirstname -> _firstNameText.value = @@ -50,7 +51,7 @@ class RegisterViewModel( is RegisterEvent.OnLoginScreen -> routerFlow.navigateTo(Screen.Login) is RegisterEvent.OnRegister -> { if (validateInput()) { - register( + super.register( User.In( firstName = firstNameText.value.firstName, name = lastNameText.value.lastName, @@ -83,45 +84,6 @@ class RegisterViewModel( return checkEmail && checkConfirmedPassword } - fun register(user: User.In) { - registerUseCases.registerUseCase(user).onEach { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Success<*> -> login( - email = emailText.value.email, - password = passwordText.value.password - ) - is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - } - }.launchIn(scope) - } - - private fun login(email: String, password: String) { - registerUseCases.loginUseCase(email, password).onEach { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - is DataResponse.Success<*> -> getMyUser() - is DataResponse.Unauthorized -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - } - }.launchIn(scope) - } - - private fun getMyUser() { - registerUseCases.getMyUserUseCase().onEach { - when (it) { - is DataResponse.Loading -> _eventFlow.emit(UiEvent.ShowLoading) - is DataResponse.Success<*> -> { - dataFlow.storeUserState(it.data!!) - routerFlow.navigateTo(Screen.Dashboard) - } - is DataResponse.Error -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - else -> _eventFlow.emit(UiEvent.ShowError(it.error!!.message)) - } - }.launchIn(scope) - } - private fun clearStateFlows() { _firstNameText.value = firstNameText.value.copy(firstName = "") _lastNameText.value = lastNameText.value.copy(lastName = "") @@ -138,7 +100,7 @@ class RegisterViewModel( @Deprecated(message = "Use ViewModel function onEvent()") fun _register(user: User.In) { - registerUseCases.registerUseCase(user).onEach { + authUseCases.registerUseCase(user).onEach { when (it) { is DataResponse.Success -> _login(user.email, user.password) is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) @@ -150,7 +112,7 @@ class RegisterViewModel( @Deprecated(message = "This is Deprecated") private fun _login(email: String, password: String) { - registerUseCases.loginUseCase(email, password).onEach { + authUseCases.loginUseCase(email, password).onEach { when (it) { is DataResponse.Success -> _getMyUser() is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) @@ -162,7 +124,7 @@ class RegisterViewModel( @Deprecated(message = "This is Deprecated") private fun _getMyUser() { - registerUseCases.getMyUserUseCase().onEach { + authUseCases.getMyUserUseCase().onEach { when (it) { is DataResponse.Success -> _state.value = UiState.Success(it.data) is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) diff --git a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/login/LoginComponent.kt b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/login/LoginComponent.kt index 4bd513f8..ecd10e54 100644 --- a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/login/LoginComponent.kt +++ b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/login/LoginComponent.kt @@ -1,7 +1,7 @@ package de.hsfl.budgetBinder.compose.login import androidx.compose.runtime.* import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.auth.login.LoginViewModel import di import org.kodein.di.compose.withDI import org.kodein.di.instance @@ -21,9 +21,9 @@ fun LoginComponent(screenState: MutableState) = withDI(di) { LoginView( state = viewState, onLoginButtonPressed = { email, password -> - viewModel.login(email, password) + viewModel._login(email, password) }, onLoginSuccess = { screenState.value = Screen.Dashboard }, onChangeToRegister = { screenState.value = Screen.Register } ) -} \ No newline at end of file +} diff --git a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/register/RegisterComponent.kt b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/register/RegisterComponent.kt index 58526f41..0dd246e8 100644 --- a/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/register/RegisterComponent.kt +++ b/budget-binder-multiplatform-app/src/jsMain/kotlin/de/hsfl/budgetBinder/compose/register/RegisterComponent.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.* import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.presentation.Screen -import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.auth.register.RegisterViewModel import di import org.kodein.di.instance @@ -29,4 +29,4 @@ fun RegisterComponent(screenState: MutableState) { }, onChangeToLogin = { screenState.value = Screen.Login } ) -} \ No newline at end of file +} diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt index e48a5513..52110abf 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/login/LoginComponent.kt @@ -16,8 +16,8 @@ import de.hsfl.budgetBinder.compose.icon.AppIcon import de.hsfl.budgetBinder.compose.textfield.EmailTextField import de.hsfl.budgetBinder.presentation.event.LifecycleEvent import de.hsfl.budgetBinder.presentation.event.UiEvent -import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginViewModel -import de.hsfl.budgetBinder.presentation.viewmodel.login.LoginEvent +import de.hsfl.budgetBinder.presentation.viewmodel.auth.login.LoginViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.auth.login.LoginEvent import kotlinx.coroutines.flow.collectLatest import org.kodein.di.instance diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt index ce5f3eb6..83e1832f 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt @@ -15,8 +15,8 @@ import de.hsfl.budgetBinder.compose.icon.AppIcon import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.compose.textfield.EmailTextField import de.hsfl.budgetBinder.presentation.event.UiEvent -import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterEvent -import de.hsfl.budgetBinder.presentation.viewmodel.register.RegisterViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.auth.register.RegisterEvent +import de.hsfl.budgetBinder.presentation.viewmodel.auth.register.RegisterViewModel import kotlinx.coroutines.flow.collectLatest import org.kodein.di.instance From a35ad57142e896c14c00f3ceb682b19425ff14bc Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 19:51:43 +0200 Subject: [PATCH 63/73] Add Lifecycle to RegisterView --- .../viewmodel/auth/register/RegisterEvent.kt | 4 ++++ .../viewmodel/auth/register/RegisterViewModel.kt | 10 ++++++++-- .../budgetBinder/screens/register/RegisterComponent.kt | 7 +++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterEvent.kt index 050b3ee5..453fd0e3 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterEvent.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterEvent.kt @@ -1,6 +1,10 @@ package de.hsfl.budgetBinder.presentation.viewmodel.auth.register +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent + sealed class RegisterEvent { + data class LifeCycle(val value: LifecycleEvent): RegisterEvent() data class EnteredFirstname(val value: String): RegisterEvent() data class EnteredLastname(val value: String): RegisterEvent() data class EnteredEmail(val value: String): RegisterEvent() diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterViewModel.kt index 42e5c3a2..e5897b8c 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/auth/register/RegisterViewModel.kt @@ -6,6 +6,7 @@ import de.hsfl.budgetBinder.common.utils.validateEmail import de.hsfl.budgetBinder.domain.usecase.AuthUseCases import de.hsfl.budgetBinder.presentation.Screen import de.hsfl.budgetBinder.presentation.UiState +import de.hsfl.budgetBinder.presentation.event.handleLifeCycle import de.hsfl.budgetBinder.presentation.flow.DataFlow import de.hsfl.budgetBinder.presentation.flow.RouterFlow import de.hsfl.budgetBinder.presentation.viewmodel.auth.AuthViewModel @@ -17,7 +18,7 @@ class RegisterViewModel( private val routerFlow: RouterFlow, dataFlow: DataFlow, private val scope: CoroutineScope -): AuthViewModel( +) : AuthViewModel( _scope = scope, _authUseCases = authUseCases, _dataFlow = dataFlow, @@ -61,6 +62,10 @@ class RegisterViewModel( ) } } + is RegisterEvent.LifeCycle -> event.value.handleLifeCycle( + onLaunch = {}, + onDispose = { resetStateFlows() } + ) } } @@ -84,11 +89,12 @@ class RegisterViewModel( return checkEmail && checkConfirmedPassword } - private fun clearStateFlows() { + private fun resetStateFlows() { _firstNameText.value = firstNameText.value.copy(firstName = "") _lastNameText.value = lastNameText.value.copy(lastName = "") _emailText.value = emailText.value.copy(email = "") _passwordText.value = passwordText.value.copy(password = "") + _confirmedPasswordText.value = _confirmedPasswordText.value.copy(confirmedPassword = "") } // OLD!!! diff --git a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt index 83e1832f..23b796c5 100644 --- a/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt +++ b/budget-binder-multiplatform-app/src/jvmMain/kotlin/de/hsfl/budgetBinder/screens/register/RegisterComponent.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp import de.hsfl.budgetBinder.compose.icon.AppIcon import de.hsfl.budgetBinder.di import de.hsfl.budgetBinder.compose.textfield.EmailTextField +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent import de.hsfl.budgetBinder.presentation.event.UiEvent import de.hsfl.budgetBinder.presentation.viewmodel.auth.register.RegisterEvent import de.hsfl.budgetBinder.presentation.viewmodel.auth.register.RegisterViewModel @@ -31,6 +32,7 @@ fun RegisterComponent() { val loadingState = remember { mutableStateOf(false) } LaunchedEffect(key1 = true) { + viewModel.onEvent(RegisterEvent.LifeCycle(LifecycleEvent.OnLaunch)) viewModel.eventFlow.collectLatest { event -> when (event) { is UiEvent.ShowLoading -> { @@ -41,6 +43,11 @@ fun RegisterComponent() { } } } + DisposableEffect(Unit) { + onDispose { + viewModel.onEvent(RegisterEvent.LifeCycle(LifecycleEvent.OnDispose)) + } + } if (loadingState.value) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) From 85a8725ae0b131bef5a75553a2e65c98a39a5d1f Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 20:20:43 +0200 Subject: [PATCH 64/73] Refactor AuthUseCase.kt --- .../domain/usecase/AuthUseCase.kt | 64 +------------------ 1 file changed, 3 insertions(+), 61 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/AuthUseCase.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/AuthUseCase.kt index a3ef9343..32dfb983 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/AuthUseCase.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/AuthUseCase.kt @@ -2,82 +2,24 @@ package de.hsfl.budgetBinder.domain.usecase import de.hsfl.budgetBinder.common.AuthToken import de.hsfl.budgetBinder.common.DataResponse -import de.hsfl.budgetBinder.common.ErrorModel import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.domain.repository.AuthRepository -import io.ktor.http.* -import io.ktor.utils.io.errors.* -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* class RegisterUseCase(private val repository: AuthRepository) { operator fun invoke(user: User.In): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.register(user).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Throwable) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.register(user) } } } class LoginUseCase(private val repository: AuthRepository) { operator fun invoke(email: String, password: String): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.authorize(email, password).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Throwable) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.authorize(email, password) } } } class LogoutUseCase(private val repository: AuthRepository) { operator fun invoke(onAllDevices: Boolean): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.logout(onAllDevices).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Throwable) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.logout(onAllDevices) } } } From 87ce06ff84b81cfe85c707b6cbc61ee63a03e437 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 20:21:12 +0200 Subject: [PATCH 65/73] Implement UseCase helper function --- .../budgetBinder/domain/usecase/FlowUtils.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/FlowUtils.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/FlowUtils.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/FlowUtils.kt new file mode 100644 index 00000000..c3d1efb7 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/FlowUtils.kt @@ -0,0 +1,29 @@ +package de.hsfl.budgetBinder.domain.usecase + +import de.hsfl.budgetBinder.common.APIResponse +import de.hsfl.budgetBinder.common.DataResponse +import de.hsfl.budgetBinder.common.ErrorModel +import io.ktor.http.* +import io.ktor.utils.io.errors.* +import kotlinx.coroutines.flow.FlowCollector + +suspend fun FlowCollector>.useCaseHelper(callback: suspend () -> APIResponse) { + try { + emit(DataResponse.Loading()) + callback().let { response -> + response.data?.let { + emit(DataResponse.Success(it)) + } ?: response.error!!.let { error -> + when (error.code) { + HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) + else -> emit(DataResponse.Error(error)) + } + } + } + } catch (e: IOException) { + emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) + } catch (e: Exception) { + e.printStackTrace() + emit(DataResponse.Error(ErrorModel("Something went wrong"))) + } +} From 3fa67d56cc5eb750979794cbd9c94096978a9b77 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 20:22:39 +0200 Subject: [PATCH 66/73] Refactor CategoriesUseCase.kt --- .../domain/usecase/CategoriesUseCase.kt | 160 ++---------------- 1 file changed, 15 insertions(+), 145 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt index 9b67d13d..42215897 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/CategoriesUseCase.kt @@ -1,181 +1,51 @@ package de.hsfl.budgetBinder.domain.usecase -import de.hsfl.budgetBinder.common.Category -import de.hsfl.budgetBinder.common.DataResponse -import de.hsfl.budgetBinder.common.Entry -import de.hsfl.budgetBinder.common.ErrorModel +import de.hsfl.budgetBinder.common.* import de.hsfl.budgetBinder.domain.repository.CategoryRepository -import io.ktor.http.* -import io.ktor.utils.io.errors.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow class GetAllCategoriesUseCase(private val repository: CategoryRepository) { - fun categories(): Flow>> = flow { - try { - emit(DataResponse.Loading()) - repository.getAllCategories().let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } - } - - fun categories(period: String): Flow>> = flow { - try { - emit(DataResponse.Loading()) - repository.getAllCategories(period).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) + operator fun invoke(period: String? = null): Flow>> = flow { + useCaseHelper { + period?.let { + repository.getAllCategories(period = it) + } ?: repository.getAllCategories() } } } class CreateCategoryUseCase(private val repository: CategoryRepository) { operator fun invoke(category: Category.In): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.createNewCategory(category).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.createNewCategory(category) } } } class GetCategoryByIdUseCase(private val repository: CategoryRepository) { operator fun invoke(id: Int): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.getCategoryById(id).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.getCategoryById(id) } } } class ChangeCategoryByIdUseCase(private val repository: CategoryRepository) { operator fun invoke(category: Category.Patch, id: Int): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.changeCategoryById(category, id).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.changeCategoryById(category, id) } } } + class DeleteCategoryByIdUseCase(private val repository: CategoryRepository) { operator fun invoke(id: Int): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.deleteCategoryById(id).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.deleteCategoryById(id) } } } class GetAllEntriesByCategoryUseCase(private val repository: CategoryRepository) { operator fun invoke(id: Int?, period: String? = null): Flow>> = flow { - try { - emit(DataResponse.Loading()) - period?.let { period -> - repository.getEntriesFromCategory(id, period).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } ?: repository.getEntriesFromCategory(id).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) + useCaseHelper { + period?.let { + repository.getEntriesFromCategory(id, period) + } ?: repository.getEntriesFromCategory(id) } } } From 22b8411d2e16b7d7fd6c70eac2e1f36d6cf78c23 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 20:26:05 +0200 Subject: [PATCH 67/73] Refactor EntriesUseCase.kt --- .../domain/usecase/EntriesUseCase.kt | 123 ++---------------- 1 file changed, 9 insertions(+), 114 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/EntriesUseCase.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/EntriesUseCase.kt index 3e5f85e4..19e9dbf3 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/EntriesUseCase.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/EntriesUseCase.kt @@ -2,145 +2,40 @@ package de.hsfl.budgetBinder.domain.usecase import de.hsfl.budgetBinder.common.DataResponse import de.hsfl.budgetBinder.common.Entry -import de.hsfl.budgetBinder.common.ErrorModel import de.hsfl.budgetBinder.domain.repository.EntryRepository -import io.ktor.http.* -import io.ktor.utils.io.errors.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow class GetAllEntriesUseCase(private val repository: EntryRepository) { - fun entries(): Flow>> = flow { - try { - emit(DataResponse.Loading()) - repository.getAllEntries().let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } - } - - fun entries(period: String): Flow>> = flow { - try { - emit(DataResponse.Loading()) - repository.getAllEntries(period).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) + operator fun invoke(period: String? = null): Flow>> = flow { + useCaseHelper { + period?.let { + repository.getAllEntries(it) + } ?: repository.getAllEntries() } } } class CreateNewEntryUseCase(private val repository: EntryRepository) { operator fun invoke(entry: Entry.In): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.createNewEntry(entry).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.createNewEntry(entry) } } } class GetEntryByIdUseCase(private val repository: EntryRepository) { operator fun invoke(id: Int): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.getEntryById(id).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.getEntryById(id) } } } class ChangeEntryByIdUseCase(private val repository: EntryRepository) { operator fun invoke(entry: Entry.Patch, id: Int): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.changeEntryById(entry, id).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.changeEntryById(entry, id) } } } class DeleteEntryByIdUseCase(private val repository: EntryRepository) { operator fun invoke(id: Int): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.deleteEntryById(id).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.deleteEntryById(id) } } } From f9f350b51f816694e9c3548717e24fa6209260bb Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 20:27:48 +0200 Subject: [PATCH 68/73] Refactor UserUseCase.kt --- .../domain/usecase/UserUseCase.kt | 60 +------------------ 1 file changed, 3 insertions(+), 57 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/UserUseCase.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/UserUseCase.kt index a508f591..1c01a9d5 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/UserUseCase.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/domain/usecase/UserUseCase.kt @@ -1,79 +1,25 @@ package de.hsfl.budgetBinder.domain.usecase import de.hsfl.budgetBinder.common.DataResponse -import de.hsfl.budgetBinder.common.ErrorModel import de.hsfl.budgetBinder.common.User import de.hsfl.budgetBinder.domain.repository.UserRepository -import io.ktor.http.* -import io.ktor.utils.io.errors.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow class GetMyUserUseCase(private val repository: UserRepository) { operator fun invoke(): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.getMyUser().let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.getMyUser() } } } class ChangeMyUserUseCase(private val repository: UserRepository) { operator fun invoke(user: User.Patch): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.changeMyUser(user).let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.changeMyUser(user) } } } class DeleteMyUserUseCase(private val repository: UserRepository) { operator fun invoke(): Flow> = flow { - try { - emit(DataResponse.Loading()) - repository.deleteMyUser().let { response -> - response.data?.let { - emit(DataResponse.Success(it)) - } ?: response.error!!.let { error -> - when (error.code) { - HttpStatusCode.Unauthorized.value -> emit(DataResponse.Unauthorized(error)) - else -> emit(DataResponse.Error(error)) - } - } - } - } catch (e: IOException) { - emit(DataResponse.Error(ErrorModel("Couldn't reach the server"))) - } catch (e: Exception) { - e.printStackTrace() - emit(DataResponse.Error(ErrorModel("Something went wrong"))) - } + useCaseHelper { repository.deleteMyUser() } } } From 49093ab9e6b71fde025769d19000093c248d42f1 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Mon, 20 Jun 2022 20:32:15 +0200 Subject: [PATCH 69/73] Fix refactor errors --- .../presentation/viewmodel/EntryViewModel.kt | 2 +- .../viewmodel/category/CategoryViewModel.kt | 2 +- .../viewmodel/category/_CategoryViewModel.kt | 2 +- .../viewmodel/dashboard/DashboardViewModel.kt | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/EntryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/EntryViewModel.kt index 3bd198ca..23ab5a8c 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/EntryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/EntryViewModel.kt @@ -17,7 +17,7 @@ class EntryViewModel( val state: StateFlow = _state fun getAllEntries() { - entriesUseCases.getAllEntriesUseCase.entries().onEach { + entriesUseCases.getAllEntriesUseCase().onEach { when (it) { is DataResponse.Success -> _state.value = UiState.Success(it.data) is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt index f5770024..d9b778af 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/CategoryViewModel.kt @@ -23,7 +23,7 @@ open class CategoryViewModel( val eventFlow = UiEventSharedFlow.mutableEventFlow protected fun getAll(onSuccess: (List) -> Unit) = scope.launch { - categoriesUseCases.getAllCategoriesUseCase.categories() + categoriesUseCases.getAllCategoriesUseCase() .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/_CategoryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/_CategoryViewModel.kt index 1c0cc181..c9efa754 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/_CategoryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/_CategoryViewModel.kt @@ -21,7 +21,7 @@ class _CategoryViewModel( @Deprecated(message = "Use StateFlow") fun getAllCategories() { - categoryUseCases.getAllCategoriesUseCase.categories().onEach { + categoryUseCases.getAllCategoriesUseCase().onEach { when (it) { is DataResponse.Success -> _state.value = UiState.Success(it.data) is DataResponse.Error -> _state.value = UiState.Error(it.error!!.message) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt index ca8d6969..50ae143c 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/dashboard/DashboardViewModel.kt @@ -99,13 +99,13 @@ class DashboardViewModel( } private fun getAllEntries(onSuccess: (List) -> Unit) = scope.launch { - dashboardUseCases.getAllEntriesUseCase.entries() + dashboardUseCases.getAllEntriesUseCase() .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } private fun getAllCategories(onSuccess: (List) -> Unit) = scope.launch { - dashboardUseCases.getAllCategoriesUseCase.categories() + dashboardUseCases.getAllCategoriesUseCase() .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } @@ -118,7 +118,7 @@ class DashboardViewModel( private fun getAllEntriesFromMonth(period: String, onSuccess: (List) -> Unit) = scope.launch { - dashboardUseCases.getAllEntriesUseCase.entries(period) + dashboardUseCases.getAllEntriesUseCase(period) .collect { it.handleDataResponse(scope = scope, routerFlow = routerFlow, onSuccess = onSuccess) } } @@ -334,7 +334,7 @@ class DashboardViewModel( @Deprecated(message = "Use new StateFlow") private fun _getAllCategories() { - dashboardUseCases.getAllCategoriesUseCase.categories().onEach { + dashboardUseCases.getAllCategoriesUseCase().onEach { when (it) { is DataResponse.Success -> _categoriesState.value = UiState.Success(it.data) is DataResponse.Error -> _categoriesState.value = UiState.Error(it.error!!.message) @@ -346,7 +346,7 @@ class DashboardViewModel( @Deprecated(message = "Use new StateFlow") private fun _getAllEntries() { - dashboardUseCases.getAllEntriesUseCase.entries().onEach { + dashboardUseCases.getAllEntriesUseCase().onEach { when (it) { is DataResponse.Success -> _entriesState.value = UiState.Success(it.data) is DataResponse.Error -> _entriesState.value = UiState.Error(it.error!!.message) From d56d6874b4433295831cc9ef5f48e85d271b74ea Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Tue, 21 Jun 2022 00:50:26 +0200 Subject: [PATCH 70/73] Calculate Budget on CategoryDetail --- .../detail/CategoryDetailViewModel.kt | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt index 757dbd26..2e8459c4 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/detail/CategoryDetailViewModel.kt @@ -26,6 +26,9 @@ open class CategoryDetailViewModel( protected val _categoryState = MutableStateFlow(initCategory) val categoryState: StateFlow = _categoryState + private val _budgetOnAllEntries = MutableStateFlow(0f) + val budgetOnAllEntries: StateFlow = _budgetOnAllEntries + private val _entryList = MutableStateFlow>(emptyList()) val entryList: StateFlow> = _entryList @@ -82,7 +85,22 @@ open class CategoryDetailViewModel( } private fun setEntryList() { - super.entries(id = currentCategoryId, onSuccess = { _entryList.value = it }) + super.entries(id = currentCategoryId, onSuccess = { + _entryList.value = it + calculateBudget(it) + }) + } + + private fun calculateBudget(entryList: List) { + var spendMoney = 0F + entryList.onEach { + if (it.amount > 0) { + spendMoney -= it.amount + } else { + spendMoney += (it.amount * -1) + } + } + _budgetOnAllEntries.value = spendMoney } /** From 481fc0e644a32c0ae9319981bd651aa60d6acdb9 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Wed, 22 Jun 2022 17:16:24 +0200 Subject: [PATCH 71/73] Implement CategoryCreateViewModel.kt --- .../category/create/CategoryCreateEvent.kt | 14 +++++ .../create/CategoryCreateViewModel.kt | 63 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/create/CategoryCreateEvent.kt create mode 100644 budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/create/CategoryCreateViewModel.kt diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/create/CategoryCreateEvent.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/create/CategoryCreateEvent.kt new file mode 100644 index 00000000..694681e1 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/create/CategoryCreateEvent.kt @@ -0,0 +1,14 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category.create + +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.presentation.event.LifecycleEvent + +sealed class CategoryCreateEvent { + data class EnteredCategoryName(val value: String): CategoryCreateEvent() + data class EnteredCategoryColor(val value: String): CategoryCreateEvent() + data class EnteredCategoryImage(val value: Category.Image): CategoryCreateEvent() + data class EnteredCategoryBudget(val value: Float): CategoryCreateEvent() + data class LifeCycle(val value: LifecycleEvent): CategoryCreateEvent() + object OnSave: CategoryCreateEvent() + object OnCancel: CategoryCreateEvent() +} diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/create/CategoryCreateViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/create/CategoryCreateViewModel.kt new file mode 100644 index 00000000..dbc50f96 --- /dev/null +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/create/CategoryCreateViewModel.kt @@ -0,0 +1,63 @@ +package de.hsfl.budgetBinder.presentation.viewmodel.category.create + +import de.hsfl.budgetBinder.common.Category +import de.hsfl.budgetBinder.domain.usecase.CategoriesUseCases +import de.hsfl.budgetBinder.presentation.Screen +import de.hsfl.budgetBinder.presentation.event.handleLifeCycle +import de.hsfl.budgetBinder.presentation.flow.RouterFlow +import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditEvent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class CategoryCreateViewModel( + _categoriesUseCases: CategoriesUseCases, _scope: CoroutineScope, private val routerFlow: RouterFlow +) : CategoryDetailViewModel( + _categoriesUseCases = _categoriesUseCases, _scope = _scope, routerFlow = routerFlow +) { + private val _categoryNameState = MutableStateFlow("") + val categoryNameState: StateFlow = _categoryNameState + + private val _categoryColorState = MutableStateFlow("") + val categoryColorState: StateFlow = _categoryColorState + + private val _categoryImageState = MutableStateFlow(Category.Image.DEFAULT) + val categoryImageState: StateFlow = _categoryImageState + + private val _categoryBudgetState = MutableStateFlow(0f) + val categoryBudgetState: StateFlow = _categoryBudgetState + + fun onEvent(event: CategoryCreateEvent) { + when (event) { + is CategoryCreateEvent.EnteredCategoryName -> _categoryNameState.value = event.value + is CategoryCreateEvent.EnteredCategoryColor -> _categoryColorState.value = event.value + is CategoryCreateEvent.EnteredCategoryImage -> _categoryImageState.value = event.value + is CategoryCreateEvent.EnteredCategoryBudget -> _categoryBudgetState.value = event.value + is CategoryCreateEvent.OnSave -> super.create(category = createCategoryInFormState(), + // TODO: Check from EntryCreate or from Summary + onSuccess = { routerFlow.navigateTo(Screen.Category.Detail(it.id)) }) + // TODO: Check from EntryCreate or from Summary + is CategoryCreateEvent.OnCancel -> routerFlow.navigateTo(Screen.Category.Summary) + is CategoryCreateEvent.LifeCycle -> event.value.handleLifeCycle(onLaunch = { }, + onDispose = { resetStateFlows() }) + } + } + + private fun resetStateFlows() { + _categoryNameState.value = "" + _categoryColorState.value = "" + _categoryImageState.value = Category.Image.DEFAULT + _categoryBudgetState.value = 0f + } + + + private fun createCategoryInFormState(): Category.In { + return Category.In( + name = categoryNameState.value, + color = categoryColorState.value, + image = categoryImageState.value, + budget = categoryBudgetState.value + ) + } +} From 80a7560894e3c514fcac503e3a6e7e7746b9d23b Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Wed, 22 Jun 2022 17:46:23 +0200 Subject: [PATCH 72/73] Add CategoryCreateViewModel in DI --- .../src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt index bc63832e..8c79f219 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/di/DI.kt @@ -21,6 +21,7 @@ import de.hsfl.budgetBinder.presentation.viewmodel.auth.register.RegisterViewMod import de.hsfl.budgetBinder.presentation.viewmodel.* import de.hsfl.budgetBinder.presentation.viewmodel.category.summary.CategorySummaryViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category._CategoryViewModel +import de.hsfl.budgetBinder.presentation.viewmodel.category.create.CategoryCreateViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category.detail.CategoryDetailViewModel import de.hsfl.budgetBinder.presentation.viewmodel.category.edit.CategoryEditViewModel import de.hsfl.budgetBinder.presentation.viewmodel.dashboard.DashboardViewModel @@ -100,6 +101,7 @@ fun kodein(ktorEngine: HttpClientEngine) = DI { bindSingleton { CategorySummaryViewModel(instance(), instance(), instance()) } bindSingleton { CategoryDetailViewModel(instance(), instance(), instance()) } bindSingleton { CategoryEditViewModel(instance(), instance(), instance()) } + bindSingleton { CategoryCreateViewModel(instance(), instance(), instance()) } bindSingleton { EntryViewModel(instance(), instance()) } bindSingleton { DashboardViewModel(instance(), instance(), instance(), instance()) } bindSingleton { NavDrawerViewModel(instance(), instance(), instance()) } From 67b7512d38a51384c755ba34637ae89b48716f56 Mon Sep 17 00:00:00 2001 From: Cedrik Hoffmann Date: Wed, 22 Jun 2022 17:47:55 +0200 Subject: [PATCH 73/73] Change navigateTo 'Screen.Category.Create' --- .../viewmodel/category/summary/CategorySummaryViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt index 3a9312ab..f4f45605 100644 --- a/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt +++ b/budget-binder-multiplatform-app/src/commonMain/kotlin/de/hsfl/budgetBinder/presentation/viewmodel/category/summary/CategorySummaryViewModel.kt @@ -25,7 +25,7 @@ class CategorySummaryViewModel( fun onEvent(event: CategorySummaryEvent) { when (event) { is CategorySummaryEvent.OnCategory -> routerFlow.navigateTo(Screen.Category.Detail(event.id)) - is CategorySummaryEvent.OnCategoryCreate -> routerFlow.navigateTo(Screen.Category.Summary) + is CategorySummaryEvent.OnCategoryCreate -> routerFlow.navigateTo(Screen.Category.Create) is CategorySummaryEvent.OnRefresh -> getAllCategories() is CategorySummaryEvent.LifeCycle -> event.value.handleLifeCycle( onLaunch = { getAllCategories() },