diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f2f3cf91..a48526cc 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -19,6 +19,26 @@ jobs:
ref: ${{ github.head_ref }}
submodules: "recursive"
+ - name: Cache Gradle
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.gradle/caches/modules-*
+ ~/.gradle/caches/jars-*
+ ~/.gradle/caches/build-cache-*
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
+
+ - name: Install Kscript
+ run: |
+ curl -s "https://get.sdkman.io" | bash
+ source "$HOME/.sdkman/bin/sdkman-init.sh"
+ source android/version.properties
+ sdk install kotlin $kotlinVersion
+ sdk install kscript $kscriptVersion
+ echo $PATH >> $GITHUB_PATH
+
# To avoid code change in git when refreshing the "sample" project
- name: Preserve the ArkanaKeys
run: mv -f ./sample/ios/ArkanaKeys ../
diff --git a/.gitmodules b/.gitmodules
index 117eab97..8b88b8ad 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,7 @@
[submodule "ios"]
path = ios
url = https://github.com/nimblehq/ios-templates.git
+[submodule "android"]
+ path = android
+ url = git@github.com:nimblehq/android-templates.git
+ branch = feature/replace-hilt-with-koin
diff --git a/android b/android
new file mode 160000
index 00000000..5cb0ea45
--- /dev/null
+++ b/android
@@ -0,0 +1 @@
+Subproject commit 5cb0ea459a57f9363406c5dddc1de0800c5e43e4
diff --git a/android/.gitignore b/android/.gitignore
deleted file mode 100644
index eb82db4a..00000000
--- a/android/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# Google services
-google-services.json
-
-# Keystore
-config/release.keystore
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
deleted file mode 100644
index b077b599..00000000
--- a/android/build.gradle.kts
+++ /dev/null
@@ -1,147 +0,0 @@
-import org.jetbrains.kotlin.konan.properties.loadProperties
-
-plugins {
- id(Plugins.ANDROID_APPLICATION)
- id(Plugins.KOVER)
- kotlin(Plugins.ANDROID)
-}
-
-val keystoreProperties = loadProperties("$rootDir/signing.properties")
-
-android {
- namespace = "co.nimblehq.kmm.template"
- compileSdk = Versions.ANDROID_COMPILE_SDK
- defaultConfig {
- applicationId = "co.nimblehq.kmm.template"
- minSdk = Versions.ANDROID_MIN_SDK
- targetSdk = Versions.ANDROID_TARGET_SDK
- versionCode = Versions.ANDROID_VERSION_CODE
- versionName = Versions.ANDROID_VERSION_NAME
- }
- buildFeatures {
- compose = true
- }
- composeOptions {
- kotlinCompilerExtensionVersion = Versions.COMPOSE_COMPILER
- }
- packaging {
- resources {
- excludes += "/META-INF/{AL2.0,LGPL2.1}"
- }
- }
- signingConfigs {
- create(BuildTypes.RELEASE) {
- // Remember to edit signing.properties to have the correct info for release build.
- storeFile = file("../config/release.keystore")
- storePassword = keystoreProperties.getProperty("KEYSTORE_PASSWORD")
- keyPassword = keystoreProperties.getProperty("KEY_PASSWORD")
- keyAlias = keystoreProperties.getProperty("KEY_ALIAS")
- }
-
- getByName(BuildTypes.DEBUG) {
- storeFile = file("../config/debug.keystore")
- //FIXME: Replace with your own password
- storePassword = "oQ4mL1jY2uX7wD8q"
- keyAlias = "debug-key-alias"
- keyPassword = "oQ4mL1jY2uX7wD8q"
- }
- }
- buildTypes {
- getByName(BuildTypes.RELEASE) {
- isMinifyEnabled = true
- isDebuggable = false
- isShrinkResources = true
- proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
- signingConfig = signingConfigs[BuildTypes.RELEASE]
- }
-
- getByName(BuildTypes.DEBUG) {
- // For quickly testing build with proguard, enable this
- isMinifyEnabled = false
- signingConfig = signingConfigs[BuildTypes.DEBUG]
- }
- }
- flavorDimensions += Flavors.DIMENSION_VERSION
- productFlavors {
- create(Flavors.STAGING) {
- applicationIdSuffix = ".staging"
- resValue("string", "app_name", "KMM Templates - Staging")
- }
-
- create(Flavors.PRODUCTION) {
- resValue("string", "app_name", "KMM Templates")
- }
- }
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_17.toString()
- }
-}
-
-dependencies {
- implementation(project(Modules.SHARED))
-
- with(Dependencies.AndroidX) {
- implementation(ACTIVITY_COMPOSE)
- }
-
- with(Dependencies.Compose) {
- implementation(UI)
- implementation(UI_GRAPHICS)
- implementation(MATERIAL)
- implementation(NAVIGATION)
- implementation(UI_TOOLING)
- }
-
- with(Dependencies.Koin) {
- implementation(CORE)
- implementation(ANDROID)
- implementation(COMPOSE)
- }
-
- with(Dependencies.Log) {
- implementation(TIMBER)
- }
-
- with(Dependencies.Test) {
- implementation(JUNIT)
- implementation(COROUTINES)
- }
-}
-
-/*
- * Kover configs
- */
-dependencies {
- kover(project(":shared"))
-}
-
-koverReport {
- defaults {
- mergeWith("stagingDebug")
-
- val excludedFiles = listOf(
- "io.mockative.*",
- "*.BuildConfig",
- "*.BuildKonfig", // BuildKonfig generated
- "*.ComposableSingletons*", // Jetpack Compose generated
- "*.*\$*Preview\$*", // Jetpack Compose Preview functions
- "*.di.*", // Koin
- "*.ui.preview.*", // Jetpack Compose Preview providers
- "*.*Test", // Test files
- "*.*Test*", // Test cases
- "*.*Mock", // mockative @Mock generated
- "*.test.*", // Test util package
- "*.*\$\$serializer", // Kotlinx serializer
- )
-
- filters {
- excludes {
- classes(excludedFiles)
- }
- }
- }
-}
diff --git a/android/src/debug/java/co/nimblehq/kmm/template/util/LogUtil.kt b/android/src/debug/java/co/nimblehq/kmm/template/util/LogUtil.kt
deleted file mode 100644
index d6aa6e3b..00000000
--- a/android/src/debug/java/co/nimblehq/kmm/template/util/LogUtil.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package co.nimblehq.kmm.template.util
-
-import timber.log.Timber
-
-object LogUtil {
-
- fun setUpLogging() {
- Timber.plant(Timber.DebugTree())
- }
-}
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
deleted file mode 100644
index 44c41ed3..00000000
--- a/android/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/src/main/java/co/nimblehq/kmm/template/MainApplication.kt b/android/src/main/java/co/nimblehq/kmm/template/MainApplication.kt
deleted file mode 100644
index ee71fa08..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/MainApplication.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package co.nimblehq.kmm.template
-
-import android.app.Application
-import co.nimblehq.kmm.template.di.androidViewModelModule
-import co.nimblehq.kmm.template.util.LogUtil
-import co.nimblehq.kmm.template.di.initKoin
-import org.koin.android.ext.koin.androidContext
-
-class MainApplication : Application() {
-
- override fun onCreate() {
- super.onCreate()
- LogUtil.setUpLogging()
- initKoin {
- androidContext(applicationContext)
- modules(androidViewModelModule)
- }
- }
-}
diff --git a/android/src/main/java/co/nimblehq/kmm/template/di/ViewModelModule.kt b/android/src/main/java/co/nimblehq/kmm/template/di/ViewModelModule.kt
deleted file mode 100644
index 69875337..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/di/ViewModelModule.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package co.nimblehq.kmm.template.di
-
-import org.koin.core.module.Module
-import org.koin.dsl.module
-
-val androidViewModelModule: Module = module {
- // TODO declare viewModel modules here
-}
diff --git a/android/src/main/java/co/nimblehq/kmm/template/lib/TypeAlias.kt b/android/src/main/java/co/nimblehq/kmm/template/lib/TypeAlias.kt
deleted file mode 100644
index 5e6a53c9..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/lib/TypeAlias.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package co.nimblehq.kmm.template.lib
-
-typealias IsLoading = Boolean
diff --git a/android/src/main/java/co/nimblehq/kmm/template/ui/base/BaseViewModel.kt b/android/src/main/java/co/nimblehq/kmm/template/ui/base/BaseViewModel.kt
deleted file mode 100644
index 65ac5a84..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/ui/base/BaseViewModel.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-package co.nimblehq.kmm.template.ui.base
-
-import androidx.compose.runtime.*
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import kotlinx.coroutines.flow.*
-import kotlinx.coroutines.launch
-import co.nimblehq.kmm.template.lib.IsLoading
-import co.nimblehq.kmm.template.ui.navigation.AppDestination
-
-@Suppress("PropertyName")
-abstract class BaseViewModel : ViewModel() {
-
- private var loadingCount: Int = 0
-
- private val _isLoading = MutableStateFlow(false)
- val isLoading: StateFlow = _isLoading
-
- protected val _error = MutableStateFlow(null)
- val error: StateFlow = _error
-
- protected val _navigator = MutableSharedFlow()
- val navigator: SharedFlow = _navigator
-
- /**
- * To show loading manually, should call `hideLoading` after
- */
- protected fun showLoading() {
- if (loadingCount == 0) {
- _isLoading.value = true
- }
- loadingCount++
- }
-
- /**
- * To hide loading manually, should be called after `showLoading`
- */
- protected fun hideLoading() {
- loadingCount--
- if (loadingCount == 0) {
- _isLoading.value = false
- }
- }
-
- protected fun Flow.injectLoading(): Flow = this
- .onStart { showLoading() }
- .onCompletion { hideLoading() }
-
- fun clearError() {
- viewModelScope.launch { _error.emit(null) }
- }
-}
diff --git a/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppDestination.kt b/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppDestination.kt
deleted file mode 100644
index 22017549..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppDestination.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package co.nimblehq.kmm.template.ui.navigation
-
-import androidx.navigation.NamedNavArgument
-
-sealed class AppDestination(val route: String = "") {
-
- open val arguments: List = emptyList()
-
- open var destination: String = route
-
- //====================================================//
-
- object Up : AppDestination()
-
- object Home : AppDestination("home")
-}
diff --git a/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppNavigation.kt b/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppNavigation.kt
deleted file mode 100644
index 2b064b81..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppNavigation.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package co.nimblehq.kmm.template.ui.navigation
-
-import androidx.compose.runtime.Composable
-import androidx.navigation.*
-import androidx.navigation.compose.*
-import co.nimblehq.kmm.template.ui.screens.home.HomeScreen
-
-@Composable
-fun AppNavigation(
- navController: NavHostController = rememberNavController(),
- startDestination: String = AppDestination.Home.destination
-) {
- NavHost(
- navController = navController,
- startDestination = startDestination
- ) {
- composable(AppDestination.Home) {
- HomeScreen(
- // TODO
- // navigator = { destination -> navController.navigate(destination) }
- )
- }
- }
-}
-
-private fun NavGraphBuilder.composable(
- destination: AppDestination,
- deepLinks: List = emptyList(),
- content: @Composable (NavBackStackEntry) -> Unit
-) {
- composable(
- route = destination.route,
- arguments = destination.arguments,
- deepLinks = deepLinks,
- content = content
- )
-}
-
-private fun NavHostController.navigate(destination: AppDestination) {
- when (destination) {
- is AppDestination.Up -> popBackStack()
- else -> navigate(route = destination.destination)
- }
-}
diff --git a/android/src/main/java/co/nimblehq/kmm/template/ui/screens/MainActivity.kt b/android/src/main/java/co/nimblehq/kmm/template/ui/screens/MainActivity.kt
deleted file mode 100644
index 75d6bbc1..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/ui/screens/MainActivity.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package co.nimblehq.kmm.template.ui.screens
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import co.nimblehq.kmm.template.ui.navigation.AppNavigation
-import co.nimblehq.kmm.template.ui.theme.ComposeTheme
-
-class MainActivity : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- ComposeTheme {
- AppNavigation()
- }
- }
- }
-}
diff --git a/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreen.kt b/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreen.kt
deleted file mode 100644
index 800e2bc7..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreen.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package co.nimblehq.kmm.template.ui.screens.home
-
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material.*
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import co.nimblehq.kmm.template.Greeting
-import co.nimblehq.kmm.template.ui.theme.ComposeTheme
-
-@Composable
-fun HomeScreen() {
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colors.background
- ) {
- Text(text = Greeting().greet())
- }
-}
-
-@Preview
-@Composable
-private fun HomeScreenPreview() {
- ComposeTheme {
- HomeScreen()
- }
-}
diff --git a/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppColors.kt b/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppColors.kt
deleted file mode 100644
index 37eca4df..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppColors.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package co.nimblehq.kmm.template.ui.theme
-
-import androidx.compose.material.*
-import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.graphics.Color
-
-/**
- * Extend final [Colors] class to provide more custom app colors.
- */
-data class AppColors(
- val themeColors: Colors,
-
- // Custom colors here
-)
-
-internal val LightColorPalette = AppColors(
- themeColors = lightColors(
- primary = Color(0xFF6200EE),
- primaryVariant = Color(0xFF3700B3),
- secondary = Color(0xFF03DAC5)
- )
-)
-
-internal val DarkColorPalette = AppColors(
- themeColors = darkColors(
- primary = Color(0xFFBB86FC),
- primaryVariant = Color(0xFF3700B3),
- secondary = Color(0xFF03DAC5)
- )
-)
-
-internal val LocalColors = staticCompositionLocalOf { LightColorPalette }
diff --git a/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppDimensions.kt b/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppDimensions.kt
deleted file mode 100644
index 3875be76..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppDimensions.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package co.nimblehq.kmm.template.ui.theme
-
-import androidx.compose.runtime.staticCompositionLocalOf
-
-class AppDimensions {
- // Custom dimensions here
-}
-
-internal val LocalAppDimensions = staticCompositionLocalOf { AppDimensions() }
diff --git a/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppStyles.kt b/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppStyles.kt
deleted file mode 100644
index b6568d36..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppStyles.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package co.nimblehq.kmm.template.ui.theme
-
-import androidx.compose.runtime.staticCompositionLocalOf
-
-class AppStyles {
- // Custom styles here
-}
-
-internal val LocalAppStyles = staticCompositionLocalOf { AppStyles() }
diff --git a/android/src/main/java/co/nimblehq/kmm/template/ui/theme/Theme.kt b/android/src/main/java/co/nimblehq/kmm/template/ui/theme/Theme.kt
deleted file mode 100644
index ae5e06ad..00000000
--- a/android/src/main/java/co/nimblehq/kmm/template/ui/theme/Theme.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-@file:Suppress("MatchingDeclarationName")
-package co.nimblehq.kmm.template.ui.theme
-
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-
-@Composable
-fun ComposeTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
- content: @Composable () -> Unit
-) {
- val colors = if (darkTheme) {
- DarkColorPalette
- } else {
- LightColorPalette
- }
- val dimensions = LocalAppDimensions.current
- val styles = LocalAppStyles.current
-
- val typography = Typography(
- body1 = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Normal,
- fontSize = 16.sp
- )
- )
- val shapes = Shapes(
- small = RoundedCornerShape(4.dp),
- medium = RoundedCornerShape(4.dp),
- large = RoundedCornerShape(0.dp)
- )
-
- CompositionLocalProvider(
- LocalColors provides colors,
- LocalAppDimensions provides dimensions,
- LocalAppStyles provides styles
- ) {
- MaterialTheme(
- colors = colors.themeColors,
- typography = typography,
- shapes = shapes,
- content = content
- )
- }
-}
-
-/**
- * Alternate to [MaterialTheme] allowing us to add our own theme systems
- * or to extend [MaterialTheme]'s types e.g. return our own [Colors] extension.
- */
-object AppTheme {
-
- val colors: AppColors
- @Composable
- @ReadOnlyComposable
- get() = LocalColors.current
-
- val typography: Typography
- @Composable
- @ReadOnlyComposable
- get() = MaterialTheme.typography
-
- val shapes: Shapes
- @Composable
- @ReadOnlyComposable
- get() = MaterialTheme.shapes
-
- val dimensions: AppDimensions
- @Composable
- @ReadOnlyComposable
- get() = LocalAppDimensions.current
-
- val styles: AppStyles
- @Composable
- @ReadOnlyComposable
- get() = LocalAppStyles.current
-}
diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml
deleted file mode 100644
index 676b4dda..00000000
--- a/android/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/android/src/release/java/co/nimblehq/kmm/template/util/LogUtil.kt b/android/src/release/java/co/nimblehq/kmm/template/util/LogUtil.kt
deleted file mode 100644
index 2ee5a119..00000000
--- a/android/src/release/java/co/nimblehq/kmm/template/util/LogUtil.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package co.nimblehq.kmm.template.util
-
-import timber.log.Timber
-
-object LogUtil {
-
- fun setUpLogging() {
- // Do nothing
- }
-}
diff --git a/android/src/test/java/co/nimblehq/kmm/template/test/CoroutineTestRule.kt b/android/src/test/java/co/nimblehq/kmm/template/test/CoroutineTestRule.kt
deleted file mode 100644
index 6288cf1c..00000000
--- a/android/src/test/java/co/nimblehq/kmm/template/test/CoroutineTestRule.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package co.nimblehq.kmm.template.test
-
-import kotlinx.coroutines.*
-import kotlinx.coroutines.test.*
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
-
-@OptIn(ExperimentalCoroutinesApi::class)
-class CoroutineTestRule(
- private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
-) : TestWatcher() {
-
- override fun starting(description: Description?) {
- super.starting(description)
- Dispatchers.setMain(testDispatcher)
- }
-
- override fun finished(description: Description?) {
- super.finished(description)
- Dispatchers.resetMain()
- }
-}
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index f7ae174a..e0790d1f 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -1,20 +1,23 @@
object Dependencies {
object AndroidX {
- const val ACTIVITY_COMPOSE = "androidx.activity:activity-compose:${Versions.ANDROIDX_ACTIVITY_COMPOSE}"
+ const val CORE_KTX = "androidx.core:core-ktx:${Versions.CORE_KTX}"
+ const val LIFECYCLE_RUNTIME_KTX = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.LIFECYCLE}"
+ const val LIFECYCLE_RUNTIME_COMPOSE = "androidx.lifecycle:lifecycle-runtime-compose:${Versions.LIFECYCLE}"
+
+ const val DATASTORE_PREFERENCES = "androidx.datastore:datastore-preferences:${Versions.DATASTORE_PREFERENCES}"
+ const val SECURITY_CRYPTO = "androidx.security:security-crypto:${Versions.SECURITY_CRYPTO}"
}
object Compose {
- const val FOUNDATION = "androidx.compose.foundation:foundation:${Versions.COMPOSE}"
-
- const val MATERIAL = "androidx.compose.material:material:${Versions.COMPOSE}"
-
+ const val BOM = "androidx.compose:compose-bom:${Versions.COMPOSE_BOM}"
+ const val UI = "androidx.compose.ui:ui"
+ const val UI_GRAPHICS = "androidx.compose.ui:ui-graphics"
+ const val UI_TOOLING = "androidx.compose.ui:ui-tooling"
+ const val MATERIAL = "androidx.compose.material:material"
const val NAVIGATION = "androidx.navigation:navigation-compose:${Versions.COMPOSE_NAVIGATION}"
- const val UI = "androidx.compose.ui:ui:${Versions.COMPOSE}"
- const val UI_GRAPHICS = "androidx.compose.ui:ui-graphics:${Versions.COMPOSE}"
- const val UI_TOOLING = "androidx.compose.ui:ui-tooling:${Versions.COMPOSE}"
- const val UI_TOOLING_PREVIEW = "androidx.compose.ui:ui-tooling-preview:${Versions.COMPOSE}"
+ const val ACCOMPANIST_PERMISSIONS = "com.google.accompanist:accompanist-permissions:${Versions.ACCOMPANIST}"
}
object Koin {
@@ -45,9 +48,12 @@ object Dependencies {
const val TIMBER = "com.jakewharton.timber:timber:${Versions.TIMBER}"
}
+ object Util {
+ const val COMMON_KTX = "com.github.nimblehq:android-common-ktx:${Versions.COMMON_KTX}"
+ }
+
object Test {
- const val COMPOSE_UI_TEST_JUNIT = "androidx.compose.ui:ui-test-junit4:${Versions.COMPOSE}"
- const val COMPOSE_UI_TEST_MANIFEST = "androidx.compose.ui:ui-test-manifest:${Versions.COMPOSE}"
+ const val COMPOSE_UI_TEST_JUNIT = "androidx.compose.ui:ui-test-junit4"
const val COROUTINES = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.KOTLIN_COROUTINES}"
const val JUNIT = "junit:junit:${Versions.JUNIT}"
diff --git a/buildSrc/src/main/java/Plugins.kt b/buildSrc/src/main/java/Plugins.kt
index 671a93ab..a0336b07 100644
--- a/buildSrc/src/main/java/Plugins.kt
+++ b/buildSrc/src/main/java/Plugins.kt
@@ -9,6 +9,8 @@ object Plugins {
const val IOS_COCOAPODS = "native.cocoapods"
+ const val KOTLIN_ANDROID = "org.jetbrains.kotlin.android"
+ const val KOTLIN_PARCELIZE = "kotlin-parcelize"
const val KOTLIN_SERIALIZATION = "plugin.serialization"
const val KOTLINX_SERIALIZATION = "kotlinx-serialization"
const val KOVER = "org.jetbrains.kotlinx.kover"
diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt
index 2bfae497..837d4ea1 100644
--- a/buildSrc/src/main/java/Versions.kt
+++ b/buildSrc/src/main/java/Versions.kt
@@ -5,31 +5,36 @@ object Versions {
const val ANDROID_VERSION_CODE = 1
const val ANDROID_VERSION_NAME = "1.0.0"
- const val ANDROIDX_ACTIVITY_COMPOSE = "1.7.1"
+ const val ACCOMPANIST = "0.30.1"
const val BUILD_KONFIG = "0.13.3"
- const val COMPOSE = "1.4.3"
+ const val COMMON_KTX = "0.1.1"
+ const val COMPOSE_BOM = "2023.04.01"
const val COMPOSE_COMPILER = "1.4.7"
const val COMPOSE_NAVIGATION = "2.6.0"
+ const val CORE_KTX = "1.10.1"
+ const val DATASTORE_PREFERENCES = "1.0.0"
const val DETEKT = "1.23.0"
- const val GRADLE = "8.0.2"
+ const val GRADLE = "8.1.0"
const val JUNIT = "4.13.2"
const val KOIN = "3.3.2"
const val KOIN_ANDROID = "3.3.2"
const val KOIN_ANDROIDX_COMPOSE = "3.4.1"
+ const val KOTEST = "5.5.4"
const val KOTLIN = "1.8.21"
const val KOTLIN_COROUTINES = "1.7.3"
- const val KOTEST = "5.5.4"
const val KOTLINX_RESOURCES = "0.2.4"
const val KOVER = "0.7.3"
const val KSP = "1.8.21-1.0.11"
const val KTOR = "2.1.1"
+ const val LIFECYCLE = "2.6.1"
+
const val MOCKATIVE = "1.3.0"
const val MOCKK = "1.13.3"
@@ -37,6 +42,8 @@ object Versions {
const val ROBOLECTRIC = "4.9.1"
+ const val SECURITY_CRYPTO = "1.0.0"
+
const val TIMBER = "5.0.1"
const val TURBINE = "0.12.1"
}
diff --git a/android/proguard-rules.pro b/custom/android/proguard-rules.pro
similarity index 100%
rename from android/proguard-rules.pro
rename to custom/android/proguard-rules.pro
diff --git a/make.sh b/make.sh
index 5d15689d..51040e27 100755
--- a/make.sh
+++ b/make.sh
@@ -1,7 +1,6 @@
#!/bin/sh
set -e
-# =====GENERATE IOS MODULE=====
# Script inspired by https://gist.github.com/szeidner/613fe4652fc86f083cefa21879d5522b
readonly PROGNAME=$(basename $0)
@@ -98,24 +97,32 @@ if ! [[ $minimum_ios_version =~ $version_regex ]]; then
minimum_ios_version="14.0"
fi
+# Reset all git submodules changes
+cd ios
+git add .
+git reset --hard
+cd ..
+cd android
+git add .
+git reset --hard
+cd ..
+
# Generate iOS module
sh make_ios.sh -b ${bundle_id_production} -s ${bundle_id_staging} -n ${project_name} -iv ${minimum_ios_version}
-# =====GENERATE ANDROID AND SHARED MODULES + REST OF COMPONENTS=====
-# TODO: Fully generate the KMM project later
-#
-# This is the initial script to generate the KMM project:
-# - Clone all project files to the "sample" directory
+# Generate Android module
+sh make_android.sh -b ${bundle_id_production} -n ${project_name}
+
+# Clone all project files to the "sample" directory
+echo "=> Clone all project files to the "sample" directory"
rsync -av \
--exclude '.git' \
--exclude '.gitmodules' \
--exclude 'make.sh' \
+ --exclude 'make_android.sh' \
--exclude 'make_ios.sh' \
+ --exclude '/custom' \
+ --exclude '/android' \
--exclude '/sample' \
./ sample/
-
-# Reset all git submodules
-cd ios
-git add .
-git reset --hard
-cd ..
+rsync -av ./android/sample/app/ sample/android/
diff --git a/make_android.sh b/make_android.sh
new file mode 100644
index 00000000..e809a079
--- /dev/null
+++ b/make_android.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+set -e
+
+bundle_id=""
+project_name=""
+
+while [ $# -gt 0 ] ; do
+ case "$1" in
+ -b|--bundle-id)
+ bundle_id="$2"
+ shift
+ ;;
+ -n|--project-name)
+ project_name="$2"
+ shift
+ ;;
+ -*)
+ usage "Unknown option '$1'"
+ ;;
+ *)
+ usage "Too many arguments"
+ ;;
+ esac
+ shift
+done
+
+echo "=> Starting generate Android project with android-templates"
+cd android
+
+# Clean up unnecessary stuff
+sed -i '' "/ cleanNewProjectFolder()*/d" scripts/new_project.kts
+sed -i '' "/ buildProjectAndRunTests()*/d" scripts/new_project.kts
+
+kscript scripts/new_project.kts package-name=${bundle_id} app-name=${project_name} template=compose
+
+# Correct dependencies
+sed -i '' 's/Modules.DATA/Modules.SHARED/' sample/app/build.gradle.kts
+sed -i '' "/implementation(project(Modules.DOMAIN))*/d" sample/app/build.gradle.kts
+sed -i '' "/kover(project(Modules.DOMAIN))*/d" sample/app/build.gradle.kts
+
+# Correct imports
+sed -i '' 's/import co.nimblehq.kmm.template.data.di.initKoin/import co.nimblehq.kmm.template.di.initKoin/' sample/app/src/main/java/co/nimblehq/kmm/template/MainApplication.kt
+
+# Correct error mapping
+sed -i '' 's/is ApiException -> error?.message/is ApiException -> message/' sample/app/src/main/java/co/nimblehq/kmm/template/ui/ErrorMapping.kt
+
+# Remove unnecessary definition of BASE_API_URL
+sed -i '' "/buildConfigField(\"String\", \"BASE_API_URL\"*/d" sample/app/build.gradle.kts
+sed -i '' "/import co.nimblehq.kmm.template.BuildConfig*/d" sample/app/src/main/java/co/nimblehq/kmm/template/di/modules/AppModule.kt
+sed -i '' "/import co.nimblehq.kmm.template.data.di.modules.BASE_API_URL*/d" sample/app/src/main/java/co/nimblehq/kmm/template/di/modules/AppModule.kt
+sed -i '' "/import org.koin.core.qualifier.named*/d" sample/app/src/main/java/co/nimblehq/kmm/template/di/modules/AppModule.kt
+sed -i '' "/ single(named(BASE_API_URL)) {\n*/d" sample/app/src/main/java/co/nimblehq/kmm/template/di/modules/AppModule.kt
+sed -i '' "/ BuildConfig.BASE_API_URL\n*/d" sample/app/src/main/java/co/nimblehq/kmm/template/di/modules/AppModule.kt
+sed -i '' "/ }\n*/d" sample/app/src/main/java/co/nimblehq/kmm/template/di/modules/AppModule.kt
+
+# Overwrite custom files
+rsync -av ../custom/android/ sample/app/
+
+# Overwrite the Kover config for KMM
+perl -i -p0e 's/koverReport (.|\n)*}$/koverReport {\
+ defaults {\
+ mergeWith("stagingDebug")\
+\
+ val excludedFiles = listOf(\
+ "io.mockative.*",\
+ "*.BuildConfig",\
+ "*.BuildKonfig", \/\/ BuildKonfig generated\
+ "*.ComposableSingletons*", \/\/ Jetpack Compose generated\
+ "*.*\\\$*Preview\\\$*", \/\/ Jetpack Compose Preview functions\
+ "*.di.*", \/\/ Koin\
+ "*.ui.preview.*", \/\/ Jetpack Compose Preview providers\
+ "*.*Test", \/\/ Test files\
+ "*.*Test*", \/\/ Test cases\
+ "*.*Mock", \/\/ mockative \@Mock generated\
+ "*.test.*", \/\/ Test util package\
+ "*.*\\\$\\\$serializer", \/\/ Kotlinx serializer\
+ )\
+ filters {\
+ excludes {\
+ classes(excludedFiles)\
+ }\
+ }\
+ }\
+}/g' sample/app/build.gradle.kts
+
+cd ..
diff --git a/sample/.github/workflows/test.yml b/sample/.github/workflows/test.yml
index f2f3cf91..a48526cc 100644
--- a/sample/.github/workflows/test.yml
+++ b/sample/.github/workflows/test.yml
@@ -19,6 +19,26 @@ jobs:
ref: ${{ github.head_ref }}
submodules: "recursive"
+ - name: Cache Gradle
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.gradle/caches/modules-*
+ ~/.gradle/caches/jars-*
+ ~/.gradle/caches/build-cache-*
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
+
+ - name: Install Kscript
+ run: |
+ curl -s "https://get.sdkman.io" | bash
+ source "$HOME/.sdkman/bin/sdkman-init.sh"
+ source android/version.properties
+ sdk install kotlin $kotlinVersion
+ sdk install kscript $kscriptVersion
+ echo $PATH >> $GITHUB_PATH
+
# To avoid code change in git when refreshing the "sample" project
- name: Preserve the ArkanaKeys
run: mv -f ./sample/ios/ArkanaKeys ../
diff --git a/sample/android/.gitignore b/sample/android/.gitignore
index eb82db4a..322c22ea 100644
--- a/sample/android/.gitignore
+++ b/sample/android/.gitignore
@@ -1,5 +1,4 @@
+/build
+
# Google services
google-services.json
-
-# Keystore
-config/release.keystore
diff --git a/sample/android/build.gradle.kts b/sample/android/build.gradle.kts
index b077b599..8b5729ee 100644
--- a/sample/android/build.gradle.kts
+++ b/sample/android/build.gradle.kts
@@ -2,52 +2,56 @@ import org.jetbrains.kotlin.konan.properties.loadProperties
plugins {
id(Plugins.ANDROID_APPLICATION)
+ id(Plugins.KOTLIN_ANDROID)
+ id(Plugins.KOTLIN_PARCELIZE)
id(Plugins.KOVER)
- kotlin(Plugins.ANDROID)
}
-val keystoreProperties = loadProperties("$rootDir/signing.properties")
+val signingProperties = loadProperties("$rootDir/signing.properties")
+val getVersionCode: () -> Int = {
+ if (project.hasProperty("versionCode")) {
+ (project.property("versionCode") as String).toInt()
+ } else {
+ Versions.ANDROID_VERSION_CODE
+ }
+}
android {
namespace = "co.nimblehq.kmm.template"
compileSdk = Versions.ANDROID_COMPILE_SDK
+
defaultConfig {
applicationId = "co.nimblehq.kmm.template"
minSdk = Versions.ANDROID_MIN_SDK
targetSdk = Versions.ANDROID_TARGET_SDK
- versionCode = Versions.ANDROID_VERSION_CODE
+ versionCode = getVersionCode()
versionName = Versions.ANDROID_VERSION_NAME
- }
- buildFeatures {
- compose = true
- }
- composeOptions {
- kotlinCompilerExtensionVersion = Versions.COMPOSE_COMPILER
- }
- packaging {
- resources {
- excludes += "/META-INF/{AL2.0,LGPL2.1}"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary = true
}
}
+
signingConfigs {
create(BuildTypes.RELEASE) {
// Remember to edit signing.properties to have the correct info for release build.
storeFile = file("../config/release.keystore")
- storePassword = keystoreProperties.getProperty("KEYSTORE_PASSWORD")
- keyPassword = keystoreProperties.getProperty("KEY_PASSWORD")
- keyAlias = keystoreProperties.getProperty("KEY_ALIAS")
+ storePassword = signingProperties.getProperty("KEYSTORE_PASSWORD") as String
+ keyPassword = signingProperties.getProperty("KEY_PASSWORD") as String
+ keyAlias = signingProperties.getProperty("KEY_ALIAS") as String
}
getByName(BuildTypes.DEBUG) {
storeFile = file("../config/debug.keystore")
- //FIXME: Replace with your own password
storePassword = "oQ4mL1jY2uX7wD8q"
keyAlias = "debug-key-alias"
keyPassword = "oQ4mL1jY2uX7wD8q"
}
}
+
buildTypes {
- getByName(BuildTypes.RELEASE) {
+ release {
isMinifyEnabled = true
isDebuggable = false
isShrinkResources = true
@@ -55,45 +59,85 @@ android {
signingConfig = signingConfigs[BuildTypes.RELEASE]
}
- getByName(BuildTypes.DEBUG) {
+ debug {
// For quickly testing build with proguard, enable this
isMinifyEnabled = false
signingConfig = signingConfigs[BuildTypes.DEBUG]
}
}
+
flavorDimensions += Flavors.DIMENSION_VERSION
productFlavors {
create(Flavors.STAGING) {
applicationIdSuffix = ".staging"
- resValue("string", "app_name", "KMM Templates - Staging")
}
- create(Flavors.PRODUCTION) {
- resValue("string", "app_name", "KMM Templates")
- }
+ create(Flavors.PRODUCTION) {}
}
+
+ sourceSets["test"].resources {
+ srcDir("src/test/resources")
+ }
+
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
+
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
+
+ buildFeatures {
+ compose = true
+ buildConfig = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = Versions.COMPOSE_COMPILER
+ }
+
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+
+ lint {
+ checkDependencies = true
+ xmlReport = true
+ xmlOutput = file("build/reports/lint/lint-result.xml")
+ }
+
+ testOptions {
+ unitTests {
+ // Robolectric resource processing/loading https://github.com/robolectric/robolectric/pull/4736
+ isIncludeAndroidResources = true
+ }
+ // Disable device's animation for instrument testing
+ // animationsDisabled = true
+ }
}
dependencies {
implementation(project(Modules.SHARED))
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
+
with(Dependencies.AndroidX) {
- implementation(ACTIVITY_COMPOSE)
+ implementation(CORE_KTX)
+ implementation(LIFECYCLE_RUNTIME_KTX)
+ implementation(LIFECYCLE_RUNTIME_COMPOSE)
}
with(Dependencies.Compose) {
+ implementation(platform(BOM))
implementation(UI)
- implementation(UI_GRAPHICS)
+ implementation(UI_TOOLING)
implementation(MATERIAL)
implementation(NAVIGATION)
- implementation(UI_TOOLING)
+
+ implementation(ACCOMPANIST_PERMISSIONS)
}
with(Dependencies.Koin) {
@@ -106,9 +150,22 @@ dependencies {
implementation(TIMBER)
}
+ with(Dependencies.Util) {
+ implementation(COMMON_KTX)
+ }
+
with(Dependencies.Test) {
- implementation(JUNIT)
- implementation(COROUTINES)
+ // Unit test
+ testImplementation(COROUTINES)
+ testImplementation(JUNIT)
+ testImplementation(KOTEST)
+ testImplementation(MOCKK)
+ testImplementation(TURBINE)
+
+ // UI test with Robolectric
+ testImplementation(platform(Dependencies.Compose.BOM))
+ testImplementation(COMPOSE_UI_TEST_JUNIT)
+ testImplementation(ROBOLECTRIC)
}
}
@@ -116,7 +173,7 @@ dependencies {
* Kover configs
*/
dependencies {
- kover(project(":shared"))
+ kover(project(Modules.SHARED))
}
koverReport {
@@ -137,7 +194,6 @@ koverReport {
"*.test.*", // Test util package
"*.*\$\$serializer", // Kotlinx serializer
)
-
filters {
excludes {
classes(excludedFiles)
diff --git a/sample/android/src/debug/AndroidManifest.xml b/sample/android/src/debug/AndroidManifest.xml
new file mode 100644
index 00000000..95ab0292
--- /dev/null
+++ b/sample/android/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/sample/android/src/debug/java/co/nimblehq/kmm/template/util/LogUtil.kt b/sample/android/src/debug/java/co/nimblehq/kmm/template/util/LogUtil.kt
deleted file mode 100644
index d6aa6e3b..00000000
--- a/sample/android/src/debug/java/co/nimblehq/kmm/template/util/LogUtil.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package co.nimblehq.kmm.template.util
-
-import timber.log.Timber
-
-object LogUtil {
-
- fun setUpLogging() {
- Timber.plant(Timber.DebugTree())
- }
-}
diff --git a/sample/android/src/main/AndroidManifest.xml b/sample/android/src/main/AndroidManifest.xml
index 44c41ed3..cfb42121 100644
--- a/sample/android/src/main/AndroidManifest.xml
+++ b/sample/android/src/main/AndroidManifest.xml
@@ -1,22 +1,31 @@
+ xmlns:tools="http://schemas.android.com/tools">
-
+
+ android:theme="@style/AppTheme"
+ tools:targetApi="31">
+
+
+
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/MainApplication.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/MainApplication.kt
index ee71fa08..eb3d4c48 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/MainApplication.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/MainApplication.kt
@@ -1,19 +1,28 @@
package co.nimblehq.kmm.template
import android.app.Application
-import co.nimblehq.kmm.template.di.androidViewModelModule
-import co.nimblehq.kmm.template.util.LogUtil
import co.nimblehq.kmm.template.di.initKoin
+import co.nimblehq.kmm.template.di.modules.appModule
+import co.nimblehq.kmm.template.di.modules.viewModelModule
import org.koin.android.ext.koin.androidContext
+import org.koin.android.ext.koin.androidLogger
+import timber.log.Timber
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
- LogUtil.setUpLogging()
initKoin {
+ androidLogger()
androidContext(applicationContext)
- modules(androidViewModelModule)
+ modules(appModule + viewModelModule)
+ }
+ setupLogging()
+ }
+
+ private fun setupLogging() {
+ if (BuildConfig.DEBUG) {
+ Timber.plant(Timber.DebugTree())
}
}
}
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/di/ViewModelModule.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/di/ViewModelModule.kt
deleted file mode 100644
index 69875337..00000000
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/di/ViewModelModule.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package co.nimblehq.kmm.template.di
-
-import org.koin.core.module.Module
-import org.koin.dsl.module
-
-val androidViewModelModule: Module = module {
- // TODO declare viewModel modules here
-}
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/di/modules/AppModule.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/di/modules/AppModule.kt
new file mode 100644
index 00000000..f7009181
--- /dev/null
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/di/modules/AppModule.kt
@@ -0,0 +1,12 @@
+package co.nimblehq.kmm.template.di.modules
+
+import co.nimblehq.kmm.template.util.DispatchersProvider
+import co.nimblehq.kmm.template.util.DispatchersProviderImpl
+import org.koin.core.module.dsl.singleOf
+import org.koin.dsl.bind
+import org.koin.dsl.module
+
+val appModule = module {
+ singleOf(::DispatchersProviderImpl) bind DispatchersProvider::class
+
+}
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/di/modules/ViewModelModule.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/di/modules/ViewModelModule.kt
new file mode 100644
index 00000000..8af638f6
--- /dev/null
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/di/modules/ViewModelModule.kt
@@ -0,0 +1,9 @@
+package co.nimblehq.kmm.template.di.modules
+
+import co.nimblehq.kmm.template.ui.screens.home.HomeViewModel
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.dsl.module
+
+val viewModelModule = module {
+ viewModelOf(::HomeViewModel)
+}
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/extensions/ContextExt.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/extensions/ContextExt.kt
new file mode 100644
index 00000000..8b96a7e3
--- /dev/null
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/extensions/ContextExt.kt
@@ -0,0 +1,7 @@
+package co.nimblehq.kmm.template.extensions
+
+import android.content.Context
+import android.widget.Toast
+
+fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) =
+ Toast.makeText(this, message, duration).show()
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/extensions/FlowExt.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/extensions/FlowExt.kt
new file mode 100644
index 00000000..60ba3212
--- /dev/null
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/extensions/FlowExt.kt
@@ -0,0 +1,19 @@
+package co.nimblehq.kmm.template.extensions
+
+import android.annotation.SuppressLint
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import kotlinx.coroutines.flow.*
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+@SuppressLint("ComposableNaming")
+@Composable
+fun Flow.collectAsEffect(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend (T) -> Unit,
+) {
+ LaunchedEffect(key1 = Unit) {
+ onEach(block).flowOn(context).launchIn(this)
+ }
+}
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppDestination.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/AppDestination.kt
similarity index 60%
rename from sample/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppDestination.kt
rename to sample/android/src/main/java/co/nimblehq/kmm/template/ui/AppDestination.kt
index 22017549..2f8a5446 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppDestination.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/AppDestination.kt
@@ -1,6 +1,6 @@
-package co.nimblehq.kmm.template.ui.navigation
+package co.nimblehq.kmm.template.ui
-import androidx.navigation.NamedNavArgument
+import androidx.navigation.*
sealed class AppDestination(val route: String = "") {
@@ -8,8 +8,6 @@ sealed class AppDestination(val route: String = "") {
open var destination: String = route
- //====================================================//
-
object Up : AppDestination()
object Home : AppDestination("home")
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppNavigation.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/AppNavigation.kt
similarity index 71%
rename from sample/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppNavigation.kt
rename to sample/android/src/main/java/co/nimblehq/kmm/template/ui/AppNavigation.kt
index 2b064b81..9012f069 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/navigation/AppNavigation.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/AppNavigation.kt
@@ -1,4 +1,4 @@
-package co.nimblehq.kmm.template.ui.navigation
+package co.nimblehq.kmm.template.ui
import androidx.compose.runtime.Composable
import androidx.navigation.*
@@ -16,8 +16,7 @@ fun AppNavigation(
) {
composable(AppDestination.Home) {
HomeScreen(
- // TODO
- // navigator = { destination -> navController.navigate(destination) }
+ navigator = { destination -> navController.navigate(destination) }
)
}
}
@@ -36,9 +35,9 @@ private fun NavGraphBuilder.composable(
)
}
-private fun NavHostController.navigate(destination: AppDestination) {
- when (destination) {
- is AppDestination.Up -> popBackStack()
- else -> navigate(route = destination.destination)
+private fun NavHostController.navigate(appDestination: AppDestination) {
+ when (appDestination) {
+ is AppDestination.Up -> navigateUp()
+ else -> navigate(route = appDestination.destination)
}
}
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/ErrorMapping.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/ErrorMapping.kt
new file mode 100644
index 00000000..b7883879
--- /dev/null
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/ErrorMapping.kt
@@ -0,0 +1,16 @@
+package co.nimblehq.kmm.template.ui
+
+import android.content.Context
+import co.nimblehq.kmm.template.R
+import co.nimblehq.kmm.template.domain.exceptions.ApiException
+import co.nimblehq.kmm.template.extensions.showToast
+
+fun Throwable.userReadableMessage(context: Context): String {
+ return when (this) {
+ is ApiException -> message
+ else -> message
+ } ?: context.getString(R.string.error_generic)
+}
+
+fun Throwable.showToast(context: Context) =
+ context.showToast(userReadableMessage(context))
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/base/BaseViewModel.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/base/BaseViewModel.kt
index 65ac5a84..89eae4fa 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/base/BaseViewModel.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/base/BaseViewModel.kt
@@ -1,12 +1,13 @@
package co.nimblehq.kmm.template.ui.base
-import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import co.nimblehq.kmm.template.lib.IsLoading
+import co.nimblehq.kmm.template.ui.AppDestination
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
-import co.nimblehq.kmm.template.lib.IsLoading
-import co.nimblehq.kmm.template.ui.navigation.AppDestination
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
@Suppress("PropertyName")
abstract class BaseViewModel : ViewModel() {
@@ -16,8 +17,8 @@ abstract class BaseViewModel : ViewModel() {
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow = _isLoading
- protected val _error = MutableStateFlow(null)
- val error: StateFlow = _error
+ protected val _error = MutableSharedFlow()
+ val error: SharedFlow = _error
protected val _navigator = MutableSharedFlow()
val navigator: SharedFlow = _navigator
@@ -42,11 +43,12 @@ abstract class BaseViewModel : ViewModel() {
}
}
+ protected fun launch(context: CoroutineContext = EmptyCoroutineContext, job: suspend () -> Unit) =
+ viewModelScope.launch(context) {
+ job.invoke()
+ }
+
protected fun Flow.injectLoading(): Flow = this
.onStart { showLoading() }
.onCompletion { hideLoading() }
-
- fun clearError() {
- viewModelScope.launch { _error.emit(null) }
- }
}
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/models/UiModel.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/models/UiModel.kt
new file mode 100644
index 00000000..40b284b7
--- /dev/null
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/models/UiModel.kt
@@ -0,0 +1,9 @@
+package co.nimblehq.kmm.template.ui.models
+
+import co.nimblehq.kmm.template.domain.models.Model
+
+data class UiModel(
+ val id: Int
+)
+
+fun Model.toUiModel() = UiModel(id = id ?: -1)
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/MainActivity.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/MainActivity.kt
index 75d6bbc1..e45f4a44 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/MainActivity.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/MainActivity.kt
@@ -3,7 +3,7 @@ package co.nimblehq.kmm.template.ui.screens
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import co.nimblehq.kmm.template.ui.navigation.AppNavigation
+import co.nimblehq.kmm.template.ui.AppNavigation
import co.nimblehq.kmm.template.ui.theme.ComposeTheme
class MainActivity : ComponentActivity() {
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreen.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreen.kt
index 800e2bc7..48d60d37 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreen.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreen.kt
@@ -1,27 +1,69 @@
package co.nimblehq.kmm.template.ui.screens.home
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
-import co.nimblehq.kmm.template.Greeting
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import co.nimblehq.kmm.template.R
+import co.nimblehq.kmm.template.extensions.collectAsEffect
+import co.nimblehq.kmm.template.ui.AppDestination
+import co.nimblehq.kmm.template.ui.models.UiModel
+import co.nimblehq.kmm.template.ui.showToast
+import co.nimblehq.kmm.template.ui.theme.AppTheme.dimensions
import co.nimblehq.kmm.template.ui.theme.ComposeTheme
+import org.koin.androidx.compose.getViewModel
+import timber.log.Timber
@Composable
-fun HomeScreen() {
- Surface(
+fun HomeScreen(
+ viewModel: HomeViewModel = getViewModel(),
+ navigator: (destination: AppDestination) -> Unit,
+) {
+ val context = LocalContext.current
+ viewModel.error.collectAsEffect { e -> e.showToast(context) }
+ viewModel.navigator.collectAsEffect { destination -> navigator(destination) }
+
+ val uiModels: List by viewModel.uiModels.collectAsStateWithLifecycle()
+
+ HomeScreenContent(
+ title = stringResource(id = R.string.app_name),
+ uiModels = uiModels
+ )
+}
+
+@Composable
+private fun HomeScreenContent(
+ title: String,
+ uiModels: List
+) {
+ Column(
modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colors.background
+ verticalArrangement = Arrangement.Center
) {
- Text(text = Greeting().greet())
+ Text(
+ text = title,
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(all = dimensions.spacingNormal)
+ )
}
+ Timber.d("Result : $uiModels")
}
-@Preview
+@Preview(showSystemUi = true)
@Composable
private fun HomeScreenPreview() {
ComposeTheme {
- HomeScreen()
+ HomeScreenContent(
+ title = stringResource(id = R.string.app_name),
+ uiModels = listOf(UiModel(1), UiModel(2), UiModel(3))
+ )
}
}
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeViewModel.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeViewModel.kt
new file mode 100644
index 00000000..f028a420
--- /dev/null
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/screens/home/HomeViewModel.kt
@@ -0,0 +1,30 @@
+package co.nimblehq.kmm.template.ui.screens.home
+
+import androidx.lifecycle.viewModelScope
+import co.nimblehq.kmm.template.domain.usecases.UseCase
+import co.nimblehq.kmm.template.ui.base.BaseViewModel
+import co.nimblehq.kmm.template.ui.models.UiModel
+import co.nimblehq.kmm.template.ui.models.toUiModel
+import co.nimblehq.kmm.template.util.DispatchersProvider
+import kotlinx.coroutines.flow.*
+
+class HomeViewModel(
+ dispatchersProvider: DispatchersProvider,
+ useCase: UseCase,
+) : BaseViewModel() {
+
+ private val _uiModels = MutableStateFlow>(emptyList())
+ val uiModels: StateFlow> = _uiModels
+
+ init {
+ useCase()
+ .injectLoading()
+ .onEach { result ->
+ val uiModels = result.map { it.toUiModel() }
+ _uiModels.emit(uiModels)
+ }
+ .flowOn(dispatchersProvider.io)
+ .catch { e -> _error.emit(e) }
+ .launchIn(viewModelScope)
+ }
+}
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppColors.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppColors.kt
index 37eca4df..1eca1eb5 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppColors.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppColors.kt
@@ -2,31 +2,25 @@ package co.nimblehq.kmm.template.ui.theme
import androidx.compose.material.*
import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.graphics.Color
+
+// Base colors here
+// e.g. internal val GreenCitrus = Color(0xFF99CC00)
/**
- * Extend final [Colors] class to provide more custom app colors.
+ * Expand the final [Colors] class to provide more custom app colors.
*/
data class AppColors(
- val themeColors: Colors,
+ val themeColors: Colors
// Custom colors here
)
internal val LightColorPalette = AppColors(
- themeColors = lightColors(
- primary = Color(0xFF6200EE),
- primaryVariant = Color(0xFF3700B3),
- secondary = Color(0xFF03DAC5)
- )
+ themeColors = lightColors()
)
internal val DarkColorPalette = AppColors(
- themeColors = darkColors(
- primary = Color(0xFFBB86FC),
- primaryVariant = Color(0xFF3700B3),
- secondary = Color(0xFF03DAC5)
- )
+ themeColors = darkColors()
)
-internal val LocalColors = staticCompositionLocalOf { LightColorPalette }
+internal val LocalAppColors = staticCompositionLocalOf { LightColorPalette }
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppDimensions.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppDimensions.kt
index 3875be76..63e23f78 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppDimensions.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppDimensions.kt
@@ -1,9 +1,11 @@
package co.nimblehq.kmm.template.ui.theme
import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.unit.dp
class AppDimensions {
// Custom dimensions here
+ val spacingNormal = 16.dp
}
internal val LocalAppDimensions = staticCompositionLocalOf { AppDimensions() }
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppShapes.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppShapes.kt
new file mode 100644
index 00000000..0a039411
--- /dev/null
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppShapes.kt
@@ -0,0 +1,10 @@
+package co.nimblehq.kmm.template.ui.theme
+
+import androidx.compose.material.Shapes
+import androidx.compose.runtime.staticCompositionLocalOf
+
+private val Shapes = Shapes(
+ // Custom shapes here
+)
+
+internal val LocalAppShapes = staticCompositionLocalOf { Shapes }
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppTypography.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppTypography.kt
new file mode 100644
index 00000000..b8c49e23
--- /dev/null
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/AppTypography.kt
@@ -0,0 +1,10 @@
+package co.nimblehq.kmm.template.ui.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.runtime.staticCompositionLocalOf
+
+private val Typography = Typography(
+ // Custom typography here
+)
+
+internal val LocalAppTypography = staticCompositionLocalOf { Typography }
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/Theme.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/Theme.kt
index ae5e06ad..5a849441 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/Theme.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/ui/theme/Theme.kt
@@ -1,15 +1,8 @@
-@file:Suppress("MatchingDeclarationName")
package co.nimblehq.kmm.template.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
@Composable
fun ComposeTheme(
@@ -21,26 +14,11 @@ fun ComposeTheme(
} else {
LightColorPalette
}
- val dimensions = LocalAppDimensions.current
- val styles = LocalAppStyles.current
-
- val typography = Typography(
- body1 = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Normal,
- fontSize = 16.sp
- )
- )
- val shapes = Shapes(
- small = RoundedCornerShape(4.dp),
- medium = RoundedCornerShape(4.dp),
- large = RoundedCornerShape(0.dp)
- )
+ val typography = LocalAppTypography.current
+ val shapes = LocalAppShapes.current
CompositionLocalProvider(
- LocalColors provides colors,
- LocalAppDimensions provides dimensions,
- LocalAppStyles provides styles
+ LocalAppColors provides colors
) {
MaterialTheme(
colors = colors.themeColors,
@@ -60,17 +38,17 @@ object AppTheme {
val colors: AppColors
@Composable
@ReadOnlyComposable
- get() = LocalColors.current
+ get() = LocalAppColors.current
val typography: Typography
@Composable
@ReadOnlyComposable
- get() = MaterialTheme.typography
+ get() = LocalAppTypography.current
val shapes: Shapes
@Composable
@ReadOnlyComposable
- get() = MaterialTheme.shapes
+ get() = LocalAppShapes.current
val dimensions: AppDimensions
@Composable
diff --git a/sample/android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProvider.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProvider.kt
index caa9ec78..809152be 100644
--- a/sample/android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProvider.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProvider.kt
@@ -1,19 +1,9 @@
package co.nimblehq.kmm.template.util
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
interface DispatchersProvider {
val io: CoroutineDispatcher
val main: CoroutineDispatcher
val default: CoroutineDispatcher
}
-
-class DispatchersProviderImpl : DispatchersProvider {
-
- override val io = Dispatchers.IO
-
- override val main = Dispatchers.Main
-
- override val default = Dispatchers.Default
-}
diff --git a/android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProvider.kt b/sample/android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProviderImpl.kt
similarity index 58%
rename from android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProvider.kt
rename to sample/android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProviderImpl.kt
index caa9ec78..62e88d40 100644
--- a/android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProvider.kt
+++ b/sample/android/src/main/java/co/nimblehq/kmm/template/util/DispatchersProviderImpl.kt
@@ -1,14 +1,7 @@
package co.nimblehq.kmm.template.util
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
-interface DispatchersProvider {
- val io: CoroutineDispatcher
- val main: CoroutineDispatcher
- val default: CoroutineDispatcher
-}
-
class DispatchersProviderImpl : DispatchersProvider {
override val io = Dispatchers.IO
diff --git a/sample/android/src/main/res/drawable/ic_launcher_background.xml b/sample/android/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..07d5da9c
--- /dev/null
+++ b/sample/android/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/android/src/main/res/drawable/ic_launcher_foreground.xml b/sample/android/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 00000000..7706ab9e
--- /dev/null
+++ b/sample/android/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..b3e26b4c
--- /dev/null
+++ b/sample/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/sample/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..b3e26b4c
--- /dev/null
+++ b/sample/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/sample/android/src/main/res/mipmap-hdpi/ic_launcher.webp b/sample/android/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 00000000..c209e78e
Binary files /dev/null and b/sample/android/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/sample/android/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/sample/android/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..b2dfe3d1
Binary files /dev/null and b/sample/android/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/sample/android/src/main/res/mipmap-mdpi/ic_launcher.webp b/sample/android/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 00000000..4f0f1d64
Binary files /dev/null and b/sample/android/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/sample/android/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/sample/android/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..62b611da
Binary files /dev/null and b/sample/android/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/sample/android/src/main/res/mipmap-xhdpi/ic_launcher.webp b/sample/android/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 00000000..948a3070
Binary files /dev/null and b/sample/android/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/sample/android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/sample/android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..1b9a6956
Binary files /dev/null and b/sample/android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/sample/android/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/sample/android/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..28d4b77f
Binary files /dev/null and b/sample/android/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/sample/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/sample/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9287f508
Binary files /dev/null and b/sample/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/sample/android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/sample/android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..aa7d6427
Binary files /dev/null and b/sample/android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/sample/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/sample/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9126ae37
Binary files /dev/null and b/sample/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/sample/android/src/main/res/values/strings.xml b/sample/android/src/main/res/values/strings.xml
new file mode 100644
index 00000000..7a69c814
--- /dev/null
+++ b/sample/android/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Sample
+ Unexpected error
+
diff --git a/sample/android/src/main/res/values/styles.xml b/sample/android/src/main/res/values/styles.xml
deleted file mode 100644
index 676b4dda..00000000
--- a/sample/android/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/sample/android/src/main/res/values/themes.xml b/sample/android/src/main/res/values/themes.xml
new file mode 100644
index 00000000..7e1674a3
--- /dev/null
+++ b/sample/android/src/main/res/values/themes.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/sample/android/src/main/res/xml/backup_rules.xml b/sample/android/src/main/res/xml/backup_rules.xml
new file mode 100644
index 00000000..148c18b6
--- /dev/null
+++ b/sample/android/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/sample/android/src/main/res/xml/data_extraction_rules.xml b/sample/android/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 00000000..0c4f95ca
--- /dev/null
+++ b/sample/android/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/sample/android/src/main/res/xml/network_security_config.xml b/sample/android/src/main/res/xml/network_security_config.xml
new file mode 100644
index 00000000..04ead227
--- /dev/null
+++ b/sample/android/src/main/res/xml/network_security_config.xml
@@ -0,0 +1 @@
+
diff --git a/sample/android/src/release/java/co/nimblehq/kmm/template/util/LogUtil.kt b/sample/android/src/release/java/co/nimblehq/kmm/template/util/LogUtil.kt
deleted file mode 100644
index 2ee5a119..00000000
--- a/sample/android/src/release/java/co/nimblehq/kmm/template/util/LogUtil.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package co.nimblehq.kmm.template.util
-
-import timber.log.Timber
-
-object LogUtil {
-
- fun setUpLogging() {
- // Do nothing
- }
-}
diff --git a/sample/android/src/staging/res/values/strings.xml b/sample/android/src/staging/res/values/strings.xml
new file mode 100644
index 00000000..0cb99f8b
--- /dev/null
+++ b/sample/android/src/staging/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Sample - Staging
+
diff --git a/sample/android/src/staging/res/xml/network_security_config.xml b/sample/android/src/staging/res/xml/network_security_config.xml
new file mode 100644
index 00000000..2a99cd31
--- /dev/null
+++ b/sample/android/src/staging/res/xml/network_security_config.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+ www.jsonplaceholder.typicode.com
+
+
+
+
+
+
diff --git a/sample/android/src/test/java/co/nimblehq/kmm/template/test/CoroutineTestRule.kt b/sample/android/src/test/java/co/nimblehq/kmm/template/test/CoroutineTestRule.kt
index 6288cf1c..967ea2d4 100644
--- a/sample/android/src/test/java/co/nimblehq/kmm/template/test/CoroutineTestRule.kt
+++ b/sample/android/src/test/java/co/nimblehq/kmm/template/test/CoroutineTestRule.kt
@@ -1,5 +1,6 @@
package co.nimblehq.kmm.template.test
+import co.nimblehq.kmm.template.util.DispatchersProvider
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
import org.junit.rules.TestWatcher
@@ -7,16 +8,26 @@ import org.junit.runner.Description
@OptIn(ExperimentalCoroutinesApi::class)
class CoroutineTestRule(
- private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
+ var testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
) : TestWatcher() {
- override fun starting(description: Description?) {
- super.starting(description)
+ val testDispatcherProvider = object : DispatchersProvider {
+
+ override val io: CoroutineDispatcher
+ get() = testDispatcher
+
+ override val main: CoroutineDispatcher
+ get() = testDispatcher
+
+ override val default: CoroutineDispatcher
+ get() = testDispatcher
+ }
+
+ override fun starting(description: Description) {
Dispatchers.setMain(testDispatcher)
}
- override fun finished(description: Description?) {
- super.finished(description)
+ override fun finished(description: Description) {
Dispatchers.resetMain()
}
}
diff --git a/sample/android/src/test/java/co/nimblehq/kmm/template/test/MockUtil.kt b/sample/android/src/test/java/co/nimblehq/kmm/template/test/MockUtil.kt
new file mode 100644
index 00000000..2a7f881a
--- /dev/null
+++ b/sample/android/src/test/java/co/nimblehq/kmm/template/test/MockUtil.kt
@@ -0,0 +1,12 @@
+package co.nimblehq.kmm.template.test
+
+import co.nimblehq.kmm.template.domain.models.Model
+
+object MockUtil {
+
+ val models = listOf(
+ Model(id = 1),
+ Model(id = 2),
+ Model(id = 3),
+ )
+}
diff --git a/sample/android/src/test/java/co/nimblehq/kmm/template/ui/screens/BaseScreenTest.kt b/sample/android/src/test/java/co/nimblehq/kmm/template/ui/screens/BaseScreenTest.kt
new file mode 100644
index 00000000..fb13263e
--- /dev/null
+++ b/sample/android/src/test/java/co/nimblehq/kmm/template/ui/screens/BaseScreenTest.kt
@@ -0,0 +1,17 @@
+package co.nimblehq.kmm.template.ui.screens
+
+import co.nimblehq.kmm.template.test.CoroutineTestRule
+import kotlinx.coroutines.test.StandardTestDispatcher
+
+abstract class BaseScreenTest {
+
+ protected val coroutinesRule = CoroutineTestRule()
+
+ protected fun setStandardTestDispatcher() {
+ coroutinesRule.testDispatcher = StandardTestDispatcher()
+ }
+
+ protected fun advanceUntilIdle() {
+ coroutinesRule.testDispatcher.scheduler.advanceUntilIdle()
+ }
+}
diff --git a/sample/android/src/test/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreenTest.kt b/sample/android/src/test/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreenTest.kt
new file mode 100644
index 00000000..06792876
--- /dev/null
+++ b/sample/android/src/test/java/co/nimblehq/kmm/template/ui/screens/home/HomeScreenTest.kt
@@ -0,0 +1,89 @@
+package co.nimblehq.kmm.template.ui.screens.home
+
+import androidx.activity.compose.setContent
+import androidx.compose.ui.test.*
+import androidx.compose.ui.test.junit4.*
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import co.nimblehq.kmm.template.R
+import co.nimblehq.kmm.template.domain.usecases.UseCase
+import co.nimblehq.kmm.template.test.MockUtil
+import co.nimblehq.kmm.template.ui.AppDestination
+import co.nimblehq.kmm.template.ui.screens.BaseScreenTest
+import co.nimblehq.kmm.template.ui.screens.MainActivity
+import co.nimblehq.kmm.template.ui.theme.ComposeTheme
+import io.kotest.matchers.shouldBe
+import io.mockk.every
+import io.mockk.mockk
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.*
+import org.junit.*
+import org.junit.runner.RunWith
+import org.koin.core.context.stopKoin
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.shadows.ShadowToast
+
+@RunWith(RobolectricTestRunner::class)
+class HomeScreenTest : BaseScreenTest() {
+
+ @get:Rule
+ val composeRule = createAndroidComposeRule()
+
+ private val mockUseCase: UseCase = mockk()
+
+ private lateinit var viewModel: HomeViewModel
+ private var expectedAppDestination: AppDestination? = null
+
+ @Before
+ fun setUp() {
+ every { mockUseCase() } returns flowOf(MockUtil.models)
+ }
+
+ @After
+ fun tearDown() {
+ stopKoin()
+ }
+
+ @Test
+ fun `When entering the Home screen, it shows UI correctly`() = initComposable {
+ onNodeWithText(activity.getString(R.string.app_name)).assertIsDisplayed()
+ }
+
+ @Test
+ fun `When entering the Home screen and loading the data failure, it shows the corresponding error`() {
+ setStandardTestDispatcher()
+
+ val error = Exception()
+ every { mockUseCase() } returns flow { throw error }
+
+ initComposable {
+ composeRule.waitForIdle()
+ advanceUntilIdle()
+
+ ShadowToast.showedToast(activity.getString(R.string.error_generic)) shouldBe true
+ }
+ }
+
+ private fun initComposable(
+ testBody: AndroidComposeTestRule, MainActivity>.() -> Unit,
+ ) {
+ initViewModel()
+
+ composeRule.activity.setContent {
+ ComposeTheme {
+ HomeScreen(
+ viewModel = viewModel,
+ navigator = { destination -> expectedAppDestination = destination }
+ )
+ }
+ }
+ testBody(composeRule)
+ }
+
+ private fun initViewModel() {
+ viewModel = HomeViewModel(
+ coroutinesRule.testDispatcherProvider,
+ mockUseCase,
+ )
+ }
+}
diff --git a/sample/android/src/test/java/co/nimblehq/kmm/template/ui/screens/home/HomeViewModelTest.kt b/sample/android/src/test/java/co/nimblehq/kmm/template/ui/screens/home/HomeViewModelTest.kt
new file mode 100644
index 00000000..730ffce4
--- /dev/null
+++ b/sample/android/src/test/java/co/nimblehq/kmm/template/ui/screens/home/HomeViewModelTest.kt
@@ -0,0 +1,72 @@
+package co.nimblehq.kmm.template.ui.screens.home
+
+import app.cash.turbine.test
+import co.nimblehq.kmm.template.domain.usecases.UseCase
+import co.nimblehq.kmm.template.test.CoroutineTestRule
+import co.nimblehq.kmm.template.test.MockUtil
+import co.nimblehq.kmm.template.ui.models.toUiModel
+import co.nimblehq.kmm.template.util.DispatchersProvider
+import io.kotest.matchers.shouldBe
+import io.mockk.every
+import io.mockk.mockk
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.*
+import org.junit.*
+
+@ExperimentalCoroutinesApi
+class HomeViewModelTest {
+
+ @get:Rule
+ val coroutinesRule = CoroutineTestRule()
+
+ private val mockUseCase: UseCase = mockk()
+
+ private lateinit var viewModel: HomeViewModel
+
+ @Before
+ fun setUp() {
+ every { mockUseCase() } returns flowOf(MockUtil.models)
+
+ initViewModel()
+ }
+
+ @Test
+ fun `When loading models successfully, it shows the model list`() = runTest {
+ viewModel.uiModels.test {
+ expectMostRecentItem() shouldBe MockUtil.models.map { it.toUiModel() }
+ }
+ }
+
+ @Test
+ fun `When loading models failed, it shows the corresponding error`() = runTest {
+ val error = Exception()
+ every { mockUseCase() } returns flow { throw error }
+ initViewModel(dispatchers = CoroutineTestRule(StandardTestDispatcher()).testDispatcherProvider)
+
+ viewModel.error.test {
+ advanceUntilIdle()
+
+ expectMostRecentItem() shouldBe error
+ }
+ }
+
+ @Test
+ fun `When loading models, it shows and hides loading correctly`() = runTest {
+ initViewModel(dispatchers = CoroutineTestRule(StandardTestDispatcher()).testDispatcherProvider)
+
+ viewModel.isLoading.test {
+ awaitItem() shouldBe false
+ awaitItem() shouldBe true
+ awaitItem() shouldBe false
+ }
+ }
+
+ private fun initViewModel(dispatchers: DispatchersProvider = coroutinesRule.testDispatcherProvider) {
+ viewModel = HomeViewModel(
+ dispatchers,
+ mockUseCase
+ )
+ }
+}
diff --git a/sample/android/src/test/resources/robolectric.properties b/sample/android/src/test/resources/robolectric.properties
new file mode 100644
index 00000000..ae44aeb6
--- /dev/null
+++ b/sample/android/src/test/resources/robolectric.properties
@@ -0,0 +1,3 @@
+# Workaround for issue https://github.com/robolectric/robolectric/issues/6593
+instrumentedPackages=androidx.loader.content
+sdk=30
diff --git a/sample/buildSrc/src/main/java/Dependencies.kt b/sample/buildSrc/src/main/java/Dependencies.kt
index f7ae174a..e0790d1f 100644
--- a/sample/buildSrc/src/main/java/Dependencies.kt
+++ b/sample/buildSrc/src/main/java/Dependencies.kt
@@ -1,20 +1,23 @@
object Dependencies {
object AndroidX {
- const val ACTIVITY_COMPOSE = "androidx.activity:activity-compose:${Versions.ANDROIDX_ACTIVITY_COMPOSE}"
+ const val CORE_KTX = "androidx.core:core-ktx:${Versions.CORE_KTX}"
+ const val LIFECYCLE_RUNTIME_KTX = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.LIFECYCLE}"
+ const val LIFECYCLE_RUNTIME_COMPOSE = "androidx.lifecycle:lifecycle-runtime-compose:${Versions.LIFECYCLE}"
+
+ const val DATASTORE_PREFERENCES = "androidx.datastore:datastore-preferences:${Versions.DATASTORE_PREFERENCES}"
+ const val SECURITY_CRYPTO = "androidx.security:security-crypto:${Versions.SECURITY_CRYPTO}"
}
object Compose {
- const val FOUNDATION = "androidx.compose.foundation:foundation:${Versions.COMPOSE}"
-
- const val MATERIAL = "androidx.compose.material:material:${Versions.COMPOSE}"
-
+ const val BOM = "androidx.compose:compose-bom:${Versions.COMPOSE_BOM}"
+ const val UI = "androidx.compose.ui:ui"
+ const val UI_GRAPHICS = "androidx.compose.ui:ui-graphics"
+ const val UI_TOOLING = "androidx.compose.ui:ui-tooling"
+ const val MATERIAL = "androidx.compose.material:material"
const val NAVIGATION = "androidx.navigation:navigation-compose:${Versions.COMPOSE_NAVIGATION}"
- const val UI = "androidx.compose.ui:ui:${Versions.COMPOSE}"
- const val UI_GRAPHICS = "androidx.compose.ui:ui-graphics:${Versions.COMPOSE}"
- const val UI_TOOLING = "androidx.compose.ui:ui-tooling:${Versions.COMPOSE}"
- const val UI_TOOLING_PREVIEW = "androidx.compose.ui:ui-tooling-preview:${Versions.COMPOSE}"
+ const val ACCOMPANIST_PERMISSIONS = "com.google.accompanist:accompanist-permissions:${Versions.ACCOMPANIST}"
}
object Koin {
@@ -45,9 +48,12 @@ object Dependencies {
const val TIMBER = "com.jakewharton.timber:timber:${Versions.TIMBER}"
}
+ object Util {
+ const val COMMON_KTX = "com.github.nimblehq:android-common-ktx:${Versions.COMMON_KTX}"
+ }
+
object Test {
- const val COMPOSE_UI_TEST_JUNIT = "androidx.compose.ui:ui-test-junit4:${Versions.COMPOSE}"
- const val COMPOSE_UI_TEST_MANIFEST = "androidx.compose.ui:ui-test-manifest:${Versions.COMPOSE}"
+ const val COMPOSE_UI_TEST_JUNIT = "androidx.compose.ui:ui-test-junit4"
const val COROUTINES = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.KOTLIN_COROUTINES}"
const val JUNIT = "junit:junit:${Versions.JUNIT}"
diff --git a/sample/buildSrc/src/main/java/Plugins.kt b/sample/buildSrc/src/main/java/Plugins.kt
index 671a93ab..a0336b07 100644
--- a/sample/buildSrc/src/main/java/Plugins.kt
+++ b/sample/buildSrc/src/main/java/Plugins.kt
@@ -9,6 +9,8 @@ object Plugins {
const val IOS_COCOAPODS = "native.cocoapods"
+ const val KOTLIN_ANDROID = "org.jetbrains.kotlin.android"
+ const val KOTLIN_PARCELIZE = "kotlin-parcelize"
const val KOTLIN_SERIALIZATION = "plugin.serialization"
const val KOTLINX_SERIALIZATION = "kotlinx-serialization"
const val KOVER = "org.jetbrains.kotlinx.kover"
diff --git a/sample/buildSrc/src/main/java/Versions.kt b/sample/buildSrc/src/main/java/Versions.kt
index 2bfae497..837d4ea1 100644
--- a/sample/buildSrc/src/main/java/Versions.kt
+++ b/sample/buildSrc/src/main/java/Versions.kt
@@ -5,31 +5,36 @@ object Versions {
const val ANDROID_VERSION_CODE = 1
const val ANDROID_VERSION_NAME = "1.0.0"
- const val ANDROIDX_ACTIVITY_COMPOSE = "1.7.1"
+ const val ACCOMPANIST = "0.30.1"
const val BUILD_KONFIG = "0.13.3"
- const val COMPOSE = "1.4.3"
+ const val COMMON_KTX = "0.1.1"
+ const val COMPOSE_BOM = "2023.04.01"
const val COMPOSE_COMPILER = "1.4.7"
const val COMPOSE_NAVIGATION = "2.6.0"
+ const val CORE_KTX = "1.10.1"
+ const val DATASTORE_PREFERENCES = "1.0.0"
const val DETEKT = "1.23.0"
- const val GRADLE = "8.0.2"
+ const val GRADLE = "8.1.0"
const val JUNIT = "4.13.2"
const val KOIN = "3.3.2"
const val KOIN_ANDROID = "3.3.2"
const val KOIN_ANDROIDX_COMPOSE = "3.4.1"
+ const val KOTEST = "5.5.4"
const val KOTLIN = "1.8.21"
const val KOTLIN_COROUTINES = "1.7.3"
- const val KOTEST = "5.5.4"
const val KOTLINX_RESOURCES = "0.2.4"
const val KOVER = "0.7.3"
const val KSP = "1.8.21-1.0.11"
const val KTOR = "2.1.1"
+ const val LIFECYCLE = "2.6.1"
+
const val MOCKATIVE = "1.3.0"
const val MOCKK = "1.13.3"
@@ -37,6 +42,8 @@ object Versions {
const val ROBOLECTRIC = "4.9.1"
+ const val SECURITY_CRYPTO = "1.0.0"
+
const val TIMBER = "5.0.1"
const val TURBINE = "0.12.1"
}
diff --git a/sample/ios/sample.xcodeproj/project.pbxproj b/sample/ios/sample.xcodeproj/project.pbxproj
index e4367d9f..02ae5367 100644
--- a/sample/ios/sample.xcodeproj/project.pbxproj
+++ b/sample/ios/sample.xcodeproj/project.pbxproj
@@ -12,25 +12,25 @@
18B4C2A55909888C5D0EDFDA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432B427D5097BCF75C4B15E /* AppDelegate.swift */; };
1F3B8CA45E426F2E4BA8A41C /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EEB8F8EE4393D2D9AC51316 /* R.generated.swift */; };
20FF012BAC088FFC92962E28 /* UIView+Subviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDBCDCBA1D855D7023A0342F /* UIView+Subviews.swift */; };
- 2DA05DE7652FF37A60BD79C3 /* Pods_sample_sampleKIFUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9C46B6E8D81B61646FFA7B2 /* Pods_sample_sampleKIFUITests.framework */; };
33209CEB34C04BC32D0FFDD4 /* UseCaseFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA52B4C8C3AF1CB2181C14D /* UseCaseFactoryProtocol.swift */; };
3559AC5988194A4701902F80 /* .gitkeep in Resources */ = {isa = PBXBuildFile; fileRef = 302BB730B8D0BF901FF489B6 /* .gitkeep */; };
53157A77A8B802D108AE9D73 /* Typealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5B819017A28FE15B242BB5B /* Typealiases.swift */; };
66C817ACCA7CE15ABF58BC06 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD1568B768046A18E011F2C /* HomeViewController.swift */; };
701258D40CE69EC8168A3D12 /* AutoMockable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF1B234787FCD92142B105D /* AutoMockable.generated.swift */; };
7CF0F8933FB47F5D003383AA /* .gitkeep in Resources */ = {isa = PBXBuildFile; fileRef = 41210B648761A8DC4ABD3BB9 /* .gitkeep */; };
- 7DA5432B529B0D7EE88CA0A2 /* Pods_sample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC5033767792384BB47D853 /* Pods_sample.framework */; };
7ED0E12DA355DEE8EED0B614 /* Color+Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = A29F97F67192DE9B66E872E6 /* Color+Application.swift */; };
840CF0D3913B0069F0F2FF12 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2F657F289C5314152FFCFA2 /* Assets.xcassets */; };
8A43095CEA3F4C83584AB199 /* .gitkeep in Resources */ = {isa = PBXBuildFile; fileRef = 05F57F86A37A0E0B6CC54CB4 /* .gitkeep */; };
9726C56684473F7E78415A58 /* Navigator+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64B0E7B034D526205048030 /* Navigator+Scene.swift */; };
+ 994FE7243003EA336583C560 /* Pods_sample_sampleKIFUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9C9B76A3588D5716699A1EC /* Pods_sample_sampleKIFUITests.framework */; };
9ACA6A5E938E3365302C3749 /* Navigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159B27C88C08E8AEB22AFD4 /* Navigator.swift */; };
BA754F29AB917D745895322D /* Constants+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222B1BD9F71FF1797F160231 /* Constants+API.swift */; };
- BFE5BBF460EF00D7CBA59A61 /* Pods_sampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92B5874F359D4331010A7D39 /* Pods_sampleTests.framework */; };
+ BBBCCAAF0A117753E7572F8C /* Pods_sample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0D8CCE8A8532576EB1AD7B8 /* Pods_sample.framework */; };
C152F2B63E42238BE4F20592 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F5C2293A2DB24CDCA81957 /* Constants.swift */; };
CA324314BE7318252E4D5D62 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6952456309E29C9609D23B3F /* LaunchScreen.storyboard */; };
D609A7DCE8B3E7A49CFACC2E /* Navigator+Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9816C413C0D0DD7B01BA9CE /* Navigator+Transition.swift */; };
DE665FDD6164065A881A2F44 /* .gitkeep in Resources */ = {isa = PBXBuildFile; fileRef = 67C0EA707F4FBB459FEBA640 /* .gitkeep */; };
+ DF4A6EFE889BD9C3A6151498 /* Pods_sampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 247CB14FAB859539C5CFC2BC /* Pods_sampleTests.framework */; };
F444409E14F8ED6BF6A2666A /* KIF+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFAE58D40BFE459B1184D994 /* KIF+Swift.swift */; };
F858855AD6392D182B853A79 /* Optional+Unwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562EF605C844B951CD47F38D /* Optional+Unwrap.swift */; };
/* End PBXBuildFile section */
@@ -88,48 +88,48 @@
/* Begin PBXFileReference section */
04B8796D7472561BF8EB10A1 /* DebugProduction.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DebugProduction.xcconfig; sourceTree = ""; };
05F57F86A37A0E0B6CC54CB4 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = ""; };
+ 10B6C9B06A3569A62950B2F7 /* Pods-sampleTests.release staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sampleTests.release staging.xcconfig"; path = "Target Support Files/Pods-sampleTests/Pods-sampleTests.release staging.xcconfig"; sourceTree = ""; };
18C176717B282B7A8842FC73 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
1EEB8F8EE4393D2D9AC51316 /* R.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = R.generated.swift; sourceTree = ""; };
222B1BD9F71FF1797F160231 /* Constants+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Constants+API.swift"; sourceTree = ""; };
- 2C841C9A9C6FDB9B0F13A8A6 /* Pods-sampleTests.release production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sampleTests.release production.xcconfig"; path = "Target Support Files/Pods-sampleTests/Pods-sampleTests.release production.xcconfig"; sourceTree = ""; };
+ 247CB14FAB859539C5CFC2BC /* Pods_sampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_sampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2B452417C1C98E3DCF7E733A /* Pods-sampleTests.debug staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sampleTests.debug staging.xcconfig"; path = "Target Support Files/Pods-sampleTests/Pods-sampleTests.debug staging.xcconfig"; sourceTree = ""; };
+ 2B8375A60711D7AA246EF5D6 /* Pods-sample-sampleKIFUITests.release staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample-sampleKIFUITests.release staging.xcconfig"; path = "Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests.release staging.xcconfig"; sourceTree = ""; };
+ 2FD6A0CEA73C5A0AEA476EF6 /* Pods-sample.debug staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample.debug staging.xcconfig"; path = "Target Support Files/Pods-sample/Pods-sample.debug staging.xcconfig"; sourceTree = ""; };
302BB730B8D0BF901FF489B6 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = ""; };
- 33505B9BF9661A91BD331CAC /* Pods-sample.debug production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample.debug production.xcconfig"; path = "Target Support Files/Pods-sample/Pods-sample.debug production.xcconfig"; sourceTree = ""; };
41210B648761A8DC4ABD3BB9 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = ""; };
414B86B12605C252A692EA37 /* sampleKIFUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = sampleKIFUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 4BC5033767792384BB47D853 /* Pods_sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_sample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
562EF605C844B951CD47F38D /* Optional+Unwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Unwrap.swift"; sourceTree = ""; };
+ 63CDA958B16A7E9EC4B2B94F /* Pods-sample.release staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample.release staging.xcconfig"; path = "Target Support Files/Pods-sample/Pods-sample.release staging.xcconfig"; sourceTree = ""; };
6432B427D5097BCF75C4B15E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
67C0EA707F4FBB459FEBA640 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = ""; };
- 6905D337038EF385175B48B6 /* Pods-sample.release production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample.release production.xcconfig"; path = "Target Support Files/Pods-sample/Pods-sample.release production.xcconfig"; sourceTree = ""; };
6952456309E29C9609D23B3F /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; };
- 69DFEA9CA5B6C8232B179885 /* Pods-sampleTests.release staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sampleTests.release staging.xcconfig"; path = "Target Support Files/Pods-sampleTests/Pods-sampleTests.release staging.xcconfig"; sourceTree = ""; };
- 7CC6C9A311897D364F439A91 /* Pods-sample.debug staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample.debug staging.xcconfig"; path = "Target Support Files/Pods-sample/Pods-sample.debug staging.xcconfig"; sourceTree = ""; };
+ 69ABB3CC62D4972A4815F4CE /* Pods-sampleTests.debug production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sampleTests.debug production.xcconfig"; path = "Target Support Files/Pods-sampleTests/Pods-sampleTests.debug production.xcconfig"; sourceTree = ""; };
82F5C2293A2DB24CDCA81957 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
8CA52B4C8C3AF1CB2181C14D /* UseCaseFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCaseFactoryProtocol.swift; sourceTree = ""; };
- 92B5874F359D4331010A7D39 /* Pods_sampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_sampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 9968633611342E4965317C10 /* Pods-sample-sampleKIFUITests.release production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample-sampleKIFUITests.release production.xcconfig"; path = "Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests.release production.xcconfig"; sourceTree = ""; };
- 99E8C285A1EB9EB8D1258F99 /* Pods-sampleTests.debug staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sampleTests.debug staging.xcconfig"; path = "Target Support Files/Pods-sampleTests/Pods-sampleTests.debug staging.xcconfig"; sourceTree = ""; };
+ 91F39615B402930809F04A62 /* Pods-sample.release production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample.release production.xcconfig"; path = "Target Support Files/Pods-sample/Pods-sample.release production.xcconfig"; sourceTree = ""; };
A29F97F67192DE9B66E872E6 /* Color+Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Application.swift"; sourceTree = ""; };
- A5148C808CDA1AD642875AB8 /* Pods-sample-sampleKIFUITests.debug staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample-sampleKIFUITests.debug staging.xcconfig"; path = "Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests.debug staging.xcconfig"; sourceTree = ""; };
A64B0E7B034D526205048030 /* Navigator+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Navigator+Scene.swift"; sourceTree = ""; };
- A9C46B6E8D81B61646FFA7B2 /* Pods_sample_sampleKIFUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_sample_sampleKIFUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ A9C9B76A3588D5716699A1EC /* Pods_sample_sampleKIFUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_sample_sampleKIFUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
ADE63A1C257C06E1D05C8136 /* DebugStaging.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DebugStaging.xcconfig; sourceTree = ""; };
- AFDE323AE90AA9F8BB784958 /* Pods-sampleTests.debug production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sampleTests.debug production.xcconfig"; path = "Target Support Files/Pods-sampleTests/Pods-sampleTests.debug production.xcconfig"; sourceTree = ""; };
B8847183C3FF3D70FE4192CD /* sampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = sampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- BA6D5691E818F93DE95E0596 /* Pods-sample.release staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample.release staging.xcconfig"; path = "Target Support Files/Pods-sample/Pods-sample.release staging.xcconfig"; sourceTree = ""; };
- BAA243538DD55675F4D219B6 /* Pods-sample-sampleKIFUITests.release staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample-sampleKIFUITests.release staging.xcconfig"; path = "Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests.release staging.xcconfig"; sourceTree = ""; };
BBD1568B768046A18E011F2C /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; };
BBF1B234787FCD92142B105D /* AutoMockable.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoMockable.generated.swift; sourceTree = ""; };
BD9D9C9A495C5EA0AE9039C6 /* ReleaseProduction.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ReleaseProduction.xcconfig; sourceTree = ""; };
+ BE47535924946BCDFBAE469D /* Pods-sampleTests.release production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sampleTests.release production.xcconfig"; path = "Target Support Files/Pods-sampleTests/Pods-sampleTests.release production.xcconfig"; sourceTree = ""; };
+ BF17318902ABB1FB871E7573 /* Pods-sample-sampleKIFUITests.release production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample-sampleKIFUITests.release production.xcconfig"; path = "Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests.release production.xcconfig"; sourceTree = ""; };
C159B27C88C08E8AEB22AFD4 /* Navigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigator.swift; sourceTree = ""; };
+ C7C0FDA261B4174C2B806574 /* Pods-sample-sampleKIFUITests.debug production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample-sampleKIFUITests.debug production.xcconfig"; path = "Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests.debug production.xcconfig"; sourceTree = ""; };
CD3A7C3AC94E15DEC4F12A85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
CDBCDCBA1D855D7023A0342F /* UIView+Subviews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Subviews.swift"; sourceTree = ""; };
+ D026A81EBD303E6D76129B03 /* Pods-sample.debug production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample.debug production.xcconfig"; path = "Target Support Files/Pods-sample/Pods-sample.debug production.xcconfig"; sourceTree = ""; };
D2D7345A2017D2D372785123 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
D2F657F289C5314152FFCFA2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ D3FB9DC520E2100DB48A4D38 /* Pods-sample-sampleKIFUITests.debug staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample-sampleKIFUITests.debug staging.xcconfig"; path = "Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests.debug staging.xcconfig"; sourceTree = ""; };
E4C58806C2B28AED0873F8AF /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = ""; };
- EBDA4BD3B01AA58FA2282832 /* Pods-sample-sampleKIFUITests.debug production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sample-sampleKIFUITests.debug production.xcconfig"; path = "Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests.debug production.xcconfig"; sourceTree = ""; };
EFAE58D40BFE459B1184D994 /* KIF+Swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KIF+Swift.swift"; sourceTree = ""; };
EFE5D281FD0FFFF44FC92B04 /* .gitkeep */ = {isa = PBXFileReference; path = .gitkeep; sourceTree = ""; };
+ F0D8CCE8A8532576EB1AD7B8 /* Pods_sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_sample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F5B819017A28FE15B242BB5B /* Typealiases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typealiases.swift; sourceTree = ""; };
F854F7439696BB6B30145F6D /* ReleaseStaging.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ReleaseStaging.xcconfig; sourceTree = ""; };
F9816C413C0D0DD7B01BA9CE /* Navigator+Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Navigator+Transition.swift"; sourceTree = ""; };
@@ -141,7 +141,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 2DA05DE7652FF37A60BD79C3 /* Pods_sample_sampleKIFUITests.framework in Frameworks */,
+ 994FE7243003EA336583C560 /* Pods_sample_sampleKIFUITests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -149,7 +149,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- BFE5BBF460EF00D7CBA59A61 /* Pods_sampleTests.framework in Frameworks */,
+ DF4A6EFE889BD9C3A6151498 /* Pods_sampleTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -157,7 +157,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 7DA5432B529B0D7EE88CA0A2 /* Pods_sample.framework in Frameworks */,
+ BBBCCAAF0A117753E7572F8C /* Pods_sample.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -190,6 +190,26 @@
path = Modules;
sourceTree = "";
};
+ 0F1AEA0C42764B1FC5C047E0 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ D026A81EBD303E6D76129B03 /* Pods-sample.debug production.xcconfig */,
+ 2FD6A0CEA73C5A0AEA476EF6 /* Pods-sample.debug staging.xcconfig */,
+ 91F39615B402930809F04A62 /* Pods-sample.release production.xcconfig */,
+ 63CDA958B16A7E9EC4B2B94F /* Pods-sample.release staging.xcconfig */,
+ C7C0FDA261B4174C2B806574 /* Pods-sample-sampleKIFUITests.debug production.xcconfig */,
+ D3FB9DC520E2100DB48A4D38 /* Pods-sample-sampleKIFUITests.debug staging.xcconfig */,
+ BF17318902ABB1FB871E7573 /* Pods-sample-sampleKIFUITests.release production.xcconfig */,
+ 2B8375A60711D7AA246EF5D6 /* Pods-sample-sampleKIFUITests.release staging.xcconfig */,
+ 69ABB3CC62D4972A4815F4CE /* Pods-sampleTests.debug production.xcconfig */,
+ 2B452417C1C98E3DCF7E733A /* Pods-sampleTests.debug staging.xcconfig */,
+ BE47535924946BCDFBAE469D /* Pods-sampleTests.release production.xcconfig */,
+ 10B6C9B06A3569A62950B2F7 /* Pods-sampleTests.release staging.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
2003C041305A8015968A9294 /* Plists */ = {
isa = PBXGroup;
children = (
@@ -261,9 +281,9 @@
52B072A663159D9D007D540F /* Frameworks */ = {
isa = PBXGroup;
children = (
- 4BC5033767792384BB47D853 /* Pods_sample.framework */,
- A9C46B6E8D81B61646FFA7B2 /* Pods_sample_sampleKIFUITests.framework */,
- 92B5874F359D4331010A7D39 /* Pods_sampleTests.framework */,
+ F0D8CCE8A8532576EB1AD7B8 /* Pods_sample.framework */,
+ A9C9B76A3588D5716699A1EC /* Pods_sample_sampleKIFUITests.framework */,
+ 247CB14FAB859539C5CFC2BC /* Pods_sampleTests.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -298,7 +318,7 @@
9B7CF20BE2915B62911C62AA /* Project */,
52B072A663159D9D007D540F /* Frameworks */,
372816F20EC291B850EECF1F /* Products */,
- DC96A509BDBA98E8281A88E1 /* Pods */,
+ 0F1AEA0C42764B1FC5C047E0 /* Pods */,
);
sourceTree = "";
};
@@ -500,26 +520,6 @@
path = sample;
sourceTree = "";
};
- DC96A509BDBA98E8281A88E1 /* Pods */ = {
- isa = PBXGroup;
- children = (
- 33505B9BF9661A91BD331CAC /* Pods-sample.debug production.xcconfig */,
- 7CC6C9A311897D364F439A91 /* Pods-sample.debug staging.xcconfig */,
- 6905D337038EF385175B48B6 /* Pods-sample.release production.xcconfig */,
- BA6D5691E818F93DE95E0596 /* Pods-sample.release staging.xcconfig */,
- EBDA4BD3B01AA58FA2282832 /* Pods-sample-sampleKIFUITests.debug production.xcconfig */,
- A5148C808CDA1AD642875AB8 /* Pods-sample-sampleKIFUITests.debug staging.xcconfig */,
- 9968633611342E4965317C10 /* Pods-sample-sampleKIFUITests.release production.xcconfig */,
- BAA243538DD55675F4D219B6 /* Pods-sample-sampleKIFUITests.release staging.xcconfig */,
- AFDE323AE90AA9F8BB784958 /* Pods-sampleTests.debug production.xcconfig */,
- 99E8C285A1EB9EB8D1258F99 /* Pods-sampleTests.debug staging.xcconfig */,
- 2C841C9A9C6FDB9B0F13A8A6 /* Pods-sampleTests.release production.xcconfig */,
- 69DFEA9CA5B6C8232B179885 /* Pods-sampleTests.release staging.xcconfig */,
- );
- name = Pods;
- path = Pods;
- sourceTree = "";
- };
E675224256C4174F5D808C42 /* Helpers */ = {
isa = PBXGroup;
children = (
@@ -570,7 +570,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 56FE2126489C1E87A2D918A3 /* Build configuration list for PBXNativeTarget "sample" */;
buildPhases = (
- 46FCA76401A1A20C46FEF5E2 /* [CP] Check Pods Manifest.lock */,
+ 630959533EC7BD759E1BB940 /* [CP] Check Pods Manifest.lock */,
B2B621CC2B1F4F58601CD98D /* Sourcery */,
7EB246CFC1E0CCBA652DC37E /* R.swift */,
CF26733D03C8D33F8823EE1F /* SwiftLint */,
@@ -580,7 +580,7 @@
494614C33B509301DBBDAD42 /* Embed Frameworks */,
89C77BA3C707AD804F79284B /* Frameworks */,
843C67A8A78058B8D8B3D11E /* Copy GoogleService-Info.plist */,
- FE7F7DFF241D42FAD7ACD7B1 /* [CP] Embed Pods Frameworks */,
+ 8A9CA3D55EA55A2A5197B970 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -595,12 +595,12 @@
isa = PBXNativeTarget;
buildConfigurationList = 31E84C030BDB7A39BCE38D8C /* Build configuration list for PBXNativeTarget "sampleKIFUITests" */;
buildPhases = (
- 66BC937C6DAD1606FDC47103 /* [CP] Check Pods Manifest.lock */,
+ 570C65EC8730E466137E8346 /* [CP] Check Pods Manifest.lock */,
21ADDF0B071B770FFB6A086A /* Sources */,
43218B877C978B4CA5B93235 /* Resources */,
392BFB718CBF57CF0D23848E /* Embed Frameworks */,
29CEF4F9FF315D4254B8BC69 /* Frameworks */,
- EC97C7D62A0D9B5846821A56 /* [CP] Embed Pods Frameworks */,
+ 3614B18F2EE8F5D38126DE14 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -616,13 +616,13 @@
isa = PBXNativeTarget;
buildConfigurationList = C3875EBB5A0F6612B3515A13 /* Build configuration list for PBXNativeTarget "sampleTests" */;
buildPhases = (
- 09983A7BAB23ED7B1EE223AD /* [CP] Check Pods Manifest.lock */,
+ 26C95A2A1183106E9D481297 /* [CP] Check Pods Manifest.lock */,
2F7B6AB35D89AA83D514ECC2 /* SwiftFormat */,
9344943859735CAEA8DDB048 /* Sources */,
E472A2AC48FE9588146AB254 /* Resources */,
51D3263E7013F91DCDB0D884 /* Embed Frameworks */,
7A7AFF7C6576A774E896CA81 /* Frameworks */,
- 0D47A32864F3079721665E44 /* [CP] Embed Pods Frameworks */,
+ DEA9B15445E8E0FF59A160F2 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -703,7 +703,7 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 09983A7BAB23ED7B1EE223AD /* [CP] Check Pods Manifest.lock */ = {
+ 26C95A2A1183106E9D481297 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -725,42 +725,42 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- 0D47A32864F3079721665E44 /* [CP] Embed Pods Frameworks */ = {
+ 2F7B6AB35D89AA83D514ECC2 /* SwiftFormat */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-sampleTests/Pods-sampleTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
- name = "[CP] Embed Pods Frameworks";
+ inputPaths = (
+ );
+ name = SwiftFormat;
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-sampleTests/Pods-sampleTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-sampleTests/Pods-sampleTests-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
+ shellScript = "if [ -z \"$CI\" ]; then\n \"${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat\" \"$SRCROOT\"\nfi";
};
- 2F7B6AB35D89AA83D514ECC2 /* SwiftFormat */ = {
+ 3614B18F2EE8F5D38126DE14 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
- inputPaths = (
- );
- name = SwiftFormat;
+ name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
- );
- outputPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "if [ -z \"$CI\" ]; then\n \"${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat\" \"$SRCROOT\"\nfi";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
};
- 46FCA76401A1A20C46FEF5E2 /* [CP] Check Pods Manifest.lock */ = {
+ 570C65EC8730E466137E8346 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -775,14 +775,14 @@
outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-sample-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-sample-sampleKIFUITests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- 66BC937C6DAD1606FDC47103 /* [CP] Check Pods Manifest.lock */ = {
+ 630959533EC7BD759E1BB940 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -797,7 +797,7 @@
outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-sample-sampleKIFUITests-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-sample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -842,6 +842,23 @@
shellPath = /bin/sh;
shellScript = "PATH_TO_GOOGLE_PLISTS=\"$SRCROOT/$PROJECT_NAME/Configurations/Plists/GoogleService\"\n\ncase \"${CONFIGURATION}\" in\n\"Debug Staging\" | \"Release Staging\" )\ncp -r \"$PATH_TO_GOOGLE_PLISTS/Staging/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\"\n;;\n\"Debug Production\" | \"Release Production\" )\ncp -r \"$PATH_TO_GOOGLE_PLISTS/Production/GoogleService-Info.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\"\n;;\n*)\n;;\nesac";
};
+ 8A9CA3D55EA55A2A5197B970 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-sample/Pods-sample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-sample/Pods-sample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-sample/Pods-sample-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
B2B621CC2B1F4F58601CD98D /* Sourcery */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -878,21 +895,21 @@
shellPath = /bin/sh;
shellScript = "if [ -z \"$CI\" ]; then\n ${PODS_ROOT}/SwiftLint/swiftlint\nfi";
};
- EC97C7D62A0D9B5846821A56 /* [CP] Embed Pods Frameworks */ = {
+ DEA9B15445E8E0FF59A160F2 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-sampleTests/Pods-sampleTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-sampleTests/Pods-sampleTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-sample-sampleKIFUITests/Pods-sample-sampleKIFUITests-frameworks.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-sampleTests/Pods-sampleTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
EF3878B82E1CD4611A640906 /* SwiftFormat Lint */ = {
@@ -913,23 +930,6 @@
shellPath = /bin/sh;
shellScript = "if [ -z \"$CI\" ]; then\n \"${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat\" \"$SRCROOT\" --lint --lenient\nfi";
};
- FE7F7DFF241D42FAD7ACD7B1 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-sample/Pods-sample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Embed Pods Frameworks";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-sample/Pods-sample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-sample/Pods-sample-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -989,7 +989,7 @@
/* Begin XCBuildConfiguration section */
21ACBFF0B81C5FE1926420D2 /* Debug Production */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = EBDA4BD3B01AA58FA2282832 /* Pods-sample-sampleKIFUITests.debug production.xcconfig */;
+ baseConfigurationReference = C7C0FDA261B4174C2B806574 /* Pods-sample-sampleKIFUITests.debug production.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1014,7 +1014,7 @@
};
252EA76B363BC50D825D8BD6 /* Debug Staging */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 99E8C285A1EB9EB8D1258F99 /* Pods-sampleTests.debug staging.xcconfig */;
+ baseConfigurationReference = 2B452417C1C98E3DCF7E733A /* Pods-sampleTests.debug staging.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1039,7 +1039,7 @@
};
388E6BE620787A06CA1CA7EE /* Release Staging */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = BA6D5691E818F93DE95E0596 /* Pods-sample.release staging.xcconfig */;
+ baseConfigurationReference = 63CDA958B16A7E9EC4B2B94F /* Pods-sample.release staging.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1063,7 +1063,7 @@
};
38F81F703A83D6A307430059 /* Release Production */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 9968633611342E4965317C10 /* Pods-sample-sampleKIFUITests.release production.xcconfig */;
+ baseConfigurationReference = BF17318902ABB1FB871E7573 /* Pods-sample-sampleKIFUITests.release production.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1087,7 +1087,7 @@
};
3BEDFE87FC2F0AE206422087 /* Release Production */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 2C841C9A9C6FDB9B0F13A8A6 /* Pods-sampleTests.release production.xcconfig */;
+ baseConfigurationReference = BE47535924946BCDFBAE469D /* Pods-sampleTests.release production.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1111,7 +1111,7 @@
};
3E22885F8CD58F60301062E2 /* Release Staging */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = BAA243538DD55675F4D219B6 /* Pods-sample-sampleKIFUITests.release staging.xcconfig */;
+ baseConfigurationReference = 2B8375A60711D7AA246EF5D6 /* Pods-sample-sampleKIFUITests.release staging.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1135,7 +1135,7 @@
};
4C1D6824327C0FD7CA0FD5EA /* Release Production */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 6905D337038EF385175B48B6 /* Pods-sample.release production.xcconfig */;
+ baseConfigurationReference = 91F39615B402930809F04A62 /* Pods-sample.release production.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1218,7 +1218,7 @@
};
65EFF1C45B23F50B02803EAD /* Debug Staging */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7CC6C9A311897D364F439A91 /* Pods-sample.debug staging.xcconfig */;
+ baseConfigurationReference = 2FD6A0CEA73C5A0AEA476EF6 /* Pods-sample.debug staging.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1243,7 +1243,7 @@
};
944F5AFF63FDFD18DB25D45F /* Debug Production */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 33505B9BF9661A91BD331CAC /* Pods-sample.debug production.xcconfig */;
+ baseConfigurationReference = D026A81EBD303E6D76129B03 /* Pods-sample.debug production.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1268,7 +1268,7 @@
};
A6CE62F8A695FBBC221E4690 /* Debug Production */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = AFDE323AE90AA9F8BB784958 /* Pods-sampleTests.debug production.xcconfig */;
+ baseConfigurationReference = 69ABB3CC62D4972A4815F4CE /* Pods-sampleTests.debug production.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1293,7 +1293,7 @@
};
B7E6733E7030283A3E74681B /* Debug Staging */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = A5148C808CDA1AD642875AB8 /* Pods-sample-sampleKIFUITests.debug staging.xcconfig */;
+ baseConfigurationReference = D3FB9DC520E2100DB48A4D38 /* Pods-sample-sampleKIFUITests.debug staging.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -1371,7 +1371,7 @@
};
BD91BBB9D406D96A13EB8291 /* Release Staging */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 69DFEA9CA5B6C8232B179885 /* Pods-sampleTests.release staging.xcconfig */;
+ baseConfigurationReference = 10B6C9B06A3569A62950B2F7 /* Pods-sampleTests.release staging.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_IDENTITY = "iPhone Developer";
diff --git a/sample/settings.gradle.kts b/sample/settings.gradle.kts
index b42ba2b2..dcc8f23e 100644
--- a/sample/settings.gradle.kts
+++ b/sample/settings.gradle.kts
@@ -10,6 +10,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven { url = uri("https://www.jitpack.io") }
}
}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b42ba2b2..dcc8f23e 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -10,6 +10,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven { url = uri("https://www.jitpack.io") }
}
}