diff --git a/.github/workflows/review_pull_request.yml b/.github/workflows/review_pull_request.yml index f3a10be29..b9c8bcb0d 100644 --- a/.github/workflows/review_pull_request.yml +++ b/.github/workflows/review_pull_request.yml @@ -29,16 +29,16 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - - name: Run Detekt on CoroutineTemplate - working-directory: ./CoroutineTemplate + - name: Run Detekt on template + working-directory: ./template run: ./gradlew detekt - - name: Run Android Lint on CoroutineTemplate - working-directory: ./CoroutineTemplate + - name: Run Android Lint on template + working-directory: ./template run: ./gradlew lint - - name: Run unit tests and Jacoco on CoroutineTemplate - working-directory: ./CoroutineTemplate + - name: Run unit tests and Jacoco on template + working-directory: ./template run: ./gradlew jacocoTestReport - name: Set up Ruby diff --git a/.github/workflows/run_detekt_and_unit_tests.yml b/.github/workflows/run_detekt_and_unit_tests.yml index 29f78f30c..5dcc405e4 100644 --- a/.github/workflows/run_detekt_and_unit_tests.yml +++ b/.github/workflows/run_detekt_and_unit_tests.yml @@ -27,22 +27,22 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - - name: Run Detekt on CoroutineTemplate - working-directory: ./CoroutineTemplate + - name: Run Detekt on template + working-directory: ./template run: ./gradlew detekt - - name: Archive Detekt reports on CoroutineTemplate + - name: Archive Detekt reports on template uses: actions/upload-artifact@v2 with: - name: DetektReportsCoroutine - path: CoroutineTemplate/build/reports/detekt/ + name: DetektReportsTemplate + path: template/build/reports/detekt/ - - name: Run unit tests and Jacoco on CoroutineTemplate - working-directory: ./CoroutineTemplate + - name: Run unit tests and Jacoco on template + working-directory: ./template run: ./gradlew jacocoTestReport - - name: Archive code coverage reports on CoroutineTemplate + - name: Archive code coverage reports on template uses: actions/upload-artifact@v2 with: - name: CodeCoverageReportsCoroutine - path: CoroutineTemplate/app/build/reports/jacoco/jacocoTestReport/ + name: CodeCoverageReportsTemplate + path: template/app/build/reports/jacoco/jacocoTestReport/ diff --git a/.github/workflows/verify_newproject_script.yml b/.github/workflows/verify_newproject_script.yml index 247bd29f7..b96891cf7 100644 --- a/.github/workflows/verify_newproject_script.yml +++ b/.github/workflows/verify_newproject_script.yml @@ -38,6 +38,6 @@ jobs: sdk install kscript 4.0.3 echo $PATH >> $GITHUB_PATH - - name: Verify generating new project from CoroutineTemplate + - name: Verify generating new project from template working-directory: scripts run: kscript new_project.kts package-name=co.myproject.example app-name="My Project" diff --git a/.gitignore b/.gitignore index 5be5842aa..ee48d0338 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ *.iml .idea .gradle -/CoroutineTemplate/local.properties +/template/local.properties /RxJavaTemplate[DEPRECATED]/local.properties .DS_Store build/ diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/di/modules/RepositoryModule.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/di/modules/RepositoryModule.kt deleted file mode 100644 index ac8f63742..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/di/modules/RepositoryModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package co.nimblehq.coroutine.di.modules - -import co.nimblehq.coroutine.data.repository.UserRepositoryImpl -import co.nimblehq.coroutine.data.service.ApiService -import co.nimblehq.coroutine.domain.repository.UserRepository -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent - -@Module -@InstallIn(ViewModelComponent::class) -class RepositoryModule { - - @Provides - fun provideUserRepository(apiService: ApiService): UserRepository = - UserRepositoryImpl(apiService) -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/lib/TypeAlias.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/lib/TypeAlias.kt deleted file mode 100644 index f2a89478a..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/lib/TypeAlias.kt +++ /dev/null @@ -1,3 +0,0 @@ -package co.nimblehq.coroutine.lib - -typealias IsLoading = Boolean diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/model/UserUiModel.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/model/UserUiModel.kt deleted file mode 100644 index f95491aa7..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/model/UserUiModel.kt +++ /dev/null @@ -1,17 +0,0 @@ -package co.nimblehq.coroutine.model - -import co.nimblehq.coroutine.domain.model.User - -data class UserUiModel( - val id: Int, - val name: String, - val username: String, - val phone: String -) - -private fun User.toUserUiModel(): UserUiModel = - UserUiModel(id = id ?: 0, name = name, username = username, phone = phone) - -fun List.toUserUiModels(): List { - return this.map { it.toUserUiModel() } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/NavigationEvent.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/NavigationEvent.kt deleted file mode 100644 index 2f9a4014b..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/NavigationEvent.kt +++ /dev/null @@ -1,8 +0,0 @@ -package co.nimblehq.coroutine.ui.base - -import co.nimblehq.coroutine.ui.screens.second.SecondBundle - -sealed class NavigationEvent { - data class Second(val bundle: SecondBundle) : NavigationEvent() - object Compose : NavigationEvent() -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/MainNavigator.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/MainNavigator.kt deleted file mode 100644 index a5b81e307..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/MainNavigator.kt +++ /dev/null @@ -1,46 +0,0 @@ -package co.nimblehq.coroutine.ui.screens - -import androidx.fragment.app.Fragment -import co.nimblehq.coroutine.R -import co.nimblehq.coroutine.ui.base.BaseNavigator -import co.nimblehq.coroutine.ui.base.BaseNavigatorImpl -import co.nimblehq.coroutine.ui.base.NavigationEvent -import co.nimblehq.coroutine.ui.screens.home.HomeFragmentDirections -import co.nimblehq.coroutine.ui.screens.second.SecondBundle -import javax.inject.Inject - -interface MainNavigator : BaseNavigator - -class MainNavigatorImpl @Inject constructor( - fragment: Fragment -) : BaseNavigatorImpl(fragment), MainNavigator { - - override val navHostFragmentId = R.id.navHostFragment - - override fun navigate(event: NavigationEvent) { - when (event) { - is NavigationEvent.Second -> navigateToSecond(event.bundle) - is NavigationEvent.Compose -> navigateToCompose() - } - } - - private fun navigateToSecond(bundle: SecondBundle) { - val navController = findNavController() - when (navController?.currentDestination?.id) { - R.id.homeFragment -> navController.navigate( - HomeFragmentDirections.actionHomeFragmentToSecondFragment(bundle) - ) - else -> unsupportedNavigation() - } - } - - private fun navigateToCompose() { - val navController = findNavController() - when (navController?.currentDestination?.id) { - R.id.homeFragment -> navController.navigate( - HomeFragmentDirections.actionHomeFragmentToComposeFragment() - ) - else -> unsupportedNavigation() - } - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/MainViewModel.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/MainViewModel.kt deleted file mode 100644 index 5d5ac7f0b..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/MainViewModel.kt +++ /dev/null @@ -1,9 +0,0 @@ -package co.nimblehq.coroutine.ui.screens - -import co.nimblehq.coroutine.ui.base.BaseViewModel -import co.nimblehq.coroutine.util.DispatchersProvider -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class MainViewModel @Inject constructor(dispatchers: DispatchersProvider) : BaseViewModel(dispatchers) diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/ComposeFragment.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/ComposeFragment.kt deleted file mode 100644 index 10c287b9e..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/ComposeFragment.kt +++ /dev/null @@ -1,33 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.fragment.app.viewModels -import co.nimblehq.coroutine.ui.base.BaseComposeFragment -import co.nimblehq.coroutine.ui.screens.compose.composables.ComposeScreen -import dagger.hilt.android.AndroidEntryPoint - -@ExperimentalComposeUiApi -@AndroidEntryPoint -class ComposeFragment : BaseComposeFragment() { - - private val viewModel: ComposeViewModel by viewModels() - - override val composeScreen: @Composable () -> Unit - get() = { - with(viewModel) { - ComposeScreen( - userUiModels = userUiModels.collectAsState().value, - showLoading = showLoading.collectAsState().value, - textFieldValue = textFieldValue.value, - onTextFieldValueChange = ::updateTextFieldValue, - onUserItemClick = toaster::display - ) - } - } - - override fun bindViewModel() { - viewModel.error bindTo toaster::display - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/ComposeViewModel.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/ComposeViewModel.kt deleted file mode 100644 index 3b94cba88..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/ComposeViewModel.kt +++ /dev/null @@ -1,57 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose - -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import co.nimblehq.coroutine.model.UserUiModel -import co.nimblehq.coroutine.model.toUserUiModels -import co.nimblehq.coroutine.ui.base.BaseViewModel -import co.nimblehq.coroutine.domain.usecase.GetUsersUseCase -import co.nimblehq.coroutine.domain.usecase.UseCaseResult -import co.nimblehq.coroutine.util.DispatchersProvider -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -interface Output { - - val userUiModels: StateFlow> - - val textFieldValue: State - - fun updateTextFieldValue(value: String) -} - -@HiltViewModel -class ComposeViewModel @Inject constructor( - private val getUsersUseCase: GetUsersUseCase, - dispatchers: DispatchersProvider -) : BaseViewModel(dispatchers), Output { - - private val _userUiModels = MutableStateFlow>(emptyList()) - override val userUiModels: StateFlow> - get() = _userUiModels - - private val _textFieldValue = mutableStateOf("") - override val textFieldValue: State - get() = _textFieldValue - - init { - fetchUsers() - } - - override fun updateTextFieldValue(value: String) { - _textFieldValue.value = value - } - - private fun fetchUsers() { - showLoading() - execute { - when (val result = getUsersUseCase.execute()) { - is UseCaseResult.Success -> _userUiModels.value = result.data.toUserUiModels() - is UseCaseResult.Error -> _error.emit(result.exception.message.orEmpty()) - } - hideLoading() - } - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/ComposeScreen.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/ComposeScreen.kt deleted file mode 100644 index 6dcb10337..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/ComposeScreen.kt +++ /dev/null @@ -1,46 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.composables - -import androidx.compose.foundation.layout.Box -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.* -import co.nimblehq.coroutine.model.UserUiModel -import co.nimblehq.coroutine.ui.screens.compose.theme.ComposeTheme - -@Suppress("LongMethod", "LongParameterList") -@ExperimentalComposeUiApi -@Composable -fun ComposeScreen( - userUiModels: List, - showLoading: Boolean, - textFieldValue: String, - onTextFieldValueChange: (String) -> Unit, - onUserItemClick: (String) -> Unit -) { - ComposeTheme { - Scaffold( - topBar = { - TitleBar( - title = "Jetpack Compose", - textFieldValue = textFieldValue, - onTextFieldValueChange = onTextFieldValueChange - ) - } - ) { - ContentCard { - Box { - UserList( - userUiModels = userUiModels, - onUserItemClick = onUserItemClick - ) - if (showLoading) { - CircularProgressIndicator( - modifier = Modifier.align(alignment = Alignment.Center) - ) - } - } - } - } - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/ContentCard.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/ContentCard.kt deleted file mode 100644 index 821127995..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/ContentCard.kt +++ /dev/null @@ -1,26 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.composables - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import co.nimblehq.coroutine.ui.screens.compose.theme.Dimension - -@Composable -fun ContentCard( - content: @Composable () -> Unit -) { - Card( - shape = RoundedCornerShape( - topStart = Dimension.Dp24, - topEnd = Dimension.Dp24, - bottomStart = Dimension.Dp0, - bottomEnd = Dimension.Dp0 - ), - elevation = Dimension.Dp0, - modifier = Modifier.fillMaxSize() - ) { - content.invoke() - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/TitleBar.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/TitleBar.kt deleted file mode 100644 index 2f84dfbe5..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/TitleBar.kt +++ /dev/null @@ -1,54 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.composables - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.runtime.Composable -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import co.nimblehq.coroutine.ui.screens.compose.theme.Dimension - -@Suppress("LongMethod") -@ExperimentalComposeUiApi -@Composable -fun TitleBar( - title: String, - textFieldValue: String, - onTextFieldValueChange: (String) -> Unit -) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(Dimension.Dp24) - ) { - val keyboardController = LocalSoftwareKeyboardController.current - - Text( - text = title, - style = MaterialTheme.typography.h6 - ) - TextField( - value = textFieldValue, - onValueChange = onTextFieldValueChange, - label = { Text(text = "Demo TextField") }, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Text, - imeAction = ImeAction.Done, - ), - keyboardActions = KeyboardActions( - onDone = { keyboardController?.hide() }, - ), - modifier = Modifier - .fillMaxWidth() - .padding(top = Dimension.Dp16) - ) - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/UserItem.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/UserItem.kt deleted file mode 100644 index ac799200f..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/UserItem.kt +++ /dev/null @@ -1,53 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.composables - -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import co.nimblehq.coroutine.R -import co.nimblehq.coroutine.model.UserUiModel -import co.nimblehq.coroutine.ui.screens.compose.theme.Dimension - -@Suppress("LongMethod") -@Composable -fun UserItem( - userUiModel: UserUiModel, - onClick: (String) -> Unit -) { - Surface( - modifier = Modifier - .fillMaxWidth() - .clickable(onClick = { onClick(userUiModel.toString()) }) - ) { - Row(modifier = Modifier.padding(Dimension.Dp16)) { - Image( - painter = painterResource(id = R.drawable.ic_launcher_foreground), - contentDescription = null, - modifier = Modifier - .width(Dimension.Dp52) - .height(Dimension.Dp52), - ) - Column( - modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = Dimension.Dp16) - ) { - with(userUiModel) { - Text( - text = name, - style = MaterialTheme.typography.subtitle1 - ) - Text( - text = phone, - style = MaterialTheme.typography.body1, - modifier = Modifier.padding(top = Dimension.Dp4) - ) - } - } - } - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/UserList.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/UserList.kt deleted file mode 100644 index 8c3ea9ed3..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/composables/UserList.kt +++ /dev/null @@ -1,28 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.composables - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.Divider -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import co.nimblehq.coroutine.model.UserUiModel -import co.nimblehq.coroutine.ui.screens.compose.theme.Dimension - -@Composable -fun UserList( - userUiModels: List, - onUserItemClick: (String) -> Unit -) { - LazyColumn { - itemsIndexed(items = userUiModels) { index, user -> - if (index == 0) Spacer(modifier = Modifier.height(Dimension.Dp8)) - UserItem( - userUiModel = user, - onClick = onUserItemClick - ) - if (index != userUiModels.size - 1) Divider() - } - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Color.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Color.kt deleted file mode 100644 index 7b22814a3..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Color.kt +++ /dev/null @@ -1,9 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.theme - -import androidx.compose.ui.graphics.Color - -@Suppress("MagicNumber") -object Color { - val BlueFreeSpeech = Color(0xFF3F51B5) - val AlmostWhite = Color(0xFFDFE4EA) -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Dimension.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Dimension.kt deleted file mode 100644 index 6389f8a20..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Dimension.kt +++ /dev/null @@ -1,13 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.theme - -import androidx.compose.ui.unit.dp - -@Suppress("MagicNumber") -object Dimension { - val Dp0 = 0.dp - val Dp4 = 4.dp - val Dp8 = 8.dp - val Dp16 = 16.dp - val Dp24 = 24.dp - val Dp52 = 52.dp -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Shape.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Shape.kt deleted file mode 100644 index 8feb9f53b..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Shape.kt +++ /dev/null @@ -1,10 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.theme - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Shapes - -object Shape { - val ComposeShapes = Shapes( - medium = RoundedCornerShape(Dimension.Dp8) - ) -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Theme.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Theme.kt deleted file mode 100644 index d34922a73..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Theme.kt +++ /dev/null @@ -1,24 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.theme - -import androidx.compose.material.MaterialTheme -import androidx.compose.material.lightColors -import androidx.compose.runtime.Composable - -object Palette { - val ComposeLightPalette = lightColors( - primary = Color.BlueFreeSpeech, - background = Color.AlmostWhite - ) -} - -@Composable -fun ComposeTheme( - content: @Composable () -> Unit -) { - MaterialTheme( - colors = Palette.ComposeLightPalette, - typography = Typography.ComposeTypography, - shapes = Shape.ComposeShapes, - content = content - ) -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Typography.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Typography.kt deleted file mode 100644 index 9e8ad213e..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/compose/theme/Typography.kt +++ /dev/null @@ -1,16 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.compose.theme - -import androidx.compose.material.Typography -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import co.nimblehq.coroutine.R - -object Typography { - private val MontserratFontFamily = FontFamily( - Font(R.font.montserrat_regular) - ) - - val ComposeTypography = Typography( - defaultFontFamily = MontserratFontFamily - ) -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/home/HomeFragment.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/home/HomeFragment.kt deleted file mode 100644 index 5d78f933c..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/home/HomeFragment.kt +++ /dev/null @@ -1,65 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.home - -import android.view.LayoutInflater -import android.view.ViewGroup -import co.nimblehq.common.extensions.visibleOrGone -import co.nimblehq.coroutine.databinding.FragmentHomeBinding -import co.nimblehq.coroutine.databinding.ViewLoadingBinding -import co.nimblehq.coroutine.extension.provideViewModels -import co.nimblehq.coroutine.lib.IsLoading -import co.nimblehq.coroutine.model.UserUiModel -import co.nimblehq.coroutine.ui.base.BaseFragment -import co.nimblehq.coroutine.ui.screens.MainNavigator -import co.nimblehq.coroutine.ui.screens.second.SecondBundle -import dagger.hilt.android.AndroidEntryPoint -import timber.log.Timber -import javax.inject.Inject - -@AndroidEntryPoint -class HomeFragment : BaseFragment() { - - @Inject - lateinit var navigator: MainNavigator - - private val viewModel: HomeViewModel by provideViewModels() - - private lateinit var viewLoadingBinding: ViewLoadingBinding - - override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentHomeBinding - get() = { inflater, container, attachToParent -> - FragmentHomeBinding.inflate(inflater, container, attachToParent) - } - - override fun setupView() { - viewLoadingBinding = ViewLoadingBinding.bind(binding.root) - } - - override fun bindViewEvents() { - super.bindViewEvents() - - with(binding) { - btNext.setOnClickListener { - viewModel.navigateToSecond(SecondBundle("From home")) - } - - btCompose.setOnClickListener { - viewModel.navigateToCompose() - } - } - } - - override fun bindViewModel() { - viewModel.userUiModels bindTo ::displayUsers - viewModel.showLoading bindTo ::bindLoading - viewModel.error bindTo toaster::display - viewModel.navigator bindTo navigator::navigate - } - - private fun displayUsers(userUiModels: List) { - Timber.d("Result : $userUiModels") - } - - private fun bindLoading(isLoading: IsLoading) { - viewLoadingBinding.pbLoading.visibleOrGone(isLoading) - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/home/HomeViewModel.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/home/HomeViewModel.kt deleted file mode 100644 index 038711054..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/home/HomeViewModel.kt +++ /dev/null @@ -1,63 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.home - -import androidx.lifecycle.viewModelScope -import co.nimblehq.coroutine.model.UserUiModel -import co.nimblehq.coroutine.model.toUserUiModels -import co.nimblehq.coroutine.ui.base.BaseViewModel -import co.nimblehq.coroutine.ui.base.NavigationEvent -import co.nimblehq.coroutine.ui.screens.second.SecondBundle -import co.nimblehq.coroutine.domain.usecase.GetUsersUseCase -import co.nimblehq.coroutine.domain.usecase.UseCaseResult -import co.nimblehq.coroutine.util.DispatchersProvider -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -interface Output { - - val userUiModels: StateFlow> - - fun navigateToSecond(bundle: SecondBundle) - - fun navigateToCompose() -} - -@HiltViewModel -class HomeViewModel @Inject constructor( - private val getUsersUseCase: GetUsersUseCase, - dispatchers: DispatchersProvider -) : BaseViewModel(dispatchers), Output { - - private val _userUiModels = MutableStateFlow>(emptyList()) - override val userUiModels: StateFlow> - get() = _userUiModels - - init { - fetchUsers() - } - - override fun navigateToSecond(bundle: SecondBundle) { - viewModelScope.launch { - _navigator.emit(NavigationEvent.Second(bundle)) - } - } - - override fun navigateToCompose() { - viewModelScope.launch { - _navigator.emit(NavigationEvent.Compose) - } - } - - private fun fetchUsers() { - showLoading() - execute { - when (val result = getUsersUseCase.execute()) { - is UseCaseResult.Success -> _userUiModels.value = result.data.toUserUiModels() - is UseCaseResult.Error -> _error.emit(result.exception.message.orEmpty()) - } - hideLoading() - } - } -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/second/SecondBundle.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/second/SecondBundle.kt deleted file mode 100644 index e94381569..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/second/SecondBundle.kt +++ /dev/null @@ -1,9 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.second - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -data class SecondBundle( - val message: String -) : Parcelable diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/second/SecondFragment.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/second/SecondFragment.kt deleted file mode 100644 index 34c082881..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/second/SecondFragment.kt +++ /dev/null @@ -1,28 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.second - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.fragment.app.viewModels -import androidx.navigation.fragment.navArgs -import co.nimblehq.coroutine.databinding.FragmentSecondBinding -import co.nimblehq.coroutine.ui.base.BaseFragment -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class SecondFragment : BaseFragment() { - - private val viewModel by viewModels() - - private val args: SecondFragmentArgs by navArgs() - - override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentSecondBinding - get() = { inflater, container, attachToParent -> - FragmentSecondBinding.inflate(inflater, container, attachToParent) - } - - override fun setupView() { - binding.tvMessage.text = args.bundle.message - } - - override fun bindViewModel() {} -} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/second/SecondViewModel.kt b/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/second/SecondViewModel.kt deleted file mode 100644 index b790f1d5d..000000000 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/second/SecondViewModel.kt +++ /dev/null @@ -1,9 +0,0 @@ -package co.nimblehq.coroutine.ui.screens.second - -import co.nimblehq.coroutine.ui.base.BaseViewModel -import co.nimblehq.coroutine.util.DispatchersProvider -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class SecondViewModel @Inject constructor(dispatchers: DispatchersProvider) : BaseViewModel(dispatchers) diff --git a/CoroutineTemplate/app/src/main/res/font/montserrat_regular.ttf b/CoroutineTemplate/app/src/main/res/font/montserrat_regular.ttf deleted file mode 100755 index 8d443d5d5..000000000 Binary files a/CoroutineTemplate/app/src/main/res/font/montserrat_regular.ttf and /dev/null differ diff --git a/CoroutineTemplate/app/src/main/res/layout/fragment_home.xml b/CoroutineTemplate/app/src/main/res/layout/fragment_home.xml deleted file mode 100644 index ba885d12f..000000000 --- a/CoroutineTemplate/app/src/main/res/layout/fragment_home.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - diff --git a/CoroutineTemplate/app/src/main/res/layout/view_loading.xml b/CoroutineTemplate/app/src/main/res/layout/view_loading.xml deleted file mode 100644 index 0ec4df1f5..000000000 --- a/CoroutineTemplate/app/src/main/res/layout/view_loading.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/CoroutineTemplate/app/src/main/res/navigation/nav_graph_main.xml b/CoroutineTemplate/app/src/main/res/navigation/nav_graph_main.xml deleted file mode 100644 index d17f16eed..000000000 --- a/CoroutineTemplate/app/src/main/res/navigation/nav_graph_main.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/CoroutineTemplate/app/src/main/res/values/colors.xml b/CoroutineTemplate/app/src/main/res/values/colors.xml deleted file mode 100644 index d442e10e8..000000000 --- a/CoroutineTemplate/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - @color/blue_free_speech - @color/blue_tory - @color/red_violet - diff --git a/CoroutineTemplate/app/src/main/res/values/colors_pallete.xml b/CoroutineTemplate/app/src/main/res/values/colors_pallete.xml deleted file mode 100644 index 1e3f7b65d..000000000 --- a/CoroutineTemplate/app/src/main/res/values/colors_pallete.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - #3F51B5 - #303F9F - #FF4081 - diff --git a/CoroutineTemplate/app/src/main/res/values/strings.xml b/CoroutineTemplate/app/src/main/res/values/strings.xml deleted file mode 100644 index 5d857f56c..000000000 --- a/CoroutineTemplate/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - Coroutine Template - Unexpected error - diff --git a/CoroutineTemplate/app/src/main/res/values/styles.xml b/CoroutineTemplate/app/src/main/res/values/styles.xml deleted file mode 100644 index 0eb88fe33..000000000 --- a/CoroutineTemplate/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/CoroutineTemplate/app/src/staging/res/values/strings.xml b/CoroutineTemplate/app/src/staging/res/values/strings.xml deleted file mode 100644 index 8ddda015b..000000000 --- a/CoroutineTemplate/app/src/staging/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Coroutine Template - Staging - diff --git a/CoroutineTemplate/data/src/main/AndroidManifest.xml b/CoroutineTemplate/data/src/main/AndroidManifest.xml deleted file mode 100644 index 729863ae3..000000000 --- a/CoroutineTemplate/data/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/repository/UserRepositoryImpl.kt b/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/repository/UserRepositoryImpl.kt deleted file mode 100644 index 17b0ac7d3..000000000 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/repository/UserRepositoryImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package co.nimblehq.coroutine.data.repository - -import co.nimblehq.coroutine.data.response.toUsers -import co.nimblehq.coroutine.data.service.ApiService -import co.nimblehq.coroutine.domain.model.User -import co.nimblehq.coroutine.domain.repository.UserRepository - -class UserRepositoryImpl constructor( - private val apiService: ApiService -) : UserRepository { - - override suspend fun getUsers(): List { - return apiService.getUsers().toUsers() - } -} diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/response/UserResponse.kt b/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/response/UserResponse.kt deleted file mode 100644 index e44b28eed..000000000 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/response/UserResponse.kt +++ /dev/null @@ -1,62 +0,0 @@ -package co.nimblehq.coroutine.data.response - -import co.nimblehq.coroutine.domain.model.User -import com.squareup.moshi.Json - -data class UserResponse( - @Json(name = "id") val id: Int?, - @Json(name = "name") val name: String?, - @Json(name = "username") val username: String?, - @Json(name = "email") val email: String?, - @Json(name = "address") val addressResponse: AddressResponse?, - @Json(name = "phone") val phone: String?, - @Json(name = "website") val website: String? -) { - - data class AddressResponse( - @Json(name = "street") val street: String?, - @Json(name = "suite") val suite: String?, - @Json(name = "city") val city: String?, - @Json(name = "zipcode") val zipCode: String?, - @Json(name = "geo") val geoResponse: GeoResponse? - ) { - - data class GeoResponse( - @Json(name = "lat") val latitude: String?, - @Json(name = "lng") val longitude: String? - ) - } -} - -fun List.toUsers(): List { - return this.map { it.toUser() } -} - -private fun UserResponse.toUser(): User { - return User( - id = this.id, - name = this.name.orEmpty(), - username = this.username.orEmpty(), - email = this.email.orEmpty(), - address = this.addressResponse?.toAddress(), - phone = this.phone.orEmpty(), - website = this.website.orEmpty() - ) -} - -private fun UserResponse.AddressResponse.toAddress(): User.Address { - return User.Address( - street = this.street.orEmpty(), - suite = this.suite.orEmpty(), - city = this.city.orEmpty(), - zipCode = this.zipCode.orEmpty(), - geo = this.geoResponse?.toGeo() - ) -} - -private fun UserResponse.AddressResponse.GeoResponse.toGeo(): User.Address.Geo { - return User.Address.Geo( - latitude = this.latitude.orEmpty(), - longitude = this.longitude.orEmpty() - ) -} diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/ApiService.kt b/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/ApiService.kt deleted file mode 100644 index bbe4a1429..000000000 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/ApiService.kt +++ /dev/null @@ -1,10 +0,0 @@ -package co.nimblehq.coroutine.data.service - -import co.nimblehq.coroutine.data.response.UserResponse -import retrofit2.http.GET - -interface ApiService { - - @GET("users") - suspend fun getUsers(): List -} diff --git a/CoroutineTemplate/data/src/test/java/co/nimblehq/coroutine/data/repository/UserRepositoryTest.kt b/CoroutineTemplate/data/src/test/java/co/nimblehq/coroutine/data/repository/UserRepositoryTest.kt deleted file mode 100644 index 76b8c5a84..000000000 --- a/CoroutineTemplate/data/src/test/java/co/nimblehq/coroutine/data/repository/UserRepositoryTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package co.nimblehq.coroutine.data.repository - -import co.nimblehq.coroutine.data.response.UserResponse -import co.nimblehq.coroutine.data.response.toUsers -import co.nimblehq.coroutine.data.service.ApiService -import co.nimblehq.coroutine.domain.repository.UserRepository -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.shouldBe -import io.mockk.coEvery -import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import org.junit.Before -import org.junit.Test - -@ExperimentalCoroutinesApi -class UserRepositoryTest { - - private lateinit var mockService: ApiService - private lateinit var repository: UserRepository - - private val userResponse = UserResponse( - id = 1, - name = "name", - username = "username", - email = "email", - addressResponse = null, - phone = null, - website = null - ) - - @Before - fun setup() { - mockService = mockk() - repository = UserRepositoryImpl(mockService) - } - - @Test - fun `When calling getUsers request successfully, it returns success response`() = runBlockingTest { - coEvery { mockService.getUsers() } returns listOf(userResponse) - - repository.getUsers() shouldBe listOf(userResponse).toUsers() - } - - @Test - fun `When calling getUsers request failed, it returns wrapped error`() = runBlockingTest { - coEvery { mockService.getUsers() } throws Throwable() - - shouldThrow { - repository.getUsers() - } - } -} diff --git a/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/model/User.kt b/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/model/User.kt deleted file mode 100644 index 04511ba83..000000000 --- a/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/model/User.kt +++ /dev/null @@ -1,26 +0,0 @@ -package co.nimblehq.coroutine.domain.model - -data class User( - val id: Int?, - val name: String, - val username: String, - val email: String, - val address: Address?, - val phone: String, - val website: String -) { - - data class Address( - val street: String, - val suite: String, - val city: String, - val zipCode: String, - val geo: Geo? - ) { - - data class Geo( - val latitude: String, - val longitude: String - ) - } -} diff --git a/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/repository/UserRepository.kt b/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/repository/UserRepository.kt deleted file mode 100644 index dca1dd2ab..000000000 --- a/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/repository/UserRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package co.nimblehq.coroutine.domain.repository - -import co.nimblehq.coroutine.domain.model.User - -interface UserRepository { - - suspend fun getUsers(): List -} diff --git a/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/usecase/GetUsersUseCase.kt b/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/usecase/GetUsersUseCase.kt deleted file mode 100644 index e71ea9c81..000000000 --- a/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/usecase/GetUsersUseCase.kt +++ /dev/null @@ -1,17 +0,0 @@ -package co.nimblehq.coroutine.domain.usecase - -import co.nimblehq.coroutine.domain.model.User -import co.nimblehq.coroutine.domain.repository.UserRepository -import javax.inject.Inject - -class GetUsersUseCase @Inject constructor(private val userRepository: UserRepository) { - - suspend fun execute(): UseCaseResult> { - return try { - val response = userRepository.getUsers() - UseCaseResult.Success(response) - } catch (e: Exception) { - UseCaseResult.Error(e) - } - } -} diff --git a/CoroutineTemplate/domain/src/test/java/co/nimblehq/coroutine/domain/usecase/GetUsersUseCaseTest.kt b/CoroutineTemplate/domain/src/test/java/co/nimblehq/coroutine/domain/usecase/GetUsersUseCaseTest.kt deleted file mode 100644 index 9b46cc64a..000000000 --- a/CoroutineTemplate/domain/src/test/java/co/nimblehq/coroutine/domain/usecase/GetUsersUseCaseTest.kt +++ /dev/null @@ -1,54 +0,0 @@ -package co.nimblehq.coroutine.domain.usecase - -import co.nimblehq.coroutine.domain.model.User -import co.nimblehq.coroutine.domain.repository.UserRepository -import io.kotest.matchers.shouldBe -import io.mockk.coEvery -import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import org.junit.Before -import org.junit.Test - -@ExperimentalCoroutinesApi -class GetUsersUseCaseTest { - - private lateinit var mockRepository: UserRepository - private lateinit var usecase: GetUsersUseCase - - private val user = User( - id = 1, - name = "name", - username = "username", - email = "email", - address = null, - phone = "", - website = "" - ) - - @Before - fun setup() { - mockRepository = mockk() - usecase = GetUsersUseCase(mockRepository) - } - - @Test - fun `When calling request successfully, it returns success response`() = runBlockingTest { - val expected = listOf(user) - coEvery { mockRepository.getUsers() } returns expected - - usecase.execute().run { - (this as UseCaseResult.Success).data shouldBe expected - } - } - - @Test - fun `When calling request failed, it returns wrapped error`() = runBlockingTest { - val expected = Exception() - coEvery { mockRepository.getUsers() } throws expected - - usecase.execute().run { - (this as UseCaseResult.Error).exception shouldBe expected - } - } -} diff --git a/CoroutineTemplate/settings.gradle.kts b/CoroutineTemplate/settings.gradle.kts deleted file mode 100644 index 6f32d6bae..000000000 --- a/CoroutineTemplate/settings.gradle.kts +++ /dev/null @@ -1,2 +0,0 @@ -// FIXME Project build error when using Configurations.Module constants -include(":app", ":data", ":domain") diff --git a/Dangerfile b/Dangerfile index 11e20dbc4..a9a3f1954 100644 --- a/Dangerfile +++ b/Dangerfile @@ -29,9 +29,9 @@ Dir[lint_dir].each do |file_name| android_lint.lint(inline_mode: true) end -# Show Danger test coverage report from Jacoco for CoroutineTemplate -jacoco_dir = "CoroutineTemplate/**/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml" -markdown "## CoroutineTemplate Jacoco report:" +# Show Danger test coverage report from Jacoco for template +jacoco_dir = "template/**/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml" +markdown "## template Jacoco report:" Dir[jacoco_dir].each do |file_name| # Report coverage of modified files, warn if total project coverage is under 80% # or if any modified file's coverage is under 95% diff --git a/README.md b/README.md index 9adcaeaa2..243bff8db 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ --- -Our Android template: **[CoroutineTemplate](https://github.com/nimblehq/android-templates/tree/develop/CoroutineTemplate)** +Our Android template: **[template](https://github.com/nimblehq/android-templates/tree/develop/template)** ## Setup @@ -23,7 +23,7 @@ Our Android template: **[CoroutineTemplate](https://github.com/nimblehq/android- Example: `kscript new_project.kts package-name=co.myproject.example app-name="My Project"` -4. Update `android_version_code` and `android_version_name` in `CoroutineTemplate/build.gradle` +4. Update `android_version_code` and `android_version_name` in `template/build.gradle` ## About diff --git a/scripts/new_project.kts b/scripts/new_project.kts index cd3fc7fa4..0546bcfc5 100644 --- a/scripts/new_project.kts +++ b/scripts/new_project.kts @@ -6,28 +6,43 @@ object NewProject { private const val DOT_SEPARATOR = "." private const val KEY_APP_NAME = "app-name" private const val KEY_PACKAGE_NAME = "package-name" - private const val TEMPLATE_APP_NAME = "Coroutine Template" - private const val TEMPLATE_APPLICATION_CLASS_NAME = "CoroutineTemplateApplication" - private const val TEMPLATE_FOLDER_NAME = "CoroutineTemplate" - private const val TEMPLATE_PACKAGE_NAME = "co.nimblehq.coroutine" - - private const val PATTERN_APP = "^[A-Z][a-zA-Z0-9\\s]*$" + private const val MINUS_SEPARATOR = "-" + private const val SPACE_SEPARATOR = " " + private const val TEMPLATE_APP_NAME = "Template" + private const val TEMPLATE_APPLICATION_CLASS_NAME = "TemplateApplication" + private const val TEMPLATE_FOLDER_NAME = "template" + private const val TEMPLATE_PACKAGE_NAME = "co.nimblehq.template" + + private const val PATTERN_APP = "^([A-Z][a-zA-Z0-9\\s]*)|([a-z][a-z0-9-]*)$" private const val PATTERN_PACKAGE = "^[a-z]+(\\.[a-z][a-z0-9]*)+$" private val modules = listOf("app", "data", "domain") private val fileSeparator = File.separator - private var appName = "" + private var appName: String = "" + set(value) { + field = if (value.contains(MINUS_SEPARATOR)) { + projectFolderName = value + value.replace(MINUS_SEPARATOR, SPACE_SEPARATOR).uppercaseEveryFirstCharacter() + } else { + value.uppercaseEveryFirstCharacter().also { + projectFolderName = it.getStringWithoutSpace() + } + } + } + private var packageName = "" private val appNameWithoutSpace: String - get() = appName.replace(" ", "") + get() = appName.getStringWithoutSpace() private val applicationClassName: String get() = "${appNameWithoutSpace}Application" + private var projectFolderName: String = "" + private val projectPath: String - get() = rootPath + appNameWithoutSpace + get() = rootPath + projectFolderName private val rootPath: String get() = System.getProperty("user.dir").replace("scripts", "") @@ -65,7 +80,7 @@ object NewProject { private fun validateAppName(value: String) { if (PATTERN_APP.toRegex().matches(value)) { - appName = value + appName = value.trim() } else { showErrorMessage("ERROR: Invalid App Name: $value (needs to follow standard pattern {MyProject} or {My Project})") } @@ -73,7 +88,7 @@ object NewProject { private fun validatePackageName(value: String) { if (PATTERN_PACKAGE.toRegex().matches(value)) { - packageName = value + packageName = value.trim() } else { showErrorMessage("ERROR: Invalid Package Name: $value (needs to follow standard pattern {com.example.package})") } @@ -137,7 +152,7 @@ object NewProject { private fun renamePackageNameWithinFiles() { showMessage("=> 🔎 Renaming package name within files...") File(projectPath) - ?.walk() + .walk() .filter { it.name.endsWith(".kt") || it.name.endsWith(".xml") } .forEach { filePath -> rename( @@ -151,7 +166,7 @@ object NewProject { private fun renameApplicationClass() { showMessage("=> 🔎 Renaming application class...") File(projectPath) - ?.walk() + .walk() .filter { it.name == "$TEMPLATE_APPLICATION_CLASS_NAME.kt" || it.name == "AndroidManifest.xml" } .forEach { file -> rename( @@ -192,14 +207,14 @@ object NewProject { process.inputStream.reader().forEachLine { println(it) } val exitValue = process.waitFor() if (exitValue != 0) { - showErrorMessage("❌ Something went wrong!", exitValue) + showErrorMessage("❌ Something went wrong! when executing command: $command", exitValue) } } private fun renameAppName() { showMessage("=> 🔎 Renaming app name...") File(projectPath) - ?.walk() + .walk() .filter { it.name == "strings.xml" } .forEach { filePath -> rename( @@ -225,6 +240,16 @@ object NewProject { println("\n${message}\n") System.exit(exitCode) } + + private fun String.uppercaseEveryFirstCharacter(): String { + return this.split(SPACE_SEPARATOR).joinToString(separator = SPACE_SEPARATOR) { string -> + string.replaceFirstChar { it.uppercase() } + } + } + + private fun String.getStringWithoutSpace(): String { + return this.replace(SPACE_SEPARATOR, "") + } } NewProject.generate(args) diff --git a/CoroutineTemplate/.gitignore b/template/.gitignore similarity index 100% rename from CoroutineTemplate/.gitignore rename to template/.gitignore diff --git a/CoroutineTemplate/README.md b/template/README.md similarity index 87% rename from CoroutineTemplate/README.md rename to template/README.md index 628857310..2a0a6d11e 100644 --- a/CoroutineTemplate/README.md +++ b/template/README.md @@ -1,4 +1,4 @@ -# Android Templates: Coroutine +# Android Templates - Our optimized Android templates used in our android projects @@ -48,4 +48,4 @@ Report is located at: `./app/build/reports/jacoco/` For `release` builds, we need to provide release keystore and signing properties: - Put the `release.keystore` file at root `config` folder. -- Put keystore signing properties in [signing.properties](https://github.com/nimblehq/android-templates/blob/develop/CoroutineTemplate/signing.properties). +- Put keystore signing properties in [signing.properties](https://github.com/nimblehq/android-templates/blob/develop/template/signing.properties). diff --git a/CoroutineTemplate/app/.gitignore b/template/app/.gitignore similarity index 100% rename from CoroutineTemplate/app/.gitignore rename to template/app/.gitignore diff --git a/CoroutineTemplate/app/build.gradle.kts b/template/app/build.gradle.kts similarity index 99% rename from CoroutineTemplate/app/build.gradle.kts rename to template/app/build.gradle.kts index 4e3e10040..d5ed08c67 100644 --- a/CoroutineTemplate/app/build.gradle.kts +++ b/template/app/build.gradle.kts @@ -33,7 +33,7 @@ android { compileSdk = Versions.ANDROID_COMPILE_SDK_VERSION defaultConfig { - applicationId = "co.nimblehq.coroutine" + applicationId = "co.nimblehq.template" minSdk = Versions.ANDROID_MIN_SDK_VERSION targetSdk = Versions.ANDROID_TARGET_SDK_VERSION versionCode = Versions.ANDROID_VERSION_CODE diff --git a/CoroutineTemplate/app/proguard-rules.pro b/template/app/proguard-rules.pro similarity index 100% rename from CoroutineTemplate/app/proguard-rules.pro rename to template/app/proguard-rules.pro diff --git a/CoroutineTemplate/app/src/debug/AndroidManifest.xml b/template/app/src/debug/AndroidManifest.xml similarity index 87% rename from CoroutineTemplate/app/src/debug/AndroidManifest.xml rename to template/app/src/debug/AndroidManifest.xml index 67f488a48..b1b4dfc1d 100644 --- a/CoroutineTemplate/app/src/debug/AndroidManifest.xml +++ b/template/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="co.nimblehq.template"> + package="co.nimblehq.template"> .toUiModels() = this.map { it.toUiModel() } diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/ErrorMapping.kt b/template/app/src/main/java/co/nimblehq/template/ui/ErrorMapping.kt similarity index 58% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/ErrorMapping.kt rename to template/app/src/main/java/co/nimblehq/template/ui/ErrorMapping.kt index 38f1980fc..824d6c0ba 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/ErrorMapping.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/ErrorMapping.kt @@ -1,9 +1,8 @@ -package co.nimblehq.coroutine.ui +package co.nimblehq.template.ui import android.content.Context -import co.nimblehq.coroutine.R +import co.nimblehq.template.R fun Throwable.userReadableMessage(context: Context): String { - // TODO implement user readable message return context.getString(R.string.error_generic) } diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseActivity.kt b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseActivity.kt similarity index 95% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseActivity.kt rename to template/app/src/main/java/co/nimblehq/template/ui/base/BaseActivity.kt index 76e1770e7..328445185 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseActivity.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseActivity.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.ui.base +package co.nimblehq.template.ui.base import android.os.Bundle import android.view.LayoutInflater diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseComposeFragment.kt b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseComposeFragment.kt similarity index 95% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseComposeFragment.kt rename to template/app/src/main/java/co/nimblehq/template/ui/base/BaseComposeFragment.kt index 90e2f962a..c95fb9714 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseComposeFragment.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseComposeFragment.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.ui.base +package co.nimblehq.template.ui.base import android.os.Bundle import android.view.LayoutInflater @@ -11,7 +11,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import co.nimblehq.coroutine.ui.common.Toaster +import co.nimblehq.template.ui.common.Toaster import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseComposeFragmentCallbacks.kt b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseComposeFragmentCallbacks.kt similarity index 97% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseComposeFragmentCallbacks.kt rename to template/app/src/main/java/co/nimblehq/template/ui/base/BaseComposeFragmentCallbacks.kt index 7a121a554..f82406192 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseComposeFragmentCallbacks.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseComposeFragmentCallbacks.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.ui.base +package co.nimblehq.template.ui.base /** * An interface provide abstract commitments for the implemented class diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseFragment.kt b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseFragment.kt similarity index 93% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseFragment.kt rename to template/app/src/main/java/co/nimblehq/template/ui/base/BaseFragment.kt index d28bc846d..7a376192b 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseFragment.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseFragment.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.ui.base +package co.nimblehq.template.ui.base import android.os.Bundle import android.view.LayoutInflater @@ -11,8 +11,8 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import co.nimblehq.common.extensions.hideSoftKeyboard -import co.nimblehq.coroutine.ui.common.Toaster -import co.nimblehq.coroutine.ui.userReadableMessage +import co.nimblehq.template.ui.common.Toaster +import co.nimblehq.template.ui.userReadableMessage import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch @@ -39,6 +39,8 @@ abstract class BaseFragment : Fragment(), BaseFragmentCallback override fun initViewModel() {} + override fun setupView() {} + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseFragmentCallbacks.kt b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseFragmentCallbacks.kt similarity index 98% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseFragmentCallbacks.kt rename to template/app/src/main/java/co/nimblehq/template/ui/base/BaseFragmentCallbacks.kt index aef405510..9e9488cb0 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseFragmentCallbacks.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseFragmentCallbacks.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.ui.base +package co.nimblehq.template.ui.base /** * An interface provide abstract commitments for the implemented class diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseNavigator.kt b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseNavigator.kt similarity index 98% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseNavigator.kt rename to template/app/src/main/java/co/nimblehq/template/ui/base/BaseNavigator.kt index 588d8c567..911eebdf7 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseNavigator.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseNavigator.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.ui.base +package co.nimblehq.template.ui.base import android.os.Bundle import android.os.Parcelable diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseViewModel.kt b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseViewModel.kt similarity index 91% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseViewModel.kt rename to template/app/src/main/java/co/nimblehq/template/ui/base/BaseViewModel.kt index 054c575c1..ca70ed4a7 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/BaseViewModel.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/base/BaseViewModel.kt @@ -1,9 +1,9 @@ -package co.nimblehq.coroutine.ui.base +package co.nimblehq.template.ui.base import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import co.nimblehq.coroutine.lib.IsLoading -import co.nimblehq.coroutine.util.DispatchersProvider +import co.nimblehq.template.lib.IsLoading +import co.nimblehq.template.util.DispatchersProvider import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch diff --git a/template/app/src/main/java/co/nimblehq/template/ui/base/NavigationEvent.kt b/template/app/src/main/java/co/nimblehq/template/ui/base/NavigationEvent.kt new file mode 100644 index 000000000..f8438c394 --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/base/NavigationEvent.kt @@ -0,0 +1,3 @@ +package co.nimblehq.template.ui.base + +sealed class NavigationEvent diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/NavigationException.kt b/template/app/src/main/java/co/nimblehq/template/ui/base/NavigationException.kt similarity index 88% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/NavigationException.kt rename to template/app/src/main/java/co/nimblehq/template/ui/base/NavigationException.kt index 97f3a6081..e69b2908e 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/base/NavigationException.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/base/NavigationException.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.ui.base +package co.nimblehq.template.ui.base sealed class NavigationException( cause: Throwable? diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/common/Toaster.kt b/template/app/src/main/java/co/nimblehq/template/ui/common/Toaster.kt similarity index 92% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/common/Toaster.kt rename to template/app/src/main/java/co/nimblehq/template/ui/common/Toaster.kt index fbb217552..f65f542c7 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/common/Toaster.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/common/Toaster.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.ui.common +package co.nimblehq.template.ui.common import android.content.Context import android.widget.Toast diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/MainActivity.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/MainActivity.kt similarity index 72% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/MainActivity.kt rename to template/app/src/main/java/co/nimblehq/template/ui/screens/MainActivity.kt index 2a1f2e4b3..7e84bc818 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/ui/screens/MainActivity.kt +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/MainActivity.kt @@ -1,9 +1,9 @@ -package co.nimblehq.coroutine.ui.screens +package co.nimblehq.template.ui.screens import android.view.LayoutInflater import androidx.activity.viewModels -import co.nimblehq.coroutine.databinding.ActivityMainBinding -import co.nimblehq.coroutine.ui.base.BaseActivity +import co.nimblehq.template.databinding.ActivityMainBinding +import co.nimblehq.template.ui.base.BaseActivity import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/MainNavigator.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/MainNavigator.kt new file mode 100644 index 000000000..f1d2a0875 --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/MainNavigator.kt @@ -0,0 +1,24 @@ +package co.nimblehq.template.ui.screens + +import androidx.fragment.app.Fragment +import co.nimblehq.template.R +import co.nimblehq.template.ui.base.BaseNavigator +import co.nimblehq.template.ui.base.BaseNavigatorImpl +import co.nimblehq.template.ui.base.NavigationEvent +import javax.inject.Inject + +interface MainNavigator : BaseNavigator + +class MainNavigatorImpl @Inject constructor( + fragment: Fragment +) : BaseNavigatorImpl(fragment), MainNavigator { + + override val navHostFragmentId = R.id.navHostFragment + + override fun navigate(event: NavigationEvent) { + val navController = findNavController() + when (navController?.currentDestination?.id) { + else -> unsupportedNavigation() + } + } +} diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/MainViewModel.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/MainViewModel.kt new file mode 100644 index 000000000..75ccbc7df --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/MainViewModel.kt @@ -0,0 +1,11 @@ +package co.nimblehq.template.ui.screens + +import co.nimblehq.template.ui.base.BaseViewModel +import co.nimblehq.template.util.DispatchersProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class MainViewModel @Inject constructor( + dispatchers: DispatchersProvider +) : BaseViewModel(dispatchers) diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/HomeComposeFragment.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/HomeComposeFragment.kt new file mode 100644 index 000000000..5215e203b --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/HomeComposeFragment.kt @@ -0,0 +1,36 @@ +package co.nimblehq.template.ui.screens.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.res.stringResource +import co.nimblehq.template.R +import co.nimblehq.template.extension.provideViewModels +import co.nimblehq.template.ui.base.BaseComposeFragment +import co.nimblehq.template.ui.screens.MainNavigator +import co.nimblehq.template.ui.screens.compose.composables.HomeScreen +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@ExperimentalComposeUiApi +@AndroidEntryPoint +class HomeComposeFragment : BaseComposeFragment() { + + @Inject + lateinit var navigator: MainNavigator + + private val viewModel: HomeComposeViewModel by provideViewModels() + + override val composeScreen: @Composable () -> Unit + get() = { + HomeScreen( + title = stringResource(id = R.string.app_name), + uiModels = viewModel.uiModels.collectAsState().value + ) + } + + override fun bindViewModel() { + viewModel.error bindTo toaster::display + viewModel.navigator bindTo navigator::navigate + } +} diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/HomeComposeViewModel.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/HomeComposeViewModel.kt new file mode 100644 index 000000000..d83d12201 --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/HomeComposeViewModel.kt @@ -0,0 +1,38 @@ +package co.nimblehq.template.ui.screens.compose + +import co.nimblehq.template.domain.usecase.UseCase +import co.nimblehq.template.domain.usecase.UseCaseResult +import co.nimblehq.template.model.UiModel +import co.nimblehq.template.model.toUiModels +import co.nimblehq.template.ui.base.BaseViewModel +import co.nimblehq.template.util.DispatchersProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@HiltViewModel +class HomeComposeViewModel @Inject constructor( + private val useCase: UseCase, + dispatchers: DispatchersProvider +) : BaseViewModel(dispatchers) { + + private val _uiModels = MutableStateFlow>(emptyList()) + val uiModels: StateFlow> + get() = _uiModels + + init { + execute { + when (val result = useCase.execute()) { + is UseCaseResult.Success -> { + val uiModels = result.data.toUiModels() + _uiModels.emit(uiModels) + } + is UseCaseResult.Error -> { + val errorMessage = result.exception.message.orEmpty() + _error.emit(errorMessage) + } + } + } + } +} diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/composables/HomeScreen.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/composables/HomeScreen.kt new file mode 100644 index 000000000..9bf533286 --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/composables/HomeScreen.kt @@ -0,0 +1,32 @@ +package co.nimblehq.template.ui.screens.compose.composables + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import co.nimblehq.template.model.UiModel +import co.nimblehq.template.ui.screens.compose.theme.Dimension.SpacingNormal +import co.nimblehq.template.ui.screens.compose.theme.Theme +import timber.log.Timber + +@Suppress("FunctionNaming") +@ExperimentalComposeUiApi +@Composable +fun HomeScreen( + title: String, + uiModels: List +) { + Theme { + Text( + text = title, + textAlign = TextAlign.Center, + modifier = Modifier + .wrapContentHeight() + .padding(all = SpacingNormal) + ) + } + Timber.d("Result : $uiModels") +} diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/theme/Color.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/theme/Color.kt new file mode 100644 index 000000000..e6afdccea --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/theme/Color.kt @@ -0,0 +1,9 @@ +package co.nimblehq.template.ui.screens.compose.theme + +import androidx.compose.ui.graphics.Color + +@Suppress("MagicNumber") +object Color { + val GreenCitrus = Color(0xFF99CC00) + val GreenChristi = Color(0xFF669900) +} diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/theme/Dimension.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/theme/Dimension.kt new file mode 100644 index 000000000..1da748be7 --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/theme/Dimension.kt @@ -0,0 +1,8 @@ +package co.nimblehq.template.ui.screens.compose.theme + +import androidx.compose.ui.unit.dp + +@Suppress("MagicNumber") +object Dimension { + val SpacingNormal = 16.dp +} diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/theme/Theme.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/theme/Theme.kt new file mode 100644 index 000000000..623484903 --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/compose/theme/Theme.kt @@ -0,0 +1,20 @@ +package co.nimblehq.template.ui.screens.compose.theme + +import androidx.compose.material.MaterialTheme +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +@Suppress("FunctionNaming") +@Composable +fun Theme( + content: @Composable () -> Unit +) { + MaterialTheme( + colors = lightColors( + primary = Color.GreenCitrus, + primaryVariant = Color.GreenChristi, + secondary = Color.GreenCitrus + ), + content = content + ) +} diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/xml/HomeFragment.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/xml/HomeFragment.kt new file mode 100644 index 000000000..e4d1e67d3 --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/xml/HomeFragment.kt @@ -0,0 +1,36 @@ +package co.nimblehq.template.ui.screens.xml + +import android.view.LayoutInflater +import android.view.ViewGroup +import co.nimblehq.template.databinding.FragmentHomeBinding +import co.nimblehq.template.extension.provideViewModels +import co.nimblehq.template.model.UiModel +import co.nimblehq.template.ui.base.BaseFragment +import co.nimblehq.template.ui.screens.MainNavigator +import dagger.hilt.android.AndroidEntryPoint +import timber.log.Timber +import javax.inject.Inject + +@AndroidEntryPoint +class HomeFragment : BaseFragment() { + + @Inject + lateinit var navigator: MainNavigator + + private val viewModel: HomeViewModel by provideViewModels() + + override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentHomeBinding + get() = { inflater, container, attachToParent -> + FragmentHomeBinding.inflate(inflater, container, attachToParent) + } + + override fun bindViewModel() { + viewModel.uiModels bindTo ::displayUiModels + viewModel.error bindTo toaster::display + viewModel.navigator bindTo navigator::navigate + } + + private fun displayUiModels(uiModels: List) { + Timber.d("Result : $uiModels") + } +} diff --git a/template/app/src/main/java/co/nimblehq/template/ui/screens/xml/HomeViewModel.kt b/template/app/src/main/java/co/nimblehq/template/ui/screens/xml/HomeViewModel.kt new file mode 100644 index 000000000..c0d598ef5 --- /dev/null +++ b/template/app/src/main/java/co/nimblehq/template/ui/screens/xml/HomeViewModel.kt @@ -0,0 +1,38 @@ +package co.nimblehq.template.ui.screens.xml + +import co.nimblehq.template.domain.usecase.UseCase +import co.nimblehq.template.domain.usecase.UseCaseResult +import co.nimblehq.template.model.UiModel +import co.nimblehq.template.model.toUiModels +import co.nimblehq.template.ui.base.BaseViewModel +import co.nimblehq.template.util.DispatchersProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor( + private val useCase: UseCase, + dispatchers: DispatchersProvider +) : BaseViewModel(dispatchers) { + + private val _uiModels = MutableStateFlow>(emptyList()) + val uiModels: StateFlow> + get() = _uiModels + + init { + execute { + when (val result = useCase.execute()) { + is UseCaseResult.Success -> { + val uiModels = result.data.toUiModels() + _uiModels.emit(uiModels) + } + is UseCaseResult.Error -> { + val errorMessage = result.exception.message.orEmpty() + _error.emit(errorMessage) + } + } + } + } +} diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/util/DispatchersProvider.kt b/template/app/src/main/java/co/nimblehq/template/util/DispatchersProvider.kt similarity index 84% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/util/DispatchersProvider.kt rename to template/app/src/main/java/co/nimblehq/template/util/DispatchersProvider.kt index 2c71b0dbe..cf2385523 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/util/DispatchersProvider.kt +++ b/template/app/src/main/java/co/nimblehq/template/util/DispatchersProvider.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.util +package co.nimblehq.template.util import kotlinx.coroutines.CoroutineDispatcher diff --git a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/util/DispatchersProviderImpl.kt b/template/app/src/main/java/co/nimblehq/template/util/DispatchersProviderImpl.kt similarity index 86% rename from CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/util/DispatchersProviderImpl.kt rename to template/app/src/main/java/co/nimblehq/template/util/DispatchersProviderImpl.kt index d5ab5e6c1..a4565e662 100644 --- a/CoroutineTemplate/app/src/main/java/co/nimblehq/coroutine/util/DispatchersProviderImpl.kt +++ b/template/app/src/main/java/co/nimblehq/template/util/DispatchersProviderImpl.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.util +package co.nimblehq.template.util import kotlinx.coroutines.Dispatchers diff --git a/CoroutineTemplate/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from CoroutineTemplate/app/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml diff --git a/CoroutineTemplate/app/src/main/res/drawable/ic_launcher_background.xml b/template/app/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from CoroutineTemplate/app/src/main/res/drawable/ic_launcher_background.xml rename to template/app/src/main/res/drawable/ic_launcher_background.xml diff --git a/CoroutineTemplate/app/src/main/res/layout/activity_main.xml b/template/app/src/main/res/layout/activity_main.xml similarity index 100% rename from CoroutineTemplate/app/src/main/res/layout/activity_main.xml rename to template/app/src/main/res/layout/activity_main.xml diff --git a/CoroutineTemplate/app/src/main/res/layout/fragment_second.xml b/template/app/src/main/res/layout/fragment_home.xml similarity index 72% rename from CoroutineTemplate/app/src/main/res/layout/fragment_second.xml rename to template/app/src/main/res/layout/fragment_home.xml index 2e2b65d6c..67fc9975c 100644 --- a/CoroutineTemplate/app/src/main/res/layout/fragment_second.xml +++ b/template/app/src/main/res/layout/fragment_home.xml @@ -1,20 +1,18 @@ + app:layout_constraintTop_toTopOf="parent" /> diff --git a/CoroutineTemplate/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/CoroutineTemplate/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/CoroutineTemplate/app/src/main/res/mipmap-hdpi/ic_launcher.png b/template/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to template/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/CoroutineTemplate/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/template/app/src/main/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-hdpi/ic_launcher_round.png rename to template/app/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/CoroutineTemplate/app/src/main/res/mipmap-mdpi/ic_launcher.png b/template/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to template/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/CoroutineTemplate/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/template/app/src/main/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-mdpi/ic_launcher_round.png rename to template/app/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/CoroutineTemplate/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/template/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to template/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/CoroutineTemplate/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png rename to template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/CoroutineTemplate/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/template/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to template/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/CoroutineTemplate/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png rename to template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/CoroutineTemplate/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/CoroutineTemplate/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from CoroutineTemplate/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png rename to template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/template/app/src/main/res/navigation/nav_graph_main.xml b/template/app/src/main/res/navigation/nav_graph_main.xml new file mode 100644 index 000000000..848398dbf --- /dev/null +++ b/template/app/src/main/res/navigation/nav_graph_main.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/template/app/src/main/res/values/colors.xml b/template/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..a88476cc8 --- /dev/null +++ b/template/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FF99CC00 + #FF669900 + diff --git a/template/app/src/main/res/values/dimens.xml b/template/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000..76beac899 --- /dev/null +++ b/template/app/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 16dp + diff --git a/template/app/src/main/res/values/strings.xml b/template/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..1096d61cb --- /dev/null +++ b/template/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Template + Unexpected error + diff --git a/template/app/src/main/res/values/styles.xml b/template/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..6190aefb9 --- /dev/null +++ b/template/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/CoroutineTemplate/app/src/main/res/xml/network_security_config.xml b/template/app/src/main/res/xml/network_security_config.xml similarity index 100% rename from CoroutineTemplate/app/src/main/res/xml/network_security_config.xml rename to template/app/src/main/res/xml/network_security_config.xml diff --git a/template/app/src/staging/res/values/strings.xml b/template/app/src/staging/res/values/strings.xml new file mode 100644 index 000000000..6bd476455 --- /dev/null +++ b/template/app/src/staging/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Template - Staging + diff --git a/CoroutineTemplate/app/src/staging/res/xml/network_security_config.xml b/template/app/src/staging/res/xml/network_security_config.xml similarity index 100% rename from CoroutineTemplate/app/src/staging/res/xml/network_security_config.xml rename to template/app/src/staging/res/xml/network_security_config.xml diff --git a/CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/test/TestModules.kt b/template/app/src/test/java/co/nimblehq/template/test/TestModules.kt similarity index 74% rename from CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/test/TestModules.kt rename to template/app/src/test/java/co/nimblehq/template/test/TestModules.kt index 2d11d151d..162531e62 100644 --- a/CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/test/TestModules.kt +++ b/template/app/src/test/java/co/nimblehq/template/test/TestModules.kt @@ -1,7 +1,7 @@ -package co.nimblehq.coroutine.test +package co.nimblehq.template.test -import co.nimblehq.coroutine.di.modules.NavigatorModule -import co.nimblehq.coroutine.ui.screens.MainNavigator +import co.nimblehq.template.di.modules.NavigatorModule +import co.nimblehq.template.ui.screens.MainNavigator import dagger.Module import dagger.Provides import dagger.hilt.android.components.FragmentComponent diff --git a/CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/test/ViewModelExt.kt b/template/app/src/test/java/co/nimblehq/template/test/ViewModelExt.kt similarity index 87% rename from CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/test/ViewModelExt.kt rename to template/app/src/test/java/co/nimblehq/template/test/ViewModelExt.kt index 8bfa67e84..2910da310 100644 --- a/CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/test/ViewModelExt.kt +++ b/template/app/src/test/java/co/nimblehq/template/test/ViewModelExt.kt @@ -1,8 +1,8 @@ -package co.nimblehq.coroutine.test +package co.nimblehq.template.test import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModel -import co.nimblehq.coroutine.extension.OverridableLazy +import co.nimblehq.template.extension.OverridableLazy import kotlin.reflect.KProperty1 import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.isAccessible diff --git a/CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/ui/BaseFragmentTest.kt b/template/app/src/test/java/co/nimblehq/template/ui/BaseFragmentTest.kt similarity index 94% rename from CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/ui/BaseFragmentTest.kt rename to template/app/src/test/java/co/nimblehq/template/ui/BaseFragmentTest.kt index bec2e0aba..2857419a6 100644 --- a/CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/ui/BaseFragmentTest.kt +++ b/template/app/src/test/java/co/nimblehq/template/ui/BaseFragmentTest.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.ui +package co.nimblehq.template.ui import android.content.ComponentName import android.content.Intent @@ -9,9 +9,9 @@ import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.viewbinding.ViewBinding -import co.nimblehq.coroutine.EmptyHiltActivity -import co.nimblehq.coroutine.R -import co.nimblehq.coroutine.ui.base.BaseFragment +import co.nimblehq.template.EmptyHiltActivity +import co.nimblehq.template.R +import co.nimblehq.template.ui.base.BaseFragment import dagger.hilt.android.testing.* import org.junit.runner.RunWith diff --git a/CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/ui/screens/home/HomeFragmentTest.kt b/template/app/src/test/java/co/nimblehq/template/ui/screens/xml/HomeFragmentTest.kt similarity index 60% rename from CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/ui/screens/home/HomeFragmentTest.kt rename to template/app/src/test/java/co/nimblehq/template/ui/screens/xml/HomeFragmentTest.kt index 4f8dc795b..7d2ae22f0 100644 --- a/CoroutineTemplate/app/src/test/java/co/nimblehq/coroutine/ui/screens/home/HomeFragmentTest.kt +++ b/template/app/src/test/java/co/nimblehq/template/ui/screens/xml/HomeFragmentTest.kt @@ -1,10 +1,11 @@ -package co.nimblehq.coroutine.ui.screens.home +package co.nimblehq.template.ui.screens.xml -import co.nimblehq.coroutine.databinding.FragmentHomeBinding -import co.nimblehq.coroutine.test.TestNavigatorModule.mockMainNavigator -import co.nimblehq.coroutine.test.getPrivateProperty -import co.nimblehq.coroutine.test.replace -import co.nimblehq.coroutine.ui.BaseFragmentTest +import co.nimblehq.template.R +import co.nimblehq.template.databinding.FragmentHomeBinding +import co.nimblehq.template.test.TestNavigatorModule.mockMainNavigator +import co.nimblehq.template.test.getPrivateProperty +import co.nimblehq.template.test.replace +import co.nimblehq.template.ui.BaseFragmentTest import dagger.hilt.android.testing.* import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -25,10 +26,9 @@ class HomeFragmentTest : BaseFragmentTest() { } @Test - fun `When initializing HomeFragment, its views display the text correctly`() { + fun `When initializing fragment, it displays the title correctly`() { launchFragment() - fragment.binding.btNext.text.toString() shouldBe "Next" - fragment.binding.btCompose.text.toString() shouldBe "Jetpack Compose" + fragment.binding.tvTitle.text.toString() shouldBe fragment.resources.getString(R.string.app_name) } private fun launchFragment() { diff --git a/CoroutineTemplate/app/src/test/resources/robolectric.properties b/template/app/src/test/resources/robolectric.properties similarity index 100% rename from CoroutineTemplate/app/src/test/resources/robolectric.properties rename to template/app/src/test/resources/robolectric.properties diff --git a/CoroutineTemplate/build.gradle.kts b/template/build.gradle.kts similarity index 100% rename from CoroutineTemplate/build.gradle.kts rename to template/build.gradle.kts diff --git a/CoroutineTemplate/buildSrc/build.gradle.kts b/template/buildSrc/build.gradle.kts similarity index 100% rename from CoroutineTemplate/buildSrc/build.gradle.kts rename to template/buildSrc/build.gradle.kts diff --git a/CoroutineTemplate/buildSrc/src/main/java/Configurations.kt b/template/buildSrc/src/main/java/Configurations.kt similarity index 100% rename from CoroutineTemplate/buildSrc/src/main/java/Configurations.kt rename to template/buildSrc/src/main/java/Configurations.kt diff --git a/CoroutineTemplate/buildSrc/src/main/java/FileExt.kt b/template/buildSrc/src/main/java/FileExt.kt similarity index 100% rename from CoroutineTemplate/buildSrc/src/main/java/FileExt.kt rename to template/buildSrc/src/main/java/FileExt.kt diff --git a/CoroutineTemplate/buildSrc/src/main/java/Versions.kt b/template/buildSrc/src/main/java/Versions.kt similarity index 100% rename from CoroutineTemplate/buildSrc/src/main/java/Versions.kt rename to template/buildSrc/src/main/java/Versions.kt diff --git a/CoroutineTemplate/buildSrc/src/main/java/plugins/jacoco-report.gradle.kts b/template/buildSrc/src/main/java/plugins/jacoco-report.gradle.kts similarity index 98% rename from CoroutineTemplate/buildSrc/src/main/java/plugins/jacoco-report.gradle.kts rename to template/buildSrc/src/main/java/plugins/jacoco-report.gradle.kts index d4e32fb88..cd5b6da19 100644 --- a/CoroutineTemplate/buildSrc/src/main/java/plugins/jacoco-report.gradle.kts +++ b/template/buildSrc/src/main/java/plugins/jacoco-report.gradle.kts @@ -40,8 +40,8 @@ val packagesExcluded = setOf( "**/com/bumptech/glide", "**/dagger/hilt/internal", "**/hilt_aggregated_deps", - "**/co/nimblehq/coroutine/databinding/**", - "**/co/nimblehq/coroutine/di/**" + "**/co/nimblehq/template/databinding/**", + "**/co/nimblehq/template/di/**" ) val fileFilter = fileGenerated + packagesExcluded diff --git a/CoroutineTemplate/config/debug.keystore b/template/config/debug.keystore similarity index 100% rename from CoroutineTemplate/config/debug.keystore rename to template/config/debug.keystore diff --git a/CoroutineTemplate/data/.gitignore b/template/data/.gitignore similarity index 100% rename from CoroutineTemplate/data/.gitignore rename to template/data/.gitignore diff --git a/CoroutineTemplate/data/build.gradle.kts b/template/data/build.gradle.kts similarity index 100% rename from CoroutineTemplate/data/build.gradle.kts rename to template/data/build.gradle.kts diff --git a/CoroutineTemplate/data/proguard-rules.pro b/template/data/proguard-rules.pro similarity index 100% rename from CoroutineTemplate/data/proguard-rules.pro rename to template/data/proguard-rules.pro diff --git a/template/data/src/main/AndroidManifest.xml b/template/data/src/main/AndroidManifest.xml new file mode 100644 index 000000000..dbba6522f --- /dev/null +++ b/template/data/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/template/data/src/main/java/co/nimblehq/template/data/repository/RepositoryImpl.kt b/template/data/src/main/java/co/nimblehq/template/data/repository/RepositoryImpl.kt new file mode 100644 index 000000000..c435b47e6 --- /dev/null +++ b/template/data/src/main/java/co/nimblehq/template/data/repository/RepositoryImpl.kt @@ -0,0 +1,13 @@ +package co.nimblehq.template.data.repository + +import co.nimblehq.template.data.response.toModels +import co.nimblehq.template.data.service.ApiService +import co.nimblehq.template.domain.model.Model +import co.nimblehq.template.domain.repository.Repository + +class RepositoryImpl constructor( + private val apiService: ApiService +) : Repository { + + override suspend fun getModels(): List = apiService.getResponses().toModels() +} diff --git a/template/data/src/main/java/co/nimblehq/template/data/response/Response.kt b/template/data/src/main/java/co/nimblehq/template/data/response/Response.kt new file mode 100644 index 000000000..6a86859ef --- /dev/null +++ b/template/data/src/main/java/co/nimblehq/template/data/response/Response.kt @@ -0,0 +1,12 @@ +package co.nimblehq.template.data.response + +import co.nimblehq.template.domain.model.Model +import com.squareup.moshi.Json + +data class Response( + @Json(name = "id") val id: Int? +) + +private fun Response.toModel() = Model(id = this.id) + +fun List.toModels() = this.map { it.toModel() } diff --git a/template/data/src/main/java/co/nimblehq/template/data/service/ApiService.kt b/template/data/src/main/java/co/nimblehq/template/data/service/ApiService.kt new file mode 100644 index 000000000..39227eb22 --- /dev/null +++ b/template/data/src/main/java/co/nimblehq/template/data/service/ApiService.kt @@ -0,0 +1,10 @@ +package co.nimblehq.template.data.service + +import co.nimblehq.template.data.response.Response +import retrofit2.http.GET + +interface ApiService { + + @GET("users") + suspend fun getResponses(): List +} diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/ApiServiceProvider.kt b/template/data/src/main/java/co/nimblehq/template/data/service/providers/ApiServiceProvider.kt similarity index 62% rename from CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/ApiServiceProvider.kt rename to template/data/src/main/java/co/nimblehq/template/data/service/providers/ApiServiceProvider.kt index ac242491f..26b9df515 100644 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/ApiServiceProvider.kt +++ b/template/data/src/main/java/co/nimblehq/template/data/service/providers/ApiServiceProvider.kt @@ -1,6 +1,6 @@ -package co.nimblehq.coroutine.data.service.providers +package co.nimblehq.template.data.service.providers -import co.nimblehq.coroutine.data.service.ApiService +import co.nimblehq.template.data.service.ApiService import retrofit2.Retrofit object ApiServiceProvider { diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/ConverterFactoryProvider.kt b/template/data/src/main/java/co/nimblehq/template/data/service/providers/ConverterFactoryProvider.kt similarity index 83% rename from CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/ConverterFactoryProvider.kt rename to template/data/src/main/java/co/nimblehq/template/data/service/providers/ConverterFactoryProvider.kt index 1abdb4f36..f77262d6a 100644 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/ConverterFactoryProvider.kt +++ b/template/data/src/main/java/co/nimblehq/template/data/service/providers/ConverterFactoryProvider.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.data.service.providers +package co.nimblehq.template.data.service.providers import com.squareup.moshi.Moshi import retrofit2.Converter diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/MoshiBuilderProvider.kt b/template/data/src/main/java/co/nimblehq/template/data/service/providers/MoshiBuilderProvider.kt similarity index 90% rename from CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/MoshiBuilderProvider.kt rename to template/data/src/main/java/co/nimblehq/template/data/service/providers/MoshiBuilderProvider.kt index d7344087d..83f667fb8 100644 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/MoshiBuilderProvider.kt +++ b/template/data/src/main/java/co/nimblehq/template/data/service/providers/MoshiBuilderProvider.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.data.service.providers +package co.nimblehq.template.data.service.providers import com.squareup.moshi.Moshi import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/RetrofitProvider.kt b/template/data/src/main/java/co/nimblehq/template/data/service/providers/RetrofitProvider.kt similarity index 88% rename from CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/RetrofitProvider.kt rename to template/data/src/main/java/co/nimblehq/template/data/service/providers/RetrofitProvider.kt index 8a0cadf6d..cb3dc8082 100644 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/service/providers/RetrofitProvider.kt +++ b/template/data/src/main/java/co/nimblehq/template/data/service/providers/RetrofitProvider.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.data.service.providers +package co.nimblehq.template.data.service.providers import okhttp3.OkHttpClient import retrofit2.Converter diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/BaseSharedPreferences.kt b/template/data/src/main/java/co/nimblehq/template/data/storage/BaseSharedPreferences.kt similarity index 96% rename from CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/BaseSharedPreferences.kt rename to template/data/src/main/java/co/nimblehq/template/data/storage/BaseSharedPreferences.kt index aa5157582..0e36dfd02 100644 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/BaseSharedPreferences.kt +++ b/template/data/src/main/java/co/nimblehq/template/data/storage/BaseSharedPreferences.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.data.storage +package co.nimblehq.template.data.storage import android.content.SharedPreferences diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/EncryptedSharedPreferences.kt b/template/data/src/main/java/co/nimblehq/template/data/storage/EncryptedSharedPreferences.kt similarity index 94% rename from CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/EncryptedSharedPreferences.kt rename to template/data/src/main/java/co/nimblehq/template/data/storage/EncryptedSharedPreferences.kt index febcaf680..9b9233629 100644 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/EncryptedSharedPreferences.kt +++ b/template/data/src/main/java/co/nimblehq/template/data/storage/EncryptedSharedPreferences.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.data.storage +package co.nimblehq.template.data.storage import android.content.Context import androidx.security.crypto.EncryptedSharedPreferences diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/NormalSharedPreferences.kt b/template/data/src/main/java/co/nimblehq/template/data/storage/NormalSharedPreferences.kt similarity index 94% rename from CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/NormalSharedPreferences.kt rename to template/data/src/main/java/co/nimblehq/template/data/storage/NormalSharedPreferences.kt index d615e4933..0c89eafd6 100644 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/NormalSharedPreferences.kt +++ b/template/data/src/main/java/co/nimblehq/template/data/storage/NormalSharedPreferences.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.data.storage +package co.nimblehq.template.data.storage import android.content.Context import android.content.SharedPreferences diff --git a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/SharedPreferencesExt.kt b/template/data/src/main/java/co/nimblehq/template/data/storage/SharedPreferencesExt.kt similarity index 81% rename from CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/SharedPreferencesExt.kt rename to template/data/src/main/java/co/nimblehq/template/data/storage/SharedPreferencesExt.kt index 0989c1b5c..f517f1078 100644 --- a/CoroutineTemplate/data/src/main/java/co/nimblehq/coroutine/data/storage/SharedPreferencesExt.kt +++ b/template/data/src/main/java/co/nimblehq/template/data/storage/SharedPreferencesExt.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.data.storage +package co.nimblehq.template.data.storage import android.content.SharedPreferences diff --git a/template/data/src/test/java/co/nimblehq/template/data/repository/RepositoryTest.kt b/template/data/src/test/java/co/nimblehq/template/data/repository/RepositoryTest.kt new file mode 100644 index 000000000..65501cba2 --- /dev/null +++ b/template/data/src/test/java/co/nimblehq/template/data/repository/RepositoryTest.kt @@ -0,0 +1,44 @@ +package co.nimblehq.template.data.repository + +import co.nimblehq.template.data.response.Response +import co.nimblehq.template.data.response.toModels +import co.nimblehq.template.data.service.ApiService +import co.nimblehq.template.domain.repository.Repository +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Before +import org.junit.Test + +@ExperimentalCoroutinesApi +class RepositoryTest { + + private lateinit var mockService: ApiService + private lateinit var repository: Repository + + private val response = Response(id = 1) + + @Before + fun setup() { + mockService = mockk() + repository = RepositoryImpl(mockService) + } + + @Test + fun `When request successful, it returns success`() = runBlockingTest { + val expected = listOf(response) + coEvery { mockService.getResponses() } returns expected + + repository.getModels() shouldBe expected.toModels() + } + + @Test + fun `When request failed, it returns error`() = runBlockingTest { + coEvery { mockService.getResponses() } throws Throwable() + + shouldThrow { repository.getModels() } + } +} diff --git a/CoroutineTemplate/detekt-config.yml b/template/detekt-config.yml similarity index 100% rename from CoroutineTemplate/detekt-config.yml rename to template/detekt-config.yml diff --git a/CoroutineTemplate/domain/.gitignore b/template/domain/.gitignore similarity index 100% rename from CoroutineTemplate/domain/.gitignore rename to template/domain/.gitignore diff --git a/CoroutineTemplate/domain/build.gradle.kts b/template/domain/build.gradle.kts similarity index 100% rename from CoroutineTemplate/domain/build.gradle.kts rename to template/domain/build.gradle.kts diff --git a/template/domain/src/main/java/co/nimblehq/template/domain/model/Model.kt b/template/domain/src/main/java/co/nimblehq/template/domain/model/Model.kt new file mode 100644 index 000000000..e901c5c12 --- /dev/null +++ b/template/domain/src/main/java/co/nimblehq/template/domain/model/Model.kt @@ -0,0 +1,5 @@ +package co.nimblehq.template.domain.model + +data class Model( + val id: Int? +) diff --git a/template/domain/src/main/java/co/nimblehq/template/domain/repository/Repository.kt b/template/domain/src/main/java/co/nimblehq/template/domain/repository/Repository.kt new file mode 100644 index 000000000..4fd30d8b6 --- /dev/null +++ b/template/domain/src/main/java/co/nimblehq/template/domain/repository/Repository.kt @@ -0,0 +1,8 @@ +package co.nimblehq.template.domain.repository + +import co.nimblehq.template.domain.model.Model + +interface Repository { + + suspend fun getModels(): List +} diff --git a/template/domain/src/main/java/co/nimblehq/template/domain/usecase/UseCase.kt b/template/domain/src/main/java/co/nimblehq/template/domain/usecase/UseCase.kt new file mode 100644 index 000000000..36ece0c9e --- /dev/null +++ b/template/domain/src/main/java/co/nimblehq/template/domain/usecase/UseCase.kt @@ -0,0 +1,17 @@ +package co.nimblehq.template.domain.usecase + +import co.nimblehq.template.domain.model.Model +import co.nimblehq.template.domain.repository.Repository +import javax.inject.Inject + +class UseCase @Inject constructor(private val repository: Repository) { + + suspend fun execute(): UseCaseResult> { + return try { + val response = repository.getModels() + UseCaseResult.Success(response) + } catch (e: IllegalStateException) { + UseCaseResult.Error(e) + } + } +} diff --git a/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/usecase/UseCaseResult.kt b/template/domain/src/main/java/co/nimblehq/template/domain/usecase/UseCaseResult.kt similarity index 80% rename from CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/usecase/UseCaseResult.kt rename to template/domain/src/main/java/co/nimblehq/template/domain/usecase/UseCaseResult.kt index 733326f47..3b102d98f 100644 --- a/CoroutineTemplate/domain/src/main/java/co/nimblehq/coroutine/domain/usecase/UseCaseResult.kt +++ b/template/domain/src/main/java/co/nimblehq/template/domain/usecase/UseCaseResult.kt @@ -1,4 +1,4 @@ -package co.nimblehq.coroutine.domain.usecase +package co.nimblehq.template.domain.usecase sealed class UseCaseResult { class Success(val data: T) : UseCaseResult() diff --git a/template/domain/src/test/java/co/nimblehq/template/domain/usecase/UseCaseTest.kt b/template/domain/src/test/java/co/nimblehq/template/domain/usecase/UseCaseTest.kt new file mode 100644 index 000000000..dbf3d966e --- /dev/null +++ b/template/domain/src/test/java/co/nimblehq/template/domain/usecase/UseCaseTest.kt @@ -0,0 +1,46 @@ +package co.nimblehq.template.domain.usecase + +import co.nimblehq.template.domain.model.Model +import co.nimblehq.template.domain.repository.Repository +import io.kotest.matchers.shouldBe +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Before +import org.junit.Test + +@ExperimentalCoroutinesApi +class UseCaseTest { + + private lateinit var mockRepository: Repository + private lateinit var useCase: UseCase + + private val model = Model(id = 1) + + @Before + fun setup() { + mockRepository = mockk() + useCase = UseCase(mockRepository) + } + + @Test + fun `When request successful, it returns success`() = runBlockingTest { + val expected = listOf(model) + coEvery { mockRepository.getModels() } returns expected + + useCase.execute().run { + (this as UseCaseResult.Success).data shouldBe expected + } + } + + @Test + fun `When request failed, it returns error`() = runBlockingTest { + val expected = IllegalStateException() + coEvery { mockRepository.getModels() } throws expected + + useCase.execute().run { + (this as UseCaseResult.Error).exception shouldBe expected + } + } +} diff --git a/CoroutineTemplate/gradle.properties b/template/gradle.properties similarity index 100% rename from CoroutineTemplate/gradle.properties rename to template/gradle.properties diff --git a/CoroutineTemplate/gradle/wrapper/gradle-wrapper.jar b/template/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from CoroutineTemplate/gradle/wrapper/gradle-wrapper.jar rename to template/gradle/wrapper/gradle-wrapper.jar diff --git a/CoroutineTemplate/gradle/wrapper/gradle-wrapper.properties b/template/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from CoroutineTemplate/gradle/wrapper/gradle-wrapper.properties rename to template/gradle/wrapper/gradle-wrapper.properties diff --git a/CoroutineTemplate/gradlew b/template/gradlew similarity index 100% rename from CoroutineTemplate/gradlew rename to template/gradlew diff --git a/CoroutineTemplate/gradlew.bat b/template/gradlew.bat similarity index 100% rename from CoroutineTemplate/gradlew.bat rename to template/gradlew.bat diff --git a/template/settings.gradle.kts b/template/settings.gradle.kts new file mode 100644 index 000000000..d2f183fcb --- /dev/null +++ b/template/settings.gradle.kts @@ -0,0 +1 @@ +include(":app", ":data", ":domain") diff --git a/CoroutineTemplate/signing.properties b/template/signing.properties similarity index 100% rename from CoroutineTemplate/signing.properties rename to template/signing.properties