Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 22 additions & 74 deletions app/src/main/java/com/washingtondcsquad/tudee/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
Expand All @@ -19,12 +18,10 @@ import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
Expand All @@ -36,14 +33,14 @@ import androidx.navigation.compose.rememberNavController
import com.washingtondcsquad.tudee.data.localSource.TudeeDataBase
import com.washingtondcsquad.tudee.domain.entity.Category
import com.washingtondcsquad.tudee.domain.services.AppPreferencesService
import com.washingtondcsquad.tudee.presentation.features.categories.CategoriesScreen
import com.washingtondcsquad.tudee.presentation.components.SnackBarCard
import com.washingtondcsquad.tudee.presentation.components.bottom_nav_bar.TudeeNavigationBar
import com.washingtondcsquad.tudee.presentation.components.bottom_nav_bar.bottomNavBarRoutes
import com.washingtondcsquad.tudee.presentation.components.bottom_nav_bar.navBarItemsList
import com.washingtondcsquad.tudee.presentation.components.snack_bar.ObserveAsEvent
import com.washingtondcsquad.tudee.presentation.components.snack_bar.SnackbarController
import com.washingtondcsquad.tudee.presentation.design.AppTheme
import com.washingtondcsquad.tudee.presentation.features.categories.CategoriesScreen
import com.washingtondcsquad.tudee.presentation.features.home.HomeScreen
import com.washingtondcsquad.tudee.presentation.features.onBoarding.OnBoardingScreen
import com.washingtondcsquad.tudee.presentation.features.tasks_screen.TasksScreen
Expand All @@ -57,42 +54,29 @@ val LocalInnerPaddingProvider =

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
class MainActivity : ComponentActivity() {
private val appPreferencesService: AppPreferencesService by inject()

private val mainViewModel: MainViewModel by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

enableEdgeToEdge()

setContent {
val isDarkMode by appPreferencesService.isDarkModeEnabled()
.collectAsState(initial = false)
val isOnboardingShownFlow = appPreferencesService.hasOnboardingBeenShown()
val isOnboardingShownState by isOnboardingShownFlow.collectAsState(initial = null)
val mainState by mainViewModel.state.collectAsState()

updateAppLocale(mainState)
AppTheme(
useDarkTheme = isDarkMode
useDarkTheme = mainState.isDarkTheme
) {
when (val isOnboardingShown = isOnboardingShownState) {
null -> {
Box(
modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}

else -> {
val startDestination = if (isOnboardingShown) {
"home"
} else {
Log.d("sdasdsad", "onCreate: ")
/// createPreDefineCategories()
"onboarding"
}
val navController = rememberNavController()
val navBackStackEntry = navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry.value?.destination?.route
val isOnboardingShown = mainState.hasOnBoardingShown
val startDestination = if (isOnboardingShown) {
"home"
} else {
Log.d("sdasdsad", "onCreate: ")
"onboarding"
}
val navController = rememberNavController()
val navBackStackEntry = navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry.value?.destination?.route

Scaffold(
bottomBar = {
Expand Down Expand Up @@ -137,7 +121,7 @@ class MainActivity : ComponentActivity() {
}
}
}
SnackbarHandler()
SnackBarHandler()
}
}
//
Expand Down Expand Up @@ -170,49 +154,13 @@ class MainActivity : ComponentActivity() {
// }
// }
// }
}


@Composable
fun SnackbarHandler() {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
ObserveAsEvent(
flow = SnackbarController.event,
onEvent = { event ->
coroutineScope.launch {
snackbarHostState.currentSnackbarData?.dismiss()
snackbarHostState.showSnackbar(
message = event.message,
duration = SnackbarDuration.Short
)
}
}
)

SnackbarHost(
hostState = snackbarHostState,
snackbar = { snackbarData ->
val (iconRes, iconTint) = when {
snackbarData.visuals.message.contains("success", ignoreCase = true) ->
Pair(R.drawable.checkmark, AppTheme.colors.greenAccent)

snackbarData.visuals.message.contains("error", ignoreCase = true) ->
Pair(R.drawable.checkmark, AppTheme.colors.error)

else ->
Pair(R.drawable.information_diamond, AppTheme.colors.error)
}
private fun updateAppLocale(state: MainActivityUiState) {
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags(state.currentAppLocale.toLanguageTag())
)
}
}

SnackBarCard(
message = snackbarData.visuals.message,
icon = painterResource(id = iconRes),
iconTint = iconTint,
iconBackgroundColor = AppTheme.colors.surface,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 48.dp)
)

}
)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.washingtondcsquad.tudee

import java.util.Locale

data class MainActivityUiState(
val isDarkTheme: Boolean = false,
val hasOnBoardingShown: Boolean = false,
val currentAppLocale: Locale = Locale.ENGLISH
)
72 changes: 72 additions & 0 deletions app/src/main/java/com/washingtondcsquad/tudee/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.washingtondcsquad.tudee

import androidx.lifecycle.viewModelScope
import com.washingtondcsquad.tudee.domain.services.AppPreferencesService
import com.washingtondcsquad.tudee.presentation.base.BaseViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.util.Locale

class MainViewModel(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i suggest to rename this to MainActivityViewModel

Comment thread
kareem-01 marked this conversation as resolved.
private val appPreferencesService: AppPreferencesService
) : BaseViewModel<MainActivityUiState>(MainActivityUiState()) {

init {
getIsDarkTheme()
getOnBoardingState()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as the above method

getAppLocale()
}

private fun getIsDarkTheme() {
Comment thread
kareem-01 marked this conversation as resolved.
tryToExecute(
request = {
appPreferencesService.isDarkModeEnabled()
Comment thread
kareem-01 marked this conversation as resolved.
},
onSuccess = ::onDarkThemeEnabled,
onError = {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it always a good practice to handle error state, help in debugging and improve user experience

)
}

private fun onDarkThemeEnabled(isDarkThemeEnabled: Flow<Boolean>) = viewModelScope.launch {
isDarkThemeEnabled.collectLatest {
updateState {
copy(isDarkTheme = it)
}
}
}

private fun getOnBoardingState() {
tryToExecute(
request = {
appPreferencesService.hasOnboardingBeenShown()
},
onSuccess = ::onOnBoardingState,
onError = {}
)
}

private fun onOnBoardingState(hasOnboardingBeenShown: Flow<Boolean>) = viewModelScope.launch {
hasOnboardingBeenShown.collectLatest {
updateState {
copy(hasOnBoardingShown = it)
}
}
}

private fun getAppLocale() {
tryToCollect(
request = {
appPreferencesService.getCurrentLocale()
},
onChange = ::onAppLanguageChanged,
onError = {},
)
}

private fun onAppLanguageChanged(newLocale: Locale) {
updateState {
copy(currentAppLocale = newLocale)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
package com.washingtondcsquad.tudee.data.services

import androidx.appcompat.app.AppCompatDelegate
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import com.washingtondcsquad.tudee.domain.services.AppPreferencesService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import java.util.Locale

class AppPreferencesServiceImpl(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where isDarkModelEnabled function ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it exists in this file down below

private val dataStore: DataStore<Preferences>
) : AppPreferencesService {

private val _currentLocale = MutableStateFlow(getCurrentAppLocale())
val currentLocale: Flow<Locale> = _currentLocale.asStateFlow()

override suspend fun getCurrentLocale(): Flow<Locale> = currentLocale.distinctUntilChanged()


private fun getCurrentAppLocale(): Locale {
val androidLocale = AppCompatDelegate.getApplicationLocales()[0]
?: Locale.getDefault()
return Locale.Builder()
.setLocale(androidLocale)
.setLanguage(androidLocale.language)
.build()
}

companion object PreferencesKeys {
val HAS_ONBOARDING_BEEN_SHOWN = booleanPreferencesKey("has_onboarding_been_shown")
val DARK_MODE_ENABLED = booleanPreferencesKey("dark_mode_enabled")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
package com.washingtondcsquad.tudee.di

import com.washingtondcsquad.tudee.MainViewModel
import com.washingtondcsquad.tudee.presentation.features.add_task.AddTaskViewModel
import com.washingtondcsquad.tudee.presentation.features.categories.CategoriesViewModel
import com.washingtondcsquad.tudee.presentation.features.edit_task.EditTaskViewModel
import com.washingtondcsquad.tudee.presentation.features.home.HomeViewModel
import com.washingtondcsquad.tudee.presentation.features.task_details.BottomSheetTaskViewModel
import com.washingtondcsquad.tudee.presentation.features.onBoarding.OnboardingViewModel
import com.washingtondcsquad.tudee.presentation.features.edit_task.EditTaskViewModel
import com.washingtondcsquad.tudee.presentation.features.task_details.BottomSheetTaskViewModel
import com.washingtondcsquad.tudee.presentation.features.tasks_screen.TasksViewModel
import org.koin.core.module.dsl.viewModel
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module
import com.washingtondcsquad.tudee.presentation.features.categories.CategoriesViewModel


val viewModelModule = module {
viewModelOf(::CategoriesViewModel)
viewModelOf(::OnboardingViewModel)
viewModelOf(::HomeViewModel)
viewModel{ TasksViewModel(
get(),
get()
) }
viewModel {
TasksViewModel(
get(),
get()
)
}
viewModelOf(::MainViewModel)
viewModel { BottomSheetTaskViewModel(get()) }
viewModelOf(::AddTaskViewModel)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.washingtondcsquad.tudee.domain.services

import kotlinx.coroutines.flow.Flow
import java.util.Locale

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try to search for kotlin version . it's not recommended to use java libraries in domain layer.


interface AppPreferencesService {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment on multiple lines

suspend fun getCurrentLocale(): Flow<Locale>
fun hasOnboardingBeenShown(): Flow<Boolean>
suspend fun setOnboardingShown()
fun isDarkModeEnabled(): Flow<Boolean>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.washingtondcsquad.tudee.presentation.utils

import androidx.compose.foundation.layout.padding
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.washingtondcsquad.tudee.R
import com.washingtondcsquad.tudee.presentation.components.SnackBarCard
import com.washingtondcsquad.tudee.presentation.components.snack_bar.ObserveAsEvent
import com.washingtondcsquad.tudee.presentation.components.snack_bar.SnackbarController
import com.washingtondcsquad.tudee.presentation.design.AppTheme
import kotlinx.coroutines.launch

@Composable
fun SnackBarHandler() {
val snackBarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
ObserveAsEvent(
flow = SnackbarController.event,
onEvent = { event ->
coroutineScope.launch {
snackBarHostState.currentSnackbarData?.dismiss()
snackBarHostState.showSnackbar(
message = event.message,
duration = SnackbarDuration.Short
)
}
}
)

SnackbarHost(
hostState = snackBarHostState,
snackbar = { snackbarData ->
val (iconRes, iconTint) = when {
snackbarData.visuals.message.contains("success", ignoreCase = true) ->
Pair(R.drawable.checkmark, AppTheme.colors.greenAccent)

snackbarData.visuals.message.contains("error", ignoreCase = true) ->
Pair(R.drawable.checkmark, AppTheme.colors.error)

else ->
Pair(R.drawable.information_diamond, AppTheme.colors.error)
}

SnackBarCard(
message = snackbarData.visuals.message,
icon = painterResource(id = iconRes),
iconTint = iconTint,
iconBackgroundColor = AppTheme.colors.surface,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 48.dp)
)

}
)
}