├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── deploymentTargetDropDown.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
└── vcs.xml
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── dev
│ │ └── roshana
│ │ └── jetpackcomposebasearch
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── dev
│ │ │ └── roshana
│ │ │ └── jetpackcomposebasearch
│ │ │ ├── BaseContent.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── app
│ │ │ └── JetpackComposeBaseApplication.kt
│ │ │ ├── di
│ │ │ └── AppModule.kt
│ │ │ └── navigation
│ │ │ ├── BottomNavigationBar.kt
│ │ │ ├── MainNavigation.kt
│ │ │ └── NavigationItem.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_baseline_add_location.xml
│ │ ├── ic_baseline_settings.xml
│ │ ├── ic_launcher_background.xml
│ │ └── logo.png
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── dev
│ └── roshana
│ └── jetpackcomposebasearch
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── buildSrc
├── .gitignore
├── build.gradle.kts
├── setting.gradle.kts
└── src
│ └── main
│ └── java
│ └── dev
│ └── roshana
│ └── buildsrc
│ ├── BuildPlugins.kt
│ ├── BuildTypes.kt
│ ├── ConfigData.kt
│ ├── Dependencies.kt
│ └── Versions.kt
├── data
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── dev
│ │ └── roshana
│ │ └── data
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── dev
│ │ └── roshana
│ │ └── data
│ │ ├── db
│ │ ├── AppDatabase.kt
│ │ ├── ArticleDao.kt
│ │ ├── OperatorTypeConverters.kt
│ │ └── di
│ │ │ └── DatabaseModule.kt
│ │ ├── network
│ │ ├── api
│ │ │ └── ApiService.kt
│ │ ├── di
│ │ │ └── NetworkModule.kt
│ │ ├── models
│ │ │ └── dto
│ │ │ │ ├── ArticleDto.kt
│ │ │ │ └── SourceDto.kt
│ │ └── utils
│ │ │ ├── ConnectivityInterceptor.kt
│ │ │ └── Constants.kt
│ │ ├── preference
│ │ └── Preference.kt
│ │ └── repository
│ │ ├── ArticleRepositoryImpl.kt
│ │ ├── DataStoreRepositoryImpl.kt
│ │ ├── base
│ │ └── BaseRepository.kt
│ │ ├── di
│ │ └── RepositoryModule.kt
│ │ ├── pagingSource
│ │ └── ArticlePagingSource.kt
│ │ └── utils
│ │ └── Utils.kt
│ └── test
│ └── java
│ └── dev
│ └── roshana
│ └── data
│ └── ExampleUnitTest.kt
├── domain
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── dev
│ │ └── roshana
│ │ └── domain
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── dev
│ │ │ └── roshana
│ │ │ └── domain
│ │ │ ├── di
│ │ │ └── UseCaseModule.kt
│ │ │ ├── models
│ │ │ ├── DataState.kt
│ │ │ ├── ResponseWrapper.kt
│ │ │ ├── article
│ │ │ │ └── Article.kt
│ │ │ └── dialogs
│ │ │ │ └── Item.kt
│ │ │ ├── repositories
│ │ │ ├── ArticleRepository.kt
│ │ │ └── DataStoreRepository.kt
│ │ │ ├── usecases
│ │ │ ├── ArticleUseCase.kt
│ │ │ └── DataStoreUseCase.kt
│ │ │ └── utils
│ │ │ ├── ConfigHelper.kt
│ │ │ └── Headers.kt
│ └── res
│ │ ├── drawable
│ │ ├── first.png
│ │ ├── food1.jpg
│ │ ├── food10.jpg
│ │ ├── food11.webp
│ │ ├── food12.webp
│ │ ├── food13.webp
│ │ ├── food14.jpg
│ │ ├── food15.webp
│ │ ├── food16.jpg
│ │ ├── food2.jpg
│ │ ├── food3.jpg
│ │ ├── food4.jpg
│ │ ├── food5.webp
│ │ ├── food6.jpg
│ │ ├── food7.webp
│ │ ├── food8.webp
│ │ ├── food9.jpg
│ │ ├── second.png
│ │ └── third.png
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── dev
│ └── roshana
│ └── domain
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── presentation
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── dev
│ │ └── roshana
│ │ └── presentation
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── dev
│ │ └── roshana
│ │ └── presentation
│ │ ├── articleUi
│ │ ├── ArticleListColumn.kt
│ │ ├── ArticleListState.kt
│ │ ├── ArticleListViewModel.kt
│ │ ├── ArticleUi.kt
│ │ ├── ArticlesListScreen.kt
│ │ └── articleNavigationGraph.kt
│ │ ├── locationUi
│ │ ├── LocationListScreen.kt
│ │ └── locationNavigationGraph.kt
│ │ ├── navigation
│ │ ├── NavigationDestinations.kt
│ │ └── Screens.kt
│ │ ├── uiComponent
│ │ ├── dialogs
│ │ │ ├── DialogState.kt
│ │ │ ├── DialogType.kt
│ │ │ └── Dialogs.kt
│ │ ├── progressbar
│ │ │ └── DialogCircularProgressBar.kt
│ │ └── ui
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Shape.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ └── welcomeUi
│ │ ├── OnBoardingPage.kt
│ │ ├── WelcomeScreen.kt
│ │ ├── WelcomeState.kt
│ │ └── WelcomeViewModel.kt
│ └── test
│ └── java
│ └── dev
│ └── roshana
│ └── presentation
│ └── ExampleUnitTest.kt
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dev.roshana.buildsrc.BuildTypes
2 | import dev.roshana.buildsrc.ConfigData
3 | import dev.roshana.buildsrc.Dependencies
4 |
5 | plugins {
6 | id("com.android.application")
7 | id("org.jetbrains.kotlin.android")
8 | id("kotlin-android")
9 | id("dagger.hilt.android.plugin")
10 | id("kotlin-kapt")
11 | }
12 |
13 | android {
14 | compileSdk = ConfigData.compileSdkVersion
15 |
16 | defaultConfig {
17 | applicationId = ConfigData.applicationId
18 | minSdk = ConfigData.minSdkVersion
19 | targetSdk = ConfigData.targetSdkVersion
20 | versionCode = ConfigData.versionCode
21 | versionName = ConfigData.versionName
22 |
23 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
24 | vectorDrawables {
25 | useSupportLibrary = true
26 | }
27 | }
28 |
29 | buildFeatures {
30 | dataBinding = true
31 | viewBinding = true
32 | }
33 |
34 | buildTypes {
35 | getByName(BuildTypes.RELEASE) {
36 | isMinifyEnabled = false
37 | proguardFiles(
38 | getDefaultProguardFile("proguard-android-optimize.txt"),
39 | "proguard-rules.pro"
40 | )
41 | }
42 | }
43 | compileOptions {
44 | sourceCompatibility = JavaVersion.VERSION_1_8
45 | targetCompatibility = JavaVersion.VERSION_1_8
46 | }
47 | kotlinOptions {
48 | jvmTarget = JavaVersion.VERSION_1_8.toString()
49 | }
50 | buildFeatures {
51 | compose = true
52 | }
53 | composeOptions {
54 | kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String
55 | }
56 | packagingOptions {
57 | resources {
58 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
59 | }
60 | }
61 | }
62 |
63 | dependencies {
64 |
65 | dependencies {
66 | implementation(project(":domain"))
67 | implementation(project(":presentation"))
68 |
69 | implementation(Dependencies.ComposeLibs.coreKtx)
70 |
71 | /** compose
72 | **/
73 | implementation(Dependencies.ComposeLibs.composeUi)
74 | implementation(Dependencies.ComposeLibs.composeMaterial)
75 | implementation(Dependencies.ComposeLibs.composeUiPreview)
76 | implementation(Dependencies.ComposeLibs.androidxLifeCycle)
77 | implementation(Dependencies.ComposeLibs.composeActivity)
78 | implementation(Dependencies.ComposeLibs.composeNav)
79 | implementation(Dependencies.ComposeLibs.composeHilt)
80 | implementation(Dependencies.ComposeLibs.composePaging)
81 | implementation(Dependencies.ComposeLibs.composeAnimNavigation)
82 | implementation(Dependencies.ComposeLibs.coreSplash)
83 |
84 |
85 |
86 | /** hilt - dagger
87 | **/
88 | implementation(Dependencies.CommonLibs.daggerAndroid)
89 | kapt(Dependencies.CommonLibs.daggerCompiler)
90 | kapt(Dependencies.CommonLibs.daggerAndroidCompiler)
91 | implementation(Dependencies.CommonLibs.hilt)
92 | //implementation(Dependencies.CommonLibs.hiltViewModel)
93 | kapt(Dependencies.CommonLibs.hiltCompiler)
94 | kapt(Dependencies.CommonLibs.hiltViewModelKapt)
95 |
96 | /** testing
97 | **/
98 | testImplementation(Dependencies.TestLibs.junit)
99 | androidTestImplementation(Dependencies.TestLibs.extJunit)
100 | androidTestImplementation(Dependencies.TestLibs.espresso)
101 | androidTestImplementation(Dependencies.TestLibs.composeUiJunit)
102 | debugImplementation(Dependencies.TestLibs.composeUiTooling)
103 | debugImplementation(Dependencies.TestLibs.composeUiTestManifest)
104 | }
105 |
106 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/dev/roshana/jetpackcomposebasearch/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.jetpackcomposebasearch
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("dev.roshana.jetpackcomposebasearch", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/roshana/jetpackcomposebasearch/BaseContent.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.jetpackcomposebasearch
2 |
3 | import androidx.compose.animation.ExperimentalAnimationApi
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.ExperimentalMaterialApi
6 | import androidx.compose.material.Scaffold
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.hilt.navigation.compose.hiltViewModel
10 | import androidx.navigation.compose.currentBackStackEntryAsState
11 | import com.google.accompanist.navigation.animation.rememberAnimatedNavController
12 | import dev.roshana.jetpackcomposebasearch.navigation.BottomNavigationBar
13 | import dev.roshana.jetpackcomposebasearch.navigation.MainNavigation
14 | import dev.roshana.presentation.navigation.Articles
15 | import dev.roshana.presentation.navigation.Locations
16 | import dev.roshana.presentation.welcomeUi.WelcomeViewModel
17 |
18 | /** PariSa;
19 | coding and smoking ;)
20 | **/
21 |
22 | @ExperimentalMaterialApi
23 | @ExperimentalAnimationApi
24 | @Composable
25 | fun BaseContent() {
26 | val navController = rememberAnimatedNavController()
27 | val bottomScreens = listOf(
28 | Articles.ARTICLESLIST,
29 | Locations.LOCATIONLIST
30 | )
31 | val showNavBar = navController
32 | .currentBackStackEntryAsState().value?.destination?.route in bottomScreens
33 |
34 | val welcomeViewModel: WelcomeViewModel = hiltViewModel()
35 | val startScreen = welcomeViewModel.startDestination
36 |
37 |
38 | Scaffold(
39 | bottomBar = {
40 | if (showNavBar) {
41 | BottomNavigationBar(navController)
42 | }
43 | }
44 | ) { padding ->
45 |
46 | MainNavigation(
47 | navHostController = navController,
48 | modifier = Modifier.padding(padding),
49 | startDestination = startScreen.value
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/roshana/jetpackcomposebasearch/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.jetpackcomposebasearch
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.animation.ExperimentalAnimationApi
7 | import androidx.compose.material.ExperimentalMaterialApi
8 | import androidx.compose.material.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.tooling.preview.Preview
11 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
12 | import androidx.hilt.navigation.compose.hiltViewModel
13 | import androidx.paging.compose.collectAsLazyPagingItems
14 | import dagger.hilt.android.AndroidEntryPoint
15 | import dev.roshana.presentation.articleUi.ArticleListViewModel
16 | import dev.roshana.presentation.uiComponent.ui.theme.JetpackComposeBaseArchTheme
17 | import dev.roshana.presentation.welcomeUi.WelcomeViewModel
18 | import javax.inject.Inject
19 |
20 | /** PariSa;
21 | coding and smoking ;)
22 | **/
23 |
24 | @AndroidEntryPoint
25 | class MainActivity : ComponentActivity() {
26 |
27 | @OptIn(
28 | ExperimentalMaterialApi::class,
29 | ExperimentalAnimationApi::class
30 | )
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 |
34 | setContent {
35 | JetpackComposeBaseArchTheme {
36 | BaseContent()
37 | }
38 | }
39 | }
40 | }
41 |
42 | @Composable
43 | fun Greeting(name: String) {
44 | val viewModel: ArticleListViewModel = hiltViewModel()
45 | val state = viewModel.articleListState.value
46 | val list = state.dataList?.collectAsLazyPagingItems()
47 | val size = list?.itemSnapshotList?.items
48 | Text(text = "Hello ${size}!")
49 | }
50 |
51 | @Preview(showBackground = true)
52 | @Composable
53 | fun DefaultPreview() {
54 | JetpackComposeBaseArchTheme {
55 | Greeting("Android")
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/roshana/jetpackcomposebasearch/app/JetpackComposeBaseApplication.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.jetpackcomposebasearch.app
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 | import dev.roshana.domain.utils.initPreferenceUtils
6 |
7 | /** PariSa;
8 | coding and smoking ;)
9 | **/
10 |
11 | @HiltAndroidApp
12 | class JetpackComposeBaseApplication : Application(){
13 | override fun onCreate() {
14 | super.onCreate()
15 | initPreferenceUtils(applicationContext)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/roshana/jetpackcomposebasearch/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.jetpackcomposebasearch.di
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.android.qualifiers.ApplicationContext
8 | import dagger.hilt.components.SingletonComponent
9 | import javax.inject.Singleton
10 |
11 | /** PariSa;
12 | coding and smoking ;)
13 | **/
14 |
15 | @Module
16 | @InstallIn(SingletonComponent::class)
17 | class AppModule() {
18 |
19 | @Provides
20 | @Singleton
21 | fun providesContext(@ApplicationContext context: Context) = context.applicationContext
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/roshana/jetpackcomposebasearch/navigation/BottomNavigationBar.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.jetpackcomposebasearch.navigation
2 |
3 | import androidx.compose.animation.ExperimentalAnimationApi
4 | import androidx.compose.animation.core.Spring
5 | import androidx.compose.animation.core.animateDpAsState
6 | import androidx.compose.animation.core.animateFloatAsState
7 | import androidx.compose.animation.core.spring
8 | import androidx.compose.foundation.background
9 | import androidx.compose.foundation.clickable
10 | import androidx.compose.foundation.layout.*
11 | import androidx.compose.foundation.shape.RoundedCornerShape
12 | import androidx.compose.material.Card
13 | import androidx.compose.material.Icon
14 | import androidx.compose.material.MaterialTheme
15 | import androidx.compose.material.Text
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.getValue
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.draw.alpha
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.res.painterResource
23 | import androidx.compose.ui.unit.dp
24 | import androidx.navigation.NavGraph.Companion.findStartDestination
25 | import androidx.navigation.NavHostController
26 | import androidx.navigation.compose.currentBackStackEntryAsState
27 |
28 | /** PariSa;
29 | coding and smoking ;)
30 | **/
31 |
32 | @OptIn(ExperimentalAnimationApi::class)
33 | @Composable
34 | fun BottomNavigationBar(navHostController: NavHostController) {
35 |
36 | val bottomScreens = listOf(
37 | NavigationItem.ArticlesScreen,
38 | NavigationItem.LocationsScreen,
39 | )
40 |
41 | Card(
42 | modifier = Modifier
43 | .fillMaxWidth()
44 | .height(80.dp),
45 | backgroundColor = Color.LightGray
46 |
47 | ) {
48 | Row(
49 | modifier = Modifier.fillMaxSize(),
50 | horizontalArrangement = Arrangement.SpaceAround,
51 | verticalAlignment = Alignment.CenterVertically
52 | ) {
53 |
54 | bottomScreens.map {
55 | val isSelected = navHostController
56 | .currentBackStackEntryAsState().value?.destination?.route == it.screen_route
57 | val animatedWeight by animateFloatAsState(targetValue = if (isSelected) 1.5f else 1f)
58 |
59 | CustomBottomNavItem(
60 | screen = it,
61 | isSelected = isSelected,
62 | modifier = Modifier
63 | .weight(animatedWeight)
64 | .clickable {
65 | navHostController.navigate(
66 | it.screen_route
67 | ) {
68 | restoreState = true
69 | launchSingleTop = true
70 |
71 | val destination = navHostController.graph.findStartDestination().id
72 | popUpTo(destination) { saveState = true }
73 | }
74 | }
75 | )
76 | }
77 |
78 | }
79 | }
80 | }
81 |
82 | @ExperimentalAnimationApi
83 | @Composable
84 | private fun CustomBottomNavItem(
85 | modifier: Modifier = Modifier,
86 | screen: NavigationItem,
87 | isSelected: Boolean,
88 | ) {
89 | val animatedIconSize by animateDpAsState(
90 | targetValue = if (isSelected) 30.dp else 25.dp,
91 | animationSpec = spring(
92 | stiffness = Spring.StiffnessLow,
93 | dampingRatio = Spring.DampingRatioMediumBouncy
94 | )
95 | )
96 |
97 | Box(
98 | modifier = modifier,
99 | contentAlignment = Alignment.Center,
100 | ) {
101 | Row(
102 | modifier = Modifier
103 | .height(if (isSelected) 50.dp else 50.dp)
104 | .background(
105 | color = MaterialTheme.colors.onBackground,
106 | shape = RoundedCornerShape(5.dp)
107 | )
108 | .padding(horizontal = 5.dp, vertical = 10.dp),
109 | verticalAlignment = Alignment.CenterVertically,
110 | horizontalArrangement = Arrangement.Center,
111 | ) {
112 | Icon(
113 | painter = painterResource(id = screen.icon), contentDescription = "bottom Bar Icon",
114 |
115 | modifier = Modifier
116 | .align(Alignment.CenterVertically)
117 | .fillMaxHeight()
118 | .padding(horizontal = 5.dp)
119 | .alpha(if (isSelected) 1f else .7f)
120 | .size(animatedIconSize),
121 | tint = if (isSelected) MaterialTheme.colors.primary else Color.Gray
122 | )
123 |
124 | if (isSelected) {
125 | Text(
126 | text = screen.title,
127 | modifier = Modifier.padding(start = 8.dp, end = 10.dp),
128 | maxLines = 1,
129 | color = MaterialTheme.colors.primary
130 | )
131 | }
132 | }
133 | }
134 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/roshana/jetpackcomposebasearch/navigation/MainNavigation.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.jetpackcomposebasearch.navigation
2 |
3 | import androidx.compose.animation.ExperimentalAnimationApi
4 | import androidx.compose.material.ExperimentalMaterialApi
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.navigation.NavHostController
8 | import androidx.navigation.compose.NavHost
9 | import androidx.navigation.compose.composable
10 | import dev.roshana.presentation.articleUi.ArticlesListScreen
11 | import dev.roshana.presentation.locationUi.LocationScreen
12 | import dev.roshana.presentation.navigation.Screens
13 | import dev.roshana.presentation.welcomeUi.WelcomeScreen
14 | import kotlinx.coroutines.ExperimentalCoroutinesApi
15 |
16 | /** PariSa;
17 | coding and smoking ;)
18 | **/
19 |
20 | @OptIn(ExperimentalCoroutinesApi::class)
21 | @ExperimentalMaterialApi
22 | @ExperimentalAnimationApi
23 | @Composable
24 | fun MainNavigation(
25 | navHostController: NavHostController,
26 | modifier: Modifier = Modifier,
27 | startDestination: String
28 | ) {
29 |
30 | NavHost(
31 | navController = navHostController,
32 | startDestination = startDestination, modifier = modifier
33 | ) {
34 | /*articleGraph(navHostController = navHostController)
35 | locationGraph(navHostController = navHostController)*/
36 |
37 | composable(Screens.WelcomeScreen.route) {
38 | WelcomeScreen(navHostController = navHostController)
39 | }
40 |
41 | composable(Screens.ArticleListScreen.route) {
42 | ArticlesListScreen()
43 | }
44 |
45 | composable(Screens.LocationScreen.route) {
46 | LocationScreen()
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/roshana/jetpackcomposebasearch/navigation/NavigationItem.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.jetpackcomposebasearch.navigation
2 |
3 | import androidx.annotation.DrawableRes
4 | import dev.roshana.jetpackcomposebasearch.R
5 | import dev.roshana.presentation.navigation.Articles
6 | import dev.roshana.presentation.navigation.Locations
7 |
8 | /** PariSa;
9 | coding and smoking ;)
10 | **/
11 |
12 | sealed class NavigationItem(
13 | var title: String,
14 | var screen_route: String,
15 | @DrawableRes var icon: Int
16 | ) {
17 | object ArticlesScreen : NavigationItem(
18 | title = "Article",
19 | screen_route = Articles.ARTICLESLIST,
20 | icon = R.drawable.ic_baseline_settings
21 | )
22 |
23 | object LocationsScreen : NavigationItem(
24 | title = "Locations",
25 | screen_route = Locations.LOCATIONLIST,
26 | R.drawable.ic_baseline_add_location
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_add_location.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_settings.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/drawable/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | JetpackComposeBaseArch
3 | اینترنت خود را بررسی نمایید و دوباره امتحان کنید
4 | متاسفانه ارتباط با سرور دچار اخلال شده است لطفا مجددا تلاش کنید
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/roshana/jetpackcomposebasearch/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.jetpackcomposebasearch
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dev.roshana.buildsrc.*
2 |
3 | plugins {
4 | id("com.android.application") version "7.2.2" apply false
5 | id("com.android.library") version "7.2.2" apply false
6 | id("org.jetbrains.kotlin.android") version "1.7.10" apply false
7 | id("org.jetbrains.kotlin.jvm") version "1.7.10" apply false
8 |
9 | }
10 |
11 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
12 | buildscript {
13 | val compose_version by extra("1.3.0")
14 | val kotlin_version by extra("1.7.10")
15 |
16 | dependencies {
17 | classpath("com.google.dagger:hilt-android-gradle-plugin:2.42")
18 | // NOTE: Do not place your application dependencies here; they belong
19 | // in the individual module build.gradle files
20 | }
21 | }
22 |
23 |
24 |
25 | /*buildscript {
26 | val compose_version by extra("1.1.0-beta01")
27 | val kotlin_version by extra("1.5.31")
28 |
29 | repositories {
30 | mavenCentral()
31 | google()
32 | }
33 | dependencies {
34 | //classpath(BuildPlugins.hiltPlugin)
35 | // NOTE: Do not place your application dependencies here; they belong
36 | // in the individual module build.gradle files
37 | }
38 | }
39 |
40 | plugins {
41 | id("com.android.application") version "7.2.2" apply false
42 | id("com.android.library") version "7.2.2" apply false
43 | id("org.jetbrains.kotlin.android") version "1.5.31" apply false
44 | id("org.jetbrains.kotlin.jvm") version "1.5.31" apply false
45 | }*/
46 |
47 | tasks.register("clean", Delete::class) {
48 | delete(rootProject.buildDir)
49 | }
--------------------------------------------------------------------------------
/buildSrc/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 | repositories {
5 | google()
6 | mavenCentral()
7 | gradlePluginPortal()
8 | }
--------------------------------------------------------------------------------
/buildSrc/setting.gradle.kts:
--------------------------------------------------------------------------------
1 | // settings.gradle.kts
2 | import de.fayard.refreshVersions.bootstrapRefreshVersionsForBuildSrc
3 |
4 | buildscript {
5 | repositories { gradlePluginPortal() }
6 | dependencies.classpath("de.fayard.refreshVersions:refreshVersions:0.9.5")
7 | }
8 |
9 | bootstrapRefreshVersionsForBuildSrc()
--------------------------------------------------------------------------------
/buildSrc/src/main/java/dev/roshana/buildsrc/BuildPlugins.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.buildsrc
2 |
3 | object BuildPlugins {
4 | const val androidGradle = "com.android.tools.build:gradle:${Versions.gradle}"
5 | const val kotlinGradlePlugin =
6 | "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
7 | const val hiltPlugin = "com.google.dagger:hilt-android-gradle-plugin:${Versions.hilt}"
8 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/dev/roshana/buildsrc/BuildTypes.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.buildsrc
2 |
3 |
4 | object BuildTypes {
5 | const val RELEASE = "release"
6 | const val DEBUG = "debug"
7 | }
8 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/dev/roshana/buildsrc/ConfigData.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.buildsrc
2 |
3 | object ConfigData {
4 | const val minSdkVersion = 21
5 | const val targetSdkVersion = 33
6 | const val compileSdkVersion = 33
7 | const val applicationId = "dev.roshana.jetpackcomposebasearch"
8 | const val versionCode = 1
9 | const val versionName = "0.1"
10 | const val versionMajor = 1
11 | const val versionMinor = 1
12 | const val versionPatch = 1
13 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/dev/roshana/buildsrc/Dependencies.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.buildsrc
2 |
3 | object Dependencies {
4 |
5 | object CommonLibs {
6 | const val material = "com.google.android.material:material:${Versions.material}"
7 | const val coroutines =
8 | "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}"
9 | const val coroutinesAndroid =
10 | "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}"
11 | const val coroutineAdapter =
12 | "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:${Versions.coroutinesAdapter}"
13 | const val arrowCore = "io.arrow-kt:arrow-core:${Versions.arrow}"
14 | const val arrowSyntax = "io.arrow-kt:arrow-syntax:${Versions.arrow}"
15 | const val arrowMeta = "io.arrow-kt:arrow-meta:${Versions.arrow}"
16 | const val dagger = "com.google.dagger:dagger:${Versions.dagger}"
17 | const val daggerCompiler = "com.google.dagger:dagger-compiler:${Versions.dagger}"
18 | const val daggerAndroid = "com.google.dagger:dagger-android-support:${Versions.dagger}"
19 | const val daggerAndroidCompiler =
20 | "com.google.dagger:dagger-android-processor:${Versions.dagger}"
21 | const val hilt = "com.google.dagger:hilt-android:${Versions.hilt}"
22 | const val hiltCompiler = "com.google.dagger:hilt-android-compiler:${Versions.hilt}"
23 | const val hiltViewModel = "androidx.hilt:hilt-lifecycle-viewmodel:${Versions.hiltViewModel}"
24 | const val hiltViewModelKapt = "androidx.hilt:hilt-compiler:${Versions.hiltViewModel}"
25 |
26 | const val gson = "com.google.code.gson:gson:${Versions.gson}"
27 | const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
28 | const val retrofitGson = "com.squareup.retrofit2:converter-gson:${Versions.retrofit}"
29 | const val okHttpInterceptor = "com.squareup.okhttp3:logging-interceptor:${Versions.okHttp}"
30 | const val kotlinSerialConverter =
31 | "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:${Versions.kotlinConverter}"
32 | const val jsonSerialization =
33 | "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.jsonSerialization}"
34 |
35 | const val stetho = "com.facebook.stetho:stetho:${Versions.stetho}"
36 | const val stetho_OkHttp = "com.facebook.stetho:stetho-okhttp3:${Versions.stetho}"
37 | const val glide = "com.github.bumptech.glide:glide:${Versions.glide}"
38 | const val glideCompiler = "com.github.bumptech.glide:compiler:${Versions.glide}"
39 |
40 | const val picasso = "com.squareup.picasso:picasso:${Versions.picasso}"
41 | const val coil = "io.coil-kt:coil-compose:${Versions.coil}"
42 | const val coilImage = "com.github.skydoves:landscapist-coil:${Versions.coilImage}"
43 | const val accompanistPager =
44 | "com.google.accompanist:accompanist-pager:${Versions.accompanist}"
45 | const val accompanistIndicator =
46 | "com.google.accompanist:accompanist-pager-indicators:${Versions.accompanist}"
47 | const val dataStore = "androidx.datastore:datastore-preferences:${Versions.dataStore}"
48 |
49 | const val room = "androidx.room:room-runtime:${Versions.room}"
50 | const val roomCompiler = "androidx.room:room-compiler:${Versions.room}"
51 | const val roomPagingCompose = "androidx.room:room-paging:${Versions.room}"
52 | const val roomKtx = "androidx.room:room-ktx:${Versions.room}"
53 | const val roomGuava = "androidx.room:room-guava:${Versions.room}"
54 | const val roomTest = "androidx.room:room-testing:${Versions.room}"
55 |
56 | }
57 |
58 | object ComposeLibs {
59 | const val coreKtx = "androidx.core:core-ktx:${Versions.coreKtx}"
60 | const val composeUi = "androidx.compose.ui:ui:${Versions.composeVersion}"
61 | const val composeMaterial = "androidx.compose.material:material:${Versions.composeVersion}"
62 | const val composeUiPreview =
63 | "androidx.compose.ui:ui-tooling-preview:${Versions.composeVersion}"
64 | const val composeActivity = "androidx.activity:activity-compose:${Versions.composeActivity}"
65 | const val composeAnimation =
66 | "androidx.compose.animation:animation:${Versions.composeVersion}"
67 | const val composeViewModel =
68 | "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.composeViewModel}"
69 | const val androidxLifeCycle =
70 | "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifeCycle}"
71 | const val composeNav = "androidx.navigation:navigation-compose:${Versions.composeNav}"
72 | const val composePaging = "androidx.paging:paging-compose:${Versions.composePaging}"
73 | const val composeHilt = "androidx.hilt:hilt-navigation-compose:${Versions.composeHilt}"
74 | const val composeAnimNavigation =
75 | "com.google.accompanist:accompanist-navigation-animation:${Versions.composeAnimNav}"
76 | const val coreSplash = "androidx.core:core-splashscreen:${Versions.coreSplash}"
77 | }
78 |
79 | object TestLibs {
80 | const val junit = "junit:junit:${Versions.junit}"
81 | const val extJunit = "androidx.test.ext:junit:${Versions.extJunit}"
82 | const val testRunner = "androidx.test:runner:${Versions.testRunner}"
83 | const val mockitoKotlin = "com.nhaarman:mockito-kotlin-kt1.1:${Versions.mockitoKotlin}"
84 | const val archTesting = "androidx.arch.core:core-testing:${Versions.archTest}"
85 | const val extJUnit = "androidx.test.ext:junit:1.1.2"
86 | const val espresso = "androidx.test.espresso:espresso-core:${Versions.espresso}"
87 | const val composeUiJunit = "androidx.compose.ui:ui-test-junit4:${Versions.composeVersion}"
88 | const val composeUiTooling = "androidx.compose.ui:ui-tooling:${Versions.composeVersion}"
89 | const val composeUiTestManifest =
90 | "androidx.compose.ui:ui-test-manifest:${Versions.composeVersion}"
91 | }
92 |
93 |
94 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/dev/roshana/buildsrc/Versions.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.buildsrc
2 |
3 | object Versions {
4 | const val composeVersion = "1.1.0-beta01"
5 | const val kotlin = "1.5.31"
6 | const val gradle = "7.2.2"
7 | const val activityCompose = "1.4.0"
8 | const val composeViewModel = "2.4.1"
9 | const val composePaging = "1.0.0-alpha16"
10 | const val composeHilt = "1.0.0"
11 | const val composeNav = "2.5.1"
12 | const val composeAnimNav = "0.20.2"
13 | const val arrow = "0.10.4"
14 | const val kt_lint = "0.37.2"
15 | const val coroutines = "1.3.3"
16 | const val coroutinesAdapter = "1.0.0"
17 | const val gradleVersions = "0.27.0"
18 | const val hilt = "2.42"
19 | const val hiltViewModel = "1.0.0-alpha03"
20 | const val arch = "2.2.0"
21 | const val archTest = "2.1.0"
22 | const val material = "1.6.1"
23 | const val constraintLayout = "1.1.3"
24 | const val room = "2.4.2"
25 | const val dagger = "2.25.4"
26 | const val okHttp = "4.10.0"
27 | const val gson = "2.8.6"
28 | const val retrofit = "2.7.1"
29 | const val stetho = "1.5.1"
30 | const val glide = "4.11.0"
31 | const val junit = "4.13.2"
32 | const val extJunit = "1.1.3"
33 | const val testRunner = "1.2.0"
34 | const val espresso = "3.4.0"
35 | const val mockitoKotlin = "1.6.0"
36 | const val navigationComponent = "2.3.0"
37 | const val fragment = "1.2.5"
38 | const val coreKtx = "1.7.0"
39 | const val lifeCycle = "2.5.1"
40 | const val composeActivity = "1.4.0"
41 | const val kotlinConverter = "0.8.0"
42 | const val jsonSerialization = "1.3.3"
43 | const val picasso = "2.8"
44 | const val coil = "2.2.1"
45 | const val coilImage = "1.6.1"
46 | const val accompanist = "0.24.2-alpha"
47 | const val dataStore = "1.0.0"
48 | const val coreSplash = "1.0.0"
49 | }
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dev.roshana.buildsrc.BuildTypes
2 | import dev.roshana.buildsrc.ConfigData
3 | import dev.roshana.buildsrc.Dependencies
4 |
5 | plugins {
6 | id("com.android.library")
7 | id("org.jetbrains.kotlin.android")
8 | id("kotlin-kapt")
9 | id("kotlin-parcelize")
10 |
11 | }
12 |
13 | android {
14 | compileSdk = ConfigData.compileSdkVersion
15 |
16 | defaultConfig {
17 | minSdk = ConfigData.minSdkVersion
18 | targetSdk = ConfigData.targetSdkVersion
19 |
20 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
21 | consumerProguardFiles("consumer-rules.pro")
22 | }
23 |
24 | buildTypes {
25 | getByName(BuildTypes.RELEASE) {
26 | isMinifyEnabled = false
27 | proguardFiles(
28 | getDefaultProguardFile("proguard-android-optimize.txt"),
29 | "proguard-rules.pro"
30 | )
31 | }
32 | }
33 | compileOptions {
34 | sourceCompatibility = JavaVersion.VERSION_1_8
35 | targetCompatibility = JavaVersion.VERSION_1_8
36 | }
37 | kotlinOptions {
38 | jvmTarget = JavaVersion.VERSION_1_8.toString()
39 | }
40 | }
41 |
42 | dependencies {
43 | implementation(project(":domain"))
44 |
45 | /** compose
46 | **/
47 | implementation(Dependencies.ComposeLibs.coreKtx)
48 | implementation(Dependencies.ComposeLibs.composePaging)
49 | implementation(Dependencies.CommonLibs.dataStore)
50 |
51 | /** hilt - dagger
52 | **/
53 | implementation(Dependencies.CommonLibs.daggerAndroid)
54 | kapt(Dependencies.CommonLibs.daggerCompiler)
55 | kapt(Dependencies.CommonLibs.daggerAndroidCompiler)
56 | implementation(Dependencies.CommonLibs.hilt)
57 | kapt(Dependencies.CommonLibs.hiltCompiler)
58 |
59 | /** retrofit, okHttp
60 | **/
61 | implementation(Dependencies.CommonLibs.retrofit)
62 | implementation(Dependencies.CommonLibs.retrofitGson)
63 | implementation(Dependencies.CommonLibs.okHttpInterceptor)
64 | implementation(Dependencies.CommonLibs.gson)
65 | implementation(Dependencies.CommonLibs.kotlinSerialConverter)
66 | implementation(Dependencies.CommonLibs.jsonSerialization)
67 |
68 | /** Coroutine
69 | **/
70 | implementation(Dependencies.CommonLibs.coroutines)
71 | implementation(Dependencies.CommonLibs.coroutinesAndroid)
72 | implementation(Dependencies.CommonLibs.coroutineAdapter)
73 |
74 | /** Room
75 | **/
76 | implementation(Dependencies.CommonLibs.room)
77 | implementation(Dependencies.CommonLibs.roomPagingCompose)
78 | implementation(Dependencies.CommonLibs.roomKtx)
79 | kapt(Dependencies.CommonLibs.roomCompiler)
80 |
81 |
82 | }
--------------------------------------------------------------------------------
/data/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/data/consumer-rules.pro
--------------------------------------------------------------------------------
/data/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/src/androidTest/java/dev/roshana/data/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("dev.roshana.data.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/db/AppDatabase.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.db
2 |
3 | import android.content.Context
4 | import androidx.annotation.VisibleForTesting
5 | import androidx.room.Database
6 | import androidx.room.Room
7 | import androidx.room.RoomDatabase
8 | import androidx.room.TypeConverters
9 | import dev.roshana.data.network.models.dto.ArticleDto
10 |
11 | /** PariSa;
12 | coding and smoking ;)
13 | **/
14 |
15 | /**
16 | * Main database description.
17 | */
18 | @Database(
19 | entities = [
20 | ArticleDto::class
21 | ],
22 | version = 1, exportSchema = false
23 | )
24 | @TypeConverters(OperatorTypeConverters::class)
25 | abstract class AppDatabase : RoomDatabase() {
26 |
27 | companion object {
28 |
29 | @VisibleForTesting
30 | private const val DATABASE_NAME = "cache.db"
31 |
32 | @Volatile
33 | private var INSTANCE: AppDatabase? = null
34 |
35 | @JvmStatic
36 | fun getInstance(context: Context, testMode: Boolean = false): AppDatabase {
37 | return INSTANCE ?: synchronized(this) {
38 | INSTANCE ?: buildDatabase(context, testMode).also {
39 | INSTANCE = it
40 | }
41 | }
42 | }
43 |
44 | @JvmStatic
45 | private fun buildDatabase(context: Context, testMode: Boolean): AppDatabase {
46 | return if (testMode) {
47 | Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
48 | .fallbackToDestructiveMigration()
49 | .allowMainThreadQueries()
50 | .build()
51 | } else {
52 | Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
53 | .allowMainThreadQueries()
54 | .fallbackToDestructiveMigration()
55 | .build()
56 | }
57 | }
58 | }
59 |
60 | abstract fun articleDao(): ArticleDao
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/db/ArticleDao.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.db
2 |
3 | import androidx.room.*
4 | import dev.roshana.data.network.models.dto.ArticleDto
5 |
6 | /** PariSa;
7 | coding and smoking ;)
8 | **/
9 |
10 | @Dao
11 | interface ArticleDao {
12 |
13 | @Query("delete from article")
14 | fun removeArticles()
15 |
16 | @Insert(onConflict = OnConflictStrategy.IGNORE)
17 | fun insertArticles(config: ArticleDto): Long
18 |
19 | /* @Transaction
20 | fun insert(config: ArticleDto): Long {
21 | removeArticles()
22 | return insertArticles(config)
23 | }*/
24 |
25 | @Query("select * from article")
26 | fun fetchAll(): List
27 |
28 | }
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/db/OperatorTypeConverters.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.db
2 |
3 | import android.util.Log
4 | import androidx.room.TypeConverter
5 | import com.google.gson.Gson
6 | import com.google.gson.reflect.TypeToken
7 | import dev.roshana.data.network.models.dto.ArticleDto
8 | import dev.roshana.data.network.models.dto.SourceDto
9 |
10 | /** PariSa;
11 | coding and smoking ;)
12 | **/
13 |
14 | object OperatorTypeConverters {
15 |
16 | @TypeConverter
17 | @JvmStatic
18 | fun stringToIntList(data: String?): List? {
19 | return data?.let {
20 | it.split(",").map {
21 | try {
22 | it.toInt()
23 | } catch (ex: NumberFormatException) {
24 | Log.e("OperatorTypeConverters", "Cannot convert ${ex.message} to number")
25 | null
26 | }
27 | }
28 | }?.filterNotNull()
29 | }
30 |
31 | @TypeConverter
32 | @JvmStatic
33 | fun listIntToString(ints: List?): String? {
34 | return ints?.joinToString(",")
35 | }
36 |
37 | @TypeConverter
38 | @JvmStatic
39 | fun stringToMap(value: String): Map =
40 | fromJson(value)
41 |
42 | @TypeConverter
43 | @JvmStatic
44 | fun mapToString(value: Map?): String =
45 | toJson(value)
46 |
47 | @TypeConverter
48 | @JvmStatic
49 | fun stringToSource(value: String): SourceDto =
50 | fromJson(value)
51 |
52 | @TypeConverter
53 | @JvmStatic
54 | fun sourceToString(value: SourceDto): String = toJson(value)
55 |
56 | @TypeConverter
57 | @JvmStatic
58 | fun stringToArticle(value: String): ArticleDto =
59 | fromJson(value)
60 |
61 | @TypeConverter
62 | @JvmStatic
63 | fun articleToString(value: ArticleDto): String = toJson(value)
64 |
65 |
66 | @TypeConverter
67 | @JvmStatic
68 | fun stringToStringList(value: String?): List {
69 | if (value.isNullOrEmpty()) {
70 | return emptyList()
71 | }
72 | return fromJson(value)
73 | }
74 |
75 | @TypeConverter
76 | @JvmStatic
77 | fun stringListToString(items: List?): String = toJson(items)
78 |
79 | inline fun toJson(value: T): String {
80 | return if (value == null) "" else Gson().toJson(value)
81 | }
82 |
83 | inline fun fromJson(value: String): T {
84 | return Gson().fromJson(value, object : TypeToken() {}.type)
85 | }
86 |
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/db/di/DatabaseModule.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.db.di
2 |
3 | import android.app.Application
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import dev.roshana.data.db.AppDatabase
9 | import dev.roshana.data.preference.Preference
10 | import dev.roshana.data.preference.PreferenceImpl
11 | import javax.inject.Singleton
12 |
13 | /** PariSa;
14 | coding and smoking ;)
15 | **/
16 |
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | class DatabaseModule {
20 |
21 | @Singleton
22 | @Provides
23 | fun provideDb(app: Application): AppDatabase {
24 | return AppDatabase.getInstance(app)
25 | }
26 |
27 | @Provides
28 | fun providePreference(app: Application): Preference {
29 | return PreferenceImpl(app)
30 | }
31 | }
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/network/api/ApiService.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.network.api
2 |
3 | import dev.roshana.data.network.models.dto.ArticleDto
4 | import dev.roshana.data.network.utils.TOP_HEAD_LINES
5 | import dev.roshana.domain.models.ResponseWrapper
6 | import retrofit2.http.GET
7 | import retrofit2.http.Query
8 |
9 | /** PariSa;
10 | coding and smoking ;)
11 | **/
12 |
13 | interface ApiService {
14 |
15 | @GET(TOP_HEAD_LINES)
16 | suspend fun getTechCrunchNews(
17 | @Query("page") page: Int,
18 | @Query("sources") sources: String,
19 | @Query("apiKey") apiKey: String
20 | ): ResponseWrapper>
21 |
22 |
23 | }
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/network/di/NetworkModule.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.network.di
2 |
3 | import android.app.Application
4 | import com.google.gson.GsonBuilder
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import dev.roshana.data.BuildConfig
10 | import dev.roshana.data.network.api.ApiService
11 | import dev.roshana.data.network.utils.BASE_URL
12 | import dev.roshana.domain.utils.getToken
13 | import dev.roshana.domain.utils.globalHeaders
14 | import dev.roshana.domain.R.string
15 | import okhttp3.Interceptor
16 | import okhttp3.OkHttpClient
17 | import okhttp3.Request
18 | import okhttp3.Response
19 | import okhttp3.logging.HttpLoggingInterceptor
20 | import retrofit2.Retrofit
21 | import retrofit2.converter.gson.GsonConverterFactory
22 | import java.io.IOException
23 | import java.net.UnknownHostException
24 | import java.util.concurrent.TimeUnit
25 | import javax.inject.Singleton
26 |
27 | /** PariSa;
28 | coding and smoking ;)
29 | **/
30 |
31 | @Module
32 | @InstallIn(SingletonComponent::class)
33 | object NetworkModule {
34 |
35 |
36 | /* @Singleton
37 | @Provides
38 | fun provideOkHttp(): OkHttpClient {
39 |
40 | val trustAllCerts = arrayOf(
41 | object : X509TrustManager {
42 | @Throws(CertificateException::class)
43 | override fun checkClientTrusted(
44 | chain: Array?,
45 | authType: String?
46 | ) {
47 | }
48 |
49 | @Throws(CertificateException::class)
50 | override fun checkServerTrusted(
51 | chain: Array?,
52 | authType: String?
53 | ) {
54 | }
55 |
56 | override fun getAcceptedIssuers(): Array? {
57 | return arrayOf()
58 | }
59 | }
60 | )
61 | val sslContext = SSLContext.getInstance("SSL")
62 | sslContext.init(null, trustAllCerts, SecureRandom())
63 | val sslSocketFactory = sslContext.socketFactory
64 |
65 | val builder = OkHttpClient.Builder()
66 | builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
67 | builder.hostnameVerifier { _, _ -> true }
68 | //builder.addInterceptor(connectivityInterceptor)
69 | builder.addInterceptor(HttpLoggingInterceptor().apply {
70 | this.setLevel(HttpLoggingInterceptor.Level.BODY)
71 | })
72 | builder.connectTimeout(1, TimeUnit.MINUTES)
73 | builder.readTimeout(1, TimeUnit.MINUTES)
74 | builder.writeTimeout(1, TimeUnit.MINUTES)
75 |
76 | return builder.build()
77 |
78 | }*/
79 |
80 |
81 | /*@ExperimentalSerializationApi
82 | @Singleton
83 | @Provides
84 | fun providesAPI(client: OkHttpClient): ApiService {
85 |
86 | return Retrofit.Builder()
87 | .addConverterFactory(GsonConverterFactory.create())
88 | .baseUrl(BASE_URL)
89 | .client(client)
90 | .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
91 | .build()
92 | .create(ApiService::class.java)
93 | }*/
94 | @Provides
95 | @Singleton
96 | internal fun provideOkHttpClient(application: Application) = OkHttpClient.Builder()
97 | .connectTimeout(30, TimeUnit.SECONDS)
98 | .writeTimeout(30, TimeUnit.SECONDS)
99 | .readTimeout(60, TimeUnit.SECONDS)
100 | .addRoshanaInterceptor(application)
101 | .addLoggerInterceptor()
102 | .build()
103 |
104 | @Provides
105 | @Singleton
106 | internal fun provideRetrofit(application: Application, okHttpClient: OkHttpClient) =
107 | Retrofit.Builder()
108 | .baseUrl(BASE_URL)
109 | .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
110 | .client(okHttpClient)
111 | .build()
112 |
113 | @Provides
114 | @Singleton
115 | internal fun provideApiService(retrofit: Retrofit) =
116 | retrofit.create(ApiService::class.java)
117 |
118 |
119 | private fun OkHttpClient.Builder.addLoggerInterceptor() = apply {
120 | if (BuildConfig.DEBUG) {
121 | addNetworkInterceptor(HttpLoggingInterceptor().apply {
122 | level = HttpLoggingInterceptor.Level.BODY
123 | })
124 | }
125 | }
126 |
127 | private fun OkHttpClient.Builder.addRoshanaInterceptor(application: Application) = apply {
128 | addInterceptor { chain ->
129 | try {
130 | handleChain(chain)
131 | } catch (ioException: UnknownHostException) {
132 | throw IOException(application.getString(string.connectionError), ioException)
133 | } catch (ioException: IOException) {
134 | throw IOException(application.getString(string.socketError), ioException)
135 | }
136 | }
137 | }
138 |
139 | private fun handleChain(chain: Interceptor.Chain): Response {
140 | return chain.request()
141 | .newBuilder()
142 | .addHeader("X-Api-Key", "de196ac120164019a0911ea8191f85e4")
143 | .addGlobalHeaders()
144 | .addToken()
145 | .build().let {
146 | chain.proceed(it)
147 | }
148 | }
149 |
150 | private fun Request.Builder.addGlobalHeaders() = apply {
151 | globalHeaders.forEach { (key, value) ->
152 | addHeader(key, value)
153 | }
154 | }
155 |
156 | private fun Request.Builder.addToken() = apply {
157 | getToken().takeIf { it.isNotEmpty() }?.let {
158 | addHeader("Authorization", "Token $it")
159 | }
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/network/models/dto/ArticleDto.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.network.models.dto
2 |
3 | import android.os.Parcelable
4 | import androidx.annotation.Keep
5 | import androidx.room.Entity
6 | import androidx.room.PrimaryKey
7 | import kotlinx.parcelize.Parcelize
8 |
9 | /** PariSa;
10 | coding and smoking ;)
11 | **/
12 |
13 | @Keep
14 | @Parcelize
15 | @Entity(tableName = "article")
16 | data class ArticleDto(
17 | @PrimaryKey(autoGenerate = true)
18 | @Keep
19 | val localId: Long = 0,
20 | @Keep
21 | var author: String? = null,
22 | @Keep
23 | var title: String? = null,
24 | @Keep
25 | var description: String? = null,
26 | @Keep
27 | var url: String? = null,
28 | @Keep
29 | var urlToImage: String? = null,
30 | @Keep
31 | var publishedAt: String? = null,
32 | @Keep
33 | var content: String? = null,
34 | @Keep
35 | var source: SourceDto? = null
36 | ) : Parcelable
37 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/network/models/dto/SourceDto.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.network.models.dto
2 |
3 | import android.os.Parcelable
4 | import androidx.annotation.Keep
5 | import kotlinx.parcelize.Parcelize
6 |
7 | /** PariSa;
8 | coding and smoking ;)
9 | **/
10 |
11 | @Parcelize
12 | data class SourceDto(
13 | @Keep val id: String? = null,
14 | @Keep val name: String? = null,
15 | ) : Parcelable
16 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/network/utils/ConnectivityInterceptor.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.network.utils
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.net.ConnectivityManager
6 | import android.net.NetworkCapabilities
7 | import android.os.Build
8 | import okhttp3.Interceptor
9 | import okhttp3.Request
10 | import okhttp3.Response
11 | import java.io.IOException
12 |
13 | /** PariSa;
14 | coding and smoking ;)
15 | **/
16 |
17 | class NoConnectivityException : IOException() {
18 | override val message: String
19 | get() = "عدم اتصال به اینترنت"
20 | }
21 |
22 | class ConnectivityInterceptor(
23 | val app: Application
24 | ) : Interceptor {
25 |
26 | @Suppress("DEPRECATION")
27 | private fun isNetworkAvailable(context: Context): Boolean {
28 | val connectivityManager =
29 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
31 | val nw = connectivityManager.activeNetwork ?: return false
32 | val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
33 | return when {
34 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
35 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
36 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
37 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
38 | else -> false
39 | }
40 | } else {
41 | return connectivityManager.activeNetworkInfo?.isConnected ?: false
42 | }
43 | }
44 |
45 | override fun intercept(chain: Interceptor.Chain): Response {
46 | synchronized(this) {
47 | val originalRequest = chain.request().newBuilder()
48 | if (!isNetworkAvailable(app)) {
49 | throw NoConnectivityException()
50 | }
51 | val request = requestHeader(originalRequest)
52 | return chain.proceed(request)
53 | }
54 | }
55 |
56 | companion object {
57 | fun requestHeader(request: Request.Builder): Request {
58 | return request
59 | .addHeader("Accept", "application/json")
60 | .addHeader("Content-type", "application/json")
61 | .build()
62 |
63 | }
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/network/utils/Constants.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.network.utils
2 |
3 | /** PariSa;
4 | coding and smoking ;)
5 | **/
6 |
7 | const val BASE_URL = "https://newsapi.org/v2/"
8 | const val TOP_HEAD_LINES = "top-headlines"
9 |
10 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/preference/Preference.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.preference
2 |
3 | import android.app.Application
4 | import android.content.SharedPreferences
5 | import android.preference.PreferenceManager
6 |
7 | /** PariSa;
8 | coding and smoking ;)
9 | **/
10 |
11 | interface Preference {
12 | fun put(key: String, value: String)
13 | fun put(key: String, value: Boolean)
14 | fun put(key: String, value: Long)
15 | fun put(key: String, value: Int)
16 |
17 | fun getString(key: String): String
18 | fun getBoolean(key: String): Boolean
19 | fun getLong(key: String): Long
20 | fun getInt(key: String): Int
21 | }
22 |
23 | class PreferenceImpl(
24 | application: Application
25 | ) : Preference {
26 |
27 | private val stringValues: HashMap = hashMapOf()
28 | private val longValues: HashMap = hashMapOf()
29 | private val intValues: HashMap = hashMapOf()
30 | private val booleanValues: HashMap = hashMapOf()
31 |
32 | var preferences: SharedPreferences =
33 | PreferenceManager.getDefaultSharedPreferences(application.applicationContext)
34 |
35 | override fun put(key: String, value: String) {
36 | synchronized(this) {
37 | stringValues[key] = value
38 | preferences.edit().putString(key, value).apply()
39 | }
40 | }
41 |
42 | override fun put(key: String, value: Boolean) {
43 | synchronized(this) {
44 | booleanValues[key] = value
45 | preferences.edit().putBoolean(key, value).apply()
46 | }
47 | }
48 |
49 | override fun put(key: String, value: Long) {
50 | synchronized(this) {
51 | longValues[key] = value
52 | preferences.edit().putLong(key, value).apply()
53 | }
54 | }
55 |
56 | override fun put(key: String, value: Int) {
57 | synchronized(this) {
58 | intValues[key] = value
59 | preferences.edit().putInt(key, value).apply()
60 | }
61 | }
62 |
63 | override fun getString(key: String): String {
64 | synchronized(this) {
65 | return if (stringValues.containsKey(key))
66 | stringValues[key] ?: ""
67 | else
68 | preferences.getString(key, "") ?: ""
69 | }
70 | }
71 |
72 | override fun getBoolean(key: String): Boolean {
73 | synchronized(this) {
74 | return if (booleanValues.containsKey(key))
75 | booleanValues[key] ?: false
76 | else
77 | preferences.getBoolean(key, false)
78 | }
79 | }
80 |
81 | override fun getLong(key: String): Long {
82 | synchronized(this) {
83 | return if (longValues.containsKey(key))
84 | longValues[key] ?: 0
85 | else
86 | preferences.getLong(key, 0)
87 | }
88 | }
89 |
90 | override fun getInt(key: String): Int {
91 | synchronized(this) {
92 | return if (intValues.containsKey(key))
93 | intValues[key] ?: 0
94 | else
95 | preferences.getInt(key, 0)
96 | }
97 | }
98 | }
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/repository/ArticleRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.repository
2 |
3 | import androidx.paging.Pager
4 | import androidx.paging.PagingConfig
5 | import androidx.paging.PagingData
6 | import dev.roshana.data.network.api.ApiService
7 | import dev.roshana.data.repository.pagingSource.ArticlePagingSource
8 | import dev.roshana.domain.models.article.Article
9 | import dev.roshana.domain.repositories.ArticleRepository
10 | import kotlinx.coroutines.flow.Flow
11 | import javax.inject.Inject
12 | import javax.inject.Singleton
13 |
14 | /** PariSa;
15 | coding and smoking ;)
16 | **/
17 |
18 | @Singleton
19 | class ArticleRepositoryImpl @Inject constructor(
20 | val apiService: ApiService
21 | ) : ArticleRepository {
22 | override suspend fun getTechCrunchNews(source: String?): Flow> {
23 |
24 | return Pager(PagingConfig(pageSize = 50)) {
25 | ArticlePagingSource(apiService = apiService)
26 | }.flow
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/repository/DataStoreRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.repository
2 |
3 | import android.content.Context
4 | import androidx.datastore.core.DataStore
5 | import androidx.datastore.preferences.core.Preferences
6 | import androidx.datastore.preferences.core.booleanPreferencesKey
7 | import androidx.datastore.preferences.core.edit
8 | import androidx.datastore.preferences.core.emptyPreferences
9 | import androidx.datastore.preferences.preferencesDataStore
10 | import dev.roshana.domain.repositories.DataStoreRepository
11 | import kotlinx.coroutines.flow.Flow
12 | import kotlinx.coroutines.flow.catch
13 | import kotlinx.coroutines.flow.map
14 | import java.io.IOException
15 | import javax.inject.Inject
16 | import javax.inject.Singleton
17 |
18 | /** PariSa;
19 | coding and smoking ;)
20 | **/
21 |
22 | @Singleton
23 | class DataStoreRepositoryImpl @Inject constructor(val context: Context) : DataStoreRepository {
24 |
25 | private object PreferencesKey {
26 | val onBoardingKey = booleanPreferencesKey(name = "on_boarding_completed")
27 | }
28 |
29 | private val dataStore = context.dataStore
30 |
31 |
32 | override suspend fun saveOnOnBoardingState(completed: Boolean) {
33 | dataStore.edit { preferences ->
34 | preferences[PreferencesKey.onBoardingKey] = completed
35 | }
36 | }
37 |
38 | override suspend fun readOnBoardingState(): Flow {
39 | return dataStore.data
40 | .catch { exception ->
41 | if (exception is IOException) {
42 | emit(emptyPreferences())
43 | } else {
44 | throw exception
45 | }
46 | }
47 | .map { preferences ->
48 | val onBoardingState = preferences[PreferencesKey.onBoardingKey] ?: false
49 | onBoardingState
50 | }
51 | }
52 | }
53 |
54 | val Context.dataStore: DataStore by preferencesDataStore(
55 | name = "on_boarding_pref"
56 | )
57 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/repository/base/BaseRepository.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.repository.base
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.withContext
5 |
6 | /** PariSa;
7 | coding and smoking ;)
8 | **/
9 |
10 | open class BaseRepository {
11 |
12 | suspend fun safeApiCall(
13 | apiCall: suspend () -> T
14 | ): Result {
15 | return withContext(Dispatchers.IO) {
16 | try {
17 | Result.success(apiCall.invoke())
18 | } catch (throwable: Throwable) {
19 | Result.failure(throwable)
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/repository/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.repository.di
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import dev.roshana.data.network.api.ApiService
9 | import dev.roshana.data.repository.ArticleRepositoryImpl
10 | import dev.roshana.data.repository.DataStoreRepositoryImpl
11 | import dev.roshana.domain.repositories.ArticleRepository
12 | import dev.roshana.domain.repositories.DataStoreRepository
13 | import retrofit2.Retrofit
14 |
15 | /** PariSa;
16 | coding and smoking ;)
17 | **/
18 |
19 | @Module
20 | @InstallIn(SingletonComponent::class)
21 | object RepositoryModule {
22 |
23 | /*@Provides
24 | @Singleton
25 | internal open fun providesArticleRepository(
26 | application: Application,
27 | apiService: ApiService
28 | ) = ArticleRepositoryImpl(application, apiService) as ArticleRepository*/
29 |
30 | /*@Binds
31 | abstract fun providesArticleRepository(impl: ArticleRepositoryImpl): ArticleRepository*/
32 |
33 | @Provides
34 | fun providesArticleRepository(retrofit: Retrofit): ArticleRepository {
35 | return ArticleRepositoryImpl(retrofit.create(ApiService::class.java))
36 | }
37 |
38 | @Provides
39 | fun providesDataStoreRepository(
40 | context: Context
41 | ): DataStoreRepository = DataStoreRepositoryImpl(context = context)
42 | }
43 |
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/repository/pagingSource/ArticlePagingSource.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.repository.pagingSource
2 |
3 | import androidx.paging.PagingSource
4 | import androidx.paging.PagingState
5 | import dev.roshana.data.network.api.ApiService
6 | import dev.roshana.domain.models.article.Article
7 |
8 | /** PariSa;
9 | coding and smoking ;)
10 | **/
11 |
12 | class ArticlePagingSource(
13 | private val apiService: ApiService
14 | ) : PagingSource() {
15 | override fun getRefreshKey(state: PagingState): Int? {
16 | return state.anchorPosition?.let { anchorposition ->
17 | state.closestPageToPosition(anchorposition)?.prevKey?.plus(1)
18 | }
19 | }
20 |
21 | override suspend fun load(params: LoadParams): LoadResult {
22 | return try {
23 | val page = params.key ?: 1
24 | val response = apiService.getTechCrunchNews(
25 | page = page,
26 | sources = "techcrunch",
27 | apiKey = "de196ac120164019a0911ea8191f85e4"
28 | )
29 | val items = response.articles?.map {
30 | Article(author = it.author, title = it.title, url = it.url)
31 | }
32 | val prevKey = if (page > 1) page - 1 else null
33 | val nextKey = if (items?.isNotEmpty() == true) page + 1 else null
34 | LoadResult.Page(
35 | data = items!!,
36 | nextKey = nextKey,
37 | prevKey = prevKey
38 | )
39 | } catch (e: Exception) {
40 | LoadResult.Error(e)
41 | }
42 | /* return try {
43 | val page = params.key ?: 1 // set page 1 as default
44 | val pageSize = params.loadSize
45 | val response = apiService.getTechCrunchNews(
46 | page = page,
47 | sources = "techcrunch",
48 | apiKey = "de196ac120164019a0911ea8191f85e4"
49 | )
50 | val items = response.articles?.map {
51 | Article(author = it.author, title = it.title, url = it.url)
52 | }
53 | val prevKey = if (page > 1) page - 1 else null
54 | val nextKey = if (items?.isNotEmpty() == true) page + 1 else null
55 | LoadResult.Page(items!!, prevKey, nextKey)
56 | } catch (e: Exception) {
57 | e.printStackTrace()
58 | LoadResult.Error(e)
59 | }*/
60 | }
61 |
62 |
63 | }
--------------------------------------------------------------------------------
/data/src/main/java/dev/roshana/data/repository/utils/Utils.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data.repository.utils
2 |
3 | import android.util.Log
4 | import java.lang.Exception
5 |
6 | /** PariSa;
7 | coding and smoking ;)
8 | **/
9 |
10 | fun getPageIntFromUrl(url: String): Int? {
11 | return try {
12 | url.substringAfterLast("=").toInt()
13 | } catch (e: Exception) {
14 | e.message?.let { Log.d("converting url", it) }
15 | null
16 | }
17 | }
18 |
19 |
20 |
21 | fun getEpisodeIntFromUrl(url: String): Int? {
22 | return try {
23 | url.substringAfterLast("/").toInt()
24 | } catch (e: Exception) {
25 | e.message?.let { Log.d("converting url", it) }
26 | null
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/data/src/test/java/dev/roshana/data/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.data
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dev.roshana.buildsrc.BuildTypes.RELEASE
2 | import dev.roshana.buildsrc.ConfigData
3 | import dev.roshana.buildsrc.Dependencies
4 |
5 | plugins {
6 | id("com.android.library")
7 | id("org.jetbrains.kotlin.android")
8 | id("kotlin-kapt")
9 | id("kotlin-parcelize")
10 | }
11 |
12 | android {
13 | compileSdk = ConfigData.compileSdkVersion
14 |
15 | defaultConfig {
16 | minSdk = ConfigData.minSdkVersion
17 | targetSdk = ConfigData.targetSdkVersion
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | consumerProguardFiles("consumer-rules.pro")
21 | }
22 |
23 | buildTypes {
24 | getByName(RELEASE) {
25 | isMinifyEnabled = false
26 | proguardFiles(
27 | getDefaultProguardFile("proguard-android-optimize.txt"),
28 | "proguard-rules.pro"
29 | )
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_1_8
34 | targetCompatibility = JavaVersion.VERSION_1_8
35 | }
36 | kotlinOptions {
37 | jvmTarget = JavaVersion.VERSION_1_8.toString()
38 | }
39 | }
40 |
41 | dependencies {
42 |
43 | implementation(Dependencies.ComposeLibs.coreKtx)
44 |
45 | /** compose
46 | **/
47 | implementation(Dependencies.ComposeLibs.composePaging)
48 |
49 | /** hilt - dagger
50 | **/
51 | implementation(Dependencies.CommonLibs.daggerAndroid)
52 | kapt(Dependencies.CommonLibs.daggerCompiler)
53 | kapt(Dependencies.CommonLibs.daggerAndroidCompiler)
54 | implementation(Dependencies.CommonLibs.hilt)
55 | kapt(Dependencies.CommonLibs.hiltCompiler)
56 |
57 | /** retrofit, okHttp
58 | **/
59 | implementation(Dependencies.CommonLibs.retrofit)
60 | implementation(Dependencies.CommonLibs.retrofitGson)
61 | implementation(Dependencies.CommonLibs.okHttpInterceptor)
62 | implementation(Dependencies.CommonLibs.gson)
63 | implementation(Dependencies.CommonLibs.kotlinSerialConverter)
64 | implementation(Dependencies.CommonLibs.jsonSerialization)
65 |
66 | /** Room
67 | **/
68 | /*implementation(Dependencies.CommonLibs.room)
69 | implementation(Dependencies.CommonLibs.roomPagingCompose)
70 | kapt(Dependencies.CommonLibs.roomCompiler)*/
71 |
72 | /** testing
73 | **/
74 | testImplementation(Dependencies.TestLibs.junit)
75 | androidTestImplementation(Dependencies.TestLibs.extJunit)
76 | androidTestImplementation(Dependencies.TestLibs.espresso)
77 |
78 |
79 | }
--------------------------------------------------------------------------------
/domain/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/consumer-rules.pro
--------------------------------------------------------------------------------
/domain/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/domain/src/androidTest/java/dev/roshana/domain/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("dev.roshana.domain.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/domain/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/di/UseCaseModule.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.di
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 | import dev.roshana.domain.repositories.ArticleRepository
8 | import dev.roshana.domain.repositories.DataStoreRepository
9 | import dev.roshana.domain.usecases.ArticleUseCase
10 | import dev.roshana.domain.usecases.DataStoreUseCase
11 | import javax.inject.Singleton
12 |
13 | /** PariSa;
14 | coding and smoking ;)
15 | **/
16 |
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | object UseCaseModule {
20 |
21 | /*@Provides
22 | @Singleton
23 | internal fun providesArticleUseCase(
24 | articleRepository: ArticleRepository
25 | ) = ArticleUseCase(articleRepository)*/
26 |
27 | @Provides
28 | @Singleton
29 | fun providesArticlesUseCase(articleRepository: ArticleRepository) =
30 | ArticleUseCase(articleRepository)
31 |
32 | @Provides
33 | @Singleton
34 | fun providesDataStoreUseCase(dataStoreRepository: DataStoreRepository) =
35 | DataStoreUseCase(dataStoreRepository)
36 |
37 | }
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/models/DataState.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.models
2 |
3 | /** PariSa;
4 | coding and smoking ;)
5 | **/
6 |
7 | open class DataState(
8 | var isLoading: Boolean = false,
9 | val data: T? = null,
10 | val errorMessage: String = ""
11 | )
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/models/ResponseWrapper.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.models
2 |
3 | import androidx.annotation.Keep
4 |
5 | /** PariSa;
6 | coding and smoking ;)
7 | **/
8 |
9 | @Keep
10 | class ResponseWrapper {
11 | @Keep
12 | var status = ""
13 |
14 | @Keep
15 | var totalResults: String? = null
16 |
17 | @Keep
18 | var message: String? = null
19 |
20 | @Keep
21 | var articles: T? = null
22 | }
23 |
24 |
25 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/models/article/Article.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.models.article
2 |
3 | /** PariSa;
4 | coding and smoking ;)
5 | **/
6 |
7 | data class Article(
8 | val author: String? = null,
9 | val title: String? = null,
10 | val description: String? = null,
11 | val url: String? = null,
12 | val urlToImage: String? = null,
13 | val publishedAt: String? = null,
14 | val content: String? = null
15 | )
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/models/dialogs/Item.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.models.dialogs
2 |
3 | /** PariSa;
4 | coding and smoking ;)
5 | **/
6 |
7 | data class Item(
8 | val id: Int,
9 | val title: String,
10 | val subtitle: String,
11 | val imageId: Int,
12 | val source: String = "demo source"
13 | )
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/repositories/ArticleRepository.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.repositories
2 |
3 | import androidx.paging.PagingData
4 | import dev.roshana.domain.models.article.Article
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | /** PariSa;
8 | coding and smoking ;)
9 | **/
10 |
11 | interface ArticleRepository {
12 | suspend fun getTechCrunchNews(source: String? = null): Flow>
13 | }
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/repositories/DataStoreRepository.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.repositories
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | /** PariSa;
6 | coding and smoking ;)
7 | **/
8 |
9 | interface DataStoreRepository {
10 |
11 | suspend fun saveOnOnBoardingState(completed: Boolean)
12 | suspend fun readOnBoardingState(): Flow
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/usecases/ArticleUseCase.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.usecases
2 |
3 | import androidx.paging.PagingData
4 | import dev.roshana.domain.models.article.Article
5 | import dev.roshana.domain.repositories.ArticleRepository
6 | import kotlinx.coroutines.flow.Flow
7 | import javax.inject.Inject
8 |
9 | /** PariSa;
10 | coding and smoking ;)
11 | **/
12 |
13 | class ArticleUseCase @Inject constructor(
14 | private val articleRepository: ArticleRepository
15 | ) {
16 | suspend operator fun invoke(source: String? = null): Flow> {
17 | return articleRepository.getTechCrunchNews(source)
18 | }
19 | }
20 |
21 |
22 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/usecases/DataStoreUseCase.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.usecases
2 |
3 | import dev.roshana.domain.repositories.DataStoreRepository
4 | import kotlinx.coroutines.flow.Flow
5 | import javax.inject.Inject
6 |
7 | /** PariSa;
8 | coding and smoking ;)
9 | **/
10 |
11 | class DataStoreUseCase @Inject constructor(
12 | private val dataStoreRepository: DataStoreRepository
13 | ) {
14 | suspend operator fun invoke(completed: Boolean) {
15 | return dataStoreRepository.saveOnOnBoardingState(completed)
16 | }
17 |
18 | suspend operator fun invoke(): Flow {
19 | return dataStoreRepository.readOnBoardingState()
20 | }
21 | }
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/utils/ConfigHelper.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.content.SharedPreferences
6 | import dev.roshana.domain.BuildConfig
7 |
8 | /** PariSa;
9 | coding and smoking ;)
10 | **/
11 |
12 | fun isDebugMode(): Boolean {
13 | if (BuildConfig.DEBUG)
14 | return true
15 | return false
16 | }
17 |
18 |
19 | const val AUTH_CODE = "auth2"
20 | private const val TEST_PREFERENCE_NAME = "testPreference"
21 | lateinit var sharedPreferences: SharedPreferences
22 |
23 | lateinit var testPreferences: SharedPreferences
24 |
25 | private val authorization: String?
26 | get() = getStringPreference(AUTH_CODE)
27 |
28 | fun initPreferenceUtils(context: Context) {
29 | sharedPreferences = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
30 | testPreferences =
31 | context.getSharedPreferences(TEST_PREFERENCE_NAME, Context.MODE_PRIVATE)
32 | }
33 |
34 | fun getStringPreference(tag: String): String? {
35 | return sharedPreferences.getString(tag, null)
36 | }
37 |
38 | @SuppressLint("ApplySharedPref")
39 | fun putStringPreference(tag: String, value: String) {
40 | sharedPreferences.edit().putString(tag, value).commit()
41 | }
42 |
43 | fun putIntTestPreference(tag: String, value: Int) {
44 | testPreferences.edit().putInt(tag, value).apply()
45 | }
46 |
47 | fun getIntTestPreference(tag: String, defaultValue: Int = -1): Int {
48 | return testPreferences.getInt(tag, defaultValue)
49 | }
50 |
51 |
52 | fun putLongTesPreference(tag: String, value: Long) {
53 | testPreferences.edit().putLong(tag, value).apply()
54 | }
55 |
56 | fun getLongTestPreference(tag: String): Long {
57 | return testPreferences.getLong(tag, -1L)
58 | }
59 |
60 | fun putLongPreferenceTagStartWith(tag: String, value: Long) {
61 | val editor = sharedPreferences.edit()
62 | for (key in sharedPreferences.all.keys)
63 | if (key.startsWith(tag))
64 | editor.putLong(key, value)
65 | editor.apply()
66 | }
67 |
68 | fun getLongPreference(tag: String): Long {
69 | return sharedPreferences.getLong(tag, -1L)
70 | }
71 |
72 | fun getIntPreference(tag: String): Int {
73 | return sharedPreferences.getInt(tag, -1)
74 | }
75 |
76 | fun putIntPreference(tag: String, value: Int) {
77 | sharedPreferences.edit().putInt(tag, value).apply()
78 | }
79 |
80 | fun getBooleanPreference(pref: String): Boolean {
81 | return sharedPreferences.getBoolean(pref, false)
82 | }
83 |
84 | fun putBooleanPreference(pref: String, value: Boolean) {
85 | sharedPreferences.edit().putBoolean(pref, value).apply()
86 | }
87 |
88 | fun saveAuthorization(authorization: String) {
89 | putStringPreference(
90 | AUTH_CODE,
91 | authorization
92 | )
93 | }
94 |
95 | fun getShownIqTests() = sharedPreferences.getStringSet("shownIqTests", mutableSetOf())
96 | fun isShownIqTests(id: Long) =
97 | sharedPreferences.getStringSet("shownIqTests", mutableSetOf())?.contains(id.toString()) ?: false
98 |
99 | fun putIqTestAsShown(id: Long) = getShownIqTests()
100 | ?.apply {
101 | add(id.toString())
102 | sharedPreferences.edit().putStringSet("shownIqTests", this).apply()
103 | }
104 |
105 | fun removeIqTestAsShown(id: Long) = getShownIqTests()
106 | ?.apply {
107 | remove(id.toString())
108 | sharedPreferences.edit().putStringSet("shownIqTests", this).apply()
109 | }
110 |
111 | fun getShownMBTITests() = sharedPreferences.getStringSet("shownMBTITests", mutableSetOf())
112 | fun isShownMBTITests(id: Long) =
113 | sharedPreferences.getStringSet("shownMBTITests", mutableSetOf())?.contains(id.toString())
114 | ?: false
115 |
116 | fun putMBTITestAsShown(id: Long) = getShownMBTITests()
117 | ?.apply {
118 | add(id.toString())
119 | sharedPreferences.edit().putStringSet("shownMBTITests", this).apply()
120 | }
121 |
122 | fun removeMBTITestAsShown(id: Long) = getShownMBTITests()
123 | ?.apply {
124 | remove(id.toString())
125 | sharedPreferences.edit().putStringSet("shownMBTITests", this).apply()
126 | }
127 |
128 | @Synchronized
129 | fun getToken(): String {
130 | return if (authorization == null) "" else getStringPreference(
131 | AUTH_CODE
132 | ).toString()
133 | }
134 |
135 | @Synchronized
136 | fun removeAuthorization() = sharedPreferences.edit().remove(
137 | AUTH_CODE
138 | ).apply()
139 |
140 |
141 | fun isUserSignedIn() = getToken().isNotEmpty()
142 |
143 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/roshana/domain/utils/Headers.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain.utils
2 |
3 | import java.util.*
4 |
5 | /** PariSa;
6 | coding and smoking ;)
7 | **/
8 |
9 | val globalHeaders by lazy {
10 | mapOf(
11 | Pair("X-CHERAGH-CLIENT", "Android"),
12 | Pair("Accept-Language", Locale.getDefault().country)
13 | )
14 | }
15 |
16 |
17 |
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/first.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/first.png
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food1.jpg
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food10.jpg
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food11.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food11.webp
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food12.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food12.webp
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food13.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food13.webp
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food14.jpg
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food15.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food15.webp
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food16.jpg
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food2.jpg
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food3.jpg
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food4.jpg
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food5.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food5.webp
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food6.jpg
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food7.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food7.webp
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food8.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food8.webp
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/food9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/food9.jpg
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/second.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/second.png
--------------------------------------------------------------------------------
/domain/src/main/res/drawable/third.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/domain/src/main/res/drawable/third.png
--------------------------------------------------------------------------------
/domain/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | اینترنت خود را بررسی نمایید و دوباره امتحان کنید
4 | متاسفانه ارتباط با سرور دچار اخلال شده است لطفا مجددا تلاش کنید
5 |
--------------------------------------------------------------------------------
/domain/src/test/java/dev/roshana/domain/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.domain
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | android.enableJetifier=true
19 | # Kotlin code style for this project: "official" or "obsolete":
20 | kotlin.code.style=official
21 | # Enables namespacing of each library's R class so that its R class includes only the
22 | # resources declared in the library itself and none from the library's dependencies,
23 | # thereby reducing the size of the R class for that library
24 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Aug 28 11:54:06 IRDT 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/presentation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/presentation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import dev.roshana.buildsrc.BuildTypes
2 | import dev.roshana.buildsrc.ConfigData
3 | import dev.roshana.buildsrc.Dependencies
4 | import org.jetbrains.kotlin.config.JvmAnalysisFlags.useIR
5 |
6 | plugins {
7 | id("com.android.library")
8 | id("org.jetbrains.kotlin.android")
9 | id("dagger.hilt.android.plugin")
10 | id("kotlin-kapt")
11 | id("kotlin-parcelize")
12 | }
13 |
14 | android {
15 | compileSdk = ConfigData.compileSdkVersion
16 |
17 | defaultConfig {
18 | minSdk = ConfigData.minSdkVersion
19 | targetSdk = ConfigData.targetSdkVersion
20 |
21 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
22 | consumerProguardFiles("consumer-rules.pro")
23 | }
24 |
25 | buildTypes {
26 | getByName(BuildTypes.RELEASE) {
27 | isMinifyEnabled = false
28 | proguardFiles(
29 | getDefaultProguardFile("proguard-android-optimize.txt"),
30 | "proguard-rules.pro"
31 | )
32 | }
33 | }
34 |
35 | buildFeatures {
36 | compose = true
37 | }
38 | composeOptions {
39 | kotlinCompilerVersion = rootProject.extra["kotlin_version"] as String
40 | kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String
41 | }
42 | compileOptions {
43 | sourceCompatibility = JavaVersion.VERSION_1_8
44 | targetCompatibility = JavaVersion.VERSION_1_8
45 | }
46 | kotlinOptions {
47 | jvmTarget = JavaVersion.VERSION_1_8.toString()
48 | useIR
49 | }
50 | }
51 |
52 | dependencies {
53 |
54 | implementation(project(":domain"))
55 | implementation(project(":data"))
56 |
57 | implementation(Dependencies.ComposeLibs.coreKtx)
58 | implementation(Dependencies.CommonLibs.material)
59 | implementation(Dependencies.CommonLibs.accompanistPager)
60 | implementation(Dependencies.CommonLibs.accompanistIndicator)
61 |
62 | /** compose
63 | **/
64 | implementation(Dependencies.ComposeLibs.composeUi)
65 | implementation(Dependencies.ComposeLibs.composeMaterial)
66 | implementation(Dependencies.ComposeLibs.composeUiPreview)
67 | implementation(Dependencies.ComposeLibs.androidxLifeCycle)
68 | implementation(Dependencies.ComposeLibs.composeActivity)
69 | implementation(Dependencies.ComposeLibs.composeHilt)
70 | implementation(Dependencies.ComposeLibs.composeNav)
71 | implementation(Dependencies.ComposeLibs.composePaging)
72 | implementation(Dependencies.ComposeLibs.composeAnimNavigation)
73 | implementation(Dependencies.ComposeLibs.coreSplash)
74 |
75 | /** hilt - dagger
76 | **/
77 | implementation(Dependencies.CommonLibs.daggerAndroid)
78 | kapt(Dependencies.CommonLibs.daggerCompiler)
79 | kapt(Dependencies.CommonLibs.daggerAndroidCompiler)
80 | implementation(Dependencies.CommonLibs.hilt)
81 | kapt(Dependencies.CommonLibs.hiltCompiler)
82 | //implementation(Dependencies.CommonLibs.hiltViewModel)
83 | kapt(Dependencies.CommonLibs.hiltViewModelKapt)
84 |
85 | /** Coroutine
86 | **/
87 | implementation(Dependencies.CommonLibs.coroutines)
88 | implementation(Dependencies.CommonLibs.coroutinesAndroid)
89 | implementation(Dependencies.CommonLibs.coroutineAdapter)
90 |
91 | /** retrofit, okHttp
92 | **/
93 | implementation(Dependencies.CommonLibs.retrofit)
94 | implementation(Dependencies.CommonLibs.retrofitGson)
95 | implementation(Dependencies.CommonLibs.okHttpInterceptor)
96 | implementation(Dependencies.CommonLibs.gson)
97 | implementation(Dependencies.CommonLibs.kotlinSerialConverter)
98 | implementation(Dependencies.CommonLibs.jsonSerialization)
99 |
100 | /** Room
101 | **/
102 | /* implementation(Dependencies.CommonLibs.room)
103 | implementation(Dependencies.CommonLibs.roomPagingCompose)
104 | kapt(Dependencies.CommonLibs.roomCompiler)*/
105 |
106 | /** testing
107 | **/
108 | testImplementation(Dependencies.TestLibs.junit)
109 | androidTestImplementation(Dependencies.TestLibs.extJunit)
110 | androidTestImplementation(Dependencies.TestLibs.espresso)
111 | androidTestImplementation(Dependencies.TestLibs.composeUiJunit)
112 | debugImplementation(Dependencies.TestLibs.composeUiTooling)
113 | debugImplementation(Dependencies.TestLibs.composeUiTestManifest)
114 | }
--------------------------------------------------------------------------------
/presentation/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttyCat/ComposeCleanArch/46eabaf49033cbbced61d07ad34370aa164cbcd8/presentation/consumer-rules.pro
--------------------------------------------------------------------------------
/presentation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/presentation/src/androidTest/java/dev/roshana/presentation/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("dev.roshana.presentation.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/presentation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/articleUi/ArticleListColumn.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.articleUi
2 |
3 | import androidx.compose.animation.ExperimentalAnimationApi
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.LazyListState
7 | import androidx.compose.foundation.lazy.rememberLazyListState
8 | import androidx.compose.material.Button
9 | import androidx.compose.material.CircularProgressIndicator
10 | import androidx.compose.material.ExperimentalMaterialApi
11 | import androidx.compose.material.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.dp
16 | import androidx.paging.LoadState
17 | import androidx.paging.compose.LazyPagingItems
18 | import androidx.paging.compose.items
19 | import dev.roshana.domain.models.article.Article
20 |
21 | /** PariSa;
22 | coding and smoking ;)
23 | **/
24 |
25 | @OptIn(ExperimentalMaterialApi::class, ExperimentalAnimationApi::class)
26 | @Composable
27 | fun ArticlesListColumn(
28 | modifier: Modifier = Modifier,
29 | items: LazyPagingItems,
30 | listState: LazyListState = rememberLazyListState(),
31 | navigate: (Int) -> Unit = {}
32 | ) {
33 | LazyColumn(
34 | state = listState,
35 | modifier = modifier
36 |
37 | ) {
38 |
39 | items(items) { article ->
40 | if (article != null) {
41 | ArticleUI(article) {}
42 | }
43 | }
44 |
45 | items.apply {
46 | when {
47 | loadState.refresh is LoadState.Loading -> {
48 | item {
49 | Box(
50 | modifier = Modifier
51 | .fillMaxSize()
52 | .padding(
53 | top = 50.dp,
54 | bottom = 50.dp
55 | ),
56 | contentAlignment = Alignment.Center
57 | ) {
58 | CircularProgressIndicator(
59 | modifier = Modifier.height(30.dp)
60 | )
61 | }
62 | }
63 | }
64 |
65 | loadState.append is LoadState.Loading -> {
66 |
67 | item {
68 | Box(
69 | modifier = Modifier.fillMaxWidth()
70 | .padding(bottom = 56.dp),
71 | contentAlignment = Alignment.Center
72 | ) {
73 | CircularProgressIndicator(modifier = Modifier.height(30.dp))
74 | }
75 | }
76 | }
77 |
78 | loadState.refresh is LoadState.Error -> {
79 | val errorMessage = items.loadState.refresh as LoadState.Error
80 | item {
81 | Box(
82 | modifier = Modifier.fillMaxWidth()
83 | .padding(bottom = 56.dp),
84 | contentAlignment = Alignment.BottomCenter
85 | ) {
86 | Column(
87 | modifier = Modifier.fillMaxWidth(),
88 | horizontalAlignment = Alignment.CenterHorizontally
89 | ) {
90 | val errorText =
91 | if (errorMessage.error.localizedMessage!!
92 | .contains("404")
93 | ) "Article not Found"
94 | else
95 | errorMessage.error.localizedMessage
96 | Text(errorText)
97 | /* Button(onClick = { retry() }) {
98 | Text(text = "Try Again")
99 | }*/
100 | }
101 | }
102 | }
103 | }
104 |
105 | loadState.append is LoadState.Error -> {
106 | val errorMessage = items.loadState.append as LoadState.Error
107 |
108 | item {
109 | Box(
110 | modifier = Modifier.fillMaxWidth()
111 | .padding(bottom = 56.dp),
112 | contentAlignment = Alignment.BottomCenter
113 | ) {
114 | Column(
115 | modifier = Modifier.fillMaxWidth(),
116 | horizontalAlignment = Alignment.CenterHorizontally
117 | ) {
118 | Text(text = errorMessage.error.localizedMessage!!)
119 | Button(onClick = { retry() }) {
120 | Text(text = "Try Again")
121 | }
122 | }
123 | }
124 | }
125 | }
126 | }
127 | }
128 | /** WARNING adding this to a lazy column makes iit lose state
129 | item {
130 | Box(modifier = Modifier.height(60.dp)
131 | .padding(56.dp))
132 | }*/
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/articleUi/ArticleListState.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.articleUi
2 |
3 | import androidx.paging.PagingData
4 | import dev.roshana.domain.models.DataState
5 | import dev.roshana.domain.models.article.Article
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | /** PariSa;
9 | coding and smoking ;)
10 | **/
11 |
12 | class ArticleListState(
13 | var loading: Boolean? = null,
14 | var dataList: Flow>? = null,
15 | var errorMsg: String? = ""
16 | ) : DataState>>(
17 | isLoading = loading == false,
18 | data = dataList,
19 | errorMessage = errorMsg!!
20 | )
21 |
22 |
23 | /* var isLoading: Boolean = false,
24 | val dataList: Flow>? = null,
25 | val errorMessage: String = ""*/
26 |
27 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/articleUi/ArticleListViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.articleUi
2 |
3 | import androidx.compose.runtime.MutableState
4 | import androidx.compose.runtime.State
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import androidx.paging.cachedIn
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import dev.roshana.domain.usecases.ArticleUseCase
11 | import kotlinx.coroutines.CoroutineExceptionHandler
12 | import kotlinx.coroutines.launch
13 | import javax.inject.Inject
14 |
15 | /** PariSa;
16 | coding and smoking ;)
17 | **/
18 |
19 | @HiltViewModel
20 | class ArticleListViewModel @Inject constructor(
21 | private val articleUseCase: ArticleUseCase
22 | ) : ViewModel() {
23 |
24 | private val _articleListState: MutableState = mutableStateOf(
25 | ArticleListState()
26 | )
27 | val articleListState: State
28 | get() = _articleListState
29 |
30 | private val handler = CoroutineExceptionHandler { _, exception ->
31 | _articleListState.value.loading = false
32 | _articleListState.value = ArticleListState(
33 | errorMsg = exception.message!!
34 | )
35 | }
36 |
37 | init {
38 | getArticles()
39 | }
40 |
41 | private fun getArticles() = viewModelScope.launch(handler) {
42 | val response = articleUseCase().cachedIn(viewModelScope)
43 | _articleListState.value = ArticleListState(
44 | dataList = response
45 | )
46 |
47 | }
48 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/articleUi/ArticleUi.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.articleUi
2 |
3 | import android.widget.Toast
4 | import androidx.compose.animation.ExperimentalAnimationApi
5 | import androidx.compose.animation.animateContentSize
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.*
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material.Card
10 | import androidx.compose.material.ExperimentalMaterialApi
11 | import androidx.compose.material.MaterialTheme
12 | import androidx.compose.material.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.platform.LocalContext
17 | import androidx.compose.ui.text.font.FontWeight
18 | import androidx.compose.ui.text.style.TextOverflow
19 | import androidx.compose.ui.unit.dp
20 | import dev.roshana.domain.models.article.Article
21 | import kotlinx.coroutines.ExperimentalCoroutinesApi
22 |
23 | /** PariSa;
24 | coding and smoking ;)
25 | **/
26 |
27 | @OptIn(ExperimentalCoroutinesApi::class)
28 | @ExperimentalMaterialApi
29 | @ExperimentalAnimationApi
30 | @Composable
31 | fun ArticleUI(article: Article, modifier: Modifier = Modifier, onClick: (Int) -> Unit) {
32 |
33 | val context = LocalContext.current
34 | Card(
35 | modifier = modifier
36 | .animateContentSize()
37 | .padding(8.dp)
38 | .clickable {
39 | Toast
40 | .makeText(context, "${article.title}", Toast.LENGTH_LONG)
41 | .show()
42 | },
43 | shape = RoundedCornerShape(8.dp),
44 | elevation = 8.dp
45 | ) {
46 | Row {
47 |
48 |
49 | /*ImageCard(
50 | imageLink = article.url!!, modifier = Modifier
51 | .fillMaxWidth(0.35f)
52 | )*/
53 |
54 | ArticleInfo(
55 | article = article,
56 | modifier = Modifier
57 | .fillMaxSize()
58 | .padding(4.dp)
59 | )
60 | }
61 | }
62 | }
63 |
64 |
65 | @Composable
66 | fun ArticleInfo(
67 | article: Article,
68 | modifier: Modifier = Modifier,
69 | showExtraInfo: Boolean = true,
70 | alignment: Alignment.Horizontal = Alignment.Start
71 | ) {
72 |
73 | Column(
74 | modifier = modifier
75 | .fillMaxHeight()
76 | .fillMaxWidth(),
77 | horizontalAlignment = alignment
78 | ) {
79 | Text(
80 | text = "Article Title : ${article.author}",
81 | fontWeight = FontWeight.Bold,
82 | style = MaterialTheme.typography.h6,
83 | maxLines = 1,
84 | overflow = TextOverflow.Ellipsis
85 | )
86 | Spacer(modifier = Modifier.height(8.dp))
87 |
88 | }
89 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/articleUi/ArticlesListScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.articleUi
2 |
3 | import androidx.compose.animation.ExperimentalAnimationApi
4 | import androidx.compose.animation.core.animateDpAsState
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.lazy.rememberLazyListState
10 | import androidx.compose.material.ExperimentalMaterialApi
11 | import androidx.compose.material.Scaffold
12 | import androidx.compose.material.Text
13 | import androidx.compose.material.TopAppBar
14 | import androidx.compose.runtime.*
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.platform.LocalContext
19 | import androidx.compose.ui.unit.dp
20 | import androidx.compose.ui.unit.max
21 | import androidx.compose.ui.unit.min
22 | import androidx.hilt.navigation.compose.hiltViewModel
23 | import androidx.paging.compose.collectAsLazyPagingItems
24 | import kotlinx.coroutines.ExperimentalCoroutinesApi
25 |
26 | /** PariSa;
27 | coding and smoking ;)
28 | **/
29 |
30 | @OptIn(ExperimentalCoroutinesApi::class)
31 | @ExperimentalMaterialApi
32 | @ExperimentalAnimationApi
33 | @Composable
34 | fun ArticlesListScreen() {
35 |
36 | val context = LocalContext.current
37 | val viewModel: ArticleListViewModel = hiltViewModel()
38 | val state = viewModel.articleListState.value
39 | val lazyListState = rememberLazyListState()
40 | //val lazyGridState = rememberLazyGridState()
41 |
42 | val showColumn by remember {
43 | mutableStateOf(true)
44 | }
45 | val scope = rememberCoroutineScope()
46 | val toolBarHeight by animateDpAsState(
47 | targetValue = min(56.dp, 100.dp),
48 | )
49 |
50 |
51 | val articles = state.dataList?.collectAsLazyPagingItems()
52 | Scaffold(topBar = {
53 | TopAppBar(
54 | modifier = Modifier
55 | .height(toolBarHeight),
56 | title = {
57 | Text(
58 | text = "Articles",
59 | color = Color.Black
60 | )
61 | },
62 | backgroundColor = Color.White,
63 | )
64 | }) {
65 |
66 | Box(modifier = Modifier.padding(it)) {
67 |
68 | if (state.errorMessage.isNotEmpty()) {
69 | Box(
70 | modifier = Modifier.fillMaxSize(),
71 | contentAlignment = Alignment.Center
72 | ) {
73 | Text(text = state.errorMessage)
74 | }
75 | } else {
76 | articles?.let { items ->
77 |
78 | if (showColumn) {
79 | ArticlesListColumn(items = items, listState = lazyListState) { articleId ->
80 |
81 | }
82 | }
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/articleUi/articleNavigationGraph.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.articleUi
2 |
3 | import androidx.compose.animation.ExperimentalAnimationApi
4 | import androidx.compose.material.ExperimentalMaterialApi
5 | import androidx.navigation.NavGraphBuilder
6 | import androidx.navigation.NavHostController
7 | import com.google.accompanist.navigation.animation.composable
8 | import com.google.accompanist.navigation.animation.navigation
9 | import dev.roshana.presentation.navigation.Articles
10 | import kotlinx.coroutines.ExperimentalCoroutinesApi
11 |
12 | /** PariSa;
13 | coding and smoking ;)
14 | **/
15 |
16 | @OptIn(ExperimentalAnimationApi::class)
17 | @ExperimentalMaterialApi
18 | @ExperimentalCoroutinesApi
19 | @ExperimentalAnimationApi
20 | fun NavGraphBuilder.articleGraph(
21 | navHostController: NavHostController
22 | ) {
23 | navigation(
24 | startDestination = Articles.ARTICLESLIST,
25 | route = Articles.ARTICLEGRAPH
26 | ) {
27 |
28 | composable(
29 | route = Articles.ARTICLESLIST
30 | ) {
31 | ArticlesListScreen()
32 | }
33 |
34 |
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/locationUi/LocationListScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.locationUi
2 |
3 | import androidx.compose.animation.ExperimentalAnimationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.material.ExperimentalMaterialApi
6 | import androidx.compose.material.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.text.font.FontWeight
11 | import androidx.compose.ui.text.style.TextAlign
12 | import androidx.compose.ui.unit.sp
13 | import kotlinx.coroutines.ExperimentalCoroutinesApi
14 |
15 | /** PariSa;
16 | coding and smoking ;)
17 | **/
18 |
19 | @OptIn(ExperimentalCoroutinesApi::class)
20 | @ExperimentalMaterialApi
21 | @ExperimentalAnimationApi
22 | @Composable
23 | fun LocationScreen() {
24 |
25 | Text(
26 | text = "Location Screen",
27 | fontWeight = FontWeight.Bold,
28 | color = Color.White,
29 | textAlign = TextAlign.Center,
30 | fontSize = 20.sp,
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/locationUi/locationNavigationGraph.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.locationUi
2 |
3 | import androidx.compose.animation.ExperimentalAnimationApi
4 | import androidx.compose.animation.core.spring
5 | import androidx.compose.animation.slideInVertically
6 | import androidx.compose.material.ExperimentalMaterialApi
7 | import androidx.navigation.NavGraphBuilder
8 | import androidx.navigation.NavHostController
9 | import com.google.accompanist.navigation.animation.composable
10 | import com.google.accompanist.navigation.animation.navigation
11 | import dev.roshana.presentation.navigation.Articles
12 | import dev.roshana.presentation.navigation.Locations
13 | import kotlinx.coroutines.ExperimentalCoroutinesApi
14 |
15 | /** PariSa;
16 | coding and smoking ;)
17 | **/
18 |
19 | @ExperimentalMaterialApi
20 | @ExperimentalCoroutinesApi
21 | @ExperimentalAnimationApi
22 | @OptIn(ExperimentalAnimationApi::class)
23 | fun NavGraphBuilder.locationGraph(
24 | navHostController: NavHostController
25 | ) {
26 | navigation(
27 | startDestination = Locations.LOCATIONLIST,
28 | route = Locations.LOCATIONGRAPH
29 | ) {
30 |
31 | composable(route = Locations.LOCATIONLIST){
32 | LocationScreen()
33 | }
34 |
35 |
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/navigation/NavigationDestinations.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.navigation
2 |
3 | /** PariSa;
4 | coding and smoking ;)
5 | **/
6 |
7 | object Welcome {
8 | const val WELCOMEGRAPH = "welcome"
9 | }
10 |
11 | object Articles {
12 | const val ARTICLEGRAPH = "article"
13 | const val ARTICLESLIST = "articlelist"
14 | const val ARTICLEDETAILS = "articledetails"
15 | }
16 |
17 | object Locations {
18 | const val LOCATIONGRAPH = "location"
19 | const val LOCATIONLIST = "locationList"
20 | }
21 |
22 | object SETTINGS {
23 | const val SETTINGSGRAPH = "settings"
24 | }
25 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/navigation/Screens.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.navigation
2 |
3 | import dev.roshana.presentation.navigation.Articles.ARTICLESLIST
4 | import dev.roshana.presentation.navigation.Locations.LOCATIONLIST
5 | import dev.roshana.presentation.navigation.Welcome.WELCOMEGRAPH
6 |
7 | /** PariSa;
8 | coding and smoking ;)
9 | **/
10 |
11 | sealed class Screens(val route: String) {
12 | object WelcomeScreen : Screens(route = WELCOMEGRAPH)
13 | object ArticleListScreen : Screens(route = ARTICLESLIST)
14 | object LocationScreen : Screens(route = LOCATIONLIST)
15 | }
16 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/uiComponent/dialogs/DialogState.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.uiComponent.dialogs
2 |
3 | /** PariSa;
4 | coding and smoking ;)
5 | **/
6 |
7 | data class DialogState(var showDialog: Boolean, var dialogType: DialogType)
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/uiComponent/dialogs/DialogType.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.uiComponent.dialogs
2 |
3 | /** PariSa;
4 | coding and smoking ;)
5 | **/
6 |
7 | enum class DialogType {
8 | SIMPLE, TITLE, VERTICALBUTTON, IMAGE, LONGDIALOG, ROUNDED, DATEPICKER, TIMEPICKER
9 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/uiComponent/dialogs/Dialogs.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.uiComponent.dialogs
2 |
3 | import androidx.compose.animation.ExperimentalAnimationApi
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.width
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.material.*
11 | import androidx.compose.material.MaterialTheme.typography
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.draw.clip
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.platform.LocalContext
18 | import androidx.compose.ui.res.painterResource
19 | import androidx.compose.ui.unit.dp
20 | import androidx.fragment.app.FragmentActivity
21 | import com.google.android.material.datepicker.MaterialDatePicker
22 | import com.google.android.material.timepicker.MaterialTimePicker
23 | import dev.roshana.domain.R
24 | import dev.roshana.domain.models.dialogs.Item
25 | import kotlinx.coroutines.ExperimentalCoroutinesApi
26 |
27 | /** PariSa;
28 | coding and smoking ;)
29 | **/
30 |
31 | @OptIn(ExperimentalCoroutinesApi::class)
32 | @ExperimentalMaterialApi
33 | @ExperimentalAnimationApi
34 | @Composable
35 | fun ShowDialog(type: DialogType, onDismiss: () -> Unit) {
36 | val item = remember { item }
37 |
38 | when (type) {
39 | DialogType.SIMPLE ->
40 | AlertDialog(
41 | text = {
42 | Text(item.subtitle)
43 | },
44 | backgroundColor = Color.White,
45 | confirmButton = {
46 | TextButton(
47 | onClick = onDismiss,
48 | modifier = Modifier.padding(8.dp)
49 | ) {
50 | Text(text = "Ok")
51 | }
52 | },
53 | onDismissRequest = onDismiss
54 | )
55 | DialogType.TITLE ->
56 | AlertDialog(
57 | title = { Text(text = item.title, style = typography.h6) },
58 | text = {
59 | Text(item.subtitle)
60 | },
61 | confirmButton = {
62 | Row(modifier = Modifier) {
63 | TextButton(
64 | onClick = onDismiss,
65 | modifier = Modifier.padding(4.dp)
66 | ) {
67 | Text(text = "Cancel", color = Color.Gray)
68 | }
69 | TextButton(
70 | onClick = onDismiss,
71 | modifier = Modifier.padding(4.dp)
72 | ) {
73 | Text(text = "Ok")
74 | }
75 | }
76 | },
77 | onDismissRequest = onDismiss
78 | )
79 | DialogType.VERTICALBUTTON ->
80 | AlertDialog(
81 | title = { Text(text = item.title, style = typography.h6) },
82 | text = {
83 | Text(item.subtitle)
84 | },
85 | confirmButton = {
86 | OutlinedButton(
87 | onClick = onDismiss,
88 | modifier = Modifier
89 | .padding(8.dp)
90 | .width(100.dp)
91 | ) {
92 | Text(text = "Cancel")
93 | }
94 | Button(
95 | onClick = onDismiss,
96 | modifier = Modifier
97 | .padding(8.dp)
98 | .width(100.dp)
99 | ) {
100 | Text(text = "Ok")
101 | }
102 | },
103 | onDismissRequest = onDismiss
104 | )
105 | DialogType.IMAGE ->
106 | AlertDialog(
107 | title = { Text(text = item.title, style = typography.h6) },
108 | text = {
109 | Text(item.subtitle, modifier = Modifier.padding(bottom = 8.dp))
110 | Image(
111 | painter = painterResource(item.imageId),
112 | contentDescription = null
113 | )
114 | },
115 | confirmButton = {
116 | TextButton(
117 | onClick = onDismiss,
118 | modifier = Modifier.padding(8.dp)
119 | ) {
120 | Text(text = "Ok")
121 | }
122 | },
123 | onDismissRequest = onDismiss
124 | )
125 | DialogType.LONGDIALOG ->
126 | AlertDialog(
127 | title = { Text(text = item.title, style = typography.h6) },
128 | text = {
129 | Column {
130 | Text(item.subtitle, modifier = Modifier.padding(bottom = 8.dp))
131 | Image(
132 | painter = painterResource(item.imageId),
133 | contentDescription = null
134 | )
135 | Text(
136 | item.subtitle + item.title + item.subtitle + item.title,
137 | style = typography.subtitle2
138 | )
139 | }
140 | },
141 | confirmButton = {
142 | TextButton(
143 | onClick = onDismiss,
144 | modifier = Modifier.padding(8.dp)
145 | ) {
146 | Text(text = "Ok")
147 | }
148 | },
149 | onDismissRequest = onDismiss,
150 | )
151 | DialogType.ROUNDED ->
152 | AlertDialog(
153 | title = { Text(text = item.title, style = typography.h6) },
154 | text = {
155 | Column {
156 | Text(item.subtitle, modifier = Modifier.padding(bottom = 8.dp))
157 | Image(
158 | painter = painterResource(item.imageId),
159 | contentDescription = null,
160 | modifier = Modifier.clip(RoundedCornerShape(16.dp))
161 | )
162 | }
163 | },
164 | confirmButton = {
165 | TextButton(
166 | onClick = onDismiss,
167 | modifier = Modifier.padding(8.dp)
168 | ) {
169 | Text(text = "Ok")
170 | }
171 | },
172 | onDismissRequest = onDismiss,
173 | shape = RoundedCornerShape(16.dp)
174 | )
175 | DialogType.DATEPICKER -> {
176 | val context = LocalContext.current
177 | (context as? FragmentActivity)?.supportFragmentManager?.let { manager ->
178 |
179 | val builder = MaterialDatePicker.Builder.datePicker()
180 | .build()
181 |
182 | builder.addOnPositiveButtonClickListener { selectedDate ->
183 |
184 | }
185 | builder.addOnDismissListener {
186 | onDismiss()
187 | }
188 | builder.show(manager, "DatePicker")
189 | }
190 | }
191 |
192 | DialogType.TIMEPICKER -> {
193 | val context = LocalContext.current
194 | (context as? FragmentActivity)?.supportFragmentManager?.let { manager ->
195 | val builder = MaterialTimePicker.Builder()
196 | .build()
197 | builder.addOnPositiveButtonClickListener {
198 |
199 | }
200 | builder.addOnDismissListener {
201 | onDismiss.invoke()
202 | }
203 | builder.show(manager, "TimePicker")
204 | }
205 | }
206 |
207 | }
208 | }
209 |
210 | val item = Item(
211 | 1,
212 | "Awesome List Item",
213 | "Very awesome list item has very awesome subtitle. This is bit long",
214 | R.drawable.food6
215 | )
216 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/uiComponent/progressbar/DialogCircularProgressBar.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.uiComponent.progressbar
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.material.CircularProgressIndicator
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.unit.dp
13 | import androidx.compose.ui.window.Dialog
14 | import androidx.compose.ui.window.DialogProperties
15 |
16 | /** PariSa;
17 | coding and smoking ;)
18 | **/
19 |
20 | @Composable
21 | fun DialogCircularProgressBar(
22 | modifier: Modifier = Modifier
23 | ) {
24 | Dialog(
25 | onDismissRequest = { },
26 | DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
27 | ) {
28 | Box(
29 | contentAlignment = Alignment.Center,
30 | modifier = Modifier
31 | .size(100.dp)
32 | .background(Color.Transparent, shape = RoundedCornerShape(8.dp))
33 | ) {
34 | CircularProgressIndicator(
35 | color = Color.Cyan
36 | )
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/uiComponent/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.uiComponent.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple200 = Color(0xFFBB86FC)
6 | val Purple500 = Color(0xFF6200EE)
7 | val Purple700 = Color(0xFF3700B3)
8 | val Teal200 = Color(0xFF03DAC5)
9 | val lightBlue = Color(0xff2ec7e6)
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/uiComponent/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.uiComponent.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/uiComponent/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.uiComponent.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 |
9 | private val DarkColorPalette = darkColors(
10 | primary = Purple200,
11 | primaryVariant = Purple700,
12 | secondary = Teal200
13 | )
14 |
15 | private val LightColorPalette = lightColors(
16 | primary = Purple500,
17 | primaryVariant = Purple700,
18 | secondary = Teal200
19 |
20 | /* Other default colors to override
21 | background = Color.White,
22 | surface = Color.White,
23 | onPrimary = Color.White,
24 | onSecondary = Color.Black,
25 | onBackground = Color.Black,
26 | onSurface = Color.Black,
27 | */
28 | )
29 |
30 | @Composable
31 | fun JetpackComposeBaseArchTheme(
32 | darkTheme: Boolean = isSystemInDarkTheme(),
33 | content: @Composable () -> Unit
34 | ) {
35 | val colors = if (darkTheme) {
36 | DarkColorPalette
37 | } else {
38 | LightColorPalette
39 | }
40 |
41 | MaterialTheme(
42 | colors = colors,
43 | typography = Typography,
44 | shapes = Shapes,
45 | content = content
46 | )
47 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/uiComponent/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.uiComponent.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/welcomeUi/OnBoardingPage.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.welcomeUi
2 |
3 | import androidx.annotation.DrawableRes
4 | import dev.roshana.domain.R
5 |
6 | /** PariSa;
7 | coding and smoking ;)
8 | **/
9 |
10 | sealed class OnBoardingPage(
11 | @DrawableRes
12 | val image: Int,
13 | val title: String,
14 | val description: String
15 | ) {
16 | object First : OnBoardingPage(
17 | image = R.drawable.first,
18 | title = "Meeting",
19 | description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod."
20 | )
21 |
22 | object Second : OnBoardingPage(
23 | image = R.drawable.second,
24 | title = "Coordination",
25 | description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod."
26 | )
27 |
28 | object Third : OnBoardingPage(
29 | image = R.drawable.third,
30 | title = "Dialogue",
31 | description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod."
32 | )
33 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/welcomeUi/WelcomeScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.welcomeUi
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.ExperimentalAnimationApi
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.material.*
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.platform.LocalContext
13 | import androidx.compose.ui.res.painterResource
14 | import androidx.compose.ui.text.font.FontWeight
15 | import androidx.compose.ui.text.style.TextAlign
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.dp
18 | import androidx.hilt.navigation.compose.hiltViewModel
19 | import androidx.navigation.NavHostController
20 | import com.google.accompanist.pager.*
21 | import dev.roshana.presentation.navigation.Screens
22 | import dev.roshana.presentation.uiComponent.ui.theme.lightBlue
23 | import kotlinx.coroutines.ExperimentalCoroutinesApi
24 |
25 | /** PariSa;
26 | coding and smoking ;)
27 | **/
28 |
29 | @OptIn(ExperimentalCoroutinesApi::class, ExperimentalPagerApi::class)
30 | @ExperimentalMaterialApi
31 | @ExperimentalAnimationApi
32 | @Composable
33 | fun WelcomeScreen(navHostController: NavHostController) {
34 | val context = LocalContext.current
35 | val viewModel: WelcomeViewModel = hiltViewModel()
36 |
37 | val pages = listOf(
38 | OnBoardingPage.First,
39 | OnBoardingPage.Second,
40 | OnBoardingPage.Third,
41 | )
42 |
43 | val pagerState = rememberPagerState()
44 |
45 | Column(
46 | modifier = Modifier.fillMaxSize(),
47 | ) {
48 |
49 | HorizontalPager(
50 | modifier = Modifier.weight(10f),
51 | count = 3,
52 | state = pagerState,
53 | verticalAlignment = Alignment.Top,
54 | ) { position ->
55 | PagerScreen(onBoardingPage = pages[position])
56 | }
57 |
58 | HorizontalPagerIndicator(
59 | pagerState = pagerState,
60 | modifier = Modifier
61 | .align(Alignment.CenterHorizontally)
62 | .weight(1f)
63 | )
64 |
65 | FinishButton(modifier = Modifier.weight(1f), pagerState = pagerState) {
66 | // Note : need to pop back to close app from home page
67 | // otherwise it will return to last onBoarding page when you click back.
68 | viewModel.saveOnBoardingState(completed = true)
69 | navHostController.popBackStack()
70 | navHostController.navigate(Screens.ArticleListScreen.route)
71 | }
72 | }
73 |
74 | }
75 |
76 | @Composable
77 | fun PagerScreen(onBoardingPage: OnBoardingPage) {
78 | Column(
79 | modifier = Modifier
80 | .fillMaxWidth(),
81 | horizontalAlignment = Alignment.CenterHorizontally,
82 | verticalArrangement = Arrangement.Top
83 | ) {
84 | Image(
85 | modifier = Modifier
86 | .fillMaxWidth(0.5f)
87 | .fillMaxHeight(0.7f),
88 | painter = painterResource(id = onBoardingPage.image),
89 | contentDescription = "Pager Image"
90 | )
91 | Text(
92 | modifier = Modifier
93 | .fillMaxWidth(),
94 | text = onBoardingPage.title,
95 | fontSize = MaterialTheme.typography.h4.fontSize,
96 | fontWeight = FontWeight.Bold,
97 | textAlign = TextAlign.Center
98 | )
99 | Text(
100 | modifier = Modifier
101 | .fillMaxWidth()
102 | .padding(horizontal = 40.dp)
103 | .padding(top = 20.dp),
104 | text = onBoardingPage.description,
105 | fontSize = MaterialTheme.typography.subtitle1.fontSize,
106 | fontWeight = FontWeight.Medium,
107 | textAlign = TextAlign.Center
108 | )
109 | }
110 | }
111 |
112 | @ExperimentalAnimationApi
113 | @ExperimentalPagerApi
114 | @Composable
115 | fun FinishButton(
116 | modifier: Modifier,
117 | pagerState: PagerState,
118 | onClick: () -> Unit
119 | ) {
120 | Row(
121 | modifier = modifier
122 | .padding(horizontal = 40.dp),
123 | verticalAlignment = Alignment.Top,
124 | horizontalArrangement = Arrangement.Center
125 | ) {
126 | AnimatedVisibility(
127 | modifier = Modifier.fillMaxWidth(),
128 | visible = pagerState.currentPage == 2
129 | ) {
130 | Button(
131 | onClick = onClick,
132 | colors = ButtonDefaults.buttonColors(
133 | backgroundColor = lightBlue,
134 | contentColor = Color.White
135 | )
136 | ) {
137 | Text(text = "Finish")
138 | }
139 | }
140 | }
141 | }
142 |
143 | @Composable
144 | @Preview(showBackground = true)
145 | fun FirstOnBoardingScreenPreview() {
146 | Column(modifier = Modifier.fillMaxSize()) {
147 | PagerScreen(onBoardingPage = OnBoardingPage.First)
148 | }
149 | }
150 |
151 | @Composable
152 | @Preview(showBackground = true)
153 | fun SecondOnBoardingScreenPreview() {
154 | Column(modifier = Modifier.fillMaxSize()) {
155 | PagerScreen(onBoardingPage = OnBoardingPage.Second)
156 | }
157 | }
158 |
159 | @Composable
160 | @Preview(showBackground = true)
161 | fun ThirdOnBoardingScreenPreview() {
162 | Column(modifier = Modifier.fillMaxSize()) {
163 | PagerScreen(onBoardingPage = OnBoardingPage.Third)
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/welcomeUi/WelcomeState.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.welcomeUi
2 |
3 | import dev.roshana.domain.models.DataState
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | /** PariSa;
7 | coding and smoking ;)
8 | **/
9 |
10 | class WelcomeState(
11 | var loading: Boolean? = true,
12 | var dataList: Flow? = null,
13 | var errorMsg: String? = ""
14 | ) : DataState>(
15 | isLoading = loading == false,
16 | data = dataList,
17 | errorMessage = errorMsg!!
18 | )
19 |
--------------------------------------------------------------------------------
/presentation/src/main/java/dev/roshana/presentation/welcomeUi/WelcomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation.welcomeUi
2 |
3 | import androidx.compose.runtime.MutableState
4 | import androidx.compose.runtime.State
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import dev.roshana.domain.usecases.DataStoreUseCase
10 | import dev.roshana.presentation.navigation.Screens
11 | import kotlinx.coroutines.CoroutineExceptionHandler
12 | import kotlinx.coroutines.flow.Flow
13 | import kotlinx.coroutines.launch
14 | import javax.inject.Inject
15 |
16 | /** PariSa;
17 | coding and smoking ;)
18 | **/
19 |
20 | @HiltViewModel
21 | class WelcomeViewModel @Inject constructor(
22 | private val dataStoreUseCase: DataStoreUseCase
23 | ) : ViewModel() {
24 |
25 | private val _onBoardingState: MutableState = mutableStateOf(false)
26 | val onBoardingState: State
27 | get() = _onBoardingState
28 |
29 | private val _welcomeState: MutableState = mutableStateOf(WelcomeState())
30 | val welcomeState: State
31 | get() = _welcomeState
32 |
33 | private val _startDestination: MutableState =
34 | mutableStateOf(Screens.WelcomeScreen.route)
35 | val startDestination: State
36 | get() = _startDestination
37 |
38 |
39 | private val handler = CoroutineExceptionHandler { _, exception ->
40 | _welcomeState.value.loading = false
41 | _welcomeState.value = WelcomeState(
42 | errorMsg = exception.message!!
43 | )
44 | }
45 |
46 |
47 | init {
48 | readOnBoardingState()
49 | }
50 |
51 |
52 | fun saveOnBoardingState(completed: Boolean) {
53 | viewModelScope.launch {
54 | dataStoreUseCase.invoke(completed)
55 | }
56 | }
57 |
58 | private fun readOnBoardingState() {
59 | viewModelScope.launch(handler) {
60 | dataStoreUseCase.invoke().collect { completed ->
61 | if (completed) {
62 | _startDestination.value = Screens.ArticleListScreen.route
63 | } else {
64 | _startDestination.value = Screens.WelcomeScreen.route
65 | }
66 | }
67 | _welcomeState.value.loading = false
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/presentation/src/test/java/dev/roshana/presentation/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dev.roshana.presentation
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "JetpackComposeBaseArch"
16 | include(":app")
17 |
18 | include(":data")
19 | include(":domain")
20 | include(":presentation")
21 |
--------------------------------------------------------------------------------