From a82d0316f4b3a8fde3efdbcc9da5a5f05d133014 Mon Sep 17 00:00:00 2001 From: stslex Date: Sun, 23 Jul 2023 09:47:11 +0300 Subject: [PATCH] init first screen --- .github/workflows/android_build.yml | 40 ++++++++++++++++ app/src/main/AndroidManifest.xml | 3 +- .../com/stslex/aproselection/MainActivity.kt | 46 ------------------- .../stslex/aproselection/SelectApplication.kt | 17 +++++++ .../navigation/NavigationHost.kt | 43 +++++++++++++++++ .../stslex/aproselection/ui/MainActivity.kt | 23 ++++++++++ .../aproselection/ui/components/InitialApp.kt | 36 +++++++++++++++ .../stslex/aproselection/ui/theme/Theme.kt | 2 +- .../core/network/AppArguments.kt | 16 +++++++ .../core/network/AppDestination.kt | 28 +++++++++++ .../aproselection/core/network/NavExt.kt | 21 +++++++++ .../core/network/NavigationScreen.kt | 26 +++++++++++ feature/auth/build.gradle.kts | 7 +++ .../feature/auth/di/FeatureAuthModule.kt | 12 +++++ .../feature/auth/ui/AuthScreen.kt | 37 +++++++++++++++ .../feature/auth/ui/AuthViewModel.kt | 15 ++++++ .../feature/auth/ui/navigation/AuthRouter.kt | 34 ++++++++++++++ gradle/libs.versions.toml | 7 ++- 18 files changed, 364 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/android_build.yml delete mode 100644 app/src/main/java/com/stslex/aproselection/MainActivity.kt create mode 100644 app/src/main/java/com/stslex/aproselection/SelectApplication.kt create mode 100644 app/src/main/java/com/stslex/aproselection/navigation/NavigationHost.kt create mode 100644 app/src/main/java/com/stslex/aproselection/ui/MainActivity.kt create mode 100644 app/src/main/java/com/stslex/aproselection/ui/components/InitialApp.kt create mode 100644 core/network/src/main/java/com/stslex/aproselection/core/network/AppArguments.kt create mode 100644 core/network/src/main/java/com/stslex/aproselection/core/network/AppDestination.kt create mode 100644 core/network/src/main/java/com/stslex/aproselection/core/network/NavExt.kt create mode 100644 core/network/src/main/java/com/stslex/aproselection/core/network/NavigationScreen.kt create mode 100644 feature/auth/src/main/java/com/stslex/aproselection/feature/auth/di/FeatureAuthModule.kt create mode 100644 feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthScreen.kt create mode 100644 feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthViewModel.kt create mode 100644 feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/navigation/AuthRouter.kt diff --git a/.github/workflows/android_build.yml b/.github/workflows/android_build.yml new file mode 100644 index 0000000..0e9b40e --- /dev/null +++ b/.github/workflows/android_build.yml @@ -0,0 +1,40 @@ +name: Android Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + + - name: Checkout branch + uses: actions/checkout@v2 + + - name: set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: set up LOCAL_PROPERTIES + env: + LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} + run: echo "$LOCAL_PROPERTIES" > ./local.properties + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew build + + - name: Junit tests with Gradle + run: ./gradlew testDebugUnitTest \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a2bb8b8..ffaeea6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> diff --git a/app/src/main/java/com/stslex/aproselection/MainActivity.kt b/app/src/main/java/com/stslex/aproselection/MainActivity.kt deleted file mode 100644 index b9b50b7..0000000 --- a/app/src/main/java/com/stslex/aproselection/MainActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.stslex.aproselection - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.stslex.aproselection.ui.theme.AProSelectionTheme - -class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - AProSelectionTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - Greeting("Android") - } - } - } - } -} - -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier - ) -} - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - AProSelectionTheme { - Greeting("Android") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stslex/aproselection/SelectApplication.kt b/app/src/main/java/com/stslex/aproselection/SelectApplication.kt new file mode 100644 index 0000000..3b44870 --- /dev/null +++ b/app/src/main/java/com/stslex/aproselection/SelectApplication.kt @@ -0,0 +1,17 @@ +package com.stslex.aproselection + +import android.app.Application +import com.stslex.aproselection.feature.auth.di.FeatureAuthModule.featureAuthModule +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin + +class SelectApplication : Application() { + + override fun onCreate() { + super.onCreate() + startKoin { + androidContext(applicationContext) + modules(featureAuthModule) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stslex/aproselection/navigation/NavigationHost.kt b/app/src/main/java/com/stslex/aproselection/navigation/NavigationHost.kt new file mode 100644 index 0000000..c5500dc --- /dev/null +++ b/app/src/main/java/com/stslex/aproselection/navigation/NavigationHost.kt @@ -0,0 +1,43 @@ +package com.stslex.aproselection.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import com.stslex.aproselection.core.network.AppDestination +import com.stslex.aproselection.core.network.NavigationScreen +import com.stslex.aproselection.feature.auth.ui.navigation.authRouter + +@Composable +fun NavigationHost( + navController: NavHostController, + modifier: Modifier = Modifier, + startDestination: AppDestination = AppDestination.AUTH +) { + val navigator: (NavigationScreen) -> Unit = { screen -> + when (screen) { + is NavigationScreen.PopBackStack -> navController.popBackStack() + else -> navController.navigateScreen(screen) + } + } + NavHost( + navController = navController, + startDestination = startDestination.route + ) { + authRouter(modifier, navigator) + } +} + +fun NavHostController.navigateScreen(screen: NavigationScreen) { + navigate(screen.screenRoute) { + if (screen.isSingleTop.not()) return@navigate + graph.startDestinationRoute?.let { route -> + popUpTo(route) { + inclusive = true + saveState = true + } + } + launchSingleTop = true + restoreState = false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stslex/aproselection/ui/MainActivity.kt b/app/src/main/java/com/stslex/aproselection/ui/MainActivity.kt new file mode 100644 index 0000000..3e956a9 --- /dev/null +++ b/app/src/main/java/com/stslex/aproselection/ui/MainActivity.kt @@ -0,0 +1,23 @@ +package com.stslex.aproselection.ui + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.core.view.WindowCompat +import com.stslex.aproselection.ui.components.InitialApp +import com.stslex.aproselection.ui.theme.AppTheme + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + WindowCompat.setDecorFitsSystemWindows(window, false) + + setContent { + AppTheme { + InitialApp() + } + } + } +} diff --git a/app/src/main/java/com/stslex/aproselection/ui/components/InitialApp.kt b/app/src/main/java/com/stslex/aproselection/ui/components/InitialApp.kt new file mode 100644 index 0000000..197ee41 --- /dev/null +++ b/app/src/main/java/com/stslex/aproselection/ui/components/InitialApp.kt @@ -0,0 +1,36 @@ +package com.stslex.aproselection.ui.components + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.compose.rememberNavController +import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.stslex.aproselection.navigation.NavigationHost +import com.stslex.aproselection.ui.theme.AppTheme + +@Composable +fun InitialApp() { + val navController = rememberNavController() + val systemUiController = rememberSystemUiController() + val isDarkTheme = isSystemInDarkTheme() + + DisposableEffect(systemUiController, isDarkTheme) { + systemUiController.setSystemBarsColor( + color = Color.Transparent, + darkIcons = isDarkTheme.not(), + ) + onDispose {} + } + + NavigationHost(navController = navController) +} + +@Preview(showBackground = true) +@Composable +fun InitialAppPreview() { + AppTheme { + InitialApp() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stslex/aproselection/ui/theme/Theme.kt b/app/src/main/java/com/stslex/aproselection/ui/theme/Theme.kt index c597e75..0de4bc3 100644 --- a/app/src/main/java/com/stslex/aproselection/ui/theme/Theme.kt +++ b/app/src/main/java/com/stslex/aproselection/ui/theme/Theme.kt @@ -38,7 +38,7 @@ private val LightColorScheme = lightColorScheme( ) @Composable -fun AProSelectionTheme( +fun AppTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/AppArguments.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/AppArguments.kt new file mode 100644 index 0000000..800d653 --- /dev/null +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/AppArguments.kt @@ -0,0 +1,16 @@ +package com.stslex.aproselection.core.network + +sealed class AppArguments { + + abstract val arguments: List + + open val argumentsForRoute: String + get() = arguments.joinToString(separator = "/", prefix = "/") + + object Empty : AppArguments() { + override val arguments: List + get() = emptyList() + override val argumentsForRoute: String + get() = String() + } +} \ No newline at end of file diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/AppDestination.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/AppDestination.kt new file mode 100644 index 0000000..08001d7 --- /dev/null +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/AppDestination.kt @@ -0,0 +1,28 @@ +package com.stslex.aproselection.core.network + +enum class AppDestination( + vararg val argsNames: String +) { + AUTH; + + val route: String + get() = StringBuilder() + .append(name, SEPARATOR_ROUTE, TAG_ROUTE) + .toString() + .lowercase() + + val navigationRoute: String + get() = "$route${argsNames.argumentsRoute}" + + private val Array.argumentsRoute: String + get() = if (isEmpty()) { + String() + } else { + joinToString(separator = "}/{", prefix = "/{", postfix = "}") + } + + companion object { + private const val SEPARATOR_ROUTE = "_" + private const val TAG_ROUTE = "route" + } +} \ No newline at end of file diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/NavExt.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/NavExt.kt new file mode 100644 index 0000000..4ca939b --- /dev/null +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/NavExt.kt @@ -0,0 +1,21 @@ +package com.stslex.aproselection.core.network + +import androidx.navigation.NamedNavArgument +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavType +import androidx.navigation.navArgument + +object NavExt { + + val AppDestination.composableArguments: List + get() = argsNames.map { name -> + navArgument(name) { NavType.StringType } + } + + val AppDestination.parseArguments: NavBackStackEntry.() -> List + get() = { + argsNames.map { name -> + arguments?.getString(name).orEmpty() + } + } +} \ No newline at end of file diff --git a/core/network/src/main/java/com/stslex/aproselection/core/network/NavigationScreen.kt b/core/network/src/main/java/com/stslex/aproselection/core/network/NavigationScreen.kt new file mode 100644 index 0000000..1b6ae5c --- /dev/null +++ b/core/network/src/main/java/com/stslex/aproselection/core/network/NavigationScreen.kt @@ -0,0 +1,26 @@ +package com.stslex.aproselection.core.network + +sealed class NavigationScreen { + + abstract val screen: AppDestination + + val screenRoute: String + get() = "${screen.route}${appArgs.argumentsForRoute}" + + open val isSingleTop: Boolean + get() = false + + open val appArgs: AppArguments + get() = AppArguments.Empty + + object Auth : NavigationScreen() { + + override val screen: AppDestination = AppDestination.AUTH + override val isSingleTop: Boolean = true + } + + object PopBackStack : NavigationScreen() { + override val screen: AppDestination = throw Exception("PopBackStack") + override val appArgs: AppArguments = throw Exception("PopBackStack") + } +} \ No newline at end of file diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts index 286e6f4..9119183 100644 --- a/feature/auth/build.gradle.kts +++ b/feature/auth/build.gradle.kts @@ -31,6 +31,13 @@ android { kotlinOptions { jvmTarget = "1.8" } + + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.3" + } } dependencies { diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/di/FeatureAuthModule.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/di/FeatureAuthModule.kt new file mode 100644 index 0000000..2c32423 --- /dev/null +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/di/FeatureAuthModule.kt @@ -0,0 +1,12 @@ +package com.stslex.aproselection.feature.auth.di + +import com.stslex.aproselection.feature.auth.ui.AuthViewModel +import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.dsl.module + +object FeatureAuthModule { + + val featureAuthModule = module { + viewModelOf(::AuthViewModel) + } +} \ No newline at end of file diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthScreen.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthScreen.kt new file mode 100644 index 0000000..99c0604 --- /dev/null +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthScreen.kt @@ -0,0 +1,37 @@ +package com.stslex.aproselection.feature.auth.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.stslex.aproselection.core.network.NavigationScreen + +@Composable +fun AuthScreen( + text: String, + navigate: (NavigationScreen) -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier.fillMaxSize() + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = text, + style = MaterialTheme.typography.headlineMedium + ) + } +} + +@Preview +@Composable +fun AuthScreenPreview() { + AuthScreen( + text = "text", + navigate = {} + ) +} \ No newline at end of file diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthViewModel.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthViewModel.kt new file mode 100644 index 0000000..379daaf --- /dev/null +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/AuthViewModel.kt @@ -0,0 +1,15 @@ +package com.stslex.aproselection.feature.auth.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn + +class AuthViewModel : ViewModel() { + + val text: StateFlow + get() = flowOf("Hello") + .stateIn(viewModelScope, SharingStarted.Lazily, "") +} \ No newline at end of file diff --git a/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/navigation/AuthRouter.kt b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/navigation/AuthRouter.kt new file mode 100644 index 0000000..a2dd811 --- /dev/null +++ b/feature/auth/src/main/java/com/stslex/aproselection/feature/auth/ui/navigation/AuthRouter.kt @@ -0,0 +1,34 @@ +package com.stslex.aproselection.feature.auth.ui.navigation + +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import com.stslex.aproselection.core.network.AppDestination +import com.stslex.aproselection.core.network.NavigationScreen +import com.stslex.aproselection.feature.auth.ui.AuthScreen +import com.stslex.aproselection.feature.auth.ui.AuthViewModel +import org.koin.androidx.compose.koinViewModel + +fun NavGraphBuilder.authRouter( + modifier: Modifier = Modifier, + navigate: (NavigationScreen) -> Unit +) { + composable( + route = AppDestination.AUTH.navigationRoute + ) { + val viewModel: AuthViewModel = koinViewModel() + + val text by remember { + viewModel.text + }.collectAsState() + + AuthScreen( + text = text, + navigate = navigate, + modifier = modifier + ) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 45e9923..e4a24a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,8 @@ compose-bom = "2023.06.01" appcompat = "1.6.1" material = "1.9.0" +accompanist = "0.30.0" + composeNavigation = "2.6.0" coilCompose = "2.2.2" @@ -43,6 +45,8 @@ material = { group = "com.google.android.material", name = "material", version.r androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation" } coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coilCompose" } +accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" } + ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktorAndroid" } @@ -76,7 +80,8 @@ compose = [ "ui-tooling-preview", "material3", "coil-compose", - "androidx-compose-navigation" + "androidx-compose-navigation", + "accompanist-systemuicontroller" ] android-test = [