diff --git a/.github/workflows/pr_checker.yml b/.github/workflows/pr_checker.yml index bf649cb..7003fa7 100644 --- a/.github/workflows/pr_checker.yml +++ b/.github/workflows/pr_checker.yml @@ -15,10 +15,10 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Set up JDK 1.8 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: 1.8 + java-version: '17' distribution: 'temurin' cache: gradle @@ -39,8 +39,5 @@ jobs: HFM_BASE_URL: ${{ secrets.BASE_URL }} run: echo "base.url=\"$BASE_URL\"" >> local.properties - - name: Lint Check - run: ./gradlew ktlintCheck -PcompileSdkVersion=34 - - name: Build with Gradle run: ./gradlew build diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6b12fa3..92c695d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,33 +1,30 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.serialization) } android { namespace = "com.beombeom.composeex" - compileSdk = 35 + compileSdk = 36 defaultConfig { applicationId = "com.beombeom.composeex" minSdk = 24 - targetSdk = 34 + targetSdk = 36 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.3" - } - - buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } @@ -53,8 +50,10 @@ dependencies { implementation(libs.koin.android) implementation(libs.koin.androidx.compose) - // Navigation - implementation(libs.androidx.navigation.compose) + // Navigation3 + implementation(libs.androidx.navigation3.runtime) + implementation(libs.androidx.navigation3.ui) + implementation(libs.kotlinx.serialization.json) // Compose implementation(libs.androidx.ui) @@ -65,9 +64,17 @@ dependencies { // Compose ViewModel implementation(libs.androidx.lifecycle.viewmodel) + implementation(libs.lifecycle.viewmodel.navigation3) implementation(libs.accompanist.systemuicontroller) // ExoPlayer implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.ui) -} \ No newline at end of file + implementation(libs.navigation3.runtime) + implementation(libs.androidx.navigation3.navigation3.runtime) + + // Test + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} diff --git a/app/src/androidTest/java/com/beombeom/composeex/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/beombeom/composeex/ExampleInstrumentedTest.kt index 6a91744..3ae9ac4 100644 --- a/app/src/androidTest/java/com/beombeom/composeex/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/beombeom/composeex/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.beombeom.composeex -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.beombeom.composeex", appContext.packageName) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/beombeom/composeex/presentation/MainActivity.kt b/app/src/main/java/com/beombeom/composeex/presentation/MainActivity.kt index f6dd793..12f8e3d 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/MainActivity.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/MainActivity.kt @@ -20,14 +20,28 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController +import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.NavBackStack +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator +import androidx.navigation3.ui.NavDisplay import com.beombeom.composeex.R import com.beombeom.composeex.presentation.common.CardSection import com.beombeom.composeex.presentation.common.MainHeader +import com.beombeom.composeex.presentation.examples.bottomSheet.BottomSheetMenu +import com.beombeom.composeex.presentation.examples.bottomSheet.BottomSheetScaffoldEx +import com.beombeom.composeex.presentation.examples.bottomSheet.ModalBottomSheetEx +import com.beombeom.composeex.presentation.examples.dropdown.DropdownMenuEx +import com.beombeom.composeex.presentation.examples.lazyLow.RecyclerViewEx +import com.beombeom.composeex.presentation.examples.pager.PagerEx +import com.beombeom.composeex.presentation.examples.pullToRefresh.FullToRefreshEx +import com.beombeom.composeex.presentation.examples.sideEffect.SideEffectEx +import com.beombeom.composeex.presentation.examples.videoPlayer.VideoPlayerEx import com.beombeom.composeex.presentation.navigation.ExItem +import com.beombeom.composeex.presentation.navigation.ExNavRoute +import com.beombeom.composeex.presentation.navigation.ExTitles import com.beombeom.composeex.presentation.util.SetSystemUI class MainActivity : ComponentActivity() { @@ -37,28 +51,52 @@ class MainActivity : ComponentActivity() { val viewModel = viewModel() Surface( color = Color.White, - contentColor = Color.Black + contentColor = Color.Black, ) { - val navController = rememberNavController() - NavHost(navController = navController, startDestination = "main") { - composable("main") { - SetSystemUI() - MainScreen(viewModel, navController) - } - // Register routes from ExItem - ExItem.getExList().forEach { item -> - composable( - route = item.route - ) { - // Pass navController if subCategory exists - if (item.subCategory.isNullOrEmpty()) { - item.content(navController, it) - } else { - item.content(null, it) - } + val backStack = rememberNavBackStack(ExNavRoute.Main) + NavDisplay( + backStack = backStack, + onBack = { backStack.removeLastOrNull() }, + entryDecorators = listOf( + // rememberSaveable 동작 보장 (configuration change, process death 대응) + rememberSaveableStateHolderNavEntryDecorator(), + // ViewModel을 NavEntry 생명주기에 스코프 + rememberViewModelStoreNavEntryDecorator(), + ), + entryProvider = entryProvider { + entry { + SetSystemUI() + MainScreen(viewModel, backStack) } - } - } + entry { + BottomSheetMenu(backStack) + } + entry { + BottomSheetScaffoldEx(ExTitles.TITLE_BOTTOM_SHEET_SCAFFOLD) + } + entry { + ModalBottomSheetEx(ExTitles.TITLE_MODAL_BOTTOM_SHEET) + } + entry { + PagerEx(ExTitles.TITLE_PAGER) + } + entry { + RecyclerViewEx(ExTitles.TITLE_RECYCLERVIEW) + } + entry { + VideoPlayerEx(ExTitles.TITLE_VIDEO_PLAYER) + } + entry { + SideEffectEx(ExTitles.TITLE_SIDE_EFFECT) + } + entry { + FullToRefreshEx(ExTitles.TITLE_PULL_TO_REFRESH) + } + entry { + DropdownMenuEx(ExTitles.TITLE_DROPDOWNMENU) + } + }, + ) } } } @@ -66,7 +104,10 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalFoundationApi::class) @Composable -fun MainScreen(viewModel: MainViewModel, navController: NavHostController) { +fun MainScreen( + viewModel: MainViewModel, + backStack: NavBackStack, +) { val context = LocalContext.current val filteredItems = ExItem.getExList().filter { it.subCategory.isNullOrEmpty() } @@ -79,11 +120,10 @@ fun MainScreen(viewModel: MainViewModel, navController: NavHostController) { items(filteredItems) { example -> Column(modifier = Modifier.fillMaxWidth()) { CardSection( - context = context, exampleTitle = example.title, exampleDescription = example.description, onButtonClick = { - navController.navigate("${example.route}?title=${example.title}") + backStack.add(example.route) }, ) } @@ -92,9 +132,9 @@ fun MainScreen(viewModel: MainViewModel, navController: NavHostController) { } } - -fun getTextStyle(fontSize: Int) = TextStyle( - fontSize = fontSize.sp, - letterSpacing = (-0.01).em, - lineHeight = 1.4.em -) \ No newline at end of file +fun getTextStyle(fontSize: Int) = + TextStyle( + fontSize = fontSize.sp, + letterSpacing = (-0.01).em, + lineHeight = 1.4.em, + ) diff --git a/app/src/main/java/com/beombeom/composeex/presentation/MainViewModel.kt b/app/src/main/java/com/beombeom/composeex/presentation/MainViewModel.kt index d0af93a..0d5e318 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/MainViewModel.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/MainViewModel.kt @@ -4,7 +4,6 @@ import android.app.Application import androidx.lifecycle.AndroidViewModel import kotlinx.coroutines.flow.MutableStateFlow -class MainViewModel(application : Application) : AndroidViewModel(application){ - +class MainViewModel(application: Application) : AndroidViewModel(application) { val toast = MutableStateFlow("") -} \ No newline at end of file +} diff --git a/app/src/main/java/com/beombeom/composeex/presentation/common/Components.kt b/app/src/main/java/com/beombeom/composeex/presentation/common/Components.kt index 1e6c6be..9ab06fb 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/common/Components.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/common/Components.kt @@ -1,6 +1,5 @@ package com.beombeom.composeex.presentation.common -import android.content.Context import android.os.Build import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -32,27 +31,28 @@ import com.beombeom.composeex.presentation.util.noRippleClickable @Composable fun MainHeader(title: String) { val context = LocalContext.current - val dynamicColorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - dynamicLightColorScheme(context) - } else { - lightColorScheme() - } + val dynamicColorScheme = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + dynamicLightColorScheme(context) + } else { + lightColorScheme() + } MaterialTheme( colorScheme = dynamicColorScheme, - typography = Typography() + typography = Typography(), ) { Box( modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colorScheme.primary) + .background(MaterialTheme.colorScheme.primary), ) { Row( modifier = Modifier .fillMaxWidth() .height(56.dp) .padding(horizontal = 20.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Spacer(modifier = Modifier.weight(1f)) } @@ -62,7 +62,7 @@ fun MainHeader(title: String) { modifier = Modifier.align(Alignment.Center), color = MaterialTheme.colorScheme.onPrimary, style = MaterialTheme.typography.displayMedium, - fontSize = 20.sp + fontSize = 20.sp, ) } } @@ -70,7 +70,6 @@ fun MainHeader(title: String) { @Composable fun CardSection( - context: Context, exampleTitle: String, exampleDescription: String, onButtonClick: () -> Unit, @@ -80,22 +79,22 @@ fun CardSection( .fillMaxWidth() .padding(horizontal = 20.dp, vertical = 10.dp), elevation = CardDefaults.cardElevation( - defaultElevation = 3.dp + defaultElevation = 3.dp, ), shape = RoundedCornerShape(12.dp), colors = CardDefaults.cardColors( containerColor = Color.DarkGray, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer - ) + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + ), ) { Column( - modifier = Modifier.padding(20.dp) + modifier = Modifier.padding(20.dp), ) { androidx.compose.material.Text( modifier = Modifier.fillMaxWidth(), text = exampleTitle, color = Color.White, - style = getTextStyle(18) + style = getTextStyle(18), ) Spacer(modifier = Modifier.height(4.dp)) @@ -113,7 +112,7 @@ fun CardSection( modifier = Modifier .fillMaxWidth(), verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { Card( modifier = Modifier @@ -123,18 +122,17 @@ fun CardSection( shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors( containerColor = Color.White, - ) + ), ) { Text( modifier = Modifier .padding(horizontal = 10.dp, vertical = 10.dp), text = "Go Example", color = Color.Black, - style = getTextStyle(16) + style = getTextStyle(16), ) } - } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/BottomSheetMenu.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/BottomSheetMenu.kt index d453875..02e9691 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/BottomSheetMenu.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/BottomSheetMenu.kt @@ -10,24 +10,29 @@ import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.navigation.NavController +import androidx.navigation3.runtime.NavBackStack +import androidx.navigation3.runtime.NavKey import com.beombeom.composeex.presentation.common.CardSection import com.beombeom.composeex.presentation.common.MainHeader import com.beombeom.composeex.presentation.navigation.ExItem import com.beombeom.composeex.presentation.navigation.SubCategories @Composable -fun BottomSheetMenu(navController: NavController) { - val filteredItems = ExItem.getExList().filter { it.subCategory == SubCategories.SUB_BOTTOM_SHEET } +fun BottomSheetMenu(backStack: NavBackStack) { + val filteredItems = + ExItem.getExList().filter { it.subCategory == SubCategories.SUB_BOTTOM_SHEET } Box(modifier = Modifier.fillMaxSize()) { - MenuContent(filteredItems = filteredItems, navController = navController) + MenuContent(filteredItems = filteredItems, backStack = backStack) } } @OptIn(ExperimentalFoundationApi::class) @Composable -fun MenuContent(filteredItems: List, navController: NavController) { +fun MenuContent( + filteredItems: List, + backStack: NavBackStack, +) { LazyColumn(modifier = Modifier.fillMaxSize()) { stickyHeader { MainHeader(title = SubCategories.SUB_BOTTOM_SHEET) @@ -36,12 +41,11 @@ fun MenuContent(filteredItems: List, navController: NavController) { items(filteredItems) { example -> Column(modifier = Modifier.fillMaxWidth()) { CardSection( - context = LocalContext.current, exampleTitle = example.title, exampleDescription = example.description, onButtonClick = { - navController.navigate(example.route) - } + backStack.add(example.route) + }, ) } } diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/BottomSheetScaffoldEx.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/BottomSheetScaffoldEx.kt index cc6c531..74d3189 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/BottomSheetScaffoldEx.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/BottomSheetScaffoldEx.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.launch fun BottomSheetScaffoldEx(title: String) { val scaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false), - snackbarHostState = remember { SnackbarHostState() } + snackbarHostState = remember { SnackbarHostState() }, ) val coroutineScope = rememberCoroutineScope() @@ -51,7 +51,7 @@ fun BottomSheetScaffoldEx(title: String) { .fillMaxSize() .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) + verticalArrangement = Arrangement.spacedBy(8.dp), ) { InfoText("This is the BottomSheet") InfoText("isVisible: ${scaffoldState.bottomSheetState.isVisible}") @@ -62,12 +62,12 @@ fun BottomSheetScaffoldEx(title: String) { "offset: ${scaffoldState.bottomSheetState.requireOffset()}" } catch (e: IllegalStateException) { "offset: (Not Available)" - } + }, ) Column( modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { Button( onClick = { @@ -76,7 +76,7 @@ fun BottomSheetScaffoldEx(title: String) { colors = ButtonDefaults.buttonColors( containerColor = Color.DarkGray, contentColor = Color.White, - ) + ), ) { Text("Expand Sheet") } @@ -87,7 +87,7 @@ fun BottomSheetScaffoldEx(title: String) { colors = ButtonDefaults.buttonColors( containerColor = Color.Gray, contentColor = Color.White, - ) + ), ) { Text("Partial Expand Sheet") } @@ -96,14 +96,14 @@ fun BottomSheetScaffoldEx(title: String) { coroutineScope.launch { scaffoldState.snackbarHostState.showSnackbar( message = "Message from BottomSheet!", - actionLabel = "Dismiss" + actionLabel = "Dismiss", ) } }, colors = ButtonDefaults.buttonColors( containerColor = Color.DarkGray, contentColor = Color.White, - ) + ), ) { Text("Show Snackbar") } @@ -114,7 +114,7 @@ fun BottomSheetScaffoldEx(title: String) { LazyColumn( modifier = Modifier .fillMaxSize() - .padding(innerPadding) + .padding(innerPadding), ) { stickyHeader { MainHeader(title = title) @@ -124,20 +124,20 @@ fun BottomSheetScaffoldEx(title: String) { Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp) + .padding(16.dp), ) { InfoText( - text = "Snackbar Visible: ${scaffoldState.snackbarHostState.currentSnackbarData != null}" + text = "Snackbar Visible: ${scaffoldState.snackbarHostState.currentSnackbarData != null}", ) InfoText( text = "Snackbar Message: ${ scaffoldState.snackbarHostState.currentSnackbarData?.visuals?.message ?: "None" - }" + }", ) InfoText( text = "Snackbar Action: ${ scaffoldState.snackbarHostState.currentSnackbarData?.visuals?.actionLabel ?: "None" - }" + }", ) Spacer(modifier = Modifier.height(8.dp)) @@ -151,14 +151,14 @@ fun BottomSheetScaffoldEx(title: String) { "offset: ${scaffoldState.bottomSheetState.requireOffset()}" } catch (e: IllegalStateException) { "offset: (Not Available)" - } + }, ) Spacer(modifier = Modifier.height(8.dp)) Column( modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { Button( onClick = { @@ -167,7 +167,7 @@ fun BottomSheetScaffoldEx(title: String) { colors = ButtonDefaults.buttonColors( containerColor = Color.DarkGray, contentColor = Color.White, - ) + ), ) { Text("Show BottomSheet") } @@ -177,14 +177,14 @@ fun BottomSheetScaffoldEx(title: String) { coroutineScope.launch { scaffoldState.snackbarHostState.showSnackbar( message = "Message from MainContent!", - actionLabel = "Dismiss" + actionLabel = "Dismiss", ) } }, colors = ButtonDefaults.buttonColors( containerColor = Color.Gray, contentColor = Color.White, - ) + ), ) { Text("Show Snackbar") } @@ -200,15 +200,15 @@ fun InfoText( text: String, fontSize: TextUnit = 16.sp, fontWeight: FontWeight = FontWeight.W400, - color: Color = MaterialTheme.colorScheme.onSurface + color: Color = MaterialTheme.colorScheme.onSurface, ) { Text( text = text, style = TextStyle( fontSize = fontSize, fontWeight = fontWeight, - color = color - ) + color = color, + ), ) Spacer(modifier = Modifier.height(4.dp)) } diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/ModalBottomSheetEx.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/ModalBottomSheetEx.kt index 1bb96d5..190e3d8 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/ModalBottomSheetEx.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/bottomSheet/ModalBottomSheetEx.kt @@ -28,22 +28,25 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable -fun ModalBottomSheetEx(title : String) { +fun ModalBottomSheetEx(title: String) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false) val coroutineScope = rememberCoroutineScope() LazyColumn( modifier = Modifier .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { stickyHeader { MainHeader(title = title) } item { - Column(modifier = Modifier.fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, top = 16.dp)) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, top = 16.dp), + ) { /* * isVisible * BottomSheet가 화면에 표시되고 있는지 여부를 나타냅니다. @@ -82,7 +85,7 @@ fun ModalBottomSheetEx(title : String) { "offset: ${sheetState.requireOffset()}" } catch (e: IllegalStateException) { "offset: (Not Available)" - } + }, ) } } @@ -93,9 +96,10 @@ fun ModalBottomSheetEx(title : String) { item { Column( - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() .padding(horizontal = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { /* * Show Bottom Sheet 버튼 @@ -107,8 +111,8 @@ fun ModalBottomSheetEx(title : String) { }, colors = ButtonDefaults.buttonColors( containerColor = Color.DarkGray, - contentColor = Color.White - ) + contentColor = Color.White, + ), ) { Text("Show Bottom Sheet") } @@ -126,8 +130,8 @@ fun ModalBottomSheetEx(title : String) { }, colors = ButtonDefaults.buttonColors( containerColor = Color.Gray, - contentColor = Color.White - ) + contentColor = Color.White, + ), ) { Text("Collapsed Bottom Sheet") } @@ -149,11 +153,11 @@ fun ModalBottomSheetEx(title : String) { sheetState = sheetState, shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), containerColor = MaterialTheme.colorScheme.surface, - tonalElevation = 16.dp + tonalElevation = 16.dp, ) { Box( modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.TopCenter + contentAlignment = Alignment.TopCenter, ) { Column { Spacer(modifier = Modifier.height(16.dp)) @@ -175,7 +179,7 @@ fun ModalBottomSheetEx(title : String) { "offset: ${sheetState.requireOffset()}" } catch (e: IllegalStateException) { "offset: (Not Available)" - } + }, ) Spacer(modifier = Modifier.height(16.dp)) @@ -194,7 +198,4 @@ fun ModalBottomSheetEx(title : String) { } } } - } - - diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/dropdown/DropdownMenuEx.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/dropdown/DropdownMenuEx.kt index ceab755..30cd42f 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/dropdown/DropdownMenuEx.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/dropdown/DropdownMenuEx.kt @@ -25,14 +25,14 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.beombeom.composeex.presentation.MainHeader +import com.beombeom.composeex.presentation.common.MainHeader @OptIn(ExperimentalMaterial3Api::class) @Composable -fun DropdownMenuEx(title : String) { +fun DropdownMenuEx(title: String) { Column( modifier = Modifier - .fillMaxSize() + .fillMaxSize(), ) { MainHeader(title = title) @@ -59,11 +59,11 @@ fun DefaultDropdownMenu() { text = items[selectedIndex], modifier = Modifier .clickable { expanded = !expanded } - .padding(16.dp) + .padding(16.dp), ) DropdownMenu( expanded = expanded, - onDismissRequest = { expanded = false } + onDismissRequest = { expanded = false }, ) { items.forEachIndexed { index, label -> DropdownMenuItem( @@ -71,7 +71,7 @@ fun DefaultDropdownMenu() { selectedIndex = index expanded = false }, - text = { Text(text = label) } + text = { Text(text = label) }, ) } } @@ -90,9 +90,8 @@ fun ExpandableDropdownMenuWithTextField() { ExposedDropdownMenuBox( modifier = Modifier.padding(horizontal = 16.dp), expanded = isDropDownMenuExpanded, - onExpandedChange = { isDropDownMenuExpanded = !isDropDownMenuExpanded } + onExpandedChange = { isDropDownMenuExpanded = !isDropDownMenuExpanded }, ) { - OutlinedTextField( value = selectedOption, onValueChange = {}, @@ -103,12 +102,12 @@ fun ExpandableDropdownMenuWithTextField() { label = { Text("Select an Option") }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = isDropDownMenuExpanded) - } + }, ) ExposedDropdownMenu( expanded = isDropDownMenuExpanded, - onDismissRequest = { isDropDownMenuExpanded = false } + onDismissRequest = { isDropDownMenuExpanded = false }, ) { menuItems.forEach { menuItem -> DropdownMenuItem( @@ -116,9 +115,9 @@ fun ExpandableDropdownMenuWithTextField() { selectedOption = menuItem // 선택된 항목 설정 isDropDownMenuExpanded = false }, - text = { Text(text = menuItem) } + text = { Text(text = menuItem) }, ) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewEntity.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewEntity.kt index dfdf8b5..95fe1a5 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewEntity.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewEntity.kt @@ -2,4 +2,4 @@ package com.beombeom.composeex.presentation.examples.lazyLow data class RecyclerViewEntity( val id: Int, -) \ No newline at end of file +) diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewEx.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewEx.kt index 10a862b..b5ef9d7 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewEx.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewEx.kt @@ -39,7 +39,7 @@ import com.beombeom.composeex.presentation.examples.bottomSheet.InfoText import kotlinx.coroutines.launch @Composable -fun RecyclerViewEx(title : String) { +fun RecyclerViewEx(title: String) { val viewModel = viewModel() val items by remember { viewModel.items } val listState = rememberLazyListState() @@ -63,7 +63,7 @@ fun RecyclerViewEx(title : String) { Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly + horizontalArrangement = Arrangement.SpaceEvenly, ) { Button( onClick = { @@ -77,7 +77,7 @@ fun RecyclerViewEx(title : String) { colors = ButtonDefaults.buttonColors( containerColor = Color.Gray, contentColor = Color.White, - ) + ), ) { Text("Previous Item") } @@ -92,7 +92,7 @@ fun RecyclerViewEx(title : String) { colors = ButtonDefaults.buttonColors( containerColor = Color.DarkGray, contentColor = Color.White, - ) + ), ) { Text("Next Item") } @@ -100,7 +100,7 @@ fun RecyclerViewEx(title : String) { Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly + horizontalArrangement = Arrangement.SpaceEvenly, ) { Button( onClick = { @@ -111,7 +111,7 @@ fun RecyclerViewEx(title : String) { colors = ButtonDefaults.buttonColors( containerColor = Color.DarkGray, contentColor = Color.White, - ) + ), ) { Text("Go to First") } @@ -125,7 +125,7 @@ fun RecyclerViewEx(title : String) { colors = ButtonDefaults.buttonColors( containerColor = Color.Gray, contentColor = Color.White, - ) + ), ) { Text("Go to Last") } @@ -133,14 +133,17 @@ fun RecyclerViewEx(title : String) { VerticalRecyclerView( items = items, - listState = listState + listState = listState, ) } } } @Composable -fun VerticalRecyclerView(items: List, listState: LazyListState) { +fun VerticalRecyclerView( + items: List, + listState: LazyListState, +) { LazyRow( state = listState, modifier = Modifier @@ -150,10 +153,10 @@ fun VerticalRecyclerView(items: List, listState: LazyListSta content = { itemsIndexed(items) { _, item -> CustomUserProfile( - id = item.id + id = item.id, ) } - } + }, ) } @@ -167,22 +170,22 @@ fun CustomUserProfile(id: Int) { shape = RoundedCornerShape(15.dp), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), colors = CardDefaults.cardColors( - containerColor = colorResource(id = R.color.aquamarine) - ) + containerColor = colorResource(id = R.color.aquamarine), + ), ) { Box( modifier = Modifier .fillMaxSize(), - contentAlignment = Alignment.Center + contentAlignment = Alignment.Center, ) { Text( text = "# $id", style = TextStyle( color = Color.Black, fontSize = 22.sp, - fontWeight = FontWeight.Bold - ) + fontWeight = FontWeight.Bold, + ), ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewViewModel.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewViewModel.kt index 6dc1f7b..d8fcd5a 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewViewModel.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/lazyLow/RecyclerViewViewModel.kt @@ -4,15 +4,15 @@ import android.app.Application import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.AndroidViewModel - class RecyclerViewViewModel(application: Application) : AndroidViewModel(application) { + val items = mutableStateOf>(generateDummyItems()) - private fun generateDummyItems(): List { + private fun generateDummyItems(): List { return List(500) { index -> RecyclerViewEntity( - id = index + id = index, ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/pager/PagerEx.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/pager/PagerEx.kt index cd1e5ca..b48b589 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/pager/PagerEx.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/pager/PagerEx.kt @@ -56,7 +56,7 @@ import kotlinx.coroutines.launch import kotlin.math.absoluteValue @Composable -fun PagerEx(title : String) { +fun PagerEx(title: String) { val viewModel = viewModel() val pagerState by viewModel.pagerState.collectAsState() val isAutoScrollEnabled by viewModel.isAutoScrollEnabled.collectAsState() @@ -89,7 +89,7 @@ fun PagerEx(title : String) { modifier = Modifier .fillMaxSize() .padding(10.dp), - verticalArrangement = Arrangement.Center + verticalArrangement = Arrangement.Center, ) { InfoText(text = "isScrollInProgress : ${pagerState.isScrollInProgress}") InfoText(text = "pageCount : ${pagerState.pageCount}") @@ -102,10 +102,10 @@ fun PagerEx(title : String) { containerColor = Color.Gray, contentColor = Color.White, ), - modifier = Modifier.align(Alignment.CenterHorizontally) + modifier = Modifier.align(Alignment.CenterHorizontally), ) { Text( - text = if (isAutoScrollEnabled) "Auto Scroll Off" else "Auto Scroll On" + text = if (isAutoScrollEnabled) "Auto Scroll Off" else "Auto Scroll On", ) } Spacer(modifier = Modifier.height(10.dp)) @@ -120,11 +120,11 @@ fun PagerEx(title : String) { goPage(pagerState, prevPage) } }, - modifier = Modifier.align(Alignment.CenterStart) + modifier = Modifier.align(Alignment.CenterStart), ) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Previous Page" + contentDescription = "Previous Page", ) } @@ -135,11 +135,11 @@ fun PagerEx(title : String) { goPage(pagerState, nextPage) } }, - modifier = Modifier.align(Alignment.CenterEnd) + modifier = Modifier.align(Alignment.CenterEnd), ) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowForward, - contentDescription = "Next Page" + contentDescription = "Next Page", ) } } @@ -153,7 +153,7 @@ fun PagerEx(title : String) { goPage(pagerState, page) } } - } + }, ) } } @@ -192,13 +192,13 @@ fun CustomViewPager(pagerState: PagerState) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { Text( text = "This is the ${page}th page.", style = TextStyle( fontSize = 20.sp, - fontWeight = FontWeight.Medium + fontWeight = FontWeight.Medium, ), ) } @@ -211,13 +211,13 @@ fun CustomViewPager(pagerState: PagerState) { fun PageIndicator( itemSize: Int, pagerState: PagerState, - onDotClick: (Int) -> Unit + onDotClick: (Int) -> Unit, ) { Row( Modifier .fillMaxWidth() .padding(vertical = 16.dp), - horizontalArrangement = Arrangement.Center + horizontalArrangement = Arrangement.Center, ) { repeat(itemSize) { index -> val color = if (pagerState.currentPage == index) Color.DarkGray else Color.LightGray @@ -227,13 +227,16 @@ fun PageIndicator( .clip(CircleShape) .background(color) .size(10.dp) - .clickable { onDotClick(index) } + .clickable { onDotClick(index) }, ) } } } -private suspend fun goPage(pagerState: PagerState, nextPage: Int) { +private suspend fun goPage( + pagerState: PagerState, + nextPage: Int, +) { if (!pagerState.isScrollInProgress) { pagerState.animateScrollToPage(nextPage) } else { @@ -250,14 +253,14 @@ fun PagerManualEx() { val coroutineScope = rememberCoroutineScope() LaunchedEffect(key1 = pagerState.isScrollInProgress) { - if (pagerState.currentPage == pagerState.pageCount - 1 - && pagerState.isScrollInProgress - && pagerState.currentPageOffsetFraction == 0f + if (pagerState.currentPage == pagerState.pageCount - 1 && + pagerState.isScrollInProgress && + pagerState.currentPageOffsetFraction == 0f ) { scrollLastPage = true - } else if (pagerState.currentPage == 0 - && pagerState.isScrollInProgress - && pagerState.currentPageOffsetFraction == 0f + } else if (pagerState.currentPage == 0 && + pagerState.isScrollInProgress && + pagerState.currentPageOffsetFraction == 0f ) { scrollFirstPage = true } @@ -277,7 +280,7 @@ fun PagerManualEx() { modifier = Modifier .fillMaxSize() .padding(10.dp), - verticalArrangement = Arrangement.Center + verticalArrangement = Arrangement.Center, ) { InfoText(text = "isScrollInProgress : ${pagerState.isScrollInProgress}") InfoText(text = "pageCount : ${pagerState.pageCount}") @@ -301,8 +304,8 @@ fun PagerManualEx() { goPage(pagerState, page) } } - } + }, ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/PullToRefreshScreen.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/PullToRefreshScreen.kt index 277dc4c..2848cef 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/PullToRefreshScreen.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/PullToRefreshScreen.kt @@ -28,19 +28,19 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.beombeom.composeex.presentation.common.MainHeader @Composable -internal fun FullToRefreshEx(title : String) { +internal fun FullToRefreshEx(title: String) { val viewModel: PullToRefreshViewModel = viewModel() val state by viewModel.screenState.collectAsState() Column { MainHeader(title = title) Scaffold( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { innerPadding -> FullToRefreshExContent( state = state, onRefreshTrigger = viewModel::onPullToRefreshTrigger, - modifier = Modifier.padding(innerPadding) + modifier = Modifier.padding(innerPadding), ) } } @@ -51,7 +51,7 @@ internal fun FullToRefreshEx(title : String) { private fun FullToRefreshExContent( state: ScreenState, onRefreshTrigger: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val refreshState = rememberPullToRefreshState() @@ -78,7 +78,7 @@ private fun RandomNumList(items: List) { LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { items(items) { item -> RandomNumItem(item = item) @@ -90,14 +90,14 @@ private fun RandomNumList(items: List) { private fun RandomNumItem(item: RandomNum) { Card( modifier = Modifier - .fillMaxWidth() + .fillMaxWidth(), ) { Row( horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxSize() - .padding(16.dp) + .padding(16.dp), ) { Text( text = "Item Number is " + item.id.toString(), diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/PullToRefreshViewModel.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/PullToRefreshViewModel.kt index cda6e5f..c7fdd08 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/PullToRefreshViewModel.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/PullToRefreshViewModel.kt @@ -12,27 +12,27 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class PullToRefreshViewModel : ViewModel() { - private val _isRefreshing = MutableStateFlow(false) - private val _counter = MutableStateFlow(0) + private val isRefreshingFlow = MutableStateFlow(false) + private val counterFlow = MutableStateFlow(0) - val screenState: StateFlow = combine(_counter, _isRefreshing) { counter, isRefreshing -> - ScreenState( - items = List(counter) { index -> RandomNum(index) }, - isRefreshing = isRefreshing + val screenState: StateFlow = + combine(counterFlow, isRefreshingFlow) { counter, isRefreshing -> + ScreenState( + items = List(counter) { index -> RandomNum(index) }, + isRefreshing = isRefreshing, + ) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = ScreenState(), ) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5000), - initialValue = ScreenState() - ) fun onPullToRefreshTrigger() { - _isRefreshing.update { true } + isRefreshingFlow.update { true } viewModelScope.launch { delay(1000) - _counter.update { it + 1 } - _isRefreshing.update { false } + counterFlow.update { it + 1 } + isRefreshingFlow.update { false } } } } - diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/RandomNum.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/RandomNum.kt index 8401715..381d0c1 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/RandomNum.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/RandomNum.kt @@ -1,6 +1,5 @@ package com.beombeom.composeex.presentation.examples.pullToRefresh - data class RandomNum( val id: Int, -) \ No newline at end of file +) diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/ScreenState.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/ScreenState.kt index c8f4f0d..dbbd3b4 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/ScreenState.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/pullToRefresh/ScreenState.kt @@ -2,5 +2,5 @@ package com.beombeom.composeex.presentation.examples.pullToRefresh data class ScreenState( val items: List = listOf(), - val isRefreshing: Boolean = false + val isRefreshing: Boolean = false, ) diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/sideEffect/SideEffectEx.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/sideEffect/SideEffectEx.kt index 9949416..f37d683 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/sideEffect/SideEffectEx.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/sideEffect/SideEffectEx.kt @@ -34,20 +34,21 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable -fun SideEffectEx(title : String) { +fun SideEffectEx(title: String) { var showGreeting by rememberSaveable { mutableStateOf(true) } var uniqueId by rememberSaveable { mutableStateOf(generateRandomString()) } val count = rememberSaveable { mutableStateOf(0) } // produceState: 비동기 데이터를 생성 val asyncData by produceState(initialValue = "No ID yet...", uniqueId) { - delay(2000) //초기값을 보여주기위한 의도적인 딜레이 + delay(2000) // 초기값을 보여주기위한 의도적인 딜레이 value = "ID (asyncData): $uniqueId" } val coroutineScope = rememberCoroutineScope { Dispatchers.IO } val greetingState = rememberUpdatedState(if (showGreeting) "Hide My ID" else "Show My ID") - val derivedCount = remember { derivedStateOf { "Derived Count: ${count.value * 2}" } } + val derivedCount = + remember { derivedStateOf { "Derived Count: ${count.value * 2}" } } Box(modifier = Modifier.fillMaxSize()) { Column( @@ -78,8 +79,8 @@ fun SideEffectEx(title : String) { } Box(modifier = Modifier.height(20.dp), contentAlignment = Alignment.Center) { - // ID 관련 SideEffect - if (showGreeting) { + // ID 관련 SideEffect + if (showGreeting) { IDSideEffects(uniqueId = uniqueId) } } @@ -144,4 +145,3 @@ fun generateRandomString(): String { .map { chars.random() } .joinToString("") } - diff --git a/app/src/main/java/com/beombeom/composeex/presentation/examples/videoPlayer/VideoPlayerEx.kt b/app/src/main/java/com/beombeom/composeex/presentation/examples/videoPlayer/VideoPlayerEx.kt index 43f2c7e..ede270f 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/examples/videoPlayer/VideoPlayerEx.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/examples/videoPlayer/VideoPlayerEx.kt @@ -38,12 +38,12 @@ class VideoPlayerViewModel : ViewModel() { } @Composable -fun VideoPlayerEx(title : String) { +fun VideoPlayerEx(title: String) { val viewModel = viewModel() var selectedVideoUri by rememberSaveable { mutableStateOf(viewModel.currentUri) } val pickVideoLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.GetContent() + contract = ActivityResultContracts.GetContent(), ) { uri: Uri? -> selectedVideoUri = uri viewModel.currentUri = uri @@ -52,13 +52,13 @@ fun VideoPlayerEx(title : String) { Column( modifier = Modifier .fillMaxSize() - .padding(16.dp) + .padding(16.dp), ) { MainHeader(title = title) Button( onClick = { pickVideoLauncher.launch("video/*") }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), ) { Text("영상 가져오기") } @@ -74,25 +74,30 @@ fun VideoPlayerEx(title : String) { } @Composable -fun VideoPlayerComponent(videoUri: Uri, viewModel: VideoPlayerViewModel) { +fun VideoPlayerComponent( + videoUri: Uri, + viewModel: VideoPlayerViewModel, +) { val context = LocalContext.current val configuration = LocalConfiguration.current - val videoHeight = if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { - 250.dp - } else { - 200.dp - } + val videoHeight = + if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + 250.dp + } else { + 200.dp + } - val exoPlayer = remember { - ExoPlayer.Builder(context).build().apply { - val mediaItem = MediaItem.fromUri(videoUri) - setMediaItem(mediaItem) - prepare() - seekTo(viewModel.currentPosition) - playWhenReady = viewModel.playWhenReady + val exoPlayer = + remember { + ExoPlayer.Builder(context).build().apply { + val mediaItem = MediaItem.fromUri(videoUri) + setMediaItem(mediaItem) + prepare() + seekTo(viewModel.currentPosition) + playWhenReady = viewModel.playWhenReady + } } - } AndroidView( factory = { @@ -103,7 +108,7 @@ fun VideoPlayerComponent(videoUri: Uri, viewModel: VideoPlayerViewModel) { modifier = Modifier .fillMaxWidth(0.95f) .padding(horizontal = 10.dp) - .height(videoHeight) + .height(videoHeight), ) DisposableEffect(videoUri) { @@ -113,4 +118,4 @@ fun VideoPlayerComponent(videoUri: Uri, viewModel: VideoPlayerViewModel) { exoPlayer.release() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/beombeom/composeex/presentation/navigation/ExItem.kt b/app/src/main/java/com/beombeom/composeex/presentation/navigation/ExItem.kt index 272a8f5..89528e4 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/navigation/ExItem.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/navigation/ExItem.kt @@ -1,150 +1,120 @@ package com.beombeom.composeex.presentation.navigation -import androidx.compose.runtime.Composable -import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavController -import com.beombeom.composeex.presentation.examples.bottomSheet.BottomSheetMenu -import com.beombeom.composeex.presentation.examples.bottomSheet.BottomSheetScaffoldEx -import com.beombeom.composeex.presentation.examples.bottomSheet.ModalBottomSheetEx -import com.beombeom.composeex.presentation.examples.dropdown.DropdownMenuEx -import com.beombeom.composeex.presentation.examples.lazyLow.RecyclerViewEx -import com.beombeom.composeex.presentation.examples.pager.PagerEx -import com.beombeom.composeex.presentation.examples.pullToRefresh.FullToRefreshEx -import com.beombeom.composeex.presentation.examples.sideEffect.SideEffectEx -import com.beombeom.composeex.presentation.examples.videoPlayer.VideoPlayerEx -import com.beombeom.composeex.presentation.navigation.ExDescriptions.DESCRIPTION_BOTTOM_SHEET_MENU -import com.beombeom.composeex.presentation.navigation.ExDescriptions.DESCRIPTION_BOTTOM_SHEET_SCAFFOLD -import com.beombeom.composeex.presentation.navigation.ExDescriptions.DESCRIPTION_DROPDOWNMENU -import com.beombeom.composeex.presentation.navigation.ExDescriptions.DESCRIPTION_MODAL_BOTTOM_SHEET -import com.beombeom.composeex.presentation.navigation.ExDescriptions.DESCRIPTION_PAGER -import com.beombeom.composeex.presentation.navigation.ExDescriptions.DESCRIPTION_PULL_TO_REFRESH -import com.beombeom.composeex.presentation.navigation.ExDescriptions.DESCRIPTION_RECYCLERVIEW -import com.beombeom.composeex.presentation.navigation.ExDescriptions.DESCRIPTION_SIDE_EFFECT -import com.beombeom.composeex.presentation.navigation.ExDescriptions.DESCRIPTION_VIDEO_PLAYER -import com.beombeom.composeex.presentation.navigation.ExTitles.TITLE_BOTTOM_SHEET_MENU -import com.beombeom.composeex.presentation.navigation.ExTitles.TITLE_BOTTOM_SHEET_SCAFFOLD -import com.beombeom.composeex.presentation.navigation.ExTitles.TITLE_DROPDOWNMENU -import com.beombeom.composeex.presentation.navigation.ExTitles.TITLE_MODAL_BOTTOM_SHEET -import com.beombeom.composeex.presentation.navigation.ExTitles.TITLE_PAGER -import com.beombeom.composeex.presentation.navigation.ExTitles.TITLE_PULL_TO_REFRESH -import com.beombeom.composeex.presentation.navigation.ExTitles.TITLE_RECYCLERVIEW -import com.beombeom.composeex.presentation.navigation.ExTitles.TITLE_SIDE_EFFECT -import com.beombeom.composeex.presentation.navigation.ExTitles.TITLE_VIDEO_PLAYER -import com.beombeom.composeex.presentation.navigation.Routes.BOTTOM_SHEET_MENU -import com.beombeom.composeex.presentation.navigation.Routes.BOTTOM_SHEET_SCAFFOLD -import com.beombeom.composeex.presentation.navigation.Routes.DROPDOWNMENU -import com.beombeom.composeex.presentation.navigation.Routes.MODAL_BOTTOM_SHEET -import com.beombeom.composeex.presentation.navigation.Routes.PAGER -import com.beombeom.composeex.presentation.navigation.Routes.PULL_TO_REFRESH -import com.beombeom.composeex.presentation.navigation.Routes.RECYCLERVIEW -import com.beombeom.composeex.presentation.navigation.Routes.SIDE_EFFECT -import com.beombeom.composeex.presentation.navigation.Routes.VIDEO_PLAYER +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +@Serializable +sealed class ExNavRoute : NavKey { + + @Serializable + data object Main : ExNavRoute() + + @Serializable + data object BottomSheetMenu : ExNavRoute() + + @Serializable + data object BottomSheetScaffold : ExNavRoute() + + @Serializable + data object ModalBottomSheet : ExNavRoute() + + @Serializable + data object Pager : ExNavRoute() + + @Serializable + data object RecyclerView : ExNavRoute() + + @Serializable + data object VideoPlayer : ExNavRoute() + + @Serializable + data object SideEffect : ExNavRoute() + + @Serializable + data object PullToRefresh : ExNavRoute() + + @Serializable + data object DropdownMenu : ExNavRoute() +} data class ExItem( - val route: String, + val route: ExNavRoute, val title: String, val subCategory: String? = "", - val content: @Composable (NavController?, NavBackStackEntry) -> Unit, val description: String = "", ) { companion object { private val BottomSheet = ExItem( - route = BOTTOM_SHEET_MENU, - title = TITLE_BOTTOM_SHEET_MENU, - content = { navController, _ -> - if (navController != null) { - BottomSheetMenu(navController) - } - }, - description = DESCRIPTION_BOTTOM_SHEET_MENU + route = ExNavRoute.BottomSheetMenu, + title = ExTitles.TITLE_BOTTOM_SHEET_MENU, + description = ExDescriptions.DESCRIPTION_BOTTOM_SHEET_MENU, ) private val BottomSheetScaffold = ExItem( - route = BOTTOM_SHEET_SCAFFOLD, - title = TITLE_BOTTOM_SHEET_SCAFFOLD, + route = ExNavRoute.BottomSheetScaffold, + title = ExTitles.TITLE_BOTTOM_SHEET_SCAFFOLD, subCategory = SubCategories.SUB_BOTTOM_SHEET, - content = { _, _ -> BottomSheetScaffoldEx(TITLE_BOTTOM_SHEET_SCAFFOLD) }, - description = DESCRIPTION_BOTTOM_SHEET_SCAFFOLD + description = ExDescriptions.DESCRIPTION_BOTTOM_SHEET_SCAFFOLD, ) private val ModalBottomSheet = ExItem( - route = MODAL_BOTTOM_SHEET, - title = TITLE_MODAL_BOTTOM_SHEET, + route = ExNavRoute.ModalBottomSheet, + title = ExTitles.TITLE_MODAL_BOTTOM_SHEET, subCategory = SubCategories.SUB_BOTTOM_SHEET, - content = { _, _ -> ModalBottomSheetEx(TITLE_MODAL_BOTTOM_SHEET) }, - description = DESCRIPTION_MODAL_BOTTOM_SHEET + description = ExDescriptions.DESCRIPTION_MODAL_BOTTOM_SHEET, ) private val Pager = ExItem( - route = PAGER, - title = TITLE_PAGER, - content = { _, _ -> PagerEx(TITLE_PAGER) }, - description = DESCRIPTION_PAGER + route = ExNavRoute.Pager, + title = ExTitles.TITLE_PAGER, + description = ExDescriptions.DESCRIPTION_PAGER, ) private val RecyclerView = ExItem( - route = RECYCLERVIEW, - title = TITLE_RECYCLERVIEW, - content = { _, _ -> RecyclerViewEx(TITLE_RECYCLERVIEW) }, - description = DESCRIPTION_RECYCLERVIEW + route = ExNavRoute.RecyclerView, + title = ExTitles.TITLE_RECYCLERVIEW, + description = ExDescriptions.DESCRIPTION_RECYCLERVIEW, ) private val VideoPlayer = ExItem( - route = VIDEO_PLAYER, - title = TITLE_VIDEO_PLAYER, - content = { _, _ -> VideoPlayerEx(TITLE_VIDEO_PLAYER) }, - description = DESCRIPTION_VIDEO_PLAYER + route = ExNavRoute.VideoPlayer, + title = ExTitles.TITLE_VIDEO_PLAYER, + description = ExDescriptions.DESCRIPTION_VIDEO_PLAYER, ) private val SideEffect = ExItem( - route = SIDE_EFFECT, - title = TITLE_SIDE_EFFECT, - content = { _, _ -> SideEffectEx(TITLE_SIDE_EFFECT) }, - description = DESCRIPTION_SIDE_EFFECT + route = ExNavRoute.SideEffect, + title = ExTitles.TITLE_SIDE_EFFECT, + description = ExDescriptions.DESCRIPTION_SIDE_EFFECT, ) private val PullToRefresh = ExItem( - route = PULL_TO_REFRESH, - title = TITLE_PULL_TO_REFRESH, - content = { _, _ -> FullToRefreshEx(TITLE_PULL_TO_REFRESH) }, - description = DESCRIPTION_PULL_TO_REFRESH + route = ExNavRoute.PullToRefresh, + title = ExTitles.TITLE_PULL_TO_REFRESH, + description = ExDescriptions.DESCRIPTION_PULL_TO_REFRESH, ) private val DropdownMenu = ExItem( - route = DROPDOWNMENU, - title = TITLE_DROPDOWNMENU, - content = { _, _ -> DropdownMenuEx(TITLE_DROPDOWNMENU) }, - description = DESCRIPTION_DROPDOWNMENU + route = ExNavRoute.DropdownMenu, + title = ExTitles.TITLE_DROPDOWNMENU, + description = ExDescriptions.DESCRIPTION_DROPDOWNMENU, ) - fun getExList(): List = listOf( - BottomSheet, - BottomSheetScaffold, - ModalBottomSheet, - Pager, - RecyclerView, - VideoPlayer, - SideEffect, - PullToRefresh, - DropdownMenu - ) + fun getExList(): List = + listOf( + BottomSheet, + BottomSheetScaffold, + ModalBottomSheet, + Pager, + RecyclerView, + VideoPlayer, + SideEffect, + PullToRefresh, + DropdownMenu, + ) } } -object Routes { - const val BOTTOM_SHEET_MENU = "bottomSheetMenu" - const val PAGER = "pager" - const val RECYCLERVIEW = "recyclerView" - const val VIDEO_PLAYER = "videoPlayer" - const val SIDE_EFFECT = "sideEffect" - const val PULL_TO_REFRESH = "pullToRefresh" - const val DROPDOWNMENU = "dropdownMenu" - - // sub routes - const val BOTTOM_SHEET_SCAFFOLD = "bottomSheetScaffold" - const val MODAL_BOTTOM_SHEET = "modalBottomSheet" -} - object ExTitles { const val TITLE_BOTTOM_SHEET_MENU = "Bottom Sheet Ex" const val TITLE_BOTTOM_SHEET_SCAFFOLD = "Bottom Sheet Scaffold Ex" @@ -171,4 +141,4 @@ object ExDescriptions { const val DESCRIPTION_SIDE_EFFECT = "다양한 SideEffect를 사용해보는 예제" const val DESCRIPTION_PULL_TO_REFRESH = "PullToRefresh를 구현해보는 예제" const val DESCRIPTION_DROPDOWNMENU = "드롭다운 메뉴를 구현해보는 예제" -} \ No newline at end of file +} diff --git a/app/src/main/java/com/beombeom/composeex/presentation/util/ModifierUtil.kt b/app/src/main/java/com/beombeom/composeex/presentation/util/ModifierUtil.kt index 0763ffb..ecc7c75 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/util/ModifierUtil.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/util/ModifierUtil.kt @@ -6,10 +6,12 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed -fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = composed { - clickable( - onClick = onClick, - indication = null, // Ripple Effect 제거 - interactionSource = remember { MutableInteractionSource() } - ) -} \ No newline at end of file +fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = + composed { + clickable( + onClick = onClick, + // Ripple Effect 제거 + indication = null, + interactionSource = remember { MutableInteractionSource() }, + ) + } diff --git a/app/src/main/java/com/beombeom/composeex/presentation/util/SetSystemUI.kt b/app/src/main/java/com/beombeom/composeex/presentation/util/SetSystemUI.kt index 4c938f6..b6b0e8c 100644 --- a/app/src/main/java/com/beombeom/composeex/presentation/util/SetSystemUI.kt +++ b/app/src/main/java/com/beombeom/composeex/presentation/util/SetSystemUI.kt @@ -15,4 +15,4 @@ fun SetSystemUI() { systemUiController.setStatusBarColor(Color.Yellow) // 하단 네비게이션 바(Navigation Bar) 색상을 지정합니다. systemUiController.setNavigationBarColor(Color.Red) -} \ No newline at end of file +} diff --git a/app/src/test/java/com/beombeom/composeex/ExampleUnitTest.kt b/app/src/test/java/com/beombeom/composeex/ExampleUnitTest.kt index 49a8eca..de96c2e 100644 --- a/app/src/test/java/com/beombeom/composeex/ExampleUnitTest.kt +++ b/app/src/test/java/com/beombeom/composeex/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.beombeom.composeex +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/build.gradle.kts b/build.gradle.kts index 922f551..5ba8ae0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,4 +2,6 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.kotlin.serialization) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a70f217..666afc5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -agp = "8.1.2" -kotlin = "1.9.10" +agp = "8.9.1" +kotlin = "2.1.20" coreKtx = "1.15.0" koinCore = "3.5.0" koinAndroid = "3.5.0" @@ -10,12 +10,18 @@ ui = "1.5.2" uiToolingPreview = "1.5.2" materialVersion = "1.5.2" material3 = "1.3.0" -lifecycleViewmodel = "2.6.2" +lifecycleViewmodel = "2.9.0" accompanistSystemuicontroller = "0.30.1" -lifecycleRuntimeCompose = "2.6.2" -navigationCompose = "2.8.4" +lifecycleRuntimeCompose = "2.9.0" +navigation3 = "1.1.2" +kotlinxSerializationJson = "1.7.3" media3Exoplayer = "1.5.0" media3Ui = "1.5.0" +navigation3Runtime = "1.1.2" +navigation3RuntimeVersion = "1.1.2" +junit = "4.13.2" +androidxJunit = "1.2.1" +espressoCore = "3.6.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -28,14 +34,24 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-material = { group = "androidx.compose.material", name = "material", version.ref = "materialVersion" } androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } androidx-lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "lifecycleViewmodel" } +lifecycle-viewmodel-navigation3 = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodel" } accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" } androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" } -androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } +androidx-navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3" } +androidx-navigation3-ui = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "navigation3" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3Exoplayer" } androidx-media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3Ui" } +navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3Runtime" } +androidx-navigation3-navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3RuntimeVersion" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4a7eadc..b190733 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ #Tue Nov 19 15:13:28 KST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists . \ No newline at end of file