diff --git a/app/src/main/java/com/example/tudee/data/dao/AppEntryDao.kt b/app/src/main/java/com/example/tudee/data/dao/AppEntryDao.kt deleted file mode 100644 index 8f437500..00000000 --- a/app/src/main/java/com/example/tudee/data/dao/AppEntryDao.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.example.tudee.data.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.Query -import com.example.tudee.data.model.AppEntryEntity - -@Dao -interface AppEntryDao { - @Query("SELECT * FROM app_entry LIMIT 1") - suspend fun getAppEntry(): AppEntryEntity? - - @Insert - suspend fun insertAppEntry(appEntry: AppEntryEntity) -} \ No newline at end of file diff --git a/app/src/main/java/com/example/tudee/data/database/AppDataBase.kt b/app/src/main/java/com/example/tudee/data/database/AppDataBase.kt index bd775953..1ed48bc9 100644 --- a/app/src/main/java/com/example/tudee/data/database/AppDataBase.kt +++ b/app/src/main/java/com/example/tudee/data/database/AppDataBase.kt @@ -2,20 +2,17 @@ package com.example.tudee.data.database import androidx.room.Database import androidx.room.RoomDatabase -import com.example.tudee.data.dao.AppEntryDao import com.example.tudee.data.dao.TaskCategoryDao import com.example.tudee.data.dao.TaskDao -import com.example.tudee.data.model.AppEntryEntity import com.example.tudee.data.model.TaskCategoryEntity import com.example.tudee.data.model.TaskEntity @Database( - entities = [TaskEntity::class, TaskCategoryEntity::class, AppEntryEntity::class], + entities = [TaskEntity::class, TaskCategoryEntity::class], version = 1, exportSchema = false ) abstract class AppDatabase : RoomDatabase() { abstract fun taskDao(): TaskDao abstract fun taskCategoryDao(): TaskCategoryDao - abstract fun appEntryDao(): AppEntryDao } \ No newline at end of file diff --git a/app/src/main/java/com/example/tudee/data/model/AppEntryEntity.kt b/app/src/main/java/com/example/tudee/data/model/AppEntryEntity.kt deleted file mode 100644 index 3a7fd028..00000000 --- a/app/src/main/java/com/example/tudee/data/model/AppEntryEntity.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.tudee.data.model - -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "app_entry") -data class AppEntryEntity( - @PrimaryKey val id: Int = 1, - val hasSeenOnboarding: Boolean = true -) \ No newline at end of file diff --git a/app/src/main/java/com/example/tudee/data/preferences/PreferenceManager.kt b/app/src/main/java/com/example/tudee/data/preferences/PreferenceManager.kt new file mode 100644 index 00000000..1a63bf67 --- /dev/null +++ b/app/src/main/java/com/example/tudee/data/preferences/PreferenceManager.kt @@ -0,0 +1,30 @@ +package com.example.tudee.data.preferences + + +import android.content.Context +import androidx.core.content.edit + +class PreferencesManager(context: Context) { + private val prefs = context.getSharedPreferences("tudee_preferences", Context.MODE_PRIVATE) + + companion object { + private const val KEY_DARK_MODE = "dark_mode" + private const val KEY_ONBOARDING_COMPLETED = "onboarding_completed" + } + + fun isDarkMode(): Boolean { + return prefs.getBoolean(KEY_DARK_MODE, false) + } + + fun setDarkMode(isDark: Boolean) { + prefs.edit { putBoolean(KEY_DARK_MODE, isDark) } + } + + fun isOnboardingCompleted(): Boolean { + return prefs.getBoolean(KEY_ONBOARDING_COMPLETED, false) + } + + fun setOnboardingCompleted() { + prefs.edit { putBoolean(KEY_ONBOARDING_COMPLETED, true) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/tudee/data/preferences/ThemePreferenceManager.kt b/app/src/main/java/com/example/tudee/data/preferences/ThemePreferenceManager.kt deleted file mode 100644 index 88cac068..00000000 --- a/app/src/main/java/com/example/tudee/data/preferences/ThemePreferenceManager.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.example.tudee.data.preferences - - -import android.content.Context - -class ThemePreferenceManager(context: Context) { - private val prefs = context.getSharedPreferences("tudee_preferences", Context.MODE_PRIVATE) - - companion object { - private const val KEY_DARK_MODE = "dark_mode" - } - - fun isDarkMode(): Boolean { - return prefs.getBoolean(KEY_DARK_MODE, false) - } - - fun setDarkMode(isDark: Boolean) { - prefs.edit().putBoolean(KEY_DARK_MODE, isDark).apply() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/tudee/di/AppModule.kt b/app/src/main/java/com/example/tudee/di/AppModule.kt index a39c4249..6fa176b2 100644 --- a/app/src/main/java/com/example/tudee/di/AppModule.kt +++ b/app/src/main/java/com/example/tudee/di/AppModule.kt @@ -2,15 +2,12 @@ package com.example.tudee.di import androidx.lifecycle.SavedStateHandle import androidx.room.Room -import com.example.tudee.data.dao.AppEntryDao import com.example.tudee.data.dao.TaskCategoryDao import com.example.tudee.data.dao.TaskDao import com.example.tudee.data.database.AppDatabase -import com.example.tudee.data.preferences.ThemePreferenceManager +import com.example.tudee.data.preferences.PreferencesManager import com.example.tudee.data.service.TaskCategoryServiceImpl import com.example.tudee.data.service.TaskServiceImpl -import com.example.tudee.domain.AppEntry -import com.example.tudee.domain.AppEntryImpl import com.example.tudee.domain.TaskCategoryService import com.example.tudee.domain.TaskService import com.example.tudee.presentation.screen.category.tasks.CategoryTasksViewModel @@ -52,10 +49,7 @@ val appModule = module { } single { OnBoardingViewModel(get(), get()) } - single { get().appEntryDao() } - single { AppEntryImpl(get()) } - - single { ThemePreferenceManager(androidContext()) } + single { PreferencesManager(androidContext()) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/tudee/domain/AppEntry.kt b/app/src/main/java/com/example/tudee/domain/AppEntry.kt deleted file mode 100644 index 7486e7e3..00000000 --- a/app/src/main/java/com/example/tudee/domain/AppEntry.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.tudee.domain - -interface AppEntry { - suspend fun saveFirstEntry() - suspend fun isFirstEntry(): Boolean -} \ No newline at end of file diff --git a/app/src/main/java/com/example/tudee/domain/AppEntryImpl.kt b/app/src/main/java/com/example/tudee/domain/AppEntryImpl.kt deleted file mode 100644 index b46c7eae..00000000 --- a/app/src/main/java/com/example/tudee/domain/AppEntryImpl.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.tudee.domain - -import com.example.tudee.data.dao.AppEntryDao -import com.example.tudee.data.model.AppEntryEntity - -class AppEntryImpl( - private val appEntryDao: AppEntryDao -) : AppEntry { - override suspend fun saveFirstEntry() { - appEntryDao.insertAppEntry(AppEntryEntity()) - } - - override suspend fun isFirstEntry(): Boolean { - return appEntryDao.getAppEntry() == null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/tudee/presentation/screen/category/viewmodel/CategoriesViewModel.kt b/app/src/main/java/com/example/tudee/presentation/screen/category/viewmodel/CategoriesViewModel.kt index 5ded5712..8fd0f503 100644 --- a/app/src/main/java/com/example/tudee/presentation/screen/category/viewmodel/CategoriesViewModel.kt +++ b/app/src/main/java/com/example/tudee/presentation/screen/category/viewmodel/CategoriesViewModel.kt @@ -2,7 +2,6 @@ package com.example.tudee.presentation.screen.category.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.tudee.data.preferences.ThemePreferenceManager import com.example.tudee.domain.TaskCategoryService import com.example.tudee.domain.TaskService import com.example.tudee.domain.request.CategoryCreationRequest diff --git a/app/src/main/java/com/example/tudee/presentation/screen/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/example/tudee/presentation/screen/home/viewmodel/HomeViewModel.kt index e6846bf8..57a7ccb2 100644 --- a/app/src/main/java/com/example/tudee/presentation/screen/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/example/tudee/presentation/screen/home/viewmodel/HomeViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.tudee.R import com.example.tudee.data.mapper.toCategoryUiState -import com.example.tudee.data.preferences.ThemePreferenceManager import com.example.tudee.domain.TaskCategoryService import com.example.tudee.domain.TaskService import com.example.tudee.domain.entity.Task diff --git a/app/src/main/java/com/example/tudee/presentation/screen/onboarding/OnBoardingScreen.kt b/app/src/main/java/com/example/tudee/presentation/screen/onboarding/OnBoardingScreen.kt index 15a8bc51..1de073af 100644 --- a/app/src/main/java/com/example/tudee/presentation/screen/onboarding/OnBoardingScreen.kt +++ b/app/src/main/java/com/example/tudee/presentation/screen/onboarding/OnBoardingScreen.kt @@ -1,6 +1,8 @@ package com.example.tudee.presentation.screen.onboarding import android.content.res.Configuration +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -12,8 +14,6 @@ import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment @@ -44,15 +44,6 @@ fun OnBoardingScreen( navController: NavController = rememberNavController(), viewModel: OnBoardingViewModel = getKoin().get() ) { - - val isFirstEntry by viewModel.isFirstEntry.collectAsState() - - if (!isFirstEntry) { - navController.navigate(Destination.HomeScreen.route) { - popUpTo(Destination.OnBoardingScreen.route) { inclusive = true } - } - } - val onboardingOnBoardingPageUiModels = listOf( OnBoardingPageUiModel( title = stringResource(R.string.on_boarding_title1), @@ -147,7 +138,13 @@ private fun OnBoardingContent( onClick = { scope.launch { if (pageState.currentPage != onBoardingPageUiModels.lastIndex) { - pageState.animateScrollToPage(pageState.currentPage + Pages.SecondPage.page) + pageState.animateScrollToPage( + page = pageState.currentPage + 1, + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ) } else { navigateToHome() } diff --git a/app/src/main/java/com/example/tudee/presentation/screen/onboarding/OnBoardingViewModel.kt b/app/src/main/java/com/example/tudee/presentation/screen/onboarding/OnBoardingViewModel.kt index 59c09718..ca7f05e4 100644 --- a/app/src/main/java/com/example/tudee/presentation/screen/onboarding/OnBoardingViewModel.kt +++ b/app/src/main/java/com/example/tudee/presentation/screen/onboarding/OnBoardingViewModel.kt @@ -2,33 +2,16 @@ package com.example.tudee.presentation.screen.onboarding import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.tudee.domain.AppEntry +import com.example.tudee.data.preferences.PreferencesManager import com.example.tudee.domain.TaskCategoryService import com.example.tudee.presentation.utils.predefinedCategories -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class OnBoardingViewModel( private val taskCategoryService: TaskCategoryService, - private val appEntry: AppEntry + private val prefs: PreferencesManager ) : ViewModel() { - - private val _isFirstEntry = MutableStateFlow(true) - val isFirstEntry = _isFirstEntry.asStateFlow() - - init { - viewModelScope.launch { - _isFirstEntry.value = appEntry.isFirstEntry() - } - } - - fun saveFirstEntry() { - viewModelScope.launch { - appEntry.saveFirstEntry() - } - } - + fun saveFirstEntry() = prefs.setOnboardingCompleted() fun loadInitialData() { viewModelScope.launch { predefinedCategories.forEach { taskCategoryService.createCategory(it) } diff --git a/app/src/main/java/com/example/tudee/presentation/screen/splash/SplashScreen.kt b/app/src/main/java/com/example/tudee/presentation/screen/splash/SplashScreen.kt index f5dfe17a..762204ef 100644 --- a/app/src/main/java/com/example/tudee/presentation/screen/splash/SplashScreen.kt +++ b/app/src/main/java/com/example/tudee/presentation/screen/splash/SplashScreen.kt @@ -20,8 +20,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.example.tudee.R +import com.example.tudee.data.preferences.PreferencesManager import com.example.tudee.designsystem.theme.TudeeTheme -import com.example.tudee.domain.AppEntry import com.example.tudee.naviagtion.Destination import com.example.tudee.presentation.themeViewModel.ThemeViewModel import kotlinx.coroutines.delay @@ -37,12 +37,12 @@ fun SplashScreen( overlayColor: Color = TudeeTheme.color.statusColors.overlay, backgroundPainter: Painter = painterResource(R.drawable.background_ellipse), iconPainter: Painter = painterResource(R.drawable.tudee_logo), - appEntry: AppEntry = getKoin().get() + prefs: PreferencesManager = getKoin().get() ) { val isDark by themeViewModel.isDarkMode.collectAsState() LaunchedEffect(Unit) { delay(3000) - if (appEntry.isFirstEntry()) { + if (prefs.isOnboardingCompleted().not()) { navController.navigate(Destination.OnBoardingScreen.route) { popUpTo(Destination.SplashScreen.route) { inclusive = true } } diff --git a/app/src/main/java/com/example/tudee/presentation/screen/task_screen/viewmodel/TasksScreenViewModel.kt b/app/src/main/java/com/example/tudee/presentation/screen/task_screen/viewmodel/TasksScreenViewModel.kt index 32857a49..5a9539a4 100644 --- a/app/src/main/java/com/example/tudee/presentation/screen/task_screen/viewmodel/TasksScreenViewModel.kt +++ b/app/src/main/java/com/example/tudee/presentation/screen/task_screen/viewmodel/TasksScreenViewModel.kt @@ -3,10 +3,9 @@ package com.example.tudee.presentation.screen.task_screen.viewmodel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.tudee.data.preferences.ThemePreferenceManager +import com.example.tudee.data.preferences.PreferencesManager import com.example.tudee.domain.TaskCategoryService import com.example.tudee.domain.TaskService -import com.example.tudee.domain.entity.TaskPriority import com.example.tudee.domain.entity.TaskStatus import com.example.tudee.presentation.components.buttons.ButtonState import com.example.tudee.presentation.screen.task_screen.interactors.TaskScreenInteractor @@ -33,7 +32,7 @@ class TasksScreenViewModel( private val taskService: TaskService, private val categoryService: TaskCategoryService, private val savedStateHandle: SavedStateHandle, - private val themePrefs: ThemePreferenceManager + private val themePrefs: PreferencesManager ) : ViewModel(), TaskScreenInteractor { private val _taskScreenUiState = MutableStateFlow(TasksScreenUiState( isDarkMode = themePrefs.isDarkMode())) val taskScreenUiState = _taskScreenUiState diff --git a/app/src/main/java/com/example/tudee/presentation/themeViewModel/ThemeViewModel.kt b/app/src/main/java/com/example/tudee/presentation/themeViewModel/ThemeViewModel.kt index 352590ee..eaf694b1 100644 --- a/app/src/main/java/com/example/tudee/presentation/themeViewModel/ThemeViewModel.kt +++ b/app/src/main/java/com/example/tudee/presentation/themeViewModel/ThemeViewModel.kt @@ -1,11 +1,11 @@ package com.example.tudee.presentation.themeViewModel import androidx.lifecycle.ViewModel -import com.example.tudee.data.preferences.ThemePreferenceManager +import com.example.tudee.data.preferences.PreferencesManager import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -class ThemeViewModel(private val prefs: ThemePreferenceManager) : ViewModel() { +class ThemeViewModel(private val prefs: PreferencesManager) : ViewModel() { private val _isDarkMode = MutableStateFlow(prefs.isDarkMode()) val isDarkMode: StateFlow = _isDarkMode diff --git a/app/src/test/java/com/example/tudee/OnBoardingViewModelTest.kt b/app/src/test/java/com/example/tudee/OnBoardingViewModelTest.kt index 6f5dfcb1..52ecf0d5 100644 --- a/app/src/test/java/com/example/tudee/OnBoardingViewModelTest.kt +++ b/app/src/test/java/com/example/tudee/OnBoardingViewModelTest.kt @@ -1,12 +1,13 @@ package com.example.tudee -import com.example.tudee.domain.AppEntry +import com.example.tudee.data.preferences.PreferencesManager import com.example.tudee.domain.TaskCategoryService import com.example.tudee.presentation.screen.onboarding.OnBoardingViewModel import com.example.tudee.presentation.utils.predefinedCategories import com.google.common.truth.Truth.assertThat import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.just import io.mockk.mockk import io.mockk.runs @@ -27,14 +28,14 @@ import org.junit.runners.JUnit4 @RunWith(JUnit4::class) class OnBoardingViewModelTest { private lateinit var viewModel: OnBoardingViewModel - private lateinit var appEntry: AppEntry + private lateinit var preferencesManager: PreferencesManager private lateinit var taskCategoryService: TaskCategoryService private val testDispatcher = StandardTestDispatcher() @Before fun setup() { Dispatchers.setMain(testDispatcher) - appEntry = mockk() + preferencesManager = mockk() taskCategoryService = mockk() } @@ -44,52 +45,25 @@ class OnBoardingViewModelTest { } @Test - fun `isFirstEntry should emit true when appEntry returns true`() = runTest { + fun `saveFirstEntry should call preferences setOnboardingCompleted`() = runTest { // Given - coEvery { appEntry.isFirstEntry() } returns true - - // When - viewModel = OnBoardingViewModel(taskCategoryService, appEntry) - advanceUntilIdle() - - // Then - assertThat(viewModel.isFirstEntry.value).isTrue() - } - - @Test - fun `isFirstEntry should emit false when appEntry returns false`() = runTest { - // Given - coEvery { appEntry.isFirstEntry() } returns false - - // When - viewModel = OnBoardingViewModel(taskCategoryService, appEntry) - advanceUntilIdle() - - // Then - assertThat(viewModel.isFirstEntry.value).isFalse() - } - - @Test - fun `saveFirstEntry should call appEntry saveFirstEntry`() = runTest { - // Given - coEvery { appEntry.isFirstEntry() } returns true - coEvery { appEntry.saveFirstEntry() } just runs - viewModel = OnBoardingViewModel(taskCategoryService, appEntry) + every { preferencesManager.isOnboardingCompleted() } returns true + every { preferencesManager.setOnboardingCompleted() } just runs + viewModel = OnBoardingViewModel(taskCategoryService, preferencesManager) // When viewModel.saveFirstEntry() - advanceUntilIdle() // Then - coVerify(exactly = 1) { appEntry.saveFirstEntry() } + coVerify(exactly = 1) { preferencesManager.setOnboardingCompleted() } } @Test fun `loadInitialData should create all predefined categories`() = runTest { // Given - coEvery { appEntry.isFirstEntry() } returns true + every { preferencesManager.isOnboardingCompleted() } returns true coEvery { taskCategoryService.createCategory(any()) } just runs - viewModel = OnBoardingViewModel(taskCategoryService, appEntry) + viewModel = OnBoardingViewModel(taskCategoryService, preferencesManager) // When viewModel.loadInitialData()