Skip to content

Commit

Permalink
Merge pull request #122 from choffmann/category-jvm-view
Browse files Browse the repository at this point in the history
Category Views in JVM
  • Loading branch information
PasclDev authored Jun 23, 2022
2 parents dbce187 + fa295a2 commit d0070c1
Show file tree
Hide file tree
Showing 28 changed files with 976 additions and 220 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ package de.hsfl.budgetBinder.common

actual object Constants {
//actual val BASE_URL: String = "http://10.0.2.2:8080"
actual val BASE_URL: String = "https://bb-server.fpcloud.de"
//actual val BASE_URL = "https://bb-server.fpcloud.de"
actual val BASE_URL: String ="http://192.168.2.104:8080"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.material.AlertDialog
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

@Composable
actual fun DeleteUserDialog(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package de.hsfl.budgetBinder.compose.dialog

import android.util.Log
import androidx.compose.animation.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.AlertDialog
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp
import de.hsfl.budgetBinder.common.Category
import de.hsfl.budgetBinder.screens.category.CategoryColorBubble
import de.hsfl.budgetBinder.screens.category.CategoryColors
import de.hsfl.budgetBinder.screens.category.CategoryListItem
import de.hsfl.budgetBinder.screens.category.toColor
import kotlinx.coroutines.launch

@OptIn(ExperimentalFoundationApi::class)
@Composable
actual fun PickColorDialog(
openDialog: Boolean,
categoryName: String,
categoryColor: Color,
categoryImage: Category.Image,
categoryBudget: Float,
onConfirm: (String) -> Unit,
onDismiss: () -> Unit
) {
val scope = rememberCoroutineScope()
val animatableColor = remember { Animatable(categoryColor) }
val rememberColorString = remember { mutableStateOf("") }
val colorList = remember { CategoryColors.colors }
val size = 50.dp
val padding = 8.dp
if (openDialog) {
Log.d("DialogDebug", "size - padding: ${size - padding}")
AlertDialog(onDismissRequest = onDismiss,
title = { Text(text = "Choose a Color for the Category") },
confirmButton = {
TextButton(onClick = {
onConfirm(rememberColorString.value)
onDismiss()
}) {
Text(text = "Confirm")
}
},
text = {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
CategoryListItem(
name = categoryName,
budget = categoryBudget.toString(),
icon = categoryImage,
color = animatableColor.value
)
LazyVerticalGrid(
cells = GridCells.Adaptive(size + padding),
contentPadding = PaddingValues(padding),
verticalArrangement = Arrangement.spacedBy(padding),
horizontalArrangement = Arrangement.spacedBy(padding)
) {
colorList.forEach { (color, colorString) ->
val colorInt = color.toArgb()
item {
CategoryColorBubble(size = size,
hasBorder = colorInt == animatableColor.value.toArgb(),
backgroundColor = color,
onClick = {
scope.launch {
animatableColor.animateTo(
targetValue = color, animationSpec = tween(durationMillis = 500)
)
}
rememberColorString.value = colorString
})
}
}
}
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package de.hsfl.budgetBinder.compose.dialog

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
import androidx.compose.material.AlertDialog
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import de.hsfl.budgetBinder.common.Category
import de.hsfl.budgetBinder.screens.category.CategoryIconBubble
import de.hsfl.budgetBinder.screens.category.CategoryListItem

@OptIn(ExperimentalFoundationApi::class)
@Composable
actual fun PickIconDialog(
openDialog: Boolean,
categoryName: String,
categoryBudget: Float,
onConfirm: (Category.Image) -> Unit,
onDismiss: () -> Unit,
selectColor: Color
) {
val rememberIcon = remember { allAvailableCategoryIcons() }
val selectedIcon = remember { mutableStateOf(Category.Image.DEFAULT) }
val size = 50.dp
val padding = 8.dp
if (openDialog) {
AlertDialog(onDismissRequest = onDismiss,
title = { Text(text = "Choose a Icon for the Category") },
confirmButton = {
TextButton(onClick = {
onConfirm(selectedIcon.value)
onDismiss()
}) {
Text(text = "Confirm")
}
},
text = {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
CategoryListItem(
name = categoryName,
budget = categoryBudget.toString(),
icon = selectedIcon.value,
color = selectColor
)
LazyVerticalGrid(
cells = GridCells.Adaptive(size + padding),
contentPadding = PaddingValues(padding),
horizontalArrangement = Arrangement.spacedBy(padding),
verticalArrangement = Arrangement.spacedBy(padding)
) {
items(rememberIcon) { icon ->
CategoryIconBubble(
size = size,
hasBorder = selectedIcon.value == icon,
onClick = { selectedIcon.value = icon },
icon = icon
)
}
}
}
})
}
}

fun allAvailableCategoryIcons() = listOf(
Category.Image.DEFAULT,
Category.Image.CHECKMARK,
Category.Image.WRONG,
Category.Image.SHOPPINGCART,
Category.Image.SHOPPINGBASKET,
Category.Image.FOOD,
Category.Image.FASTFOOD,
Category.Image.RESTAURANT,
Category.Image.MONEY,
Category.Image.HOME,
Category.Image.FAMILY,
Category.Image.HEALTH,
Category.Image.MEDICATION,
Category.Image.KEYBOARD,
Category.Image.PRINTER,
Category.Image.INVEST,
Category.Image.SPORT,
Category.Image.CLOTH,
Category.Image.GIFT,
Category.Image.WEALTH,
Category.Image.FLOWER,
Category.Image.PET,
Category.Image.BILLS,
Category.Image.WATER,
Category.Image.FIRE,
Category.Image.STAR,
Category.Image.SAVINGS,
Category.Image.CAR,
Category.Image.BIKE,
Category.Image.TRAIN,
Category.Image.MOTORCYCLE,
Category.Image.MOPED,
Category.Image.ELECTRONICS,
Category.Image.BOOK,
Category.Image.FLIGHT,
Category.Image.WORK,
Category.Image.MOON,
Category.Image.LOCK,
Category.Image.PHONE,
Category.Image.STORE,
Category.Image.BAR,
Category.Image.FOREST,
Category.Image.HARDWARE,
Category.Image.PEST
)
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,18 @@ actual fun DeleteForeverIcon() {
actual fun SaveIcon() {
Icon(imageVector = Icons.Default.Save, contentDescription = null)
}

@Composable
actual fun ReplyIcon() {
Icon(imageVector = Icons.Default.Reply, contentDescription = null)
}

@Composable
actual fun ForwardIcon() {
Icon(imageVector = Icons.Default.Forward, contentDescription = null)
}

@Composable
actual fun EuroIcon() {
Icon(imageVector = Icons.Default.Euro, contentDescription = null)
}
Original file line number Diff line number Diff line change
@@ -1,52 +1,36 @@
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<T>(val data: T? = null, val error: ErrorModel? = null) {
class Success<T>(data: T) : DataResponse<T>(data)
class Error<T>(error: ErrorModel?, data: T? = null) : DataResponse<T>(data, error)
class Loading<T>(data: T? = null) : DataResponse<T>(data)
class Unauthorized<T>(error: ErrorModel? = null, data: T? = null) : DataResponse<T>(data, error)
}
sealed class DataResponse<T> {
class Success<T>(val data: T) : DataResponse<T>()
class Error<T>(val error: ErrorModel) : DataResponse<T>()
class Loading<T> : DataResponse<T>()
class Unauthorized<T>(val error: ErrorModel) : DataResponse<T>()

fun <T> DataResponse<T>.handleDataResponse(
scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()),
routerFlow: RouterFlow = RouterFlow(NavigateToScreenUseCase(), scope),
onSuccess: (T) -> Unit,
onError: ((ErrorModel) -> Unit)? = null,
onLoading: (() -> Unit)? = null,
onUnauthorized: (() -> Unit)? = null
) = scope.launch {
when (this@handleDataResponse) {
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)
suspend fun <out: T> handleDataResponse(
routerFlow: RouterFlow,
onSuccess: suspend (T) -> Unit,
onError: suspend (ErrorModel) -> Unit = {
UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowError(it.message))
},
onLoading: suspend () -> Unit = {
UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowLoading)
},
onUnauthorized: suspend (ErrorModel) -> Unit = {
routerFlow.navigateTo(Screen.Login)
UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowError(it.message))
}
is DataResponse.Success -> {
) = when (this) {
is Error -> onError(this.error)
is Loading -> onLoading()
is Success -> {
UiEventSharedFlow.mutableEventFlow.emit(UiEvent.HideSuccess)
onSuccess(this@handleDataResponse.data!!)
}
is DataResponse.Unauthorized -> {
onUnauthorized?.let {
onUnauthorized()
} ?: run {
routerFlow.navigateTo(Screen.Login)
UiEventSharedFlow.mutableEventFlow.emit(UiEvent.ShowError(this@handleDataResponse.error!!.message))
}
onSuccess(this.data)
}
is Unauthorized -> onUnauthorized(this.error)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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
Expand All @@ -28,17 +27,21 @@ open class AuthViewModel(

protected fun register(user: User.In) = scope.launch {
authUseCases.registerUseCase(user)
.collect { it.handleDataResponse(onSuccess = { login(email = user.email, password = user.password) }) }
.collect {
it.handleDataResponse<User>(
routerFlow = routerFlow,
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() }) }
.collect { it.handleDataResponse<Nothing>(routerFlow = routerFlow, onSuccess = { getMyUser() }) }
}

private fun getMyUser() = scope.launch {
authUseCases.getMyUserUseCase().collect {
it.handleDataResponse(onSuccess = { user ->
it.handleDataResponse<User>(routerFlow = routerFlow, onSuccess = { user ->
dataFlow.storeUserState(user)
routerFlow.navigateTo(Screen.Dashboard)
})
Expand Down
Loading

0 comments on commit d0070c1

Please sign in to comment.