From 58ef1d7ba7e10aeb5e5f449f977fb035e3174acf Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Mon, 23 Jun 2025 16:13:02 +0900 Subject: [PATCH 01/16] [CHORE] Finalize Application Theme - Finalize Application Color Theme for Container Background. * Start Destination of Each Graph containerBackgroundSolid * Other destinations displayBackground --- common/src/main/java/com/doyoonkim/common/theme/Color.kt | 3 +++ common/src/main/java/com/doyoonkim/common/theme/Theme.kt | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/com/doyoonkim/common/theme/Color.kt b/common/src/main/java/com/doyoonkim/common/theme/Color.kt index 9052e620..5a3f4b79 100644 --- a/common/src/main/java/com/doyoonkim/common/theme/Color.kt +++ b/common/src/main/java/com/doyoonkim/common/theme/Color.kt @@ -35,6 +35,9 @@ val SubtitleAny = Color(0xFF787879) val ButtonDark = Color(0xFF3C3C3C) val ButtonLight = Color(0xFFF3F3F3) +val SurfaceDark = Color(0xFF4F4F4F) +val SurfaceLight = Color(0xFFA1A1A1) + val GradientStartDark = Color(0xFF4F4F4F) val GradientStartLight = Color(0xFFA1A1A1) diff --git a/common/src/main/java/com/doyoonkim/common/theme/Theme.kt b/common/src/main/java/com/doyoonkim/common/theme/Theme.kt index b4af8e49..6e3dfb61 100644 --- a/common/src/main/java/com/doyoonkim/common/theme/Theme.kt +++ b/common/src/main/java/com/doyoonkim/common/theme/Theme.kt @@ -57,16 +57,20 @@ val ColorScheme.displayBackground: Color val ColorScheme.containerBackground: Color @Composable - get() = if(isSystemInDarkTheme()) ContainerDark else ContainerWhite + get() = if(isSystemInDarkTheme()) ContainerDark else ContainerLight val ColorScheme.containerBackgroundSolid: Color @Composable - get() = if(isSystemInDarkTheme()) ContainerBlack else ContainerLight + get() = if(isSystemInDarkTheme()) ContainerBlack else ContainerWhite val ColorScheme.containerGray: Color @Composable get() = if(isSystemInDarkTheme()) Color.Gray else Color.LightGray +val ColorScheme.surfaceBackground: Color + @Composable + get() = if(isSystemInDarkTheme()) SurfaceDark else SurfaceLight + val ColorScheme.bottomNavContainer: Color @Composable get() = if(isSystemInDarkTheme()) bottomNavBarBlack else bottomNavBarWhite From 98ca9cf2cd363eec032c9a120a816ebbb73989b0 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Mon, 23 Jun 2025 16:14:04 +0900 Subject: [PATCH 02/16] [REFACTOR] Apply Dynamic Background Color - Dynamic background color is being applied to the Scaffold and TopAppBar corresponding to the destination. --- .../com/doyoonkim/knutice/MainActivity.kt | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt b/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt index d9e20ff3..b07fbc4d 100644 --- a/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt +++ b/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt @@ -10,6 +10,10 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.Ease +import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -63,6 +67,8 @@ import com.doyoonkim.common.theme.subTitle import com.doyoonkim.common.theme.title import com.doyoonkim.common.ui.PermissionRationaleComposable import com.doyoonkim.common.R +import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.theme.displayBackground import com.doyoonkim.notification.local.NotificationAlarmScheduler import kotlinx.coroutines.delay import javax.inject.Inject @@ -92,13 +98,23 @@ class MainActivity : ComponentActivity() { navController = rememberNavController() // Bottom Bar Handling - var bottomBarState = Triple(true, false, false) + var bottomBarState: Pair + var isBackgroundSolid by remember { mutableStateOf(true) } val backStackEntryState by navController.currentBackStackEntryAsState() backStackEntryState?.destination?.route.let { route -> - bottomBarState = when(route) { - NavRoutes.Home.route -> Triple(true, true, false) - NavRoutes.Bookmark.route -> Triple(true, false, true) - else -> Triple(false, false, false) + when(route) { + NavRoutes.Home.route -> { + bottomBarState = Pair(true, false) + isBackgroundSolid = true + } + NavRoutes.Bookmark.route -> { + bottomBarState = Pair(false, true) + isBackgroundSolid = true + } + else -> { + bottomBarState = Pair(false, false) + isBackgroundSolid = false + } } } @@ -126,9 +142,18 @@ class MainActivity : ComponentActivity() { ) } + // Animated Background Color + val animatedBackgroundColor by animateColorAsState( + targetValue = if (isBackgroundSolid){ + MaterialTheme.colorScheme.containerBackgroundSolid + } else { + MaterialTheme.colorScheme.displayBackground + }, + animationSpec = tween(50) + ) + Scaffold( - modifier = Modifier.fillMaxSize() - .background(MaterialTheme.colorScheme.containerBackgroundSolid), + modifier = Modifier.fillMaxSize(), topBar = { TopAppBar( title = { @@ -137,7 +162,7 @@ class MainActivity : ComponentActivity() { horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { - if (!bottomBarState.first) { + if (!bottomBarState.first && !bottomBarState.second) { IconButton( onClick = { navController.popBackStack() @@ -165,7 +190,7 @@ class MainActivity : ComponentActivity() { } }, actions = { - if (bottomBarState.first) { + if (bottomBarState.first || bottomBarState.second) { IconButton( onClick = { navController.navigate(NavRoutes.NoticeSearch.route) @@ -193,13 +218,13 @@ class MainActivity : ComponentActivity() { } }, colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.containerBackgroundSolid, + containerColor = animatedBackgroundColor, titleContentColor = MaterialTheme.colorScheme.title ) ) }, bottomBar = { - if (bottomBarState.first) { + if (bottomBarState.first || bottomBarState.second) { BottomAppBar( modifier = Modifier .wrapContentSize() @@ -208,10 +233,10 @@ class MainActivity : ComponentActivity() { actions = { // https://developer.android.com/develop/ui/compose/navigation#bottom-nav BottomNavigationItem( - selected = bottomBarState.second, + selected = bottomBarState.first, enabled = true, onClick = { - if (!bottomBarState.second) { + if (!bottomBarState.first) { navController.navigate(NavRoutes.Home.route) } }, @@ -229,10 +254,10 @@ class MainActivity : ComponentActivity() { unselectedContentColor = MaterialTheme.colorScheme.subTitle ) BottomNavigationItem( - selected = bottomBarState.third, + selected = bottomBarState.second, enabled = true, onClick = { - if (!bottomBarState.third) { + if (!bottomBarState.second) { navController.navigate(NavRoutes.Bookmark.route) } }, @@ -253,11 +278,13 @@ class MainActivity : ComponentActivity() { ) } }, - containerColor = MaterialTheme.colorScheme.containerBackgroundSolid, + containerColor = animatedBackgroundColor ) { contentPadding -> AppNavHost( - modifier = Modifier, + modifier = Modifier.background( + animatedBackgroundColor + ), contentPadding = contentPadding, navController = navController, viewModelFactory = viewModelFactory, From d3febb16bb4c2029000c5a54ca9fd9ff3399a707 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Mon, 23 Jun 2025 16:14:44 +0900 Subject: [PATCH 03/16] [REFACTOR] Apply finalized color theme - Ally finalized background color theme for better appearance. --- .../com/doyoonkim/main/notice/NoticesInCategoryScreen.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticesInCategoryScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticesInCategoryScreen.kt index e3d41858..3f11e42e 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticesInCategoryScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticesInCategoryScreen.kt @@ -31,7 +31,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.doyoonkim.common.theme.containerBackground -import com.doyoonkim.common.theme.containerBackgroundSolid +import com.doyoonkim.common.theme.displayBackground import com.doyoonkim.common.theme.textPurple import com.doyoonkim.common.ui.NotificationPreview import com.doyoonkim.main.viewmodel.NoticesInCategoryViewModel @@ -65,7 +65,6 @@ fun NoticesInCategoryScreen( Box( modifier = modifier.fillMaxWidth() - .background(MaterialTheme.colorScheme.containerBackground) .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) .pullRefresh(pullRefreshState) ) { @@ -84,7 +83,7 @@ fun NoticesInCategoryScreen( CircularProgressIndicator( modifier = Modifier.wrapContentSize(), color = MaterialTheme.colorScheme.textPurple, - trackColor = MaterialTheme.colorScheme.containerBackground + trackColor = MaterialTheme.colorScheme.displayBackground ) } viewModel.requestMoreNotices() @@ -92,7 +91,7 @@ fun NoticesInCategoryScreen( if (index != 0) { HorizontalDivider( Modifier.fillMaxWidth(), - color =MaterialTheme.colorScheme.containerBackgroundSolid, + color =MaterialTheme.colorScheme.containerBackground, thickness = 1.2.dp ) } From 1a0dd4a4872ded64e33391a97d9e12eae027233d Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Mon, 23 Jun 2025 16:15:25 +0900 Subject: [PATCH 04/16] [REFACTOR] Apply transition during navigation. - Apply appropriate transition on navigation. --- .../bookmark/bookmarkServiceGraph.kt | 24 ++- .../com/doyoonkim/main/MainServiceNavGraph.kt | 166 ++++++++++++++++-- .../main/notice/NoticeSearchScreen.kt | 14 +- 3 files changed, 191 insertions(+), 13 deletions(-) diff --git a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt index 584df150..5c1c64a9 100644 --- a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt +++ b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt @@ -1,7 +1,13 @@ package com.doyoonkim.bookmark +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.core.EaseIn +import androidx.compose.animation.core.EaseOut +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModelProvider @@ -17,6 +23,8 @@ import com.doyoonkim.bookmark.viewmodel.EditBookmarkViewModel import com.doyoonkim.common.navigation.BookmarkInfo import com.doyoonkim.common.navigation.NavRoutes import com.doyoonkim.common.navigation.NoticeDetail +import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.theme.displayBackground fun NavGraphBuilder.bookmarkServiceGraph( navController: NavController, @@ -46,7 +54,19 @@ fun NavGraphBuilder.bookmarkServiceGraph( navDeepLink { uriPattern = "knutice://service/bookmark/{id}/{title}/{info}" } - ) + ), + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Up + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.Down + ) + }, ) { backStackEntry -> val bookmarkInfo = backStackEntry.arguments?.let { BookmarkInfo( @@ -57,7 +77,7 @@ fun NavGraphBuilder.bookmarkServiceGraph( } ?: BookmarkInfo(0, "", "") EditBookmarkScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier.padding(5.dp).background(MaterialTheme.colorScheme.displayBackground), viewModel = viewModel(factory = viewModelFactory), bookmarkInfo = bookmarkInfo, onNoticeSelected = { onNoticeDetailRequested(it) }, diff --git a/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt b/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt index 63dd317a..48fee482 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt @@ -1,6 +1,12 @@ package com.doyoonkim.main import android.net.Uri +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.core.EaseIn +import androidx.compose.animation.core.EaseOut +import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -9,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable @@ -41,6 +48,7 @@ fun NavGraphBuilder.mainServiceNavGraph( onBookmarkServiceRequested: (BookmarkInfo) -> Unit, onExit: () -> Unit = { } ) { + // ViewModels will be injected via ViewModelFactory composable(NavRoutes.Home.route) { HomeScreen( @@ -66,7 +74,21 @@ fun NavGraphBuilder.mainServiceNavGraph( ) } - composable(NavRoutes.NoticeSearch.route) { + composable( + route = NavRoutes.NoticeSearch.route, + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Up + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.Down + ) + } + ) { NoticeSearchScreen( modifier = Modifier.padding(5.dp), viewModel = viewModel(factory = viewModelFactory), @@ -77,7 +99,21 @@ fun NavGraphBuilder.mainServiceNavGraph( ) } - composable(NavRoutes.GeneralNotices.route) { + composable( + route = NavRoutes.GeneralNotices.route, + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Start + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.End + ) + } + ) { NoticesInCategoryScreen( modifier = Modifier.padding(5.dp), category = NoticeCategory.GENERAL_NEWS, @@ -89,7 +125,21 @@ fun NavGraphBuilder.mainServiceNavGraph( ) } - composable(NavRoutes.AcademicNotices.route) { + composable( + route = NavRoutes.AcademicNotices.route, + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Start + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.End + ) + } + ) { NoticesInCategoryScreen( modifier = Modifier.padding(5.dp), category = NoticeCategory.ACADEMIC_NEWS, @@ -101,7 +151,21 @@ fun NavGraphBuilder.mainServiceNavGraph( ) } - composable(NavRoutes.ScholarshipNotices.route) { + composable( + route = NavRoutes.ScholarshipNotices.route, + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Start + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.End + ) + } + ) { NoticesInCategoryScreen( modifier = Modifier.padding(5.dp), category = NoticeCategory.SCHOLARSHIP_NEWS, @@ -113,7 +177,21 @@ fun NavGraphBuilder.mainServiceNavGraph( ) } - composable(NavRoutes.EventNotices.route) { + composable( + route = NavRoutes.EventNotices.route, + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Start + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.End + ) + } + ) { NoticesInCategoryScreen( modifier = Modifier.padding(5.dp), category = NoticeCategory.EVENT_NEWS, @@ -126,7 +204,21 @@ fun NavGraphBuilder.mainServiceNavGraph( } // preferences - composable(NavRoutes.Settings.route) { + composable( + route = NavRoutes.Settings.route, + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Start + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.End + ) + } + ) { UserPreferenceScreen( modifier = Modifier.padding(5.dp), onNotificationPreferenceClicked = { navController.navigate(NavRoutes.NotificationPreferences.route) }, @@ -136,7 +228,21 @@ fun NavGraphBuilder.mainServiceNavGraph( ) } - composable(NavRoutes.NotificationPreferences.route) { + composable( + route = NavRoutes.NotificationPreferences.route, + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Start + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.End + ) + } + ) { NotificationPreferencesScreen( modifier = Modifier.padding(5.dp), viewModel = viewModel(factory = viewModelFactory), @@ -144,7 +250,21 @@ fun NavGraphBuilder.mainServiceNavGraph( ) } - composable(NavRoutes.CustomerService.route) { + composable( + route = NavRoutes.CustomerService.route, + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Start + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.End + ) + } + ) { CustomerServiceScreen( modifier = Modifier.padding(5.dp), viewModel = viewModel(factory = viewModelFactory), @@ -152,7 +272,21 @@ fun NavGraphBuilder.mainServiceNavGraph( ) } - composable(NavRoutes.OpenSource.route) { + composable( + route = NavRoutes.OpenSource.route, + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Start + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.End + ) + } + ) { OssNoticeScreen( modifier = Modifier.padding(5.dp), onBackPressed = { navController.popBackStack() } @@ -166,7 +300,19 @@ fun NavGraphBuilder.mainServiceNavGraph( navDeepLink { uriPattern = "knutice://service/noticeDetail/{nttId}/{contentUrl}/{isFabVisible}" } - ) + ), + enterTransition = { + slideIntoContainer( + animationSpec = tween(300, easing = EaseIn), + towards = AnimatedContentTransitionScope.SlideDirection.Start + ) + }, + exitTransition = { + slideOutOfContainer( + animationSpec = tween(300, easing = EaseOut), + towards = AnimatedContentTransitionScope.SlideDirection.End + ) + } ) { backStackEntry -> val noticeInfo = backStackEntry.arguments?.let { Triple( diff --git a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt index cc423a90..a9cb8013 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt @@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -34,6 +35,8 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -44,6 +47,7 @@ import com.doyoonkim.common.theme.subTitle import com.doyoonkim.common.theme.textPurple import com.doyoonkim.common.theme.title import com.doyoonkim.common.ui.NotificationPreview +import kotlin.math.sin @Composable fun NoticeSearchScreen( @@ -53,6 +57,7 @@ fun NoticeSearchScreen( onNoticeSelected: (Int, String) -> Unit ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val localFocusManager = LocalFocusManager.current BackHandler { onBackPressed() } @@ -65,6 +70,11 @@ fun NoticeSearchScreen( .fillMaxWidth() .wrapContentHeight() .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) + .pointerInput(Unit) { + detectTapGestures( + onTap = { localFocusManager.clearFocus() } + ) + } ) { Row( modifier = Modifier @@ -90,7 +100,9 @@ fun NoticeSearchScreen( unfocusedIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent ), - shape = RoundedCornerShape(15.dp) + shape = RoundedCornerShape(15.dp), + singleLine = true, + ) } From 67177ca6993259b94e972b261f7aa5b02e43c896 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Mon, 23 Jun 2025 19:58:17 +0900 Subject: [PATCH 05/16] [REFACTOR] Theme Modification - Theme Color Modification (in-progress) --- common/src/main/java/com/doyoonkim/common/theme/Theme.kt | 4 ++-- .../com/doyoonkim/main/preference/UserPreferencesScreen.kt | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/com/doyoonkim/common/theme/Theme.kt b/common/src/main/java/com/doyoonkim/common/theme/Theme.kt index 6e3dfb61..d4322361 100644 --- a/common/src/main/java/com/doyoonkim/common/theme/Theme.kt +++ b/common/src/main/java/com/doyoonkim/common/theme/Theme.kt @@ -57,11 +57,11 @@ val ColorScheme.displayBackground: Color val ColorScheme.containerBackground: Color @Composable - get() = if(isSystemInDarkTheme()) ContainerDark else ContainerLight + get() = if(isSystemInDarkTheme()) ContainerDark else ContainerWhite val ColorScheme.containerBackgroundSolid: Color @Composable - get() = if(isSystemInDarkTheme()) ContainerBlack else ContainerWhite + get() = if(isSystemInDarkTheme()) ContainerBlack else ContainerLight val ColorScheme.containerGray: Color @Composable diff --git a/feature/main/src/main/java/com/doyoonkim/main/preference/UserPreferencesScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/preference/UserPreferencesScreen.kt index 5b185e31..155f50c7 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/preference/UserPreferencesScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/preference/UserPreferencesScreen.kt @@ -15,6 +15,8 @@ import androidx.compose.ui.unit.dp import com.doyoonkim.common.R import com.doyoonkim.common.theme.buttonContainer import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.theme.containerBackgroundSolid +import com.doyoonkim.common.theme.containerGray import com.doyoonkim.common.theme.subTitle import com.doyoonkim.common.theme.title import com.doyoonkim.common.ui.CircleGoButton @@ -39,7 +41,7 @@ fun UserPreferenceScreen( horizontalAlignment = Alignment.CenterHorizontally ) { RoundedCornerColumn( - backgroundColor = MaterialTheme.colorScheme.containerBackground + backgroundColor = MaterialTheme.colorScheme.containerBackgroundSolid ) { RoundedCornerColumnTextItemWithExtraOnRight( verticalPadding = 10.dp, @@ -51,7 +53,7 @@ fun UserPreferenceScreen( ) { CircleGoButton( modifier = Modifier.weight(1f), - containerColor = MaterialTheme.colorScheme.buttonContainer, + containerColor = MaterialTheme.colorScheme.containerGray, contentColor = MaterialTheme.colorScheme.subTitle, onClick = onNotificationPreferenceClicked ) From bd3b735aa7b472fb27802ea71d7b492992576b0e Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Mon, 23 Jun 2025 19:58:42 +0900 Subject: [PATCH 06/16] [REFACTOR] Separate Scaffold - Separate Scaffold (in-progress) --- app/src/main/java/com/doyoonkim/knutice/MainActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt b/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt index b07fbc4d..265a5e5f 100644 --- a/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt +++ b/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt @@ -274,7 +274,8 @@ class MainActivity : ComponentActivity() { selectedContentColor = MaterialTheme.colorScheme.title, unselectedContentColor = MaterialTheme.colorScheme.subTitle ) - } + }, + containerColor = MaterialTheme.colorScheme.displayBackground ) } }, From e85df103fe8b3282d405304d7315501a58f9325f Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 00:29:20 +0900 Subject: [PATCH 07/16] KAN-14 [REFACTOR] Theme Color Scheme Finalization - Theme Color Scheme has been finalized (Jun 24, 2025) (https://knutice.atlassian.net/wiki/spaces/KNUTICE/pages/1736705) --- .../java/com/doyoonkim/common/theme/Color.kt | 26 ++++++++--------- .../java/com/doyoonkim/common/theme/Theme.kt | 28 ++++++------------- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/common/src/main/java/com/doyoonkim/common/theme/Color.kt b/common/src/main/java/com/doyoonkim/common/theme/Color.kt index 5a3f4b79..737b3171 100644 --- a/common/src/main/java/com/doyoonkim/common/theme/Color.kt +++ b/common/src/main/java/com/doyoonkim/common/theme/Color.kt @@ -12,20 +12,18 @@ val Pink40 = Color(0xFF7D5260) // Color Scheme val Notification01 = Color(0xFFE65C19) -val Notification02 = Color(0xFFFFC55A) +val Notification02 = Color(0xFFF59E0B) val Notification03 = Color(0xFF7FD099) val Notification04 = Color(0xFF4294F7) +val Notification05 = Color(0xFF8B5CF6) -val WhiteBackground = Color(0xFFFFFFFF) -val DarkBackground = Color(0xFF262729) +val WhiteBackground = Color(0xFFF3F4F6) +val DarkBackground = Color(0xFF000000) -val ContainerLight = Color(0xFFF3F3F3) -val ContainerDark = Color(0xFF333437) -val ContainerBlack = Color(0xFF000000) -val ContainerWhite = Color(0xFFFFFFFF) +val SecondaryLight = Color(0xFFFFFFFF) +val SecondaryDark = Color(0xFF1B1C20) -val bottomNavBarWhite = Color(0xFFFFFFFF) -val bottomNavBarBlack = Color(0xFF424448) +val VariantPurple = Color(0xFF6b79fc) val TitleBlack = Color(0xFF000000) val TitleWhite = Color(0xFFFFFFFF) @@ -35,14 +33,14 @@ val SubtitleAny = Color(0xFF787879) val ButtonDark = Color(0xFF3C3C3C) val ButtonLight = Color(0xFFF3F3F3) -val SurfaceDark = Color(0xFF4F4F4F) -val SurfaceLight = Color(0xFFA1A1A1) +val OnBackgroundDark = Color(0xFF2F2F2F) +val OnBackgroundLight = Color(0xFFECECEC) -val GradientStartDark = Color(0xFF4F4F4F) +val GradientStartDark = Color(0xFF1E1E1E) val GradientStartLight = Color(0xFFA1A1A1) -val GradientIntermediateDark = Color(0xFF5C5C5C) +val GradientIntermediateDark = Color(0xFF383838) val GradientIntermediateLight = Color(0xFFD2D2D2) -val GradientEndDark = Color(0xFF848484) +val GradientEndDark = Color(0xFF444444) val GradientEndLight = Color(0xFFE5E5E5) \ No newline at end of file diff --git a/common/src/main/java/com/doyoonkim/common/theme/Theme.kt b/common/src/main/java/com/doyoonkim/common/theme/Theme.kt index d4322361..6c69e5e1 100644 --- a/common/src/main/java/com/doyoonkim/common/theme/Theme.kt +++ b/common/src/main/java/com/doyoonkim/common/theme/Theme.kt @@ -55,25 +55,17 @@ val ColorScheme.displayBackground: Color @Composable get() = if(isSystemInDarkTheme()) DarkBackground else WhiteBackground -val ColorScheme.containerBackground: Color +val ColorScheme.secondaryBackground: Color @Composable - get() = if(isSystemInDarkTheme()) ContainerDark else ContainerWhite - -val ColorScheme.containerBackgroundSolid: Color - @Composable - get() = if(isSystemInDarkTheme()) ContainerBlack else ContainerLight + get() = if(isSystemInDarkTheme()) SecondaryDark else SecondaryLight val ColorScheme.containerGray: Color @Composable get() = if(isSystemInDarkTheme()) Color.Gray else Color.LightGray -val ColorScheme.surfaceBackground: Color - @Composable - get() = if(isSystemInDarkTheme()) SurfaceDark else SurfaceLight - -val ColorScheme.bottomNavContainer: Color +val ColorScheme.onAnyBackground: Color @Composable - get() = if(isSystemInDarkTheme()) bottomNavBarBlack else bottomNavBarWhite + get() = if(isSystemInDarkTheme()) OnBackgroundDark else OnBackgroundLight val ColorScheme.title: Color @Composable @@ -83,17 +75,13 @@ val ColorScheme.subTitle: Color @Composable get() = SubtitleAny -val ColorScheme.buttonContainer: Color +val ColorScheme.variantPurple: Color @Composable - get() = if(isSystemInDarkTheme()) ButtonDark else ButtonLight - -val ColorScheme.buttonPurple: Color - @Composable - get() = Purple40 + get() = VariantPurple -val ColorScheme.textPurple: Color +val ColorScheme.buttonOnBackground: Color @Composable - get() = if(isSystemInDarkTheme()) Purple80 else Purple40 + get() = if(isSystemInDarkTheme()) ButtonDark else ButtonLight val ColorScheme.animationGradientStart: Color @Composable From 6fa0ad9a6e475a7b4173779a91b61aa40db152e9 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 00:30:10 +0900 Subject: [PATCH 08/16] KAN-14 [REFACTOR] Theme Color Scheme Finalization - Apply finalized theme color scheme to globally shared composable under :common. --- .../java/com/doyoonkim/common/ui/DateTimePicker.kt | 5 +++-- .../com/doyoonkim/common/ui/LabeledToggleSwitch.kt | 4 ++-- .../com/doyoonkim/common/ui/NotificationPreview.kt | 6 +++--- .../doyoonkim/common/ui/NotificationPreviewCard.kt | 4 ++-- .../common/ui/PermissionRationaleComposable.kt | 4 ++-- .../java/com/doyoonkim/common/ui/TimePickerWheel.kt | 11 ++++++----- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/common/src/main/java/com/doyoonkim/common/ui/DateTimePicker.kt b/common/src/main/java/com/doyoonkim/common/ui/DateTimePicker.kt index f5c8e633..b7f78dbf 100644 --- a/common/src/main/java/com/doyoonkim/common/ui/DateTimePicker.kt +++ b/common/src/main/java/com/doyoonkim/common/ui/DateTimePicker.kt @@ -28,7 +28,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.doyoonkim.common.theme.containerGray +import com.doyoonkim.common.theme.onAnyBackground +import com.doyoonkim.common.theme.secondaryBackground import com.doyoonkim.common.theme.title import java.text.SimpleDateFormat import java.util.Calendar @@ -66,7 +67,7 @@ fun DatePickerDialog( .background(Color.Transparent) .clip(RoundedCornerShape(10.dp)) .clickable { pickerVisible = !pickerVisible }, - color = MaterialTheme.colorScheme.containerGray + color = MaterialTheme.colorScheme.onAnyBackground ) { Text( text = datePickerState.selectedDateMillis!!.toFormattedString(), diff --git a/common/src/main/java/com/doyoonkim/common/ui/LabeledToggleSwitch.kt b/common/src/main/java/com/doyoonkim/common/ui/LabeledToggleSwitch.kt index d851c435..6feee093 100644 --- a/common/src/main/java/com/doyoonkim/common/ui/LabeledToggleSwitch.kt +++ b/common/src/main/java/com/doyoonkim/common/ui/LabeledToggleSwitch.kt @@ -19,9 +19,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.doyoonkim.common.theme.buttonPurple import com.doyoonkim.common.theme.subTitle import com.doyoonkim.common.theme.title +import com.doyoonkim.common.theme.variantPurple @Composable fun LabeledToggleSwitch( @@ -67,7 +67,7 @@ fun LabeledToggleSwitch( Switch( checked = isChecked, colors = SwitchDefaults.colors().copy( - checkedTrackColor = MaterialTheme.colorScheme.buttonPurple, + checkedTrackColor = MaterialTheme.colorScheme.variantPurple, checkedThumbColor = Color.White ), onCheckedChange = { diff --git a/common/src/main/java/com/doyoonkim/common/ui/NotificationPreview.kt b/common/src/main/java/com/doyoonkim/common/ui/NotificationPreview.kt index a41084f6..089dd909 100644 --- a/common/src/main/java/com/doyoonkim/common/ui/NotificationPreview.kt +++ b/common/src/main/java/com/doyoonkim/common/ui/NotificationPreview.kt @@ -37,7 +37,7 @@ fun NotificationPreview( ) { Column( Modifier.padding(10.dp), - verticalArrangement = Arrangement.spacedBy(7.dp) + verticalArrangement = Arrangement.spacedBy(5.dp) ) { if (isLoading) { AnimatedGradient(Modifier.height(24.dp)) @@ -67,7 +67,7 @@ fun NotificationPreview( .padding(top = 7.dp, start = 5.dp, end = 5.dp), text = notificationTitle, textAlign = TextAlign.Start, - fontSize = 13.sp, + fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.title, maxLines = 1, @@ -78,7 +78,7 @@ fun NotificationPreview( .padding(top = 1.dp, start = 5.dp, bottom = 5.dp, end = 5.dp), text = notificationInfo, textAlign = TextAlign.Start, - fontSize = 9.sp, + fontSize = 12.sp, color = MaterialTheme.colorScheme.subTitle ) } diff --git a/common/src/main/java/com/doyoonkim/common/ui/NotificationPreviewCard.kt b/common/src/main/java/com/doyoonkim/common/ui/NotificationPreviewCard.kt index 55f0311d..c15b65da 100644 --- a/common/src/main/java/com/doyoonkim/common/ui/NotificationPreviewCard.kt +++ b/common/src/main/java/com/doyoonkim/common/ui/NotificationPreviewCard.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.theme.secondaryBackground @Composable fun NotificationPreviewCard( @@ -30,7 +30,7 @@ fun NotificationPreviewCard( onClick() }, colors = CardColors( - containerColor = MaterialTheme.colorScheme.containerBackground, + containerColor = MaterialTheme.colorScheme.secondaryBackground, contentColor = Color.Unspecified, disabledContainerColor = Color.Unspecified, disabledContentColor = Color.Unspecified diff --git a/common/src/main/java/com/doyoonkim/common/ui/PermissionRationaleComposable.kt b/common/src/main/java/com/doyoonkim/common/ui/PermissionRationaleComposable.kt index 3321d7cb..7f44f605 100644 --- a/common/src/main/java/com/doyoonkim/common/ui/PermissionRationaleComposable.kt +++ b/common/src/main/java/com/doyoonkim/common/ui/PermissionRationaleComposable.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.doyoonkim.common.R -import com.doyoonkim.common.theme.buttonPurple +import com.doyoonkim.common.theme.variantPurple @Composable fun PermissionRationaleComposable( @@ -73,7 +73,7 @@ fun PermissionRationaleComposable( Button( modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.buttonColors().copy( - containerColor = MaterialTheme.colorScheme.buttonPurple, + containerColor = MaterialTheme.colorScheme.variantPurple, contentColor = Color.White, ), onClick = onPermissionDecided diff --git a/common/src/main/java/com/doyoonkim/common/ui/TimePickerWheel.kt b/common/src/main/java/com/doyoonkim/common/ui/TimePickerWheel.kt index 8835fa66..ebe4d1b0 100644 --- a/common/src/main/java/com/doyoonkim/common/ui/TimePickerWheel.kt +++ b/common/src/main/java/com/doyoonkim/common/ui/TimePickerWheel.kt @@ -43,11 +43,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import com.doyoonkim.common.R -import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.theme.secondaryBackground import com.doyoonkim.common.theme.containerGray +import com.doyoonkim.common.theme.onAnyBackground import com.doyoonkim.common.theme.subTitle -import com.doyoonkim.common.theme.textPurple import com.doyoonkim.common.theme.title +import com.doyoonkim.common.theme.variantPurple import kotlinx.coroutines.flow.filter import java.util.Calendar @@ -84,7 +85,7 @@ fun TimePickerDialog( .background(Color.Transparent) .clip(RoundedCornerShape(10.dp)) .clickable { pickerVisible = !pickerVisible }, - color = MaterialTheme.colorScheme.containerGray + color = MaterialTheme.colorScheme.onAnyBackground ) { Text( text = "${hours[hourSelected]}:${minutes[minuteSelected]}", @@ -110,7 +111,7 @@ fun TimePickerDialog( modifier = Modifier .clip(RoundedCornerShape(10.dp)) .background(Color.Transparent), - color = MaterialTheme.colorScheme.containerBackground + color = MaterialTheme.colorScheme.secondaryBackground ) { Column( modifier = Modifier.wrapContentSize() @@ -248,7 +249,7 @@ internal fun WheelPickerItem( fontSize = 25.sp, textAlign = TextAlign.Center, color = if (isHighlighted) { - MaterialTheme.colorScheme.textPurple + MaterialTheme.colorScheme.variantPurple } else { MaterialTheme.colorScheme.subTitle }, From f78c6be29abdc218672e97492d7be5358eca9646 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 00:32:46 +0900 Subject: [PATCH 09/16] KAN-15 [REFACTOR] Decentralize Top-Level Scaffold - Decentralization fo Top-level centralized scaffold. * Each Screen would now have their own Scaffold. * HomeScreen & BookmarkListScreen would have a shared Scaffold defined under MainActivity to handle navigation triggered by BottomNavBar. --- .../com/doyoonkim/knutice/MainActivity.kt | 127 ++---- .../bookmark/bookmarkServiceGraph.kt | 3 +- .../bookmark/edit/EditBookmarkScreen.kt | 379 +++++++++--------- .../bookmark/list/BookmarkListScreen.kt | 2 - .../com/doyoonkim/main/MainServiceNavGraph.kt | 18 +- .../com/doyoonkim/main/home/HomeScreen.kt | 2 +- .../main/notice/NoticeDetailScreen.kt | 254 ++++++------ .../main/notice/NoticeSearchScreen.kt | 119 +++--- .../main/notice/NoticesInCategoryScreen.kt | 141 ++++--- .../main/preference/CustomerServiceScreen.kt | 236 +++++------ .../NotificationPreferencesScreen.kt | 279 +++++++------ .../main/preference/OssNoticeScreen.kt | 32 +- .../main/preference/UserPreferencesScreen.kt | 155 +++---- 13 files changed, 904 insertions(+), 843 deletions(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt b/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt index 265a5e5f..a1ef25ac 100644 --- a/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt +++ b/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt @@ -10,15 +10,9 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.AnimationSpec -import androidx.compose.animation.core.Ease -import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -62,13 +56,12 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.doyoonkim.common.navigation.NavRoutes import com.doyoonkim.common.theme.KNUTICETheme -import com.doyoonkim.common.theme.containerBackgroundSolid import com.doyoonkim.common.theme.subTitle import com.doyoonkim.common.theme.title import com.doyoonkim.common.ui.PermissionRationaleComposable import com.doyoonkim.common.R -import com.doyoonkim.common.theme.containerBackground import com.doyoonkim.common.theme.displayBackground +import com.doyoonkim.common.theme.onAnyBackground import com.doyoonkim.notification.local.NotificationAlarmScheduler import kotlinx.coroutines.delay import javax.inject.Inject @@ -97,24 +90,14 @@ class MainActivity : ComponentActivity() { var showPermissionRationale by remember { mutableStateOf(false) } navController = rememberNavController() - // Bottom Bar Handling - var bottomBarState: Pair - var isBackgroundSolid by remember { mutableStateOf(true) } + // SharedScaffoldHandling + var sharedScaffoldState: Triple val backStackEntryState by navController.currentBackStackEntryAsState() - backStackEntryState?.destination?.route.let { route -> - when(route) { - NavRoutes.Home.route -> { - bottomBarState = Pair(true, false) - isBackgroundSolid = true - } - NavRoutes.Bookmark.route -> { - bottomBarState = Pair(false, true) - isBackgroundSolid = true - } - else -> { - bottomBarState = Pair(false, false) - isBackgroundSolid = false - } + backStackEntryState?.destination?.route.let { + sharedScaffoldState = when(it) { + NavRoutes.Home.route -> Triple(true, true, false) + NavRoutes.Bookmark.route -> Triple(true, false, true) + else -> Triple(false, false, false) } } @@ -142,55 +125,29 @@ class MainActivity : ComponentActivity() { ) } - // Animated Background Color - val animatedBackgroundColor by animateColorAsState( - targetValue = if (isBackgroundSolid){ - MaterialTheme.colorScheme.containerBackgroundSolid - } else { - MaterialTheme.colorScheme.displayBackground - }, - animationSpec = tween(50) - ) - Scaffold( modifier = Modifier.fillMaxSize(), topBar = { - TopAppBar( - title = { - Row( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - if (!bottomBarState.first && !bottomBarState.second) { - IconButton( - onClick = { - navController.popBackStack() - } - ) { - Image( - painter = painterResource(R.drawable.baseline_arrow_back_ios_new_24), - contentDescription = "back", - modifier = Modifier.wrapContentSize(), - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.title) - ) - } + if (sharedScaffoldState.first) { + TopAppBar( + title = { + Row( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.app_name), + textAlign = TextAlign.Left, + fontSize = 20.sp, + fontWeight = FontWeight.W900, + maxLines = 1, + color = MaterialTheme.colorScheme.title + ) } - - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.app_name), - textAlign = TextAlign.Left, - fontSize = 20.sp, - fontWeight = FontWeight.ExtraBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.title - ) - } - }, - actions = { - if (bottomBarState.first || bottomBarState.second) { + }, + actions = { IconButton( onClick = { navController.navigate(NavRoutes.NoticeSearch.route) @@ -215,16 +172,16 @@ class MainActivity : ComponentActivity() { colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.title) ) } - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = animatedBackgroundColor, - titleContentColor = MaterialTheme.colorScheme.title + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.displayBackground, + titleContentColor = MaterialTheme.colorScheme.title + ) ) - ) + } }, bottomBar = { - if (bottomBarState.first || bottomBarState.second) { + if (sharedScaffoldState.first) { BottomAppBar( modifier = Modifier .wrapContentSize() @@ -233,10 +190,10 @@ class MainActivity : ComponentActivity() { actions = { // https://developer.android.com/develop/ui/compose/navigation#bottom-nav BottomNavigationItem( - selected = bottomBarState.first, + selected = sharedScaffoldState.second, enabled = true, onClick = { - if (!bottomBarState.first) { + if (!sharedScaffoldState.second) { navController.navigate(NavRoutes.Home.route) } }, @@ -254,10 +211,10 @@ class MainActivity : ComponentActivity() { unselectedContentColor = MaterialTheme.colorScheme.subTitle ) BottomNavigationItem( - selected = bottomBarState.second, + selected = sharedScaffoldState.third, enabled = true, onClick = { - if (!bottomBarState.second) { + if (!sharedScaffoldState.third) { navController.navigate(NavRoutes.Bookmark.route) } }, @@ -275,17 +232,15 @@ class MainActivity : ComponentActivity() { unselectedContentColor = MaterialTheme.colorScheme.subTitle ) }, - containerColor = MaterialTheme.colorScheme.displayBackground + containerColor = MaterialTheme.colorScheme.onAnyBackground ) } }, - containerColor = animatedBackgroundColor + containerColor = MaterialTheme.colorScheme.displayBackground ) { contentPadding -> AppNavHost( - modifier = Modifier.background( - animatedBackgroundColor - ), + modifier = Modifier, contentPadding = contentPadding, navController = navController, viewModelFactory = viewModelFactory, diff --git a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt index 5c1c64a9..6179ef04 100644 --- a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt +++ b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt @@ -23,7 +23,6 @@ import com.doyoonkim.bookmark.viewmodel.EditBookmarkViewModel import com.doyoonkim.common.navigation.BookmarkInfo import com.doyoonkim.common.navigation.NavRoutes import com.doyoonkim.common.navigation.NoticeDetail -import com.doyoonkim.common.theme.containerBackground import com.doyoonkim.common.theme.displayBackground fun NavGraphBuilder.bookmarkServiceGraph( @@ -77,7 +76,7 @@ fun NavGraphBuilder.bookmarkServiceGraph( } ?: BookmarkInfo(0, "", "") EditBookmarkScreen( - modifier = Modifier.padding(5.dp).background(MaterialTheme.colorScheme.displayBackground), + modifier = Modifier, viewModel = viewModel(factory = viewModelFactory), bookmarkInfo = bookmarkInfo, onNoticeSelected = { onNoticeDetailRequested(it) }, diff --git a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/edit/EditBookmarkScreen.kt b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/edit/EditBookmarkScreen.kt index 8bb8fbee..05079c7a 100644 --- a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/edit/EditBookmarkScreen.kt +++ b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/edit/EditBookmarkScreen.kt @@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -32,6 +33,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults @@ -45,6 +47,8 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -55,11 +59,11 @@ import com.doyoonkim.bookmark.viewmodel.EditBookmarkViewModel import com.doyoonkim.common.navigation.BookmarkInfo import com.doyoonkim.common.navigation.NoticeDetail import com.doyoonkim.common.R -import com.doyoonkim.common.theme.buttonPurple -import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.theme.displayBackground +import com.doyoonkim.common.theme.secondaryBackground import com.doyoonkim.common.theme.subTitle -import com.doyoonkim.common.theme.textPurple import com.doyoonkim.common.theme.title +import com.doyoonkim.common.theme.variantPurple import com.doyoonkim.common.ui.DatePickerDialog import com.doyoonkim.common.ui.NotificationPreviewCard import com.doyoonkim.common.ui.RoundedCornerColumn @@ -67,6 +71,7 @@ import com.doyoonkim.common.ui.RoundedCornerColumnItem import com.doyoonkim.common.ui.RoundedCornerColumnTextItemWithExtraOnRight import com.doyoonkim.common.ui.TextSize import com.doyoonkim.common.ui.TimePickerDialog +import com.doyoonkim.common.ui.TopAppBarWithBackButton @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -80,6 +85,7 @@ fun EditBookmarkScreen( ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() val adjustImePadding = Modifier.consumeWindowInsets(WindowInsets.ime).imePadding() + val localFocusManager = LocalFocusManager.current BackHandler { onBackPressed() @@ -94,226 +100,241 @@ fun EditBookmarkScreen( } } - Column( - modifier = modifier.fillMaxSize() - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) - ) { - NotificationPreviewCard( - modifier = Modifier.padding(5.dp), - isLoading = false, - notificationTitle = bookmarkInfo.noticeTitle, - notificationInfo = bookmarkInfo.noticeInfo + Scaffold( + topBar = { + TopAppBarWithBackButton( + titleText = stringResource(R.string.title_edit_bookmark), + onBackPressed = onBackPressed + ) + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> + Column( + modifier = modifier.fillMaxSize() + .padding(innerPadding) + .pointerInput(Unit) { + detectTapGestures( + onTap = { localFocusManager.clearFocus() } + ) + } ) { - // Request Full Content - uiState.targetNotice?.let { onNoticeSelected(NoticeDetail(it.nttId, it.url, false)) } - } - - Spacer(Modifier.height(30.dp)) - - RoundedCornerColumn( - backgroundColor = MaterialTheme.colorScheme.containerBackground - ) { - RoundedCornerColumnTextItemWithExtraOnRight( - verticalPadding = 12.dp, - titleText = stringResource(R.string.subtitle_get_reminder), - subTitleText = null, - fontSize = TextSize.Small, - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = uiState.isReminderRequested + NotificationPreviewCard( + modifier = Modifier.padding(5.dp), + isLoading = false, + notificationTitle = bookmarkInfo.noticeTitle, + notificationInfo = bookmarkInfo.noticeInfo ) { - Switch( - checked = uiState.isReminderRequested && uiState.alarmPermissionStatus, - enabled = true, - modifier = Modifier.padding(10.dp).weight(1f), - colors = SwitchDefaults.colors().copy( - checkedTrackColor = MaterialTheme.colorScheme.buttonPurple, - checkedThumbColor = Color.White - ), - onCheckedChange = { viewModel.updateReminderOptions(requested = !uiState.isReminderRequested) } - ) + // Request Full Content + uiState.targetNotice?.let { onNoticeSelected(NoticeDetail(it.nttId, it.url, false)) } } - AnimatedVisibility( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - visible = uiState.isReminderRequested, - enter = slideInVertically(), - exit = slideOutVertically() + Spacer(Modifier.height(30.dp)) + + RoundedCornerColumn( + backgroundColor = MaterialTheme.colorScheme.secondaryBackground ) { - RoundedCornerColumnItem( + RoundedCornerColumnTextItemWithExtraOnRight( verticalPadding = 12.dp, - hasBottomDivider = false, + titleText = stringResource(R.string.subtitle_get_reminder), + subTitleText = null, + fontSize = TextSize.Small, + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = uiState.isReminderRequested + ) { + Switch( + checked = uiState.isReminderRequested && uiState.alarmPermissionStatus, + enabled = true, + modifier = Modifier.padding(10.dp).weight(1f), + colors = SwitchDefaults.colors().copy( + checkedTrackColor = MaterialTheme.colorScheme.variantPurple, + checkedThumbColor = Color.White + ), + onCheckedChange = { viewModel.updateReminderOptions(requested = !uiState.isReminderRequested) } + ) + } + + AnimatedVisibility( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + visible = uiState.isReminderRequested, + enter = slideInVertically(), + exit = slideOutVertically() ) { - Row( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(3.dp) + RoundedCornerColumnItem( + verticalPadding = 12.dp, + hasBottomDivider = false, ) { - Text( - text = stringResource(R.string.text_set_reminder_date_time), - textAlign = TextAlign.Start, - fontSize = 14.sp, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.title, - modifier = Modifier.padding(10.dp).weight(2f) - ) + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(3.dp) + ) { + Text( + text = stringResource(R.string.text_set_reminder_date_time), + textAlign = TextAlign.Start, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.title, + modifier = Modifier.padding(10.dp).weight(2f) + ) - DatePickerDialog( - initialTime = uiState.timeForRemind - ) { year, month, day -> - viewModel.updateDateInfo(year, month, day) - } - TimePickerDialog( - initialTime = uiState.timeForRemind - ) { hour, min -> - viewModel.updateTimeInfo(hour, min) + DatePickerDialog( + initialTime = uiState.timeForRemind + ) { year, month, day -> + viewModel.updateDateInfo(year, month, day) + } + TimePickerDialog( + initialTime = uiState.timeForRemind + ) { hour, min -> + viewModel.updateTimeInfo(hour, min) + } } } } } - } - - Spacer(Modifier.height(15.dp)) - Text( - text = stringResource(R.string.subtitle_notes), - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.title, - modifier = Modifier.padding(10.dp) - ) - - Box( - modifier = Modifier.fillMaxWidth() - .fillMaxHeight() - .weight(1f) - .padding(top = 5.dp, bottom = 20.dp) - .then(adjustImePadding) - ) { - TextField( - modifier = Modifier.fillMaxSize().padding(bottom = 5.dp), - value = uiState.bookmarkNote, - placeholder = { Text(text = stringResource(R.string.placeholder_notes)) }, - enabled = true, - onValueChange = { - viewModel.updateBookmarkNotes(it) - }, - colors = TextFieldDefaults.colors( - focusedTextColor = MaterialTheme.colorScheme.title, - unfocusedTextColor = MaterialTheme.colorScheme.subTitle, - focusedContainerColor = MaterialTheme.colorScheme.containerBackground, - unfocusedContainerColor = MaterialTheme.colorScheme.containerBackground, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent - ), - shape = RoundedCornerShape(15.dp) - ) + Spacer(Modifier.height(15.dp)) Text( - text = "${uiState.bookmarkNote.length}/500", - fontSize = 12.sp, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.subTitle, - modifier = Modifier.wrapContentSize() - .padding(15.dp) - .align(Alignment.BottomEnd) + text = stringResource(R.string.subtitle_notes), + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.title, + modifier = Modifier.padding(10.dp) ) - } - Row( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(5.dp) - ) { - Button( - modifier = Modifier.wrapContentHeight() - .weight(1f), - enabled = true, - colors = ButtonDefaults.buttonColors().copy( - containerColor = MaterialTheme.colorScheme.buttonPurple, - contentColor = Color.White, - ), - shape = RoundedCornerShape(10.dp), - onClick = { - viewModel.submitBookmark() - } + Box( + modifier = Modifier.fillMaxWidth() + .fillMaxHeight() + .weight(1f) + .padding(top = 5.dp, bottom = 20.dp) + .then(adjustImePadding) ) { + TextField( + modifier = Modifier.fillMaxSize().padding(bottom = 5.dp), + value = uiState.bookmarkNote, + placeholder = { Text(text = stringResource(R.string.placeholder_notes)) }, + enabled = true, + onValueChange = { + viewModel.updateBookmarkNotes(it) + }, + colors = TextFieldDefaults.colors( + focusedTextColor = MaterialTheme.colorScheme.title, + unfocusedTextColor = MaterialTheme.colorScheme.subTitle, + focusedContainerColor = MaterialTheme.colorScheme.secondaryBackground, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryBackground, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp) + ) + Text( - text = stringResource(R.string.btn_save), - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(10.dp) + text = "${uiState.bookmarkNote.length}/500", + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.subTitle, + modifier = Modifier.wrapContentSize() + .padding(15.dp) + .align(Alignment.BottomEnd) ) } - if (!uiState.requireCreation) { + Row( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp) + ) { Button( - modifier = Modifier.wrapContentHeight().weight(1f), + modifier = Modifier.wrapContentHeight() + .weight(1f), enabled = true, - shape = RoundedCornerShape(10.dp), colors = ButtonDefaults.buttonColors().copy( - containerColor = MaterialTheme.colorScheme.subTitle, - contentColor = Color.White + containerColor = MaterialTheme.colorScheme.variantPurple, + contentColor = Color.White, ), + shape = RoundedCornerShape(10.dp), onClick = { - viewModel.removeBookmark() + viewModel.submitBookmark() } ) { Text( - text = stringResource(R.string.btn_delete), + text = stringResource(R.string.btn_save), fontSize = 16.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(10.dp) ) } + + if (!uiState.requireCreation) { + Button( + modifier = Modifier.wrapContentHeight().weight(1f), + enabled = true, + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors().copy( + containerColor = MaterialTheme.colorScheme.subTitle, + contentColor = Color.White + ), + onClick = { + viewModel.removeBookmark() + } + ) { + Text( + text = stringResource(R.string.btn_delete), + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(10.dp) + ) + } + } } } - } - if (uiState.isCompleted) { - BasicAlertDialog( - onDismissRequest = { - // Dismiss the dialog when the user clicks outside the dialog or on the back - // button. If you want to disable that functionality, simply use an empty - // onDismissRequest. + if (uiState.isCompleted) { + BasicAlertDialog( + onDismissRequest = { + // Dismiss the dialog when the user clicks outside the dialog or on the back + // button. If you want to disable that functionality, simply use an empty + // onDismissRequest. - } - ) { - Surface( - modifier = Modifier.wrapContentWidth().wrapContentHeight(), - shape = MaterialTheme.shapes.large, - tonalElevation = AlertDialogDefaults.TonalElevation, - color = MaterialTheme.colorScheme.containerBackground + } ) { - Column(modifier = Modifier.padding(30.dp)) { - Text( - text = if (uiState.isSuccessful) { - stringResource(R.string.text_save_successful) - } else { - stringResource(R.string.text_save_unsuccessful) - }, - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.title - ) - Spacer(modifier = Modifier.height(24.dp)) - TextButton( - onClick = { - if (uiState.isSuccessful) onCompleted() - viewModel.updateCompletionStatus(false) // Set false for completion status for marking a new transaction is ready to start. - }, - modifier = Modifier.align(Alignment.End) - ) { + Surface( + modifier = Modifier.wrapContentWidth().wrapContentHeight(), + shape = MaterialTheme.shapes.large, + tonalElevation = AlertDialogDefaults.TonalElevation, + color = MaterialTheme.colorScheme.secondaryBackground + ) { + Column(modifier = Modifier.padding(30.dp)) { Text( - text = stringResource(R.string.btn_confirm), + text = if (uiState.isSuccessful) { + stringResource(R.string.text_save_successful) + } else { + stringResource(R.string.text_save_unsuccessful) + }, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, textAlign = TextAlign.Center, - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.textPurple + color = MaterialTheme.colorScheme.title ) + Spacer(modifier = Modifier.height(24.dp)) + TextButton( + onClick = { + if (uiState.isSuccessful) onCompleted() + viewModel.updateCompletionStatus(false) // Set false for completion status for marking a new transaction is ready to start. + }, + modifier = Modifier.align(Alignment.End) + ) { + Text( + text = stringResource(R.string.btn_confirm), + textAlign = TextAlign.Center, + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.variantPurple + ) + } } } } diff --git a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/list/BookmarkListScreen.kt b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/list/BookmarkListScreen.kt index 6d277925..d476d060 100644 --- a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/list/BookmarkListScreen.kt +++ b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/list/BookmarkListScreen.kt @@ -30,7 +30,6 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.doyoonkim.bookmark.viewmodel.BookmarkListViewModel import com.doyoonkim.common.navigation.BookmarkInfo -import com.doyoonkim.common.theme.containerBackgroundSolid import com.doyoonkim.common.R import com.doyoonkim.common.theme.subTitle import com.doyoonkim.common.ui.NotificationPreviewCardMarked @@ -53,7 +52,6 @@ fun BookmarkListScreen( Box( modifier = modifier.fillMaxSize() - .background(MaterialTheme.colorScheme.containerBackgroundSolid) ) { if (uiState.bookmarks.isEmpty()) { Column( diff --git a/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt b/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt index 48fee482..db5c9111 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt @@ -90,7 +90,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { NoticeSearchScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier, viewModel = viewModel(factory = viewModelFactory), onBackPressed = { navController.popBackStack() }, onNoticeSelected = { id, url -> @@ -115,7 +115,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { NoticesInCategoryScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier, category = NoticeCategory.GENERAL_NEWS, viewModel = viewModel(factory = viewModelFactory), onBackButtonPressed = { navController.popBackStack() }, @@ -141,7 +141,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { NoticesInCategoryScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier, category = NoticeCategory.ACADEMIC_NEWS, viewModel = viewModel(factory = viewModelFactory), onBackButtonPressed = { navController.popBackStack() }, @@ -167,7 +167,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { NoticesInCategoryScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier, category = NoticeCategory.SCHOLARSHIP_NEWS, viewModel = viewModel(factory = viewModelFactory), onBackButtonPressed = { navController.popBackStack() }, @@ -193,7 +193,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { NoticesInCategoryScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier, category = NoticeCategory.EVENT_NEWS, viewModel = viewModel(factory = viewModelFactory), onBackButtonPressed = { navController.popBackStack() }, @@ -220,7 +220,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { UserPreferenceScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier, onNotificationPreferenceClicked = { navController.navigate(NavRoutes.NotificationPreferences.route) }, onCustomerServiceClicked = { navController.navigate(NavRoutes.CustomerService.route) }, onOssClicked = { navController.navigate(NavRoutes.OpenSource.route) }, @@ -244,7 +244,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { NotificationPreferencesScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier, viewModel = viewModel(factory = viewModelFactory), onBackPressed = { navController.popBackStack() } ) @@ -266,7 +266,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { CustomerServiceScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier, viewModel = viewModel(factory = viewModelFactory), onBackPressed = { navController.popBackStack() } ) @@ -288,7 +288,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { OssNoticeScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier, onBackPressed = { navController.popBackStack() } ) } diff --git a/feature/main/src/main/java/com/doyoonkim/main/home/HomeScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/home/HomeScreen.kt index a20af81e..ed7ab9c5 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/home/HomeScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/home/HomeScreen.kt @@ -135,7 +135,7 @@ fun NotificationPreviewList( text = listTitle, color = titleColor, fontSize = 18.sp, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.ExtraBold ) TextButton( diff --git a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeDetailScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeDetailScreen.kt index 39e3402d..8ca028ac 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeDetailScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeDetailScreen.kt @@ -34,12 +34,14 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -48,8 +50,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.doyoonkim.main.viewmodel.NoticeDetailViewModel import com.doyoonkim.model.NoticeVO import com.doyoonkim.common.R +import com.doyoonkim.common.theme.displayBackground import com.doyoonkim.common.theme.subTitle -import com.doyoonkim.common.theme.textPurple +import com.doyoonkim.common.theme.variantPurple +import com.doyoonkim.common.ui.TopAppBarWithBackButton @Composable fun NoticeDetailScreen( @@ -67,127 +71,135 @@ fun NoticeDetailScreen( if (!uiState.isReceived) viewModel.getTargetNoticeById(noticeInfo.first) } - Column( - modifier = modifier - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)), - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally - ) { - if (noticeInfo.second.isNotBlank()) { - if (!uiState.isLoadingCompleted) { - LinearProgressIndicator( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - progress = { - uiState.loadingStatus - } - ) - } - Box( - modifier = Modifier.fillMaxSize() - ) { - AndroidView( - modifier = Modifier.fillMaxSize(), - factory = { context -> - WebView(context).apply { - //Enable Javascript - // Security Alert: XSS Vulnerability - settings.javaScriptEnabled = true - settings.defaultTextEncodingName = "UTF-8" + Scaffold( + topBar = { + TopAppBarWithBackButton( + titleText = uiState.receivedNotice?.title ?: "", + onBackPressed = onBackPressed + ) + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> + Column( + modifier = modifier + .padding(innerPadding), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (noticeInfo.second.isNotBlank()) { + if (!uiState.isLoadingCompleted) { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + progress = { + uiState.loadingStatus + } + ) + } + Box( + modifier = Modifier.fillMaxSize() + ) { + AndroidView( + modifier = Modifier.fillMaxSize(), + factory = { context -> + WebView(context).apply { + //Enable Javascript + // Security Alert: XSS Vulnerability + settings.javaScriptEnabled = true + settings.defaultTextEncodingName = "UTF-8" - webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView?, url: String?) { - val theme = context.resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { - evaluateJavascript( - """ - let div_accessibility = document.getElementById('accessibility'); - let div_header = document.getElementById('header'); - let div_point = document.getElementById('point'); - let div_footer = document.getElementById('footer'); - let div_footer_root = document.getElementById('fb-root'); - - let section_svisual = document.getElementById('svisual'); - let section_location = document.getElementById('location'); - let aside_remote = document.getElementById('remote'); - - let p_board_butt = document.getElementsByClassName('board_butt'); - - div_accessibility.remove(); - div_header.remove(); - div_footer.remove(); - - aside_remote.remove(); - p_board_butt[0].remove(); - - """.trimIndent(), - ) { result -> - Log.d("Android Web View Client", "RESULT: $result") - visibility = View.VISIBLE + evaluateJavascript( + """ + let div_accessibility = document.getElementById('accessibility'); + let div_header = document.getElementById('header'); + let div_point = document.getElementById('point'); + let div_footer = document.getElementById('footer'); + let div_footer_root = document.getElementById('fb-root'); + + let section_svisual = document.getElementById('svisual'); + let section_location = document.getElementById('location'); + let aside_remote = document.getElementById('remote'); + + let p_board_butt = document.getElementsByClassName('board_butt'); + + div_accessibility.remove(); + div_header.remove(); + div_footer.remove(); + + aside_remote.remove(); + p_board_butt[0].remove(); + """.trimIndent(), + ) { result -> + Log.d("Android Web View Client", "RESULT: $result") + visibility = View.VISIBLE + } + super.onPageFinished(view, url) } - super.onPageFinished(view, url) } - } - // For Progress Indicator - webChromeClient = object: WebChromeClient() { - override fun onProgressChanged(view: WebView?, newProgress: Int) { - // Update progress status - viewModel.updateLoadingStatus(newProgress) - super.onProgressChanged(view, newProgress) + // For Progress Indicator + webChromeClient = object: WebChromeClient() { + override fun onProgressChanged(view: WebView?, newProgress: Int) { + // Update progress status + viewModel.updateLoadingStatus(newProgress) + super.onProgressChanged(view, newProgress) + } } - } - setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength -> - val request = DownloadManager.Request(url.toUri()) - val filename = URLUtil.guessFileName(url, contentDisposition, mimetype).also { Log.d("DownloadManager", "Filename: $it") } - // save session data before downloading the target file. - val cookies = CookieManager.getInstance().getCookie(url) + setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength -> + val request = DownloadManager.Request(url.toUri()) + val filename = URLUtil.guessFileName(url, contentDisposition, mimetype).also { Log.d("DownloadManager", "Filename: $it") } + // save session data before downloading the target file. + val cookies = CookieManager.getInstance().getCookie(url) - request.apply { - setMimeType(mimetype) - addRequestHeader("cookie", cookies) - addRequestHeader("User-Agent", userAgent) - setDescription("Downloading File") - setTitle(filename) -// allowScanningByMediaScanner() Deprecated. - setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename) - } - val downloadManager = context.getSystemService(DOWNLOAD_SERVICE) as DownloadManager - downloadManager.enqueue(request).also { - Toast.makeText(context, R.string.text_download, Toast.LENGTH_LONG).show() - // Guide user to the File application - context.startActivity(Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)) + request.apply { + setMimeType(mimetype) + addRequestHeader("cookie", cookies) + addRequestHeader("User-Agent", userAgent) + setDescription("Downloading File") + setTitle(filename) +// allowScanningByMediaScanner() Deprecated. + setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename) + } + val downloadManager = context.getSystemService(DOWNLOAD_SERVICE) as DownloadManager + downloadManager.enqueue(request).also { + Toast.makeText(context, R.string.text_download, Toast.LENGTH_LONG).show() + // Guide user to the File application + context.startActivity(Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)) + } } - } - visibility = View.INVISIBLE - settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW - loadUrl(noticeInfo.second) + visibility = View.INVISIBLE + settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW + loadUrl(noticeInfo.second) + } } - } - ) + ) - if (noticeInfo.third) { - FloatingActionButton( - modifier = Modifier.wrapContentSize() - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) - .padding(end = 10.dp, bottom = 30.dp) - .align(Alignment.BottomEnd), - onClick = { - if (uiState.isReceived) uiState.receivedNotice?.let(onBookmarkCreate) - }, - containerColor = if (uiState.isReceived) { - MaterialTheme.colorScheme.textPurple - } else { - MaterialTheme.colorScheme.subTitle + if (noticeInfo.third) { + FloatingActionButton( + modifier = Modifier.wrapContentSize() + .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) + .padding(end = 10.dp, bottom = 30.dp) + .align(Alignment.BottomEnd), + onClick = { + if (uiState.isReceived) uiState.receivedNotice?.let(onBookmarkCreate) + }, + containerColor = if (uiState.isReceived) { + MaterialTheme.colorScheme.variantPurple + } else { + MaterialTheme.colorScheme.subTitle + } + ) { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = "Add to bookmark", + tint = Color.White + ) } - ) { - Icon( - imageVector = Icons.Filled.Add, - contentDescription = "Add to bookmark", - tint = Color.White - ) } } } @@ -198,29 +210,5 @@ fun NoticeDetailScreen( @Preview(showSystemUi = true, showBackground = true) @Composable fun NoticeDetailScreen_Preview() { - Box( - modifier = Modifier.fillMaxSize() - ) { - FloatingActionButton( - modifier = Modifier.wrapContentSize() - .padding(end = 10.dp, bottom = 30.dp) - .align(Alignment.BottomEnd), - onClick = { - // TODO Set proper paramter for onBookmarkCreate() -// if (true) uiState.receivedNotice?.let() - }, - containerColor = if (true) { - MaterialTheme.colorScheme.textPurple - } else { - MaterialTheme.colorScheme.subTitle - } - ) { - Icon( - imageVector = Icons.Filled.Add, - contentDescription = "Add to bookmark", - tint = Color.White - ) - } - } } \ No newline at end of file diff --git a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt index a9cb8013..24ae5174 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt @@ -4,51 +4,51 @@ import androidx.activity.compose.BackHandler import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.doyoonkim.main.viewmodel.NoticeSearchViewModel import com.doyoonkim.common.R -import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.theme.secondaryBackground +import com.doyoonkim.common.theme.displayBackground import com.doyoonkim.common.theme.subTitle -import com.doyoonkim.common.theme.textPurple import com.doyoonkim.common.theme.title +import com.doyoonkim.common.theme.variantPurple import com.doyoonkim.common.ui.NotificationPreview -import kotlin.math.sin +@OptIn(ExperimentalMaterial3Api::class) @Composable fun NoticeSearchScreen( modifier: Modifier = Modifier, @@ -65,62 +65,67 @@ fun NoticeSearchScreen( viewModel.observeKeywordInput() } - Column( - modifier = modifier - .fillMaxWidth() - .wrapContentHeight() - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) - .pointerInput(Unit) { - detectTapGestures( - onTap = { localFocusManager.clearFocus() } + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + TopAppBar( + title = { + TextField( + modifier = Modifier + .fillMaxSize() + .wrapContentHeight() + .padding(2.dp), + value = uiState.searchKeyword, + placeholder = { Text(stringResource(R.string.title_search)) }, + onValueChange = { viewModel.updateSearchKeyword(it) }, + colors = TextFieldDefaults.colors( + focusedTextColor = MaterialTheme.colorScheme.title, + unfocusedTextColor = MaterialTheme.colorScheme.subTitle, + focusedContainerColor = MaterialTheme.colorScheme.secondaryBackground, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryBackground, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp), + singleLine = true + ) + }, + actions = { + IconButton( + onClick = { onBackPressed() } + ) { + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = null, + tint = MaterialTheme.colorScheme.title + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.displayBackground, + titleContentColor = MaterialTheme.colorScheme.title ) - } - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - horizontalArrangement = Arrangement.spacedBy(3.dp), - verticalAlignment = Alignment.CenterVertically - ) { - TextField( - modifier = Modifier - .weight(8f) - .wrapContentHeight() - .padding(2.dp), - value = uiState.searchKeyword, - placeholder = { Text(stringResource(R.string.title_search)) }, - onValueChange = { viewModel.updateSearchKeyword(it) }, - colors = TextFieldDefaults.colors( - focusedTextColor = MaterialTheme.colorScheme.title, - unfocusedTextColor = MaterialTheme.colorScheme.subTitle, - focusedContainerColor = MaterialTheme.colorScheme.containerBackground, - unfocusedContainerColor = MaterialTheme.colorScheme.containerBackground, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent - ), - shape = RoundedCornerShape(15.dp), - singleLine = true, - ) - } - + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() - .align(Alignment.CenterHorizontally) + .padding(innerPadding) ) { LazyColumn( modifier = Modifier .fillMaxWidth() - .wrapContentHeight(), + .wrapContentHeight() + .align(Alignment.TopCenter), contentPadding = PaddingValues(3.dp) ) { items(uiState.fetchResult) { notice -> HorizontalDivider( Modifier.fillMaxWidth().padding(start = 10.dp, end = 10.dp), - color = MaterialTheme.colorScheme.containerBackground + color = MaterialTheme.colorScheme.secondaryBackground ) Row( @@ -140,15 +145,15 @@ fun NoticeSearchScreen( } } - androidx.compose.animation.AnimatedVisibility( + androidx.compose.animation.AnimatedVisibility( visible = uiState.isFetching, modifier = Modifier.wrapContentSize().align(Alignment.Center), enter = scaleIn(), exit = scaleOut() ) { CircularProgressIndicator( - color = MaterialTheme.colorScheme.textPurple, - trackColor = MaterialTheme.colorScheme.containerBackground + color = MaterialTheme.colorScheme.variantPurple, + trackColor = MaterialTheme.colorScheme.secondaryBackground ) } } diff --git a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticesInCategoryScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticesInCategoryScreen.kt index 3f11e42e..efd69c4a 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticesInCategoryScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticesInCategoryScreen.kt @@ -1,7 +1,6 @@ package com.doyoonkim.main.notice import androidx.activity.compose.BackHandler -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -17,27 +16,44 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.R +import com.doyoonkim.common.theme.secondaryBackground import com.doyoonkim.common.theme.displayBackground -import com.doyoonkim.common.theme.textPurple +import com.doyoonkim.common.theme.onAnyBackground +import com.doyoonkim.common.theme.title +import com.doyoonkim.common.theme.variantPurple import com.doyoonkim.common.ui.NotificationPreview +import com.doyoonkim.common.ui.TopAppBarWithBackButton import com.doyoonkim.main.viewmodel.NoticesInCategoryViewModel import com.doyoonkim.model.NoticeCategory -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun NoticesInCategoryScreen( modifier: Modifier, @@ -47,7 +63,13 @@ fun NoticesInCategoryScreen( onNoticeSelected: (Int, String) -> Unit ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() - + val scaffoldTitle = when (category) { + NoticeCategory.GENERAL_NEWS -> R.string.general_news + NoticeCategory.ACADEMIC_NEWS -> R.string.academic_news + NoticeCategory.SCHOLARSHIP_NEWS -> R.string.scholarship_news + NoticeCategory.EVENT_NEWS -> R.string.event_news + else -> R.string.app_name + } // Back Handler BackHandler { onBackButtonPressed() } @@ -63,59 +85,70 @@ fun NoticesInCategoryScreen( viewModel.getNoticesPerPageInCategory(category) } - Box( - modifier = modifier.fillMaxWidth() - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) - .pullRefresh(pullRefreshState) - ) { - LazyColumn( - Modifier.fillMaxWidth().wrapContentHeight(), - verticalArrangement = Arrangement.spacedBy(5.dp), - userScrollEnabled = true + Scaffold( + topBar = { + TopAppBarWithBackButton( + titleText = stringResource(scaffoldTitle), + onBackPressed = onBackButtonPressed + ) + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> + Box( + modifier = modifier.fillMaxWidth() + .padding(innerPadding) +// .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) + .pullRefresh(pullRefreshState) ) { - items(uiState.notices.size) { index -> - if (index == uiState.notices.size - 1) { - Row( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - CircularProgressIndicator( - modifier = Modifier.wrapContentSize(), - color = MaterialTheme.colorScheme.textPurple, - trackColor = MaterialTheme.colorScheme.displayBackground - ) - } - viewModel.requestMoreNotices() - } else { - if (index != 0) { - HorizontalDivider( - Modifier.fillMaxWidth(), - color =MaterialTheme.colorScheme.containerBackground, - thickness = 1.2.dp - ) - } - val notice = uiState.notices[index] - Row( - modifier = Modifier.wrapContentSize() - .clickable { onNoticeSelected(notice.nttId, notice.url) } - ) { - NotificationPreview( - isLoading = uiState.isLoading, - notificationTitle = notice.title, - notificationInfo = "[${notice.departName}] ${notice.timestamp}", - isImageContained = notice.imageUrl != null, - imageUrl = notice.imageUrl ?: "" - ) + LazyColumn( + Modifier.fillMaxWidth().wrapContentHeight(), + verticalArrangement = Arrangement.spacedBy(5.dp), + userScrollEnabled = true + ) { + items(uiState.notices.size) { index -> + if (index == uiState.notices.size - 1) { + Row( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + CircularProgressIndicator( + modifier = Modifier.wrapContentSize(), + color = MaterialTheme.colorScheme.variantPurple, + trackColor = MaterialTheme.colorScheme.displayBackground + ) + } + viewModel.requestMoreNotices() + } else { + if (index != 0) { + HorizontalDivider( + Modifier.fillMaxWidth(), + color =MaterialTheme.colorScheme.onAnyBackground, + thickness = 1.2.dp + ) + } + val notice = uiState.notices[index] + Row( + modifier = Modifier.wrapContentSize() + .clickable { onNoticeSelected(notice.nttId, notice.url) } + ) { + NotificationPreview( + isLoading = uiState.isLoading, + notificationTitle = notice.title, + notificationInfo = "[${notice.departName}] ${notice.timestamp}", + isImageContained = notice.imageUrl != null, + imageUrl = notice.imageUrl ?: "" + ) + } } } } + PullRefreshIndicator( + modifier = Modifier.align(Alignment.TopCenter) + .padding(top = 10.dp), + refreshing = uiState.isRefreshRequested, + state = pullRefreshState + ) } - PullRefreshIndicator( - modifier = Modifier.align(Alignment.TopCenter) - .padding(top = 10.dp), - refreshing = uiState.isRefreshRequested, - state = pullRefreshState - ) } } \ No newline at end of file diff --git a/feature/main/src/main/java/com/doyoonkim/main/preference/CustomerServiceScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/preference/CustomerServiceScreen.kt index ddce10dd..d6d3877d 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/preference/CustomerServiceScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/preference/CustomerServiceScreen.kt @@ -4,28 +4,24 @@ import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button -import androidx.compose.material3.ButtonColors import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextField @@ -36,6 +32,8 @@ 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.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -43,12 +41,12 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.doyoonkim.main.viewmodel.CustomerServiceViewModel import com.doyoonkim.common.R -import com.doyoonkim.common.theme.buttonContainer -import com.doyoonkim.common.theme.buttonPurple -import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.theme.displayBackground +import com.doyoonkim.common.theme.secondaryBackground import com.doyoonkim.common.theme.subTitle -import com.doyoonkim.common.theme.textPurple import com.doyoonkim.common.theme.title +import com.doyoonkim.common.theme.variantPurple +import com.doyoonkim.common.ui.TopAppBarWithBackButton @Composable fun CustomerServiceScreen( @@ -60,135 +58,147 @@ fun CustomerServiceScreen( // Version Information for Report Submission val versionInfo = stringResource(R.string.version_code) val adjustImePadding = Modifier.consumeWindowInsets(WindowInsets.ime).imePadding() - + val localFocusManager = LocalFocusManager.current BackHandler { onBackPressed() } - Box( - modifier = modifier.fillMaxSize() - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) - ) { - Column( - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = stringResource(R.string.customer_service_subtitile_1), - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.textPurple, - fontSize = 14.sp, - modifier = Modifier.fillMaxWidth() - ) - Text( - text = stringResource(R.string.customer_service_subtitle_2), - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.textPurple, - fontSize = 14.sp, - modifier = Modifier.fillMaxWidth() + Scaffold( + topBar = { + TopAppBarWithBackButton( + titleText = stringResource(R.string.title_customer_service), + onBackPressed = onBackPressed ) - - - Box( - modifier = Modifier.fillMaxWidth().weight(5f) - .padding(top = 25.dp, bottom = 25.dp) - .then(adjustImePadding) + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> + Box( + modifier = modifier.fillMaxSize() + .padding(innerPadding) + .pointerInput(Unit) { + detectTapGestures( + onTap = { localFocusManager.clearFocus() } + ) + } + ) { + Column( + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally ) { - TextField( - modifier = Modifier.fillMaxSize(), - value = uiState.userReport, - placeholder = { Text(stringResource(R.string.placeholder_customer_report)) }, - enabled = !uiState.isSubmissionCompleted, - onValueChange = { - viewModel.updateUserReportContent(it) - }, - colors = TextFieldDefaults.colors( - focusedTextColor = MaterialTheme.colorScheme.title, - unfocusedTextColor = MaterialTheme.colorScheme.subTitle, - focusedContainerColor = MaterialTheme.colorScheme.containerBackground, - unfocusedContainerColor = MaterialTheme.colorScheme.containerBackground, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent - ), - shape = RoundedCornerShape(15.dp) - ) - Text( - text = "${uiState.userReport.length}/500", - fontSize = 12.sp, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.subTitle, - modifier = Modifier.wrapContentSize() - .padding(15.dp) - .align(Alignment.BottomEnd) + text = stringResource(R.string.customer_service_subtitile_1), + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.variantPurple, + fontSize = 14.sp, + modifier = Modifier.fillMaxWidth() ) - } - - Button( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(start = 3.dp, end = 3.dp) - , - enabled = !uiState.isSubmissionCompleted && uiState.exceedMinCharacters, - shape = RoundedCornerShape(10.dp), - colors = ButtonDefaults.buttonColors().copy( - containerColor = MaterialTheme.colorScheme.buttonPurple, - contentColor = Color.White, - ), - onClick = { viewModel.submitUserReport(versionInfo) } - ) { Text( - text = stringResource(R.string.btn_submit), - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(10.dp) + text = stringResource(R.string.customer_service_subtitle_2), + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.variantPurple, + fontSize = 14.sp, + modifier = Modifier.fillMaxWidth() ) - } - } - AnimatedVisibility( - modifier = Modifier.wrapContentSize().align(Alignment.Center), - visible = uiState.isSubmissionCompleted, - enter = scaleIn(), - exit = scaleOut() - ) { - Surface( - modifier = Modifier.padding(15.dp) - .clip(RoundedCornerShape(15.dp)), - color = MaterialTheme.colorScheme.surfaceBright - ) { - Column( - modifier = Modifier.wrapContentHeight() - .padding(30.dp), - verticalArrangement = Arrangement.spacedBy(10.dp) + Box( + modifier = Modifier.fillMaxWidth().weight(5f) + .padding(top = 25.dp, bottom = 25.dp) + .then(adjustImePadding) ) { + TextField( + modifier = Modifier.fillMaxSize(), + value = uiState.userReport, + placeholder = { Text(stringResource(R.string.placeholder_customer_report)) }, + enabled = !uiState.isSubmissionCompleted, + onValueChange = { + viewModel.updateUserReportContent(it) + }, + colors = TextFieldDefaults.colors( + focusedTextColor = MaterialTheme.colorScheme.title, + unfocusedTextColor = MaterialTheme.colorScheme.subTitle, + focusedContainerColor = MaterialTheme.colorScheme.secondaryBackground, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryBackground, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent + ), + shape = RoundedCornerShape(15.dp) + ) + Text( - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - text = stringResource(R.string.submission_completed_title) + text = "${uiState.userReport.length}/500", + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.subTitle, + modifier = Modifier.wrapContentSize() + .padding(15.dp) + .align(Alignment.BottomEnd) ) + } + + Button( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(start = 3.dp, end = 3.dp) + , + enabled = !uiState.isSubmissionCompleted && uiState.exceedMinCharacters, + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors().copy( + containerColor = MaterialTheme.colorScheme.variantPurple, + contentColor = Color.White, + ), + onClick = { viewModel.submitUserReport(versionInfo) } + ) { Text( - fontSize = 14.sp, + text = stringResource(R.string.btn_submit), + fontSize = 16.sp, fontWeight = FontWeight.Bold, - text = stringResource(R.string.submission_completed__subtitle) + modifier = Modifier.padding(10.dp) ) - Button( - onClick = { viewModel.resetSubmissionStatus() }, - modifier = Modifier.fillMaxWidth() + } + } + + AnimatedVisibility( + modifier = Modifier.wrapContentSize().align(Alignment.Center), + visible = uiState.isSubmissionCompleted, + enter = scaleIn(), + exit = scaleOut() + ) { + Surface( + modifier = Modifier.padding(15.dp) + .clip(RoundedCornerShape(15.dp)), + color = MaterialTheme.colorScheme.surfaceBright + ) { + Column( + modifier = Modifier.wrapContentHeight() + .padding(30.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) ) { + Text( + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + text = stringResource(R.string.submission_completed_title) + ) Text( fontSize = 14.sp, fontWeight = FontWeight.Bold, - text = stringResource(R.string.btn_confirm) + text = stringResource(R.string.submission_completed__subtitle) ) + Button( + onClick = { viewModel.resetSubmissionStatus() }, + modifier = Modifier.fillMaxWidth() + ) { + Text( + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + text = stringResource(R.string.btn_confirm) + ) + } } } - } } - } } \ No newline at end of file diff --git a/feature/main/src/main/java/com/doyoonkim/main/preference/NotificationPreferencesScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/preference/NotificationPreferencesScreen.kt index 84e24106..2659c625 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/preference/NotificationPreferencesScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/preference/NotificationPreferencesScreen.kt @@ -9,12 +9,14 @@ import android.content.pm.PackageManager import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults @@ -33,13 +35,15 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.doyoonkim.common.R -import com.doyoonkim.common.theme.buttonPurple -import com.doyoonkim.common.theme.containerBackground +import com.doyoonkim.common.theme.displayBackground +import com.doyoonkim.common.theme.onAnyBackground +import com.doyoonkim.common.theme.secondaryBackground import com.doyoonkim.common.theme.subTitle -import com.doyoonkim.common.theme.textPurple import com.doyoonkim.common.theme.title +import com.doyoonkim.common.theme.variantPurple import com.doyoonkim.common.ui.RoundedCornerColumn import com.doyoonkim.common.ui.RoundedCornerColumnTextItemWithExtraOnRight +import com.doyoonkim.common.ui.TopAppBarWithBackButton import com.doyoonkim.main.viewmodel.NotificationPreferencesViewModel @Composable @@ -72,145 +76,154 @@ fun NotificationPreferencesScreen( ) } - RoundedCornerColumn( - modifier = Modifier.fillMaxWidth().padding(10.dp), - backgroundColor = MaterialTheme.colorScheme.containerBackground - ) { - RoundedCornerColumnTextItemWithExtraOnRight( - verticalPadding = 15.dp, - titleText = stringResource(R.string.enable_notification_title), - subTitleText = stringResource(R.string.enable_service_notification_sub), - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = true - ) { - Switch( - checked = uiStatus.isMainNotificationPermissionGranted, - colors = SwitchDefaults.colors().copy( - checkedTrackColor = MaterialTheme.colorScheme.buttonPurple, - checkedThumbColor = Color.White - ), - onCheckedChange = { - val settingIntent = Intent( - "android.settings.APP_NOTIFICATION_SETTINGS" - ).apply { - this.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - this.putExtra( - "android.provider.extra.APP_PACKAGE", - context.packageName - ) - } - context.startActivity(settingIntent) - }, - enabled = uiStatus.isSyncCompleted + Scaffold( + topBar = { + TopAppBarWithBackButton( + titleText = stringResource(R.string.title_notification_pref), + onBackPressed = onBackPressed ) - } - - RoundedCornerColumnTextItemWithExtraOnRight( - modifier = Modifier.padding(start = 10.dp), - verticalPadding = 15.dp, - titleText = stringResource(R.string.general_notificaiton_channel_name), - subTitleText = stringResource(R.string.general_notification_channel_description), - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = true + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> + RoundedCornerColumn( + modifier = Modifier.fillMaxWidth().padding(innerPadding), + backgroundColor = MaterialTheme.colorScheme.secondaryBackground ) { - Switch( - checked = uiStatus.isEachChannelAllowed[0], - colors = SwitchDefaults.colors().copy( - checkedTrackColor = MaterialTheme.colorScheme.buttonPurple, - checkedThumbColor = Color.White - ), - onCheckedChange = { - viewModel.updateChannelPreferenceState(0, it) - }, - enabled = uiStatus.isMainNotificationPermissionGranted && uiStatus.isSyncCompleted - ) - } + RoundedCornerColumnTextItemWithExtraOnRight( + verticalPadding = 15.dp, + titleText = stringResource(R.string.enable_notification_title), + subTitleText = stringResource(R.string.enable_service_notification_sub), + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = true + ) { + Switch( + checked = uiStatus.isMainNotificationPermissionGranted, + colors = SwitchDefaults.colors().copy( + checkedTrackColor = MaterialTheme.colorScheme.variantPurple, + checkedThumbColor = Color.White + ), + onCheckedChange = { + val settingIntent = Intent( + "android.settings.APP_NOTIFICATION_SETTINGS" + ).apply { + this.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + this.putExtra( + "android.provider.extra.APP_PACKAGE", + context.packageName + ) + } + context.startActivity(settingIntent) + }, + enabled = uiStatus.isSyncCompleted + ) + } - RoundedCornerColumnTextItemWithExtraOnRight( - modifier = Modifier.padding(start = 10.dp), - verticalPadding = 15.dp, - titleText = stringResource(R.string.academic_notification_channel_name), - subTitleText = stringResource(R.string.academic_notification_channel_description), - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = true - ) { - Switch( - checked = uiStatus.isEachChannelAllowed[1], - colors = SwitchDefaults.colors().copy( - checkedTrackColor = MaterialTheme.colorScheme.buttonPurple, - checkedThumbColor = Color.White - ), - onCheckedChange = { - viewModel.updateChannelPreferenceState(1, it) - }, - enabled = uiStatus.isMainNotificationPermissionGranted && uiStatus.isSyncCompleted - ) - } + RoundedCornerColumnTextItemWithExtraOnRight( + modifier = Modifier.padding(start = 10.dp), + verticalPadding = 15.dp, + titleText = stringResource(R.string.general_notificaiton_channel_name), + subTitleText = stringResource(R.string.general_notification_channel_description), + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = true + ) { + Switch( + checked = uiStatus.isEachChannelAllowed[0], + colors = SwitchDefaults.colors().copy( + checkedTrackColor = MaterialTheme.colorScheme.variantPurple, + checkedThumbColor = Color.White + ), + onCheckedChange = { + viewModel.updateChannelPreferenceState(0, it) + }, + enabled = uiStatus.isMainNotificationPermissionGranted && uiStatus.isSyncCompleted + ) + } - RoundedCornerColumnTextItemWithExtraOnRight( - modifier = Modifier.padding(start = 10.dp), - verticalPadding = 15.dp, - titleText = stringResource(R.string.scholarship_notification_channel_name), - subTitleText = stringResource(R.string.scholarship_notification_channel_description), - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = true - ) { - Switch( - checked = uiStatus.isEachChannelAllowed[2], - colors = SwitchDefaults.colors().copy( - checkedTrackColor = MaterialTheme.colorScheme.buttonPurple, - checkedThumbColor = Color.White - ), - onCheckedChange = { - viewModel.updateChannelPreferenceState(2, it) - }, - enabled = uiStatus.isMainNotificationPermissionGranted && uiStatus.isSyncCompleted - ) - } + RoundedCornerColumnTextItemWithExtraOnRight( + modifier = Modifier.padding(start = 10.dp), + verticalPadding = 15.dp, + titleText = stringResource(R.string.academic_notification_channel_name), + subTitleText = stringResource(R.string.academic_notification_channel_description), + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = true + ) { + Switch( + checked = uiStatus.isEachChannelAllowed[1], + colors = SwitchDefaults.colors().copy( + checkedTrackColor = MaterialTheme.colorScheme.variantPurple, + checkedThumbColor = Color.White + ), + onCheckedChange = { + viewModel.updateChannelPreferenceState(1, it) + }, + enabled = uiStatus.isMainNotificationPermissionGranted && uiStatus.isSyncCompleted + ) + } - RoundedCornerColumnTextItemWithExtraOnRight( - modifier = Modifier.padding(start = 10.dp), - verticalPadding = 15.dp, - titleText = stringResource(R.string.event_notification_channel_name), - subTitleText = stringResource(R.string.event_notification_channel_description), - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = false - ) { - Switch( - checked = uiStatus.isEachChannelAllowed[3], - colors = SwitchDefaults.colors().copy( - checkedTrackColor = MaterialTheme.colorScheme.buttonPurple, - checkedThumbColor = Color.White - ), - onCheckedChange = { - viewModel.updateChannelPreferenceState(3, it) - }, - enabled = uiStatus.isMainNotificationPermissionGranted && uiStatus.isSyncCompleted - ) - } - } + RoundedCornerColumnTextItemWithExtraOnRight( + modifier = Modifier.padding(start = 10.dp), + verticalPadding = 15.dp, + titleText = stringResource(R.string.scholarship_notification_channel_name), + subTitleText = stringResource(R.string.scholarship_notification_channel_description), + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = true + ) { + Switch( + checked = uiStatus.isEachChannelAllowed[2], + colors = SwitchDefaults.colors().copy( + checkedTrackColor = MaterialTheme.colorScheme.variantPurple, + checkedThumbColor = Color.White + ), + onCheckedChange = { + viewModel.updateChannelPreferenceState(2, it) + }, + enabled = uiStatus.isMainNotificationPermissionGranted && uiStatus.isSyncCompleted + ) + } - if (!uiStatus.isSyncCompleted) { - Box( - modifier = Modifier.fillMaxSize() - .background(Color.Transparent) - ) { - Surface( - modifier = Modifier.align(Alignment.Center) + RoundedCornerColumnTextItemWithExtraOnRight( + modifier = Modifier.padding(start = 10.dp), + verticalPadding = 15.dp, + titleText = stringResource(R.string.event_notification_channel_name), + subTitleText = stringResource(R.string.event_notification_channel_description), + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = false + ) { + Switch( + checked = uiStatus.isEachChannelAllowed[3], + colors = SwitchDefaults.colors().copy( + checkedTrackColor = MaterialTheme.colorScheme.variantPurple, + checkedThumbColor = Color.White + ), + onCheckedChange = { + viewModel.updateChannelPreferenceState(3, it) + }, + enabled = uiStatus.isMainNotificationPermissionGranted && uiStatus.isSyncCompleted + ) + } + } + if (!uiStatus.isSyncCompleted) { + Box( + modifier = Modifier.fillMaxSize() .background(Color.Transparent) - .clip(RoundedCornerShape(20.dp)), - color = MaterialTheme.colorScheme.containerBackground ) { - CircularProgressIndicator( + Surface( modifier = Modifier.align(Alignment.Center) - .padding(25.dp), - color = MaterialTheme.colorScheme.textPurple - ) + .background(Color.Transparent) + .clip(RoundedCornerShape(20.dp)), + color = MaterialTheme.colorScheme.onAnyBackground + ) { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center) + .padding(25.dp), + color = MaterialTheme.colorScheme.variantPurple + ) + } } } } diff --git a/feature/main/src/main/java/com/doyoonkim/main/preference/OssNoticeScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/preference/OssNoticeScreen.kt index ddcf9867..b934b2f4 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/preference/OssNoticeScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/preference/OssNoticeScreen.kt @@ -4,12 +4,20 @@ import android.webkit.WebView import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView +import com.doyoonkim.common.ui.TopAppBarWithBackButton +import com.doyoonkim.common.R +import com.doyoonkim.common.theme.displayBackground @Composable fun OssNoticeScreen( @@ -18,12 +26,22 @@ fun OssNoticeScreen( ) { BackHandler { onBackPressed() } - AndroidView( - modifier = modifier.windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)), - factory = { context -> - WebView(context).apply { - loadUrl("https://knutice.github.io/KNUTICE-OpenSourceLicense/Android/opensource.html") + Scaffold( + topBar = { + TopAppBarWithBackButton( + titleText = stringResource(R.string.oss_notice), + onBackPressed = onBackPressed + ) + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> + AndroidView( + modifier = modifier.fillMaxSize().padding(innerPadding), + factory = { context -> + WebView(context).apply { + loadUrl("https://knutice.github.io/KNUTICE-OpenSourceLicense/Android/opensource.html") + } } - } - ) + ) + } } \ No newline at end of file diff --git a/feature/main/src/main/java/com/doyoonkim/main/preference/UserPreferencesScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/preference/UserPreferencesScreen.kt index 155f50c7..e20ab27d 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/preference/UserPreferencesScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/preference/UserPreferencesScreen.kt @@ -5,24 +5,35 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.doyoonkim.common.R -import com.doyoonkim.common.theme.buttonContainer -import com.doyoonkim.common.theme.containerBackground -import com.doyoonkim.common.theme.containerBackgroundSolid -import com.doyoonkim.common.theme.containerGray +import com.doyoonkim.common.theme.buttonOnBackground +import com.doyoonkim.common.theme.secondaryBackground +import com.doyoonkim.common.theme.displayBackground import com.doyoonkim.common.theme.subTitle import com.doyoonkim.common.theme.title import com.doyoonkim.common.ui.CircleGoButton import com.doyoonkim.common.ui.RoundedCornerColumn import com.doyoonkim.common.ui.RoundedCornerColumnTextItem import com.doyoonkim.common.ui.RoundedCornerColumnTextItemWithExtraOnRight +import com.doyoonkim.common.ui.TopAppBarWithBackButton @Composable fun UserPreferenceScreen( @@ -34,78 +45,88 @@ fun UserPreferenceScreen( ) { BackHandler { onBackPressed() } - Column( - modifier = modifier.fillMaxWidth() - .padding(10.dp), - verticalArrangement = Arrangement.spacedBy(30.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - RoundedCornerColumn( - backgroundColor = MaterialTheme.colorScheme.containerBackgroundSolid + Scaffold( + topBar = { + TopAppBarWithBackButton( + titleText = stringResource(R.string.title_preference), + onBackPressed = onBackPressed + ) + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> + Column( + modifier = modifier.fillMaxWidth() + .padding(innerPadding), + verticalArrangement = Arrangement.spacedBy(30.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { - RoundedCornerColumnTextItemWithExtraOnRight( - verticalPadding = 10.dp, - titleText = stringResource(R.string.enable_notification_title), - subTitleText = null, - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = false + RoundedCornerColumn( + backgroundColor = MaterialTheme.colorScheme.secondaryBackground ) { - CircleGoButton( - modifier = Modifier.weight(1f), - containerColor = MaterialTheme.colorScheme.containerGray, - contentColor = MaterialTheme.colorScheme.subTitle, - onClick = onNotificationPreferenceClicked - ) + RoundedCornerColumnTextItemWithExtraOnRight( + verticalPadding = 10.dp, + titleText = stringResource(R.string.enable_notification_title), + subTitleText = null, + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = false + ) { + CircleGoButton( + modifier = Modifier.weight(1f), + containerColor = MaterialTheme.colorScheme.buttonOnBackground, + contentColor = MaterialTheme.colorScheme.subTitle, + onClick = onNotificationPreferenceClicked + ) + } } - } - RoundedCornerColumn( - backgroundColor = MaterialTheme.colorScheme.containerBackground - ) { - RoundedCornerColumnTextItemWithExtraOnRight( - verticalPadding = 10.dp, - titleText = stringResource(R.string.title_support), - subTitleText = null, - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = false + RoundedCornerColumn( + backgroundColor = MaterialTheme.colorScheme.secondaryBackground ) { - CircleGoButton( - modifier = Modifier.weight(1f), - containerColor = MaterialTheme.colorScheme.buttonContainer, - contentColor = MaterialTheme.colorScheme.subTitle, - onClick = onCustomerServiceClicked - ) + RoundedCornerColumnTextItemWithExtraOnRight( + verticalPadding = 10.dp, + titleText = stringResource(R.string.title_support), + subTitleText = null, + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = false + ) { + CircleGoButton( + modifier = Modifier.weight(1f), + containerColor = MaterialTheme.colorScheme.buttonOnBackground, + contentColor = MaterialTheme.colorScheme.subTitle, + onClick = onCustomerServiceClicked + ) + } } - } - RoundedCornerColumn( - backgroundColor = MaterialTheme.colorScheme.containerBackground - ) { - RoundedCornerColumnTextItem( - verticalPadding = 12.dp, - titleText = stringResource(R.string.about_version), - subTitleText = stringResource(R.string.version_code), - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = true - ) - - RoundedCornerColumnTextItemWithExtraOnRight( - verticalPadding = 10.dp, - titleText = stringResource(R.string.about_oss), - subTitleText = null, - primaryColor = MaterialTheme.colorScheme.title, - secondaryColor = MaterialTheme.colorScheme.subTitle, - hasBottomDivider = false + RoundedCornerColumn( + backgroundColor = MaterialTheme.colorScheme.secondaryBackground ) { - CircleGoButton( - modifier = Modifier.weight(1f), - containerColor = MaterialTheme.colorScheme.buttonContainer, - contentColor = MaterialTheme.colorScheme.subTitle, - onClick = onOssClicked + RoundedCornerColumnTextItem( + verticalPadding = 12.dp, + titleText = stringResource(R.string.about_version), + subTitleText = stringResource(R.string.version_code), + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = true ) + + RoundedCornerColumnTextItemWithExtraOnRight( + verticalPadding = 10.dp, + titleText = stringResource(R.string.about_oss), + subTitleText = null, + primaryColor = MaterialTheme.colorScheme.title, + secondaryColor = MaterialTheme.colorScheme.subTitle, + hasBottomDivider = false + ) { + CircleGoButton( + modifier = Modifier.weight(1f), + containerColor = MaterialTheme.colorScheme.buttonOnBackground, + contentColor = MaterialTheme.colorScheme.subTitle, + onClick = onOssClicked + ) + } } } } From 38047f1d2d1342be1886ffc701dc6dced0f9ff46 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 00:34:17 +0900 Subject: [PATCH 10/16] KAN-15 [FEAT] Add Shared TopAppBar Composable - Add globally used pre-defined TopAppBar Comopsable to increase efficiency. * TopAppBar would have a title text and pre-defined 'back button' with Backward Arrow Icon. --- .../common/ui/TopAppBarWithBackButton.kt | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 common/src/main/java/com/doyoonkim/common/ui/TopAppBarWithBackButton.kt diff --git a/common/src/main/java/com/doyoonkim/common/ui/TopAppBarWithBackButton.kt b/common/src/main/java/com/doyoonkim/common/ui/TopAppBarWithBackButton.kt new file mode 100644 index 00000000..aad0a17c --- /dev/null +++ b/common/src/main/java/com/doyoonkim/common/ui/TopAppBarWithBackButton.kt @@ -0,0 +1,64 @@ +package com.doyoonkim.common.ui + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.sp +import com.doyoonkim.common.R +import com.doyoonkim.common.theme.displayBackground +import com.doyoonkim.common.theme.title + +/** + * @author kimdoyoon + * Created 6/23/25 at 11:38 PM + */ + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TopAppBarWithBackButton( + modifier: Modifier = Modifier, + titleText: String, + onBackPressed: () -> Unit +) { + TopAppBar( + title = { + Text( + modifier = modifier.fillMaxWidth(), + text = titleText, + textAlign = TextAlign.Left, + fontSize = 20.sp, + fontWeight = FontWeight.ExtraBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = { + IconButton( + onClick = { onBackPressed() } + ) { + Icon( + imageVector = Icons.AutoMirrored.Default.ArrowBack, + contentDescription = null + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.displayBackground, + titleContentColor = MaterialTheme.colorScheme.title, + navigationIconContentColor = MaterialTheme.colorScheme.title + ) + ) +} \ No newline at end of file From ebeac0b7b760ed0962f4ee83f4c7c54140f7a9b8 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 02:56:45 +0900 Subject: [PATCH 11/16] KAN-15 [REFACTOR] Separate TopAppBar from Shared Scaffold - Centralized TopAppBar has been decentralized from the Shared Scaffold. * Each Home and BookmarkListScreen would now have their own TopAppBar. * Globally used TopAppBar (TopAppBarWithActions) has been defined under :common. * Duplicated Top Padding has been removed. --- .../java/com/doyoonkim/knutice/AppNavHost.kt | 7 +- ...pAppBarWithBackButton.kt => TopAppBars.kt} | 45 ++++++ .../bookmark/list/BookmarkListScreen.kt | 95 +++++++++--- .../com/doyoonkim/main/home/HomeScreen.kt | 137 ++++++++++++------ 4 files changed, 217 insertions(+), 67 deletions(-) rename common/src/main/java/com/doyoonkim/common/ui/{TopAppBarWithBackButton.kt => TopAppBars.kt} (57%) diff --git a/app/src/main/java/com/doyoonkim/knutice/AppNavHost.kt b/app/src/main/java/com/doyoonkim/knutice/AppNavHost.kt index 0c3f28dc..c67b368d 100644 --- a/app/src/main/java/com/doyoonkim/knutice/AppNavHost.kt +++ b/app/src/main/java/com/doyoonkim/knutice/AppNavHost.kt @@ -2,9 +2,12 @@ package com.doyoonkim.knutice import android.net.Uri import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.LayoutDirection import androidx.lifecycle.ViewModelProvider import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -23,8 +26,8 @@ fun AppNavHost( NavHost( modifier = modifier.padding( PaddingValues( - top = contentPadding.calculateTopPadding(), -// bottom = contentPadding.calculateBottomPadding() + start = contentPadding.calculateStartPadding(LayoutDirection.Ltr), + end = contentPadding.calculateEndPadding(LayoutDirection.Ltr) ) ), navController = navController, diff --git a/common/src/main/java/com/doyoonkim/common/ui/TopAppBarWithBackButton.kt b/common/src/main/java/com/doyoonkim/common/ui/TopAppBars.kt similarity index 57% rename from common/src/main/java/com/doyoonkim/common/ui/TopAppBarWithBackButton.kt rename to common/src/main/java/com/doyoonkim/common/ui/TopAppBars.kt index aad0a17c..1b3e6c19 100644 --- a/common/src/main/java/com/doyoonkim/common/ui/TopAppBarWithBackButton.kt +++ b/common/src/main/java/com/doyoonkim/common/ui/TopAppBars.kt @@ -1,6 +1,12 @@ package com.doyoonkim.common.ui +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api @@ -11,7 +17,10 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -61,4 +70,40 @@ fun TopAppBarWithBackButton( navigationIconContentColor = MaterialTheme.colorScheme.title ) ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TopAppBarWithActions( + modifier: Modifier = Modifier, + titleText: String, + actions: @Composable (RowScope.() -> Unit) +) { + TopAppBar( + modifier = modifier, + title = { + Row( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = titleText, + textAlign = TextAlign.Left, + fontSize = 20.sp, + fontWeight = FontWeight.W900, + maxLines = 1, + color = MaterialTheme.colorScheme.title + ) + } + }, + actions = { + actions() + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.displayBackground, + titleContentColor = MaterialTheme.colorScheme.title + ) + ) } \ No newline at end of file diff --git a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/list/BookmarkListScreen.kt b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/list/BookmarkListScreen.kt index d476d060..f26027a0 100644 --- a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/list/BookmarkListScreen.kt +++ b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/list/BookmarkListScreen.kt @@ -2,43 +2,63 @@ package com.doyoonkim.bookmark.list import android.util.Log import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.doyoonkim.bookmark.viewmodel.BookmarkListViewModel import com.doyoonkim.common.navigation.BookmarkInfo import com.doyoonkim.common.R +import com.doyoonkim.common.theme.displayBackground import com.doyoonkim.common.theme.subTitle +import com.doyoonkim.common.theme.title import com.doyoonkim.common.ui.NotificationPreviewCardMarked +import com.doyoonkim.common.ui.TopAppBarWithActions +@OptIn(ExperimentalMaterial3Api::class) @Composable fun BookmarkListScreen( modifier: Modifier = Modifier, viewModel: BookmarkListViewModel, bottomPadding: Dp = 0.dp, + onSettingsRequested: () -> Unit, onBookmarkSelected: (BookmarkInfo) -> Unit, onBackPressed: () -> Unit ) { @@ -50,28 +70,54 @@ fun BookmarkListScreen( viewModel.getAllBookmarks() } - Box( - modifier = modifier.fillMaxSize() - ) { + Scaffold( + modifier = modifier.fillMaxSize(), + topBar = { + TopAppBarWithActions( + titleText = stringResource(R.string.bottom_bar_bookmark) + ) { + IconButton( + onClick = onSettingsRequested + ) { + Image( + painter = painterResource(R.drawable.baseline_settings_24), + contentDescription = "Settings", + modifier = Modifier.wrapContentSize(), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.title) + ) + } + } + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> + if (uiState.bookmarks.isEmpty()) { Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, + modifier = modifier.fillMaxSize().padding(innerPadding), + verticalArrangement = Arrangement.spacedBy(3.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Text( - text = stringResource(R.string.text_no_bookmark), - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.subTitle - ) + Box( + modifier = Modifier.wrapContentSize() + .weight(1f) + ) { + Text( + text = stringResource(R.string.text_no_bookmark), + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.subTitle, + modifier = Modifier.wrapContentSize() + .align(Alignment.Center) + ) + } Spacer(Modifier.height(bottomPadding)) } } else { LazyColumn( - modifier = Modifier.wrapContentHeight() + modifier = modifier.wrapContentHeight() .fillMaxWidth() - .padding(top = 12.dp, bottom = bottomPadding) + .padding(top = innerPadding.calculateTopPadding() + 12.dp) .background(Color.Transparent), verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, @@ -85,17 +131,22 @@ fun BookmarkListScreen( NotificationPreviewCardMarked( noticeTitle = it.second.title, noticeSubtitle = "[${it.second.departName}] ${it.second.timestamp}", - onItemClicked = { onBookmarkSelected( - it.second.run { - BookmarkInfo( - noticeId = this.nttId, - noticeTitle = this.title, - noticeInfo = "[${this.departName}] ${this.timestamp}" - ) - } - ) } + onItemClicked = { + onBookmarkSelected( + it.second.run { + BookmarkInfo( + noticeId = this.nttId, + noticeTitle = this.title, + noticeInfo = "[${this.departName}] ${this.timestamp}" + ) + } + ) + } ) } + item { + Spacer(Modifier.height(bottomPadding)) + } } } } diff --git a/feature/main/src/main/java/com/doyoonkim/main/home/HomeScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/home/HomeScreen.kt index ed7ab9c5..083bba52 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/home/HomeScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/home/HomeScreen.kt @@ -1,20 +1,28 @@ package com.doyoonkim.main.home import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -22,8 +30,11 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -34,15 +45,22 @@ import com.doyoonkim.common.theme.notificationType4 import com.doyoonkim.common.theme.subTitle import com.doyoonkim.common.R import com.doyoonkim.common.navigation.Destination +import com.doyoonkim.common.navigation.NavRoutes +import com.doyoonkim.common.theme.displayBackground +import com.doyoonkim.common.theme.title import com.doyoonkim.common.ui.NotificationPreviewCard +import com.doyoonkim.common.ui.TopAppBarWithActions import com.doyoonkim.main.viewmodel.HomeViewModel import com.doyoonkim.model.NoticeVO +@OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen( modifier: Modifier = Modifier, viewModel: HomeViewModel, bottomPadding: Dp = 0.dp, + onSearchRequested: () -> Unit, + onSettingsRequested: () -> Unit, onGoBackAction: () -> Unit, onMoreNoticeRequested: (Destination) -> Unit, onFullContentRequested: (Int, String) -> Unit @@ -58,54 +76,87 @@ fun HomeScreen( viewModel.getTopThreeNotices() } - Column( - modifier = modifier.verticalScroll( - rememberScrollState(0) - ), - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally - ) { - NotificationPreviewList ( - listTitle = stringResource(R.string.general_news), - titleColor = MaterialTheme.colorScheme.notificationType1, - isContentLoading = uiState.isLoading, - contents = uiState.notificationGeneral, - onMoreClicked = { onMoreNoticeRequested(Destination.MORE_GENERAL) } + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + TopAppBarWithActions( + titleText = stringResource(R.string.app_name) + ) { + IconButton( + onClick = onSearchRequested + ) { + Image( + painter = painterResource(R.drawable.baseline_search_24), + contentDescription = "Search", + modifier = Modifier.wrapContentSize(), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.title) + ) + } + IconButton( + onClick = onSettingsRequested + ) { + Image( + painter = painterResource(R.drawable.baseline_settings_24), + contentDescription = "Settings", + modifier = Modifier.wrapContentSize(), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.title) + ) + } + } + }, + containerColor = MaterialTheme.colorScheme.displayBackground + ) { innerPadding -> + Column( + modifier = modifier + .padding(innerPadding) + .verticalScroll(rememberScrollState(0)), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally ) { - onFullContentRequested(it.nttId, it.url) - } - NotificationPreviewList( - listTitle = stringResource(R.string.academic_news), - titleColor = MaterialTheme.colorScheme.notificationType2, - isContentLoading = uiState.isLoading, - contents = uiState.notificationAcademic, - onMoreClicked = { onMoreNoticeRequested(Destination.MORE_ACADEMIC) } - ) { - onFullContentRequested(it.nttId, it.url) - } - NotificationPreviewList( - listTitle = stringResource(R.string.scholarship_news), - titleColor = MaterialTheme.colorScheme.notificationType3, - isContentLoading = uiState.isLoading, - contents = uiState.notificationScholarship, - onMoreClicked = { onMoreNoticeRequested(Destination.MORE_SCHOLARSHIP) } - ) { - onFullContentRequested(it.nttId, it.url) - } + NotificationPreviewList ( + listTitle = stringResource(R.string.general_news), + titleColor = MaterialTheme.colorScheme.notificationType1, + isContentLoading = uiState.isLoading, + contents = uiState.notificationGeneral, + onMoreClicked = { onMoreNoticeRequested(Destination.MORE_GENERAL) } + ) { + onFullContentRequested(it.nttId, it.url) + } - NotificationPreviewList( - listTitle = stringResource(R.string.event_news), - titleColor = MaterialTheme.colorScheme.notificationType4, - isContentLoading = uiState.isLoading, - contents = uiState.notificationEvent, - onMoreClicked = { onMoreNoticeRequested(Destination.MORE_EVENT) } - ) { - onFullContentRequested(it.nttId, it.url) - } + NotificationPreviewList( + listTitle = stringResource(R.string.academic_news), + titleColor = MaterialTheme.colorScheme.notificationType2, + isContentLoading = uiState.isLoading, + contents = uiState.notificationAcademic, + onMoreClicked = { onMoreNoticeRequested(Destination.MORE_ACADEMIC) } + ) { + onFullContentRequested(it.nttId, it.url) + } - Spacer(Modifier.height(bottomPadding)) + NotificationPreviewList( + listTitle = stringResource(R.string.scholarship_news), + titleColor = MaterialTheme.colorScheme.notificationType3, + isContentLoading = uiState.isLoading, + contents = uiState.notificationScholarship, + onMoreClicked = { onMoreNoticeRequested(Destination.MORE_SCHOLARSHIP) } + ) { + onFullContentRequested(it.nttId, it.url) + } + + NotificationPreviewList( + listTitle = stringResource(R.string.event_news), + titleColor = MaterialTheme.colorScheme.notificationType4, + isContentLoading = uiState.isLoading, + contents = uiState.notificationEvent, + onMoreClicked = { onMoreNoticeRequested(Destination.MORE_EVENT) } + ) { + onFullContentRequested(it.nttId, it.url) + } + + Spacer(Modifier.height(bottomPadding)) + } } } From 52ac49c09eddc18aa748fae7de66a1c44cea4fff Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 02:58:22 +0900 Subject: [PATCH 12/16] KAN-15 [REFACTOR] Separate TopAppBar from Shared Scaffold - Additional arguments (listeners for button under TopAppBar actions) have been provided. - Missing UI attributes have been applied. --- .../com/doyoonkim/bookmark/bookmarkServiceGraph.kt | 5 +++-- .../java/com/doyoonkim/main/MainServiceNavGraph.kt | 12 +++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt index 6179ef04..2ac46642 100644 --- a/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt +++ b/feature/bookmark/src/main/java/com/doyoonkim/bookmark/bookmarkServiceGraph.kt @@ -35,9 +35,10 @@ fun NavGraphBuilder.bookmarkServiceGraph( composable(NavRoutes.Bookmark.route) { BookmarkListScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier.padding(horizontal = 5.dp), viewModel = viewModel(factory = viewModelFactory), bottomPadding = contentPadding.calculateBottomPadding(), + onSettingsRequested = { navController.navigate(NavRoutes.Settings.route) }, onBookmarkSelected = { navController.navigate("bookmark/${it.noticeId}/${it.noticeTitle}/${it.noticeInfo}") }, @@ -76,7 +77,7 @@ fun NavGraphBuilder.bookmarkServiceGraph( } ?: BookmarkInfo(0, "", "") EditBookmarkScreen( - modifier = Modifier, + modifier = Modifier.padding(horizontal = 10.dp), viewModel = viewModel(factory = viewModelFactory), bookmarkInfo = bookmarkInfo, onNoticeSelected = { onNoticeDetailRequested(it) }, diff --git a/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt b/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt index db5c9111..5fe9e872 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/MainServiceNavGraph.kt @@ -52,9 +52,11 @@ fun NavGraphBuilder.mainServiceNavGraph( // ViewModels will be injected via ViewModelFactory composable(NavRoutes.Home.route) { HomeScreen( - modifier = Modifier.padding(5.dp), + modifier = Modifier.padding(horizontal = 5.dp), viewModel = viewModel(factory = viewModelFactory), bottomPadding = contentPadding.calculateBottomPadding(), + onSearchRequested = { navController.navigate(NavRoutes.NoticeSearch.route) }, + onSettingsRequested = { navController.navigate(NavRoutes.Settings.route) }, onGoBackAction = { navController.popBackStack().also { if (!it) onExit() } }, @@ -220,7 +222,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { UserPreferenceScreen( - modifier = Modifier, + modifier = Modifier.padding(horizontal = 10.dp), onNotificationPreferenceClicked = { navController.navigate(NavRoutes.NotificationPreferences.route) }, onCustomerServiceClicked = { navController.navigate(NavRoutes.CustomerService.route) }, onOssClicked = { navController.navigate(NavRoutes.OpenSource.route) }, @@ -244,7 +246,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { NotificationPreferencesScreen( - modifier = Modifier, + modifier = Modifier.padding(horizontal = 10.dp), viewModel = viewModel(factory = viewModelFactory), onBackPressed = { navController.popBackStack() } ) @@ -266,7 +268,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { CustomerServiceScreen( - modifier = Modifier, + modifier = Modifier.padding(horizontal = 10.dp), viewModel = viewModel(factory = viewModelFactory), onBackPressed = { navController.popBackStack() } ) @@ -288,7 +290,7 @@ fun NavGraphBuilder.mainServiceNavGraph( } ) { OssNoticeScreen( - modifier = Modifier, + modifier = Modifier.padding(horizontal = 10.dp), onBackPressed = { navController.popBackStack() } ) } From 88075da5612f2f473aa31e1d1dc8b32401deecbb Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 02:59:05 +0900 Subject: [PATCH 13/16] KAN-15 [REFACTOR] UI Improvement - Missing UI attributes have been applied to the Composable located under TopBar section. --- .../main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt index 24ae5174..d26582b5 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt @@ -69,6 +69,7 @@ fun NoticeSearchScreen( modifier = Modifier.fillMaxSize(), topBar = { TopAppBar( + modifier = Modifier.padding(top = 5.dp), title = { TextField( modifier = Modifier From e6df88efde6913823f27c7263fed7006e6463f9d Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 03:00:29 +0900 Subject: [PATCH 14/16] KAN-15 [REFACTOR] Separate TopAppBar from Shared Scaffold - Remove Centralized TopAppBar under Top-Level Shared Scaffold. --- .../com/doyoonkim/knutice/MainActivity.kt | 70 ------------------- 1 file changed, 70 deletions(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt b/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt index a1ef25ac..4e46c739 100644 --- a/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt +++ b/app/src/main/java/com/doyoonkim/knutice/MainActivity.kt @@ -10,44 +10,30 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomNavigationItem import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -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.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.core.view.WindowCompat import androidx.lifecycle.ViewModelProvider @@ -66,7 +52,6 @@ import com.doyoonkim.notification.local.NotificationAlarmScheduler import kotlinx.coroutines.delay import javax.inject.Inject -@OptIn(ExperimentalMaterial3Api::class) class MainActivity : ComponentActivity() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory @@ -127,59 +112,6 @@ class MainActivity : ComponentActivity() { Scaffold( modifier = Modifier.fillMaxSize(), - topBar = { - if (sharedScaffoldState.first) { - TopAppBar( - title = { - Row( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.app_name), - textAlign = TextAlign.Left, - fontSize = 20.sp, - fontWeight = FontWeight.W900, - maxLines = 1, - color = MaterialTheme.colorScheme.title - ) - } - }, - actions = { - IconButton( - onClick = { - navController.navigate(NavRoutes.NoticeSearch.route) - } - ) { - Image( - painter = painterResource(R.drawable.baseline_search_24), - contentDescription = "Search", - modifier = Modifier.wrapContentSize(), - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.title) - ) - } - IconButton( - onClick = { - navController.navigate(NavRoutes.Settings.route) - } - ) { - Image( - painter = painterResource(R.drawable.baseline_settings_24), - contentDescription = "Settings", - modifier = Modifier.wrapContentSize(), - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.title) - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.displayBackground, - titleContentColor = MaterialTheme.colorScheme.title - ) - ) - } - }, bottomBar = { if (sharedScaffoldState.first) { BottomAppBar( @@ -238,7 +170,6 @@ class MainActivity : ComponentActivity() { }, containerColor = MaterialTheme.colorScheme.displayBackground ) { contentPadding -> - AppNavHost( modifier = Modifier, contentPadding = contentPadding, @@ -246,7 +177,6 @@ class MainActivity : ComponentActivity() { viewModelFactory = viewModelFactory, onExit = { activity.finish() } ) - LaunchedEffect(Unit) { // Intent handling (access application via onCreate call; click push notification when app is closed.) Log.d("MainActivity", "Intent received: ${launchedIntent?.data}") From 9c40f73f5c86724d0adbf626258cac5e92bd23ae Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 03:01:43 +0900 Subject: [PATCH 15/16] [REFACTOR] Replace Misplaced Modifier Instance - Replace misplaced Modifier Instance to modifier instance provided over argument. --- .../doyoonkim/main/preference/NotificationPreferencesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/main/src/main/java/com/doyoonkim/main/preference/NotificationPreferencesScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/preference/NotificationPreferencesScreen.kt index 2659c625..0189e226 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/preference/NotificationPreferencesScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/preference/NotificationPreferencesScreen.kt @@ -86,7 +86,7 @@ fun NotificationPreferencesScreen( containerColor = MaterialTheme.colorScheme.displayBackground ) { innerPadding -> RoundedCornerColumn( - modifier = Modifier.fillMaxWidth().padding(innerPadding), + modifier = modifier.fillMaxWidth().padding(innerPadding), backgroundColor = MaterialTheme.colorScheme.secondaryBackground ) { RoundedCornerColumnTextItemWithExtraOnRight( From c86b8279e65adc313dcaad2c13e53ef9a2a5b92d Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 24 Jun 2025 03:09:58 +0900 Subject: [PATCH 16/16] [REFACTOR] Add missing UI configuration - Missing UI Configurations have been applied. --- .../java/com/doyoonkim/main/notice/NoticeDetailScreen.kt | 1 - .../java/com/doyoonkim/main/notice/NoticeSearchScreen.kt | 9 ++++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeDetailScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeDetailScreen.kt index 8ca028ac..8d8b2c3a 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeDetailScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeDetailScreen.kt @@ -182,7 +182,6 @@ fun NoticeDetailScreen( if (noticeInfo.third) { FloatingActionButton( modifier = Modifier.wrapContentSize() - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)) .padding(end = 10.dp, bottom = 30.dp) .align(Alignment.BottomEnd), onClick = { diff --git a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt index d26582b5..288c6dc0 100644 --- a/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt +++ b/feature/main/src/main/java/com/doyoonkim/main/notice/NoticeSearchScreen.kt @@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -35,6 +36,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -69,7 +71,7 @@ fun NoticeSearchScreen( modifier = Modifier.fillMaxSize(), topBar = { TopAppBar( - modifier = Modifier.padding(top = 5.dp), + modifier = Modifier.padding(vertical = 10.dp), title = { TextField( modifier = Modifier @@ -115,6 +117,11 @@ fun NoticeSearchScreen( modifier = Modifier .fillMaxSize() .padding(innerPadding) + .pointerInput(Unit) { + detectTapGestures( + onTap = { localFocusManager.clearFocus() } + ) + } ) { LazyColumn( modifier = Modifier