├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── gradle.xml ├── misc.xml ├── shelf │ ├── Uncommitted_changes_before_Update_at_2_13_2023_7_58_AM_[Changes] │ │ └── shelved.patch │ └── Uncommitted_changes_before_Update_at_2_13_2023_7_58_AM__Changes_.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── mvvmKotlinJetpackCompose │ │ ├── ExampleInstrumentedTest.kt │ │ └── ui │ │ ├── BaseInstrument.kt │ │ ├── dashboard │ │ └── DashboardActivityTest.kt │ │ ├── login │ │ └── LoginActivityTest.kt │ │ └── splash │ │ └── SplashActivityTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── mvvmKotlinJetpackCompose │ │ │ ├── MvvmKotlinJetpackCompose.kt │ │ │ ├── data │ │ │ ├── network │ │ │ │ ├── ApiHeader.kt │ │ │ │ ├── ApiHelper.kt │ │ │ │ ├── AppApiHelper.kt │ │ │ │ ├── Resource.kt │ │ │ │ ├── Service.kt │ │ │ │ ├── ServiceGenerator.kt │ │ │ │ ├── model │ │ │ │ │ ├── DashboardResponse.kt │ │ │ │ │ ├── Data.kt │ │ │ │ │ ├── LoginRequest.kt │ │ │ │ │ └── LoginResponse.kt │ │ │ │ └── moshiFactories │ │ │ │ │ ├── MyKotlinJsonAdapter.kt │ │ │ │ │ └── MyStandardJsonAdapters.java │ │ │ ├── others │ │ │ │ └── MenuItem.kt │ │ │ ├── prefs │ │ │ │ ├── AppPreferencesHelper.kt │ │ │ │ └── PreferencesHelper.kt │ │ │ └── repos │ │ │ │ ├── DashboardRepository.kt │ │ │ │ └── LoginRepository.kt │ │ │ ├── di │ │ │ ├── AppModule.kt │ │ │ ├── EmptyString.java │ │ │ ├── ExternalLibAppModule.kt │ │ │ ├── PreferenceInfo.java │ │ │ └── login │ │ │ │ ├── LoginComponent.kt │ │ │ │ ├── LoginComponentBuilder.kt │ │ │ │ ├── LoginComponentManager.kt │ │ │ │ ├── LoginEntryPoint.kt │ │ │ │ └── LoginScope.kt │ │ │ ├── ui │ │ │ ├── ViewModelFactory.kt │ │ │ ├── base │ │ │ │ ├── BaseComponentActivity.kt │ │ │ │ ├── BaseRepository.kt │ │ │ │ ├── BaseUseCase.kt │ │ │ │ ├── BaseViewModel.kt │ │ │ │ ├── BaseViewModelRepository.kt │ │ │ │ └── BaseViewModelUseCase.kt │ │ │ ├── dashboard │ │ │ │ ├── DashboardActivity.kt │ │ │ │ ├── DashboardUseCase.kt │ │ │ │ ├── DashboardViewModel.kt │ │ │ │ └── compose │ │ │ │ │ ├── BottomNavData.kt │ │ │ │ │ ├── DashboardCompose.kt │ │ │ │ │ ├── DrawerCompose.kt │ │ │ │ │ ├── NavDrawerItem.kt │ │ │ │ │ └── TopBarCompose.kt │ │ │ ├── login │ │ │ │ ├── LoginActivity.kt │ │ │ │ └── LoginViewModel.kt │ │ │ ├── splash │ │ │ │ ├── SplashActivity.kt │ │ │ │ └── SplashViewModel.kt │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Shape.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── util │ │ │ ├── AppConstants.kt │ │ │ ├── LiveDataExt.kt │ │ │ ├── NetworkUtils.kt │ │ │ ├── SingleEvent.kt │ │ │ └── coroutines │ │ │ ├── AppDispatcherProvider.kt │ │ │ └── DispatcherProvider.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_baseline_history_24.xml │ │ ├── ic_baseline_home_24.xml │ │ ├── ic_baseline_lock_24.xml │ │ ├── ic_baseline_person_24.xml │ │ ├── ic_baseline_settings_24.xml │ │ ├── ic_billpayment.png │ │ ├── ic_bus.png │ │ ├── ic_datacard.png │ │ ├── ic_deposit.png │ │ ├── ic_dtf.png │ │ ├── ic_electricity.png │ │ ├── ic_flight.png │ │ ├── ic_fund_recieve.png │ │ ├── ic_gas.png │ │ ├── ic_home.png │ │ ├── ic_hotel_booking.png │ │ ├── ic_kbit.png │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_logout.png │ │ ├── ic_passbook.png │ │ ├── ic_postpaid.png │ │ ├── ic_prepaid.png │ │ ├── ic_qr_code.png │ │ ├── ic_upi.png │ │ ├── ic_water.png │ │ └── jetpack_logo.png │ │ ├── font │ │ └── averta_regular.ttf │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ ├── java │ └── com │ │ └── example │ │ └── mvvmKotlinJetpackCompose │ │ ├── BaseViewModelRepositoryTest.kt │ │ ├── BaseViewModelUseCaseTest.kt │ │ ├── TestDataClassGenerator.kt │ │ ├── data │ │ └── network │ │ │ └── AppApiHelperTest.kt │ │ ├── ui │ │ ├── dashboard │ │ │ └── DashboardViewModelTest.kt │ │ ├── login │ │ │ ├── LoginRepositoryTest.kt │ │ │ └── LoginViewModelTest.kt │ │ └── splash │ │ │ └── SplashViewModelViewModelRepositoryTest.kt │ │ └── util │ │ └── coroutines │ │ ├── CoroutineTestRule.kt │ │ ├── MainCoroutineRule.kt │ │ └── TestDispatcherProvider.kt │ └── resources │ ├── LoginApiResponse.json │ └── LoginFailedApiResponse.json ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/shelf/Uncommitted_changes_before_Update_at_2_13_2023_7_58_AM_[Changes]/shelved.patch: -------------------------------------------------------------------------------- 1 | Index: app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/AppModule.kt 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | diff --git a/app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/AppModule.kt b/app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/ExternalLibAppModule.kt 7 | rename from app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/AppModule.kt 8 | rename to app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/ExternalLibAppModule.kt 9 | --- a/app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/AppModule.kt 10 | +++ b/app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/ExternalLibAppModule.kt 11 | @@ -1,84 +1,93 @@ 12 | package com.example.mvvmKotlinJetpackCompose.di 13 | 14 | -import com.example.mvvmKotlinJetpackCompose.data.network.ApiHeader 15 | -import com.example.mvvmKotlinJetpackCompose.data.network.ApiHelper 16 | -import com.example.mvvmKotlinJetpackCompose.data.network.AppApiHelper 17 | -import com.example.mvvmKotlinJetpackCompose.data.prefs.AppPreferencesHelper 18 | -import com.example.mvvmKotlinJetpackCompose.data.prefs.PreferencesHelper 19 | -import com.example.mvvmKotlinJetpackCompose.ui.base.BaseRepository 20 | -import com.example.mvvmKotlinJetpackCompose.ui.dashboard.DashboardRepo 21 | -import com.example.mvvmKotlinJetpackCompose.ui.login.RegistrationRepo 22 | -import com.example.mvvmKotlinJetpackCompose.util.PREF_NAME 23 | -import com.example.mvvmKotlinJetpackCompose.util.coroutines.AppDispatcherProvider 24 | -import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 25 | +import com.example.mvvmKotlinJetpackCompose.BuildConfig 26 | +import com.example.mvvmKotlinJetpackCompose.data.network.* 27 | +import com.example.mvvmKotlinJetpackCompose.data.network.moshiFactories.MyKotlinJsonAdapterFactory 28 | +import com.example.mvvmKotlinJetpackCompose.data.network.moshiFactories.MyStandardJsonAdapters 29 | +import com.squareup.moshi.Moshi 30 | import dagger.Provides 31 | import dagger.Module 32 | import dagger.hilt.InstallIn 33 | import dagger.hilt.components.SingletonComponent 34 | +import okhttp3.Interceptor 35 | +import okhttp3.OkHttpClient 36 | +import okhttp3.logging.HttpLoggingInterceptor 37 | +import retrofit2.Retrofit 38 | +import retrofit2.converter.moshi.MoshiConverterFactory 39 | +import java.util.concurrent.TimeUnit 40 | import javax.inject.Singleton 41 | 42 | @Module 43 | @InstallIn(SingletonComponent::class) 44 | - class AppModule { 45 | - 46 | + class ExternalLibAppModule { 47 | 48 | - @Provides 49 | - @Singleton 50 | - fun provideApiHelper(apiHelper: AppApiHelper): ApiHelper { 51 | - return apiHelper 52 | - } 53 | + @Provides 54 | + @Singleton 55 | + fun provideHeaderInterceptor(protectedApiHeader : ApiHeader.ProtectedApiHeader): Interceptor{ 56 | + return Interceptor { chain -> 57 | + val original = chain.request() 58 | + val request = original.newBuilder() 59 | + .header(contentType, contentTypeValue) 60 | + .header("Authorization", "Bearer " + protectedApiHeader.accessToken) 61 | + .method(original.method, original.body) 62 | + .build() 63 | + 64 | + chain.proceed(request) 65 | + } 66 | 67 | - @Provides 68 | - @PreferenceInfo 69 | - fun providePreferenceName(): String { 70 | - return PREF_NAME 71 | - } 72 | + } 73 | 74 | - @Provides 75 | - @ApiInfo 76 | - fun provideApiKey(): String { 77 | - return "" 78 | - } 79 | - 80 | - 81 | - @Provides 82 | - @Singleton 83 | - fun provideProtectedApiHeader(@ApiInfo apiKey:String,preferencesHelper : PreferencesHelper) 84 | - : ApiHeader.ProtectedApiHeader{ 85 | - return ApiHeader.ProtectedApiHeader( 86 | - preferencesHelper.getAccessToken() ?:apiKey, 87 | - preferencesHelper.getCurrentUserId(), 88 | - preferencesHelper.getAccessToken()) 89 | - } 90 | + @Provides 91 | + @Singleton 92 | + fun provideLoggingInterceptor(): HttpLoggingInterceptor { 93 | + return HttpLoggingInterceptor().apply { 94 | + if (BuildConfig.DEBUG) { 95 | + level = HttpLoggingInterceptor.Level.BODY 96 | + } 97 | + } 98 | + } 99 | + 100 | 101 | - @Provides 102 | - @Singleton 103 | - fun providePreferenceHelper(appPreferencesHelper: AppPreferencesHelper): PreferencesHelper { 104 | - return appPreferencesHelper 105 | - } 106 | - 107 | + @Provides 108 | + @Singleton 109 | + fun provideRetrofitClient(headerInterceptor : Interceptor,loggingInterceptor: HttpLoggingInterceptor) : OkHttpClient.Builder{ 110 | + 111 | + val okHttpBuilder: OkHttpClient.Builder = OkHttpClient.Builder() 112 | + okHttpBuilder.addInterceptor(headerInterceptor) 113 | + okHttpBuilder.addInterceptor(loggingInterceptor) 114 | + okHttpBuilder.connectTimeout(timeoutConnect.toLong(), TimeUnit.SECONDS) 115 | + okHttpBuilder.readTimeout(timeoutRead.toLong(), TimeUnit.SECONDS) 116 | + return okHttpBuilder 117 | + 118 | + } 119 | 120 | @Provides 121 | @Singleton 122 | - fun provideDispatcher(dispatcherProvider: AppDispatcherProvider): DispatcherProvider { 123 | - return dispatcherProvider 124 | - } 125 | - 126 | + fun provideRetrofit(okHttpBuilder: OkHttpClient.Builder, moshi: Moshi):Retrofit{ 127 | + return Retrofit.Builder() 128 | + .baseUrl(baseUrl) 129 | + .client(okHttpBuilder.build()) 130 | + .addConverterFactory(MoshiConverterFactory.create(moshi)) 131 | + .build() 132 | + } 133 | 134 | @Provides 135 | @Singleton 136 | -// @RegistrationScope 137 | - fun provideRegistrationRepo(registrationRepo: RegistrationRepo): BaseRepository { 138 | - return registrationRepo 139 | + fun provideService(retrofit: Retrofit): Service { 140 | + return retrofit.create(Service::class.java) 141 | } 142 | + 143 | 144 | @Provides 145 | - @Singleton 146 | - fun provideDashboardRepo(dashboardRepo: DashboardRepo): BaseRepository { 147 | - return dashboardRepo 148 | - } 149 | + @Singleton 150 | + fun provideMoshi():Moshi{ 151 | + return Moshi.Builder() 152 | + .add(MyStandardJsonAdapters.FACTORY) 153 | + .add(MyKotlinJsonAdapterFactory()).build() 154 | + } 155 | 156 | 157 | 158 | 159 | + 160 | } 161 | \ No newline at end of file 162 | Index: app/src/test/java/com/example/mvvmKotlinJetpackCompose/ui/login/RegistrationRepoTest.kt 163 | IDEA additional info: 164 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 165 | <+>UTF-8 166 | =================================================================== 167 | diff --git a/app/src/test/java/com/example/mvvmKotlinJetpackCompose/ui/login/RegistrationRepoTest.kt b/app/src/test/java/com/example/mvvmKotlinJetpackCompose/ui/login/LoginRepoTest.kt 168 | rename from app/src/test/java/com/example/mvvmKotlinJetpackCompose/ui/login/RegistrationRepoTest.kt 169 | rename to app/src/test/java/com/example/mvvmKotlinJetpackCompose/ui/login/LoginRepoTest.kt 170 | --- a/app/src/test/java/com/example/mvvmKotlinJetpackCompose/ui/login/RegistrationRepoTest.kt 171 | +++ b/app/src/test/java/com/example/mvvmKotlinJetpackCompose/ui/login/LoginRepoTest.kt 172 | @@ -14,14 +14,14 @@ 173 | import org.junit.Before 174 | import org.junit.Test 175 | 176 | -class RegistrationRepoTest { 177 | +class LoginRepoTest { 178 | 179 | 180 | lateinit var apiHelper: ApiHelper 181 | 182 | lateinit var preferencesHelper: PreferencesHelper 183 | 184 | - lateinit var repoUnderTest: RegistrationRepo 185 | + lateinit var repoUnderTest: LoginRepo 186 | 187 | protected val testDataClassGenerator: TestDataClassGenerator = TestDataClassGenerator() 188 | 189 | @@ -29,7 +29,7 @@ 190 | fun setTup() { 191 | apiHelper = mockk(relaxUnitFun = true) 192 | preferencesHelper = mockk(relaxUnitFun = true) 193 | - repoUnderTest = RegistrationRepo(apiHelper, preferencesHelper) 194 | + repoUnderTest = LoginRepo(apiHelper, preferencesHelper) 195 | 196 | } 197 | 198 | Index: app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/login/RegistrationRepo.kt 199 | IDEA additional info: 200 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 201 | <+>UTF-8 202 | =================================================================== 203 | diff --git a/app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/login/RegistrationRepo.kt b/app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/login/LoginRepo.kt 204 | rename from app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/login/RegistrationRepo.kt 205 | rename to app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/login/LoginRepo.kt 206 | --- a/app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/login/RegistrationRepo.kt 207 | +++ b/app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/login/LoginRepo.kt 208 | @@ -5,16 +5,25 @@ 209 | import com.example.mvvmKotlinJetpackCompose.data.network.Resource 210 | import com.example.mvvmKotlinJetpackCompose.data.network.model.LoginResponse 211 | import com.example.mvvmKotlinJetpackCompose.data.prefs.PreferencesHelper 212 | +import com.example.mvvmKotlinJetpackCompose.di.login.LoginScope 213 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseRepository 214 | import kotlinx.coroutines.delay 215 | import kotlinx.coroutines.flow.Flow 216 | import kotlinx.coroutines.flow.flow 217 | import javax.inject.Inject 218 | 219 | -class RegistrationRepo @Inject constructor( 220 | +@LoginScope 221 | +class LoginRepo @Inject constructor( 222 | apiHelper: ApiHelper, 223 | preferencesHelper: PreferencesHelper, 224 | ) : BaseRepository(apiHelper, preferencesHelper) { 225 | + var printStatus = 0 226 | + 227 | + fun print() { 228 | + printStatus++ 229 | + println("login repo print $printStatus") 230 | + 231 | + } 232 | 233 | 234 | fun login(email: String, password: String): Flow> { 235 | Index: app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/ApiInfo.java 236 | IDEA additional info: 237 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 238 | <+>UTF-8 239 | =================================================================== 240 | diff --git a/app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/ApiInfo.java b/app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/EmptyString.java 241 | rename from app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/ApiInfo.java 242 | rename to app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/EmptyString.java 243 | --- a/app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/ApiInfo.java 244 | +++ b/app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/EmptyString.java 245 | @@ -9,5 +9,5 @@ 246 | 247 | @Qualifier 248 | @Retention(RetentionPolicy.RUNTIME) 249 | -public @interface ApiInfo { 250 | +public @interface EmptyString { 251 | } 252 | -------------------------------------------------------------------------------- /.idea/shelf/Uncommitted_changes_before_Update_at_2_13_2023_7_58_AM__Changes_.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2021] [Rizwan Sayyed] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # MvvmKotlinJetpackCompose 3 | 4 | 5 | alt text      alt text      alt text 6 | 7 | alt text      alt text      alt text 8 | 9 | 10 | 11 | ## Why do we need an architecture even when you can make an app without it? 12 | 13 | let's say you created a project without any architecture simply doing all the things in the activity class. ie calling API, database interaction, parsing logic etc 14 | even though you have created separate classes for those, your Activity class is handling multiple things, which makes it god object, doing all the things alone 15 | now your app is published and users are using it, Suddenly there is a change in your requirement, for example, UI changes, 16 | 17 | Now your activity is so tightly coupled to other functionality like parsing logic, API calls and database interaction, that changing your UI will take more time, also we have a 18 | risk of introducing new bugs along the way if we did something wrong, it becomes hard to maintain difficult to track bugs and we can't reuse the activity, also changing activity UI unnecessarily affecting other functionalities, managing codebase becomes a headache. 19 | 20 | Architecture Solves this Problem 21 | 22 | 23 | ## Architectue 24 | 25 | An Architecture Makes your project Robust, Extensible, Maintainable, Scalable, Reusable and Testable(Unit testing). 26 | it's an organizational structure of a system including its decomposition(breaking down a complex problem or system into smaller parts) into parts, their connectivity, interaction mechanisms, and the guiding principles and decisions that you use in the design of a system, architecture reduces chaos 27 | 28 | 29 | it becomes easy to 30 | add features, 31 | make changes, 32 | write Unit test cases, 33 | maintain and reuse 34 | 35 | ## How? 36 | 37 | Because it follows Some Principles known as SOLID. 38 | A design principle is a technique that can be applied to designing or writing code to make that code more maintainable, flexible and extensible 39 | 40 | SOLID is an acronym for. 41 | 42 | ## S - Single Responsibility Principle(SRP) 43 | A class should have only one reason to change the easiest way to make your software resilient to change is to make each class has only one reason to change. 44 | 45 | How do you know if your class has only one responsibility to change? 46 | compare Class name with methods(behaviour) look through the methods of your classes do they all relate to the name of your class, if you have a method that looks out of place it might belong to another class 47 | 48 | for example - 49 | A car class can have a start method, it can start, it can break, can it change its tires no .. that is the job of another object 50 | In android, we keep our Activity methods related to Ui nothing else it has only one responsibility which is rendering and maintaining the state of the UI 51 | Another Example can be AppPreferenceHelper class which has only one responsibility interacting with Shared Preferences 52 | 53 | ## O - Open close Principle(OCP) 54 | A class should be open for extension but closed for modification, OCP lets you extend your working code, without changing that code, lets take an example of BaseActivity 55 | which has ShowLoading() Method that shows a circular progress bar which is used by all other SubActivities, Now in one Activity You don't want to show circular progress, you would 56 | like to show a shimmer so for that we can easily override the method from our BaseActivity and provide specific implementation to our subActivity to show shimmer, OCP at its work, we extended functionality without modifying our BaseClass 57 | 58 | Inheritance is a way to apply the open-closed principle the other way is defining private methods which closed for modification and using it in public methods to extend their behaviour 59 | 60 | ## L - Liskov substituion Principle(LSP) 61 | The LSP is all about well-designed inheritance. when you inherit from a base class, you must be able to substitute your subclass for that base class without things going terribly wrong, otherwise, you have used inheritance incorrectly, which means you with LSP you can store Child Object in Parent Class Variable (Animal animal=new Tiger() ) 62 | 63 | Lsp reveals hidden inheritance problem 64 | if subtypes cannot substitute for base type then your inheritance has problems, inheritance (and the LSP) indicate that any method on the Base class should be able to be used on the subclass 65 | 66 | it's hard to understand the code that misuses inheritance, if you have used inheritance badly, then you are going to end up with a lot of methods that u dont want because they probably do not make sense to your subclasses following LSP will solve the problem 67 | 68 | ## I - Interface Segregation Principle(ISP) 69 | A class should not implement an interface that it does not use, A class should not override methods that do nothing or are empty. if that is the case consider creating another interface for that empty methods and using them where it's needed, so the class which does not need to have methods, it does not use. 70 | 71 | ## D - Dependency Inversion Principle(DIP) 72 | Classes should depend on abstraction not on concreate types, when interacting in between 2 classes or modules (higher-level module and lower-level modules) you should depend on an interface not on a concrete class, with this you reduced the dependencies between your modules or classes and it becomes loosely coupled, loosely couple classes can easily take out from one project and can use in another project without unnecessary changes. 73 | 74 | building an app that works but is poorly designed satisfies the customer but will leave you with pain, suffering, and lots of late-night fixing problems. 75 | 76 | There are many other Design principles as well 77 | 1)favour coding to the interface, not the implementation 78 | 2)Do not repeat yourself (DRY) 79 | 3)Principle of least knowledge etc 80 | 81 | if you want to learn more about these topics i would highly recommend reading these books 82 | 83 | 1)https://www.amazon.in/Head-First-Object-Oriented-Analysis-Design/dp/8184042213 84 | 2)https://www.amazon.in/Head-First-Design-Patterns-Object-Oriented/dp/9385889753/ref=pd_bxgy_img_1/257-3183565-0278961?pd_rd_w=3Nel7&pf_rd_p=e23e9300-1f33-4d32-86fc-f4fcff7c4b49&pf_rd_r=K311FD7EF9DDBZ8NRVGM&pd_rd_r=77d901f5-c7b2-4c83-98a5-d6341d45d494&pd_rd_wg=32sm9&pd_rd_i=9385889753&psc=1 85 | 86 | ## Jetpack Compose 87 | It's a declarative style API, which means instead of creating the layout and initializing it you tell compose how your UI should look like and compose compiler will create UI for you. 88 | which recompose itself when data changes mean you dont have to explicitly change the UI state like we used to do with the Android view System(XML) 89 | 90 | 1)state hoisting is a pattern of moving state up to make a component stateless 91 | i) it's easier to test 92 | ii)tend to have fewer bugs, open up more opportunities to reuse 93 | 94 | 2)Avoid passing ViewModel reference to composing instead pass raw data, helps to render preview(spend a lot of time on this) 95 | 96 | 3)Pass Child composable in Parent Composable method as the parameter this way you can make the composable stateless 97 | 98 | ## Unit test 99 | Why do we need a Unit test if we can test it manually on the device, Unit testing has several benefits 100 | 101 | 1)it enforces you to have a good app architecture without good architecture you can't Unit test 102 | 2)let say you have added functionality to a class that already has its unit test in place, now you will verify by running all existing Tests so to check that you did not break anything else when adding new Functionality, it gives you confidence that your code is working. 103 | 3)There are 2 cases where a Unit test Fails 104 | i)when it does not meet the requirement (when we write the test the first time) 105 | ii)when the requirement is changed (then you have to change test cases as well) 106 | 107 | Don't pass context in viewmodel it makes it easier to test 108 | 109 | 110 | ## License 111 | 112 | This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details. 113 | 114 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 115 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | id 'dagger.hilt.android.plugin' 6 | } 7 | 8 | android { 9 | compileSdk 33 10 | 11 | defaultConfig { 12 | applicationId "com.example.MvvmKotlinJetpackCompose" 13 | minSdk 21 14 | targetSdk 33 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | vectorDrawables { 20 | useSupportLibrary true 21 | } 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | kotlinOptions { 35 | jvmTarget = '1.8' 36 | } 37 | buildFeatures { 38 | compose true 39 | } 40 | composeOptions { 41 | kotlinCompilerExtensionVersion '1.4.0' 42 | kotlinCompilerVersion '1.7.20' 43 | } 44 | packagingOptions { 45 | resources { 46 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 47 | } 48 | } 49 | kapt { 50 | correctErrorTypes true 51 | 52 | } 53 | } 54 | 55 | dependencies { 56 | 57 | implementation 'androidx.core:core-ktx:1.9.0' 58 | implementation 'androidx.appcompat:appcompat:1.6.0' 59 | implementation 'com.google.android.material:material:1.8.0' 60 | implementation "org.jetbrains.kotlin:kotlin-reflect:1.7.20" 61 | // 'do not update above library for now it will throw build error' 62 | 63 | 64 | //compose 65 | implementation "androidx.compose.ui:ui:1.3.3" 66 | implementation "androidx.compose.material:material:1.3.1" 67 | implementation "androidx.compose.ui:ui-tooling-preview:1.3.3" 68 | implementation 'androidx.activity:activity-compose:1.6.1' 69 | implementation "androidx.compose.runtime:runtime-livedata:1.3.3" 70 | 71 | debugImplementation "androidx.compose.ui:ui-tooling:1.3.3" 72 | implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1" 73 | implementation "androidx.navigation:navigation-compose:2.6.0-alpha04" 74 | 75 | 76 | //hilt DI 77 | implementation "com.google.dagger:hilt-android:$hilt_version" 78 | kapt "com.google.dagger:hilt-compiler:$hilt_version" 79 | kapt "com.google.dagger:hilt-android-compiler:$hilt_version" 80 | 81 | //lifecycle 82 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' 83 | 84 | //coroutines 85 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" 86 | 87 | //retrofit 88 | implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion" 89 | implementation "com.squareup.moshi:moshi:$moshiVersion" 90 | kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" 91 | implementation "com.squareup.okhttp3:logging-interceptor:$okhttpInterceptorVersion" 92 | 93 | 94 | //test 95 | testImplementation 'junit:junit:4.13.2' 96 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_test_version" 97 | testImplementation 'io.mockk:mockk:1.12.0' 98 | testImplementation 'androidx.arch.core:core-testing:2.1.0' 99 | 100 | //integration test 101 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 102 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.3.3" 103 | androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version" 104 | kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version" 105 | } -------------------------------------------------------------------------------- /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. 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/com/example/mvvmKotlinJetpackCompose/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose 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("com.example.liquorcn", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/mvvmKotlinJetpackCompose/ui/BaseInstrument.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui 2 | 3 | import android.app.Activity 4 | import androidx.activity.ComponentActivity 5 | import androidx.compose.foundation.ExperimentalFoundationApi 6 | import androidx.compose.ui.test.junit4.AndroidComposeTestRule 7 | import androidx.compose.ui.test.junit4.createAndroidComposeRule 8 | import androidx.test.ext.junit.rules.ActivityScenarioRule 9 | import com.example.mvvmKotlinJetpackCompose.ui.splash.SplashActivity 10 | import org.junit.Before 11 | import org.junit.Rule 12 | 13 | @ExperimentalFoundationApi 14 | abstract class BaseInstrument { 15 | 16 | 17 | 18 | @Before 19 | abstract fun setUp() 20 | 21 | 22 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/mvvmKotlinJetpackCompose/ui/dashboard/DashboardActivityTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.dashboard 2 | 3 | import androidx.activity.ComponentActivity 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.ui.test.assertIsDisplayed 6 | import androidx.compose.ui.test.junit4.createAndroidComposeRule 7 | import androidx.compose.ui.test.onNodeWithTag 8 | import androidx.compose.ui.test.onNodeWithText 9 | import androidx.compose.ui.test.performClick 10 | import com.example.mvvmKotlinJetpackCompose.R 11 | import com.example.mvvmKotlinJetpackCompose.ui.BaseInstrument 12 | import com.example.mvvmKotlinJetpackCompose.ui.login.LoginActivity 13 | import org.junit.Assert.* 14 | import org.junit.Before 15 | import org.junit.Rule 16 | import org.junit.Test 17 | 18 | @ExperimentalFoundationApi 19 | class DashboardActivityTest :BaseInstrument(){ 20 | 21 | 22 | @get : Rule 23 | val composeTestRule = createAndroidComposeRule(DashboardActivity::class.java) 24 | lateinit var activity: ComponentActivity 25 | 26 | @Before 27 | override fun setUp(){ 28 | activity=composeTestRule.activity 29 | } 30 | 31 | 32 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/mvvmKotlinJetpackCompose/ui/login/LoginActivityTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.login 2 | 3 | import androidx.activity.ComponentActivity 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.ui.test.* 6 | import androidx.compose.ui.test.junit4.createAndroidComposeRule 7 | import com.example.mvvmKotlinJetpackCompose.R 8 | import com.example.mvvmKotlinJetpackCompose.ui.BaseInstrument 9 | import com.example.mvvmKotlinJetpackCompose.util.ENTER_EMAIL_ID 10 | import com.example.mvvmKotlinJetpackCompose.util.ENTER_PASSWORD 11 | import org.junit.Before 12 | import org.junit.Rule 13 | import org.junit.Test 14 | 15 | @ExperimentalFoundationApi 16 | class LoginActivityTest : BaseInstrument(){ 17 | // @get:Rule 18 | // val composeRule=createAndroidComposeRule()//if you want to test only single composable 19 | 20 | @get : Rule 21 | val composeTestRule = createAndroidComposeRule(LoginActivity::class.java) 22 | lateinit var activity: ComponentActivity 23 | 24 | @Before 25 | override fun setUp() { 26 | activity = composeTestRule.activity 27 | composeTestRule 28 | .onNodeWithTag(activity.getString(R.string.email_address)) 29 | .performTextClearance() 30 | composeTestRule 31 | .onNodeWithTag(activity.getString(R.string.password)) 32 | .performTextClearance() 33 | } 34 | 35 | @Test 36 | fun login_emptyEmail_showMessage() { 37 | composeTestRule 38 | .onNodeWithTag(activity.getString(R.string.password)) 39 | .performTextInput("12323") 40 | 41 | composeTestRule.onNodeWithTag(activity.getString(R.string.sign_in)) 42 | .performClick() 43 | 44 | composeTestRule.onNodeWithText(ENTER_EMAIL_ID) 45 | .assertIsDisplayed() 46 | 47 | 48 | } 49 | 50 | 51 | @Test 52 | fun login_emptyPass_showMessage() { 53 | composeTestRule 54 | .onNodeWithTag(activity.getString(R.string.email_address)) 55 | .performTextInput("12323") 56 | 57 | composeTestRule.onNodeWithTag(activity.getString(R.string.sign_in)) 58 | .performClick() 59 | 60 | composeTestRule.onNodeWithText(ENTER_PASSWORD) 61 | .assertIsDisplayed() 62 | 63 | 64 | } 65 | 66 | @Test 67 | fun login_filledEmailAndPass_openDashboard() { 68 | composeTestRule 69 | .onNodeWithTag(activity.getString(R.string.email_address)) 70 | .performTextInput("suorizwansayyed786@gmail.com") 71 | 72 | composeTestRule 73 | .onNodeWithTag(activity.getString(R.string.password)) 74 | .performTextInput("SuoRizwan") 75 | 76 | composeTestRule.onNodeWithTag(activity.getString(R.string.sign_in)) 77 | .performClick() 78 | 79 | composeTestRule. 80 | onNodeWithTag(activity.getString(R.string.dashboard_content_tag)) 81 | .assertIsDisplayed() 82 | 83 | } 84 | 85 | 86 | 87 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/mvvmKotlinJetpackCompose/ui/splash/SplashActivityTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.splash 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.ui.test.* 5 | import androidx.compose.ui.test.junit4.createAndroidComposeRule 6 | import com.example.mvvmKotlinJetpackCompose.R 7 | import com.example.mvvmKotlinJetpackCompose.ui.BaseInstrument 8 | import org.junit.Assert.* 9 | import org.junit.Rule 10 | import org.junit.Test 11 | 12 | @ExperimentalFoundationApi 13 | class SplashActivityTest : BaseInstrument(){ 14 | 15 | // @get:Rule 16 | // val composeRule=createAndroidComposeRule()//if you want to test only single composable 17 | 18 | @get : Rule 19 | val composeTestRule= createAndroidComposeRule(SplashActivity::class.java) 20 | 21 | override fun setUp() { 22 | 23 | } 24 | 25 | @Test 26 | fun splash_title_isDisplayed(){ 27 | // composeRule.setContent { //use for only single composable 28 | // CoinTheme{ 29 | // YourCompose { 30 | // 31 | // } 32 | // } 33 | // 34 | // } 35 | composeTestRule 36 | .onNodeWithText( 37 | composeTestRule.activity.getString(R.string.app_name) 38 | ) 39 | .assertIsDisplayed() 40 | } 41 | 42 | 43 | @Test 44 | fun splash_circularProgress_isDisplayed(){ 45 | // composeRule.setContent { //use for only single composable 46 | // CoinTheme{ 47 | // YourCompose { 48 | // 49 | // } 50 | // } 51 | // 52 | // } 53 | composeTestRule.onRoot().printToLog("MY TAG") 54 | 55 | composeTestRule 56 | .onNodeWithTag(composeTestRule.activity.getString(R.string.test_tag_circular_progress)) 57 | .assertIsDisplayed() 58 | } 59 | 60 | 61 | 62 | 63 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/MvvmKotlinJetpackCompose.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class MvvmKotlinJetpackCompose : Application() { 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/ApiHeader.kt: -------------------------------------------------------------------------------- 1 | 2 | package com.example.mvvmKotlinJetpackCompose.data.network 3 | 4 | import com.example.mvvmKotlinJetpackCompose.di.EmptyString 5 | import javax.inject.Inject 6 | 7 | 8 | class ApiHeader @Inject constructor(val publicApiHeader: PublicApiHeader, val protectedApiHeader: ProtectedApiHeader) { 9 | 10 | class PublicApiHeader @Inject constructor( @EmptyString var apiKey: String) 11 | 12 | class ProtectedApiHeader @Inject constructor(@EmptyString var apiKey: String, @EmptyString var userId: String?, @EmptyString var accessToken: String?) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/ApiHelper.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network 2 | 3 | import com.example.mvvmKotlinJetpackCompose.data.network.model.DashboardResponse 4 | import com.example.mvvmKotlinJetpackCompose.data.network.model.LoginResponse 5 | 6 | 7 | interface ApiHelper { 8 | 9 | fun getApiHeader(): ApiHeader? 10 | fun updateToken(token: String) 11 | fun login(email: String, password: String): Resource 12 | 13 | suspend fun getDashboardData(): Resource 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/AppApiHelper.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network 2 | 3 | import android.content.Context 4 | import androidx.annotation.VisibleForTesting 5 | import androidx.annotation.VisibleForTesting.Companion.PRIVATE 6 | import com.example.mvvmKotlinJetpackCompose.data.network.model.DashboardResponse 7 | import com.example.mvvmKotlinJetpackCompose.data.network.model.Data 8 | import com.example.mvvmKotlinJetpackCompose.data.network.model.LoginResponse 9 | import com.example.mvvmKotlinJetpackCompose.util.* 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import kotlinx.coroutines.delay 12 | import retrofit2.Response 13 | import java.io.IOException 14 | import javax.inject.Inject 15 | 16 | 17 | class AppApiHelper @Inject constructor( 18 | @ApplicationContext val context: Context, 19 | private val apiHeader: ApiHeader, 20 | ) : ApiHelper { 21 | 22 | @Inject 23 | lateinit var serviceGenerator: ServiceGenerator 24 | 25 | 26 | 27 | override fun getApiHeader(): ApiHeader { 28 | 29 | return apiHeader 30 | } 31 | 32 | override fun updateToken(token: String) { 33 | serviceGenerator.protectedApiHeader = getApiHeader().protectedApiHeader.apply { 34 | accessToken = token 35 | } 36 | 37 | } 38 | 39 | 40 | override fun login(email: String, password: String): Resource { 41 | // val service = serviceGenerator.service 42 | // 43 | // val request = LoginRequest(email, password, "", "") 44 | // 45 | // return when (val responseBodyPojo = processCall { service.login(request) } 46 | // ) { 47 | // is LoginResponse -> Success(data = responseBodyPojo) 48 | // 49 | // else -> DataError(responseBodyPojo as String) 50 | // } 51 | val data = Data( 52 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InN1b3JpendhbnN" + 53 | "heXllZDc4NkBnbWFpbC5jb20iLCJuYmYiOjE2Mzc2NTE1MjMsImV4cCI6MTY0NTQyNzUyMywiaWF0" + 54 | "IjoxNjM3NjUxNTIzfQ.qKA55avNfOJMU3lHE-e88jfAVwE_T7E12cbCwXAfYAU", 55 | "suorizwansayyed786@gmail.com", "User" 56 | ) 57 | return Success(LoginResponse(data, "User login successful", true)) 58 | 59 | } 60 | 61 | 62 | override suspend fun getDashboardData(): Resource { 63 | delay(3000) 64 | 65 | // serviceGenerator.protectedApiHeader = getApiHeader().protectedApiHeader 66 | // val service = serviceGenerator.getService() 67 | // 68 | // return when (val response = processCall { service.getDashboardData() }) { 69 | // is DashboardResponse -> Success(response) 70 | // else -> DataError(response as String) 71 | // } 72 | val data = DashboardResponse.Data( 73 | balanceINR = 6502.50, balanceLiqr = 260.10, 74 | balanceUSD = 28.61, liqrToINR = 25.00, serviceCharge = 15.0, redeemBalance = 65.025000, 75 | ) 76 | return Success(DashboardResponse(data, "success", true)) 77 | } 78 | 79 | @VisibleForTesting(otherwise = PRIVATE) 80 | public inline fun processCall(responseCall: () -> Response<*>): Any? { 81 | if (!NetworkUtils.isNetworkAvailable(context)) { 82 | 83 | return NO_INTERNET_CONNECTION 84 | } 85 | return try { 86 | val response = responseCall.invoke() 87 | val responseCode = response.code() 88 | if (response.isSuccessful) { 89 | response.body() 90 | } else { 91 | getResponseCodeString(responseCode) 92 | } 93 | } catch (e: IOException) { 94 | NETWORK_ERROR 95 | } 96 | } 97 | 98 | public fun getResponseCodeString(responseCode: Int): String { 99 | if (responseCode in 400..499) { 100 | return CLIENT_SIDE_ERROR 101 | } else if (responseCode in 500..599) { 102 | return SERVER_SIDE_ERROR 103 | } 104 | return SOMETHING_WENT_WRONG 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network 2 | 3 | import com.example.mvvmKotlinJetpackCompose.util.SOMETHING_WENT_WRONG 4 | 5 | 6 | sealed class Resource( 7 | val data: T? = null, 8 | val errorDescription: String = SOMETHING_WENT_WRONG, 9 | ) { 10 | 11 | 12 | override fun toString(): String { 13 | return when (this) { 14 | is Success<*> -> "Success[data=$data]" 15 | is DataError -> "Error[exception=$errorDescription]" 16 | } 17 | } 18 | 19 | } 20 | 21 | class Success(data: T) : Resource(data) 22 | class DataError(errorDescription: String) : Resource(null, errorDescription) 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/Service.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network 2 | 3 | 4 | import com.example.mvvmKotlinJetpackCompose.data.network.model.* 5 | import retrofit2.Response 6 | import retrofit2.http.* 7 | 8 | 9 | interface Service { 10 | 11 | @POST("login") 12 | fun login(@Body request: LoginRequest): Response 13 | 14 | 15 | @GET("dashboard") 16 | suspend fun getDashboardData():Response 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/ServiceGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network 2 | 3 | import com.example.mvvmKotlinJetpackCompose.BuildConfig 4 | import com.example.mvvmKotlinJetpackCompose.data.network.moshiFactories.MyKotlinJsonAdapterFactory 5 | import com.example.mvvmKotlinJetpackCompose.data.network.moshiFactories.MyStandardJsonAdapters 6 | import com.squareup.moshi.Moshi 7 | import okhttp3.Interceptor 8 | import okhttp3.OkHttpClient 9 | import okhttp3.logging.HttpLoggingInterceptor 10 | import retrofit2.Retrofit 11 | import retrofit2.converter.moshi.MoshiConverterFactory 12 | import java.util.concurrent.TimeUnit 13 | import javax.inject.Inject 14 | import javax.inject.Singleton 15 | 16 | const val timeoutRead = 30 17 | const val contentType = "Content-Type" 18 | const val contentTypeValue = "application/json" 19 | const val timeoutConnect = 30 20 | const val baseUrl="https://your.com/Api/" 21 | 22 | @Singleton 23 | class ServiceGenerator @Inject constructor(){ 24 | 25 | @Inject 26 | lateinit var service : Service 27 | 28 | @Inject 29 | lateinit var retrofit: Retrofit 30 | 31 | @Inject 32 | lateinit var protectedApiHeader:ApiHeader.ProtectedApiHeader 33 | 34 | 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/model/DashboardResponse.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network.model 2 | 3 | 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | @JsonClass(generateAdapter = true) 8 | data class DashboardResponse( 9 | @Json(name = "data") 10 | val `data`: Data = Data(), 11 | @Json(name = "message") 12 | val message: String = "", 13 | @Json(name = "status") 14 | val status: Boolean = false 15 | ) { 16 | @JsonClass(generateAdapter = true) 17 | data class Data( 18 | @Json(name = "Balance_INR") 19 | val balanceINR: Double = 0.0, 20 | @Json(name = "Balance_Liqr") 21 | val balanceLiqr: Double = 0.0, 22 | @Json(name = "Balance_USD") 23 | val balanceUSD: Double = 0.0, 24 | @Json(name = "LiqrToINR") 25 | val liqrToINR: Double = 0.0, 26 | @Json(name = "Redeem_Balance") 27 | val redeemBalance: Double = 0.0, 28 | @Json(name = "ServiceCharge") 29 | val serviceCharge: Double = 0.0 30 | ) 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/model/Data.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network.model 2 | 3 | 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | @JsonClass(generateAdapter = true) 8 | data class Data( 9 | @Json(name = "token") 10 | var token: String, 11 | @Json(name = "userId") 12 | var userId: String, 13 | @Json(name = "userType") 14 | var userType: String 15 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/model/LoginRequest.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network.model 2 | 3 | 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | @JsonClass(generateAdapter = true) 8 | data class LoginRequest( 9 | @Json(name = "userId") 10 | val userId: String, 11 | @Json(name = "password") 12 | val password: String, 13 | @Json(name = "ipAddress") 14 | val ipAddress: String, 15 | @Json(name = "url") 16 | val url: String, 17 | 18 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/model/LoginResponse.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network.model 2 | 3 | 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | @JsonClass(generateAdapter = true) 8 | data class LoginResponse( 9 | @Json(name = "data") 10 | var `data`: Data=Data("","",""), 11 | @Json(name = "message") 12 | var message: String, 13 | @Json(name = "status") 14 | var status: Boolean 15 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/network/moshiFactories/MyKotlinJsonAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.network.moshiFactories 2 | 3 | import com.squareup.moshi.* 4 | import com.squareup.moshi.internal.Util 5 | import com.squareup.moshi.internal.Util.generatedAdapter 6 | import com.squareup.moshi.internal.Util.resolve 7 | import java.lang.reflect.Modifier 8 | import java.lang.reflect.Type 9 | import java.util.AbstractMap.SimpleEntry 10 | import kotlin.collections.Map.Entry 11 | import kotlin.reflect.* 12 | import kotlin.reflect.full.findAnnotation 13 | import kotlin.reflect.full.memberProperties 14 | import kotlin.reflect.full.primaryConstructor 15 | import kotlin.reflect.jvm.isAccessible 16 | import kotlin.reflect.jvm.javaField 17 | import com.squareup.moshi.JsonAdapter 18 | 19 | import com.squareup.moshi.Moshi 20 | 21 | 22 | /** Classes annotated with this are eligible for this adapter. */ 23 | private val KOTLIN_METADATA = Class.forName("kotlin.Metadata") as Class 24 | 25 | /** 26 | * Placeholder value used when a field is absent from the JSON. Note that this code 27 | * distinguishes between absent values and present-but-null values. 28 | */ 29 | private val ABSENT_VALUE = Any() 30 | 31 | /** 32 | * This class encodes Kotlin classes using their properties. It decodes them by first invoking the 33 | * constructor, and then by setting any additional properties that exist, if any. 34 | */ 35 | internal class MyKotlinJsonAdapter( 36 | val constructor: KFunction, 37 | val bindings: List?>, 38 | val options: JsonReader.Options, 39 | ) : JsonAdapter() { 40 | 41 | override fun fromJson(reader: JsonReader): T { 42 | val constructorSize = constructor.parameters.size 43 | 44 | // Read each value into its slot in the array. 45 | val values = Array(bindings.size) { ABSENT_VALUE } 46 | reader.beginObject() 47 | while (reader.hasNext()) { 48 | val index = reader.selectName(options) 49 | val binding = if (index != -1) bindings[index] else null 50 | 51 | if (binding == null) { 52 | reader.skipName() 53 | reader.skipValue() 54 | continue 55 | } 56 | 57 | if (values[index] !== ABSENT_VALUE) { 58 | throw JsonDataException( 59 | "Multiple values for '${constructor.parameters[index].name}' at ${reader.path}") 60 | } 61 | 62 | values[index] = binding.adapter.fromJson(reader) 63 | 64 | // if (values[index] == null && !binding.property.returnType.isMarkedNullable) { 65 | // throw JsonDataException( 66 | // "Non-null value '${binding.property.name}' was null at ${reader.path}") 67 | // } 68 | } 69 | reader.endObject() 70 | 71 | // Confirm all parameters are present, optional, or nullable. 72 | for (i in 0 until constructorSize) { 73 | if (values[i] === ABSENT_VALUE && !constructor.parameters[i].isOptional) { 74 | if (!constructor.parameters[i].type.isMarkedNullable) { 75 | throw JsonDataException( 76 | "Required value '${constructor.parameters[i].name}' missing at ${reader.path}") 77 | } 78 | values[i] = null // Replace absent with null. 79 | } 80 | } 81 | 82 | // Call the constructor using a Map so that absent optionals get defaults. 83 | val result = constructor.callBy( 84 | IndexedParameterMap( 85 | constructor.parameters, 86 | values 87 | ) 88 | ) 89 | 90 | // Set remaining properties. 91 | for (i in constructorSize until bindings.size) { 92 | val binding = bindings[i]!! 93 | val value = values[i] 94 | binding.set(result, value) 95 | } 96 | 97 | return result 98 | } 99 | 100 | override fun toJson(writer: JsonWriter, value: T?) { 101 | if (value == null) throw NullPointerException("value == null") 102 | 103 | writer.beginObject() 104 | for (binding in bindings) { 105 | if (binding == null) continue // Skip constructor parameters that aren't properties. 106 | 107 | writer.name(binding.name) 108 | binding.adapter.toJson(writer, binding.get(value)) 109 | } 110 | writer.endObject() 111 | } 112 | 113 | override fun toString() = "KotlinJsonAdapter(${constructor.returnType})" 114 | 115 | data class Binding( 116 | val name: String, 117 | val adapter: JsonAdapter

, 118 | val property: KProperty1, 119 | val parameter: KParameter?, 120 | ) { 121 | fun get(value: K) = property.get(value) 122 | 123 | fun set(result: K, value: P) { 124 | if (value !== ABSENT_VALUE) { 125 | (property as KMutableProperty1).set(result, value) 126 | } 127 | } 128 | } 129 | 130 | /** A simple [Map] that uses parameter indexes instead of sorting or hashing. */ 131 | class IndexedParameterMap(val parameterKeys: List, val parameterValues: Array) 132 | : AbstractMap() { 133 | 134 | override val entries: Set> 135 | get() { 136 | val allPossibleEntries = parameterKeys.mapIndexed { index, value -> 137 | SimpleEntry(value, parameterValues[index]) 138 | } 139 | return allPossibleEntries.filterTo(LinkedHashSet>()) { 140 | it.value !== ABSENT_VALUE && it.value!=null 141 | } 142 | } 143 | 144 | override fun containsKey(key: KParameter) = parameterValues[key.index] !== ABSENT_VALUE && parameterValues[key.index] !=null 145 | 146 | override fun get(key: KParameter): Any? { 147 | val value = parameterValues[key.index] 148 | return if (value !== ABSENT_VALUE) value else null 149 | } 150 | } 151 | } 152 | 153 | class MyKotlinJsonAdapterFactory : JsonAdapter.Factory { 154 | @ExperimentalStdlibApi 155 | override fun create(type: Type, annotations: MutableSet, moshi: Moshi) 156 | : JsonAdapter<*>? { 157 | if (annotations.isNotEmpty()) return null 158 | 159 | val rawType = Types.getRawType(type) 160 | if (rawType.isInterface) return null 161 | if (rawType.isEnum) return null 162 | if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null 163 | if (Util.isPlatformType(rawType)) return null 164 | try { 165 | val generatedAdapter = generatedAdapter(moshi, type, rawType) 166 | if (generatedAdapter != null) { 167 | return generatedAdapter 168 | } 169 | } catch (e: RuntimeException) { 170 | if (e.cause !is ClassNotFoundException) { 171 | throw e 172 | } 173 | // Fall back to a reflective adapter when the generated adapter is not found. 174 | } 175 | 176 | if (rawType.isLocalClass) { 177 | throw IllegalArgumentException("Cannot serialize local class or object expression ${rawType.name}") 178 | } 179 | val rawTypeKotlin = rawType.kotlin 180 | if (rawTypeKotlin.isAbstract) { 181 | throw IllegalArgumentException("Cannot serialize abstract class ${rawType.name}") 182 | } 183 | if (rawTypeKotlin.isInner) { 184 | throw IllegalArgumentException("Cannot serialize inner class ${rawType.name}") 185 | } 186 | if (rawTypeKotlin.objectInstance != null) { 187 | throw IllegalArgumentException("Cannot serialize object declaration ${rawType.name}") 188 | } 189 | 190 | val constructor = rawTypeKotlin.primaryConstructor ?: return null 191 | val parametersByName = constructor.parameters.associateBy { it.name } 192 | constructor.isAccessible = true 193 | 194 | val bindingsByName = LinkedHashMap>() 195 | 196 | for (property in rawTypeKotlin.memberProperties) { 197 | val parameter = parametersByName[property.name] 198 | 199 | if (Modifier.isTransient(property.javaField?.modifiers ?: 0)) { 200 | if (parameter != null && !parameter.isOptional) { 201 | throw IllegalArgumentException( 202 | "No default value for transient constructor $parameter") 203 | } 204 | continue 205 | } 206 | 207 | if (parameter != null && parameter.type != property.returnType) { 208 | throw IllegalArgumentException("'${property.name}' has a constructor parameter of type " + 209 | "${parameter.type} but a property of type ${property.returnType}.") 210 | } 211 | 212 | if (property !is KMutableProperty1 && parameter == null) continue 213 | 214 | property.isAccessible = true 215 | var allAnnotations = property.annotations 216 | var jsonAnnotation = property.findAnnotation() 217 | 218 | if (parameter != null) { 219 | allAnnotations += parameter.annotations 220 | if (jsonAnnotation == null) { 221 | jsonAnnotation = parameter.findAnnotation() 222 | } 223 | } 224 | 225 | val name = jsonAnnotation?.name ?: property.name 226 | val resolvedPropertyType = resolve(type, rawType, property.returnType.javaType) 227 | val adapter = moshi.adapter( 228 | resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()), property.name) 229 | 230 | bindingsByName[property.name] = 231 | MyKotlinJsonAdapter.Binding( 232 | name, adapter, 233 | property as KProperty1, parameter 234 | ) 235 | } 236 | 237 | val bindings = ArrayList?>() 238 | 239 | for (parameter in constructor.parameters) { 240 | val binding = bindingsByName.remove(parameter.name) 241 | if (binding == null && !parameter.isOptional) { 242 | throw IllegalArgumentException("No property for required constructor $parameter") 243 | } 244 | bindings += binding 245 | } 246 | 247 | bindings += bindingsByName.values 248 | 249 | val options = JsonReader.Options.of(*bindings.map { it?.name ?: "\u0000" }.toTypedArray()) 250 | return MyKotlinJsonAdapter( 251 | constructor, 252 | bindings, 253 | options 254 | ).nullSafe() 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/others/MenuItem.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.others 2 | 3 | data class MenuItem(val icon:Int,val text:String) 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/prefs/AppPreferencesHelper.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.prefs 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.example.mvvmKotlinJetpackCompose.di.PreferenceInfo 6 | import com.example.mvvmKotlinJetpackCompose.util.* 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import javax.inject.Inject 9 | 10 | class AppPreferencesHelper @Inject constructor( 11 | @ApplicationContext val context: Context, 12 | @PreferenceInfo val prefFileName: String?, 13 | ) : PreferencesHelper { 14 | 15 | 16 | private var mPrefs: SharedPreferences = 17 | context.getSharedPreferences(prefFileName, Context.MODE_PRIVATE); 18 | 19 | override fun getCurrentUserId(): String? { 20 | val userId = 21 | mPrefs.getString(PREF_KEY_CURRENT_USER_ID, NULL_INDEX) 22 | return if (userId == NULL_INDEX) null else userId 23 | } 24 | 25 | override fun setCurrentUserId(userId: String?) { 26 | val id = userId ?: NULL_INDEX 27 | mPrefs.putAny(PREF_KEY_CURRENT_USER_ID, id) 28 | 29 | } 30 | 31 | override fun getCurrentUserName(): String? { 32 | return mPrefs.getString(PREF_KEY_CURRENT_USER_NAME, null) 33 | } 34 | 35 | override fun setCurrentUserName(userName: String?) { 36 | mPrefs.putAny(PREF_KEY_CURRENT_USER_NAME, userName) 37 | 38 | } 39 | 40 | override fun getCurrentUserEmail(): String? { 41 | return mPrefs.getString(PREF_KEY_CURRENT_USER_EMAIL, null) 42 | } 43 | 44 | override fun setCurrentUserEmail(email: String?) { 45 | mPrefs.putAny(PREF_KEY_CURRENT_USER_EMAIL, email) 46 | 47 | } 48 | 49 | override fun getCurrentUserProfilePicUrl(): String? { 50 | return mPrefs.getString(PREF_KEY_CURRENT_USER_PROFILE_PIC_URL, null) 51 | } 52 | 53 | override fun setCurrentUserProfilePicUrl(profilePicUrl: String?) { 54 | 55 | mPrefs.putAny(PREF_KEY_CURRENT_USER_PROFILE_PIC_URL, profilePicUrl) 56 | 57 | } 58 | 59 | override suspend fun getCurrentUserLoggedInMode(): Int { 60 | return mPrefs.getInt(PREF_KEY_USER_LOGGED_IN_MODE, LoggedInMode.LOGGED_IN_MODE_LOGGED_OUT.type) 61 | } 62 | 63 | override fun setCurrentUserLoggedInMode(mode: LoggedInMode) { 64 | mPrefs.putAny(PREF_KEY_USER_LOGGED_IN_MODE, mode.type) 65 | 66 | } 67 | 68 | override fun getAccessToken(): String? { 69 | return mPrefs.getString(PREF_KEY_ACCESS_TOKEN, null) 70 | } 71 | 72 | override fun setAccessToken(accessToken: String?) { 73 | mPrefs.putAny(PREF_KEY_ACCESS_TOKEN, accessToken) 74 | } 75 | 76 | 77 | override fun setUserLoggedIn( 78 | userId: String, 79 | userName: String, 80 | email: String, 81 | accessToken: String, 82 | profile: String 83 | ) { 84 | 85 | setCurrentUserId(userId) 86 | setCurrentUserName(userName) 87 | setCurrentUserEmail(email) 88 | setCurrentUserProfilePicUrl(profile) 89 | setAccessToken(accessToken) 90 | setCurrentUserLoggedInMode(LoggedInMode.LOGGED_IN_MODE_SERVER) 91 | } 92 | 93 | override fun updateUserInfo( 94 | accessToken: String?, 95 | userId: String?, 96 | loggedInMode: LoggedInMode, 97 | userName: String?, 98 | email: String?, 99 | profilePicPath: String? 100 | ) { 101 | setAccessToken(accessToken) 102 | setCurrentUserId(userId ?:"-1") 103 | setCurrentUserLoggedInMode(loggedInMode) 104 | setCurrentUserName(userName) 105 | setCurrentUserEmail(email) 106 | setCurrentUserProfilePicUrl(profilePicPath) 107 | 108 | } 109 | 110 | override fun clearAll() { 111 | mPrefs.edit().clear().apply() 112 | } 113 | 114 | private fun SharedPreferences.putAny(name: String, any: Any?) { 115 | when (any) { 116 | is String -> edit().putString(name, any).apply() 117 | is Int -> edit().putInt(name, any).apply() 118 | is Boolean -> edit().putBoolean(name,any).apply() 119 | is Float -> edit().putFloat(name,any).apply() 120 | } 121 | } 122 | 123 | private fun SharedPreferences.remove(name:String){ 124 | edit().remove(name).apply() 125 | } 126 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/prefs/PreferencesHelper.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.prefs 2 | 3 | import com.example.mvvmKotlinJetpackCompose.util.LoggedInMode 4 | 5 | 6 | interface PreferencesHelper { 7 | 8 | suspend fun getCurrentUserLoggedInMode(): Int? 9 | 10 | fun setCurrentUserLoggedInMode(mode: LoggedInMode) 11 | 12 | fun getCurrentUserId(): String? 13 | 14 | fun setCurrentUserId(userId: String?) 15 | 16 | fun getCurrentUserName(): String? 17 | 18 | fun setCurrentUserName(userName: String?) 19 | 20 | fun getCurrentUserEmail(): String? 21 | 22 | fun setCurrentUserEmail(email: String?) 23 | 24 | fun getCurrentUserProfilePicUrl(): String? 25 | 26 | fun setCurrentUserProfilePicUrl(profilePicUrl: String?) 27 | 28 | fun getAccessToken(): String? 29 | 30 | fun setAccessToken(accessToken: String?) 31 | fun setUserLoggedIn( 32 | userId: String, 33 | userName: String, 34 | email: String, 35 | accessToken: String, 36 | profile: String = "" 37 | ) 38 | 39 | fun updateUserInfo( 40 | accessToken: String?, 41 | userId: String?, 42 | loggedInMode: LoggedInMode, 43 | userName: String?, 44 | email: String?, 45 | profilePicPath: String?) 46 | 47 | fun clearAll() 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/repos/DashboardRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.repos 2 | 3 | import com.example.mvvmKotlinJetpackCompose.data.network.ApiHelper 4 | import com.example.mvvmKotlinJetpackCompose.data.network.Resource 5 | import com.example.mvvmKotlinJetpackCompose.data.network.model.DashboardResponse 6 | import com.example.mvvmKotlinJetpackCompose.data.prefs.PreferencesHelper 7 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseRepository 8 | import com.example.mvvmKotlinJetpackCompose.util.LoggedInMode 9 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 10 | import kotlinx.coroutines.flow.flow 11 | import kotlinx.coroutines.flow.flowOn 12 | import javax.inject.Inject 13 | 14 | class DashboardRepository @Inject constructor( 15 | appDispatcher: DispatcherProvider, 16 | apiHelper: ApiHelper, 17 | preferencesHelper: PreferencesHelper, 18 | ) : BaseRepository(appDispatcher,apiHelper, preferencesHelper) { 19 | 20 | fun logout() { 21 | 22 | flow { 23 | setUserAsLoggedOut() 24 | }.flowOn(getAppDispatcher().computation()) 25 | 26 | } 27 | 28 | suspend fun getDashboardData(): Resource { 29 | 30 | return getApiHelper().getDashboardData() 31 | 32 | } 33 | 34 | 35 | private fun setUserAsLoggedOut() { 36 | getPreferencesHelper().updateUserInfo( 37 | null, 38 | null, 39 | LoggedInMode.LOGGED_IN_MODE_LOGGED_OUT, 40 | null, 41 | null, 42 | null 43 | ) 44 | } 45 | 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/data/repos/LoginRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.data.repos 2 | 3 | import com.example.mvvmKotlinJetpackCompose.data.network.ApiHelper 4 | import com.example.mvvmKotlinJetpackCompose.data.network.DataError 5 | import com.example.mvvmKotlinJetpackCompose.data.network.Resource 6 | import com.example.mvvmKotlinJetpackCompose.data.network.model.LoginResponse 7 | import com.example.mvvmKotlinJetpackCompose.data.prefs.PreferencesHelper 8 | import com.example.mvvmKotlinJetpackCompose.di.login.LoginScope 9 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseRepository 10 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.AppDispatcherProvider 11 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 12 | import kotlinx.coroutines.delay 13 | import kotlinx.coroutines.flow.Flow 14 | import kotlinx.coroutines.flow.flow 15 | import kotlinx.coroutines.flow.flowOn 16 | import javax.inject.Inject 17 | 18 | @LoginScope 19 | class LoginRepository @Inject constructor( 20 | appDispatcherProvider: DispatcherProvider, 21 | apiHelper: ApiHelper, 22 | preferencesHelper: PreferencesHelper, 23 | ) : BaseRepository(appDispatcherProvider,apiHelper, preferencesHelper) { 24 | 25 | 26 | fun login(email: String, password: String): Flow> { 27 | val loginResult = getApiHelper().login(email, password) 28 | 29 | loginResult.data?.let { 30 | if (it.status) { 31 | val loginResponse = loginResult.data 32 | 33 | if (loginResponse.status) { 34 | val userId = loginResponse.data.userId 35 | val token = loginResponse.data.token 36 | val userType = loginResponse.data.userType 37 | 38 | getPreferencesHelper().setUserLoggedIn( 39 | userId = userId, 40 | userName = userType, 41 | email = userId, accessToken = token 42 | ) 43 | getApiHelper().updateToken(token) 44 | 45 | } 46 | } else { 47 | return flow { 48 | emit(DataError(loginResult.data.message)) 49 | } 50 | } 51 | } 52 | 53 | return flow { 54 | emit(loginResult) 55 | } 56 | } 57 | 58 | suspend fun isUserLoggedIn(): Flow { 59 | 60 | return flow { 61 | delay(3000) 62 | 63 | emit(getPreferencesHelper().getCurrentUserLoggedInMode()) 64 | }.flowOn(getAppDispatcher().computation()) 65 | 66 | } 67 | 68 | 69 | 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.di 2 | 3 | import com.example.mvvmKotlinJetpackCompose.data.network.ApiHelper 4 | import com.example.mvvmKotlinJetpackCompose.data.network.AppApiHelper 5 | import com.example.mvvmKotlinJetpackCompose.data.prefs.AppPreferencesHelper 6 | import com.example.mvvmKotlinJetpackCompose.data.prefs.PreferencesHelper 7 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseRepository 8 | import com.example.mvvmKotlinJetpackCompose.data.repos.DashboardRepository 9 | import com.example.mvvmKotlinJetpackCompose.util.PREF_NAME 10 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.AppDispatcherProvider 11 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 12 | import dagger.Binds 13 | import dagger.Module 14 | import dagger.Provides 15 | import dagger.hilt.InstallIn 16 | import dagger.hilt.components.SingletonComponent 17 | import javax.inject.Singleton 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | abstract class AppModule { 22 | 23 | companion object{ 24 | @Provides 25 | @PreferenceInfo 26 | fun providePreferenceName(): String { 27 | return PREF_NAME 28 | } 29 | 30 | @Provides 31 | @EmptyString 32 | fun provideApiKey(): String { 33 | return "" 34 | } 35 | 36 | } 37 | 38 | @Binds 39 | @Singleton 40 | abstract fun provideApiHelper(appApiHelper: AppApiHelper): ApiHelper 41 | 42 | @Binds 43 | @Singleton 44 | abstract fun providePreferenceHelper(appPreferencesHelper: AppPreferencesHelper): PreferencesHelper 45 | 46 | 47 | @Binds 48 | @Singleton 49 | abstract fun provideDispatcher(dispatcherProvider: AppDispatcherProvider): DispatcherProvider 50 | 51 | @Binds 52 | @Singleton 53 | abstract fun provideDashboardRepo(dashboardRepository: DashboardRepository): BaseRepository 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/EmptyString.java: -------------------------------------------------------------------------------- 1 | 2 | package com.example.mvvmKotlinJetpackCompose.di; 3 | 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | 7 | import javax.inject.Qualifier; 8 | 9 | 10 | @Qualifier 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface EmptyString { 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/ExternalLibAppModule.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.di 2 | 3 | import com.example.mvvmKotlinJetpackCompose.BuildConfig 4 | import com.example.mvvmKotlinJetpackCompose.data.network.* 5 | import com.example.mvvmKotlinJetpackCompose.data.network.moshiFactories.MyKotlinJsonAdapterFactory 6 | import com.example.mvvmKotlinJetpackCompose.data.network.moshiFactories.MyStandardJsonAdapters 7 | import com.squareup.moshi.Moshi 8 | import dagger.Provides 9 | import dagger.Module 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | import okhttp3.Interceptor 13 | import okhttp3.OkHttpClient 14 | import okhttp3.logging.HttpLoggingInterceptor 15 | import retrofit2.Retrofit 16 | import retrofit2.converter.moshi.MoshiConverterFactory 17 | import java.util.concurrent.TimeUnit 18 | import javax.inject.Singleton 19 | 20 | @Module 21 | @InstallIn(SingletonComponent::class) 22 | class ExternalLibAppModule { 23 | 24 | @Provides 25 | @Singleton 26 | fun provideHeaderInterceptor(protectedApiHeader : ApiHeader.ProtectedApiHeader): Interceptor{ 27 | return Interceptor { chain -> 28 | val original = chain.request() 29 | val request = original.newBuilder() 30 | .header(contentType, contentTypeValue) 31 | .header("Authorization", "Bearer " + protectedApiHeader.accessToken) 32 | .method(original.method, original.body) 33 | .build() 34 | 35 | chain.proceed(request) 36 | } 37 | 38 | } 39 | 40 | @Provides 41 | @Singleton 42 | fun provideLoggingInterceptor(): HttpLoggingInterceptor { 43 | return HttpLoggingInterceptor().apply { 44 | if (BuildConfig.DEBUG) { 45 | level = HttpLoggingInterceptor.Level.BODY 46 | } 47 | } 48 | } 49 | 50 | 51 | @Provides 52 | @Singleton 53 | fun provideRetrofitClient(headerInterceptor : Interceptor,loggingInterceptor: HttpLoggingInterceptor) : OkHttpClient.Builder{ 54 | 55 | val okHttpBuilder: OkHttpClient.Builder = OkHttpClient.Builder() 56 | okHttpBuilder.addInterceptor(headerInterceptor) 57 | okHttpBuilder.addInterceptor(loggingInterceptor) 58 | okHttpBuilder.connectTimeout(timeoutConnect.toLong(), TimeUnit.SECONDS) 59 | okHttpBuilder.readTimeout(timeoutRead.toLong(), TimeUnit.SECONDS) 60 | return okHttpBuilder 61 | 62 | } 63 | 64 | @Provides 65 | @Singleton 66 | fun provideRetrofit(okHttpBuilder: OkHttpClient.Builder, moshi: Moshi):Retrofit{ 67 | return Retrofit.Builder() 68 | .baseUrl(baseUrl) 69 | .client(okHttpBuilder.build()) 70 | .addConverterFactory(MoshiConverterFactory.create(moshi)) 71 | .build() 72 | } 73 | 74 | @Provides 75 | @Singleton 76 | fun provideService(retrofit: Retrofit): Service { 77 | return retrofit.create(Service::class.java) 78 | } 79 | 80 | 81 | @Provides 82 | @Singleton 83 | fun provideMoshi():Moshi{ 84 | return Moshi.Builder() 85 | .add(MyStandardJsonAdapters.FACTORY) 86 | .add(MyKotlinJsonAdapterFactory()).build() 87 | } 88 | 89 | 90 | 91 | 92 | 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/PreferenceInfo.java: -------------------------------------------------------------------------------- 1 | 2 | package com.example.mvvmKotlinJetpackCompose.di; 3 | 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | 7 | import javax.inject.Qualifier; 8 | 9 | 10 | @Qualifier 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface PreferenceInfo { 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/login/LoginComponent.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.di.login 2 | 3 | import com.example.mvvmKotlinJetpackCompose.di.login.LoginScope 4 | import dagger.hilt.DefineComponent 5 | import dagger.hilt.components.SingletonComponent 6 | 7 | @LoginScope 8 | @DefineComponent(parent = SingletonComponent::class) 9 | interface LoginComponent { 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/login/LoginComponentBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.di.login 2 | 3 | import com.example.mvvmKotlinJetpackCompose.data.repos.LoginRepository 4 | import dagger.BindsInstance 5 | import dagger.hilt.DefineComponent 6 | 7 | @DefineComponent.Builder 8 | interface LoginComponentBuilder { 9 | fun bindLoginRepo(@BindsInstance loginRepository: LoginRepository): LoginComponentBuilder 10 | fun build(): LoginComponent? 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/login/LoginComponentManager.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.di.login 2 | 3 | import com.example.mvvmKotlinJetpackCompose.data.network.ApiHelper 4 | import com.example.mvvmKotlinJetpackCompose.data.prefs.PreferencesHelper 5 | import com.example.mvvmKotlinJetpackCompose.data.repos.LoginRepository 6 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.AppDispatcherProvider 7 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 8 | import javax.inject.Inject 9 | import javax.inject.Provider 10 | import javax.inject.Singleton 11 | 12 | @Singleton 13 | class LoginComponentManager @Inject constructor( 14 | private val appDispatcherProvider: DispatcherProvider, 15 | private val loginComponentProvider: Provider, 16 | private val apiHelper: ApiHelper, 17 | private val preferencesHelper: PreferencesHelper, 18 | ) 19 | { 20 | 21 | var loginComponent: LoginComponent? = null 22 | 23 | 24 | fun getComponent():LoginComponent{ 25 | if(loginComponent==null){ 26 | val loginRepository = LoginRepository(appDispatcherProvider, apiHelper, preferencesHelper) 27 | loginComponent= loginComponentProvider.get().bindLoginRepo(loginRepository).build() 28 | 29 | } 30 | 31 | return loginComponent!! 32 | } 33 | 34 | fun destroyLoginComponent() { 35 | loginComponent = null 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/login/LoginEntryPoint.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.di.login 2 | 3 | import com.example.mvvmKotlinJetpackCompose.data.repos.LoginRepository 4 | import dagger.hilt.EntryPoint 5 | import dagger.hilt.InstallIn 6 | 7 | @EntryPoint 8 | @InstallIn(LoginComponent::class) 9 | interface LoginEntryPoint { 10 | fun getLoginRepo(): LoginRepository 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/di/login/LoginScope.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.di.login 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(value = AnnotationRetention.RUNTIME) 7 | annotation class LoginScope -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import androidx.lifecycle.viewmodel.CreationExtras 6 | import com.example.mvvmKotlinJetpackCompose.data.repos.LoginRepository 7 | import com.example.mvvmKotlinJetpackCompose.ui.login.LoginViewModel 8 | import com.example.mvvmKotlinJetpackCompose.ui.splash.SplashViewModel 9 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 10 | 11 | 12 | class ViewModelFactory( 13 | private val repository: LoginRepository, 14 | ) : ViewModelProvider.Factory { 15 | 16 | @Suppress("UNCHECKED_CAST") 17 | override fun create( 18 | modelClass: Class, 19 | extras: CreationExtras 20 | ): T { 21 | if (modelClass.isAssignableFrom(LoginViewModel::class.java)) { 22 | return LoginViewModel(repository) as T 23 | } 24 | if (modelClass.isAssignableFrom(SplashViewModel::class.java)) { 25 | return SplashViewModel(repository) as T 26 | } 27 | throw IllegalArgumentException("Unknown ViewModel class") 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/base/BaseComponentActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.base 2 | 3 | 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import androidx.activity.ComponentActivity 8 | import androidx.activity.compose.setContent 9 | import androidx.compose.foundation.background 10 | import androidx.compose.foundation.layout.Box 11 | import androidx.compose.foundation.layout.size 12 | import androidx.compose.foundation.shape.RoundedCornerShape 13 | import androidx.compose.material.* 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.runtime.livedata.observeAsState 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.graphics.Color 19 | import androidx.compose.ui.res.stringResource 20 | import androidx.compose.ui.unit.dp 21 | import androidx.compose.ui.unit.sp 22 | import androidx.compose.ui.window.Dialog 23 | import androidx.compose.ui.window.DialogProperties 24 | import com.example.mvvmKotlinJetpackCompose.R 25 | import com.example.mvvmKotlinJetpackCompose.data.network.DataError 26 | import com.example.mvvmKotlinJetpackCompose.data.network.Success 27 | import com.example.mvvmKotlinJetpackCompose.ui.theme.CoinTheme 28 | import com.example.mvvmKotlinJetpackCompose.ui.theme.White 29 | 30 | abstract class BaseComponentActivity> : ComponentActivity() { 31 | 32 | abstract val viewModel: VM 33 | 34 | //override in child class if you don't want to use global loading state 35 | open val wantToShowCustomLoading = false 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | setContent { 40 | CoinTheme { 41 | ProvideCompose() 42 | SetUpLoadingDialog() 43 | SetUpErrorDialog() 44 | } 45 | } 46 | 47 | 48 | } 49 | 50 | @Composable 51 | private fun SetUpLoadingDialog() { 52 | if (!wantToShowCustomLoading) { 53 | val loadingValue = viewModel.showDialogLoadingPrivate.observeAsState() 54 | 55 | if (loadingValue.value == true) { 56 | ShowLoading() 57 | } 58 | } 59 | 60 | } 61 | 62 | @Composable 63 | private fun SetUpErrorDialog() { 64 | var dialogState = false 65 | var errorDescription = "" 66 | val vmLoadinState = viewModel.showMessageDialog.observeAsState() 67 | when (vmLoadinState.value) { 68 | is DataError -> { 69 | errorDescription = (vmLoadinState.value as DataError).errorDescription 70 | dialogState = true 71 | } 72 | 73 | is Success -> { 74 | dialogState = false 75 | } 76 | 77 | else -> {} 78 | } 79 | if (dialogState) { 80 | ShowErrorDialog(errorDescription) 81 | 82 | } 83 | 84 | 85 | } 86 | 87 | 88 | @Composable 89 | protected open fun ShowLoading() { 90 | 91 | Dialog( 92 | onDismissRequest = { viewModel.hideLoading() }, 93 | DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false) 94 | ) { 95 | Box( 96 | contentAlignment = Alignment.Center, 97 | modifier = Modifier 98 | .size(100.dp) 99 | .background(White, shape = RoundedCornerShape(8.dp)) 100 | ) { 101 | CircularProgressIndicator() 102 | } 103 | } 104 | } 105 | 106 | 107 | @Composable 108 | private fun ShowErrorDialog(errorDescription: String) { 109 | 110 | AlertDialog( 111 | onDismissRequest = { 112 | }, 113 | 114 | title = { 115 | Text(stringResource(R.string.error), style = MaterialTheme.typography.h4) 116 | }, 117 | text = { 118 | Text(errorDescription, fontSize = 16.sp) 119 | }, 120 | confirmButton = { 121 | }, 122 | 123 | dismissButton = { 124 | TextButton( 125 | onClick = { 126 | viewModel.hideMessageDialog(Success("")) 127 | }) { 128 | Text( 129 | "Ok", 130 | style = MaterialTheme.typography.body1, 131 | color = MaterialTheme.colors.onSecondary 132 | ) 133 | } 134 | }, 135 | backgroundColor = MaterialTheme.colors.secondary, 136 | contentColor = Color.White 137 | ) 138 | 139 | } 140 | 141 | 142 | @Composable 143 | abstract fun ProvideCompose() 144 | 145 | @Composable 146 | abstract fun ProvideComposeLightPreview() 147 | 148 | 149 | inline fun Context.startActivity(block: Intent.() -> Unit = {}) { 150 | 151 | startActivity(Intent(this, T::class.java).apply(block)) 152 | } 153 | 154 | 155 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/base/BaseRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.base 2 | 3 | import com.example.mvvmKotlinJetpackCompose.data.network.ApiHelper 4 | import com.example.mvvmKotlinJetpackCompose.data.prefs.PreferencesHelper 5 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 6 | 7 | 8 | open class BaseRepository( 9 | private val appDispatcher: DispatcherProvider, 10 | private val apiHelper: ApiHelper, 11 | private val preferencesHelper: PreferencesHelper 12 | ) { 13 | 14 | fun getAppDispatcher(): DispatcherProvider { 15 | return appDispatcher 16 | } 17 | 18 | fun getApiHelper(): ApiHelper { 19 | 20 | return apiHelper 21 | } 22 | 23 | fun getPreferencesHelper(): PreferencesHelper { 24 | return preferencesHelper 25 | } 26 | 27 | fun getUserId(): String { 28 | return getPreferencesHelper().getCurrentUserId() ?: "" 29 | 30 | } 31 | 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/base/BaseUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.base 2 | 3 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 4 | 5 | open class BaseUseCase( private val repository: R, private val appDispatcher: DispatcherProvider) { 6 | // add any common business logic 7 | 8 | fun getRepository():R{ 9 | return repository 10 | } 11 | 12 | fun getAppDispatcher(): DispatcherProvider { 13 | return appDispatcher 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/base/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.base 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.annotation.VisibleForTesting.Companion.PRIVATE 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.ViewModel 8 | import com.example.mvvmKotlinJetpackCompose.data.network.DataError 9 | import com.example.mvvmKotlinJetpackCompose.data.network.Resource 10 | import com.example.mvvmKotlinJetpackCompose.data.network.Success 11 | import com.example.mvvmKotlinJetpackCompose.util.SOMETHING_WENT_WRONG 12 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 13 | import kotlinx.coroutines.CoroutineExceptionHandler 14 | 15 | open class BaseViewModel( 16 | val anyType:T, 17 | ) : ViewModel() { 18 | 19 | @VisibleForTesting(otherwise = PRIVATE) 20 | val showDialogLoadingPrivate = MutableLiveData(false) 21 | 22 | val showMessageDialog = MutableLiveData>() 23 | 24 | private val onErrorDialogDismissPrivate = MutableLiveData>() 25 | val onErrorDialogDismiss: LiveData> get() = onErrorDialogDismissPrivate 26 | 27 | protected val exceptionHandler = CoroutineExceptionHandler { context, exception -> 28 | hideLoading() 29 | showMessageDialog(DataError(SOMETHING_WENT_WRONG)) 30 | 31 | } 32 | 33 | 34 | fun isLoading(): Boolean { 35 | return showDialogLoadingPrivate.value!! 36 | } 37 | 38 | 39 | fun showLoading() { 40 | if (!showDialogLoadingPrivate.value!!) { 41 | showDialogLoadingPrivate.value = true 42 | } 43 | 44 | } 45 | 46 | fun hideLoading() { 47 | if (showDialogLoadingPrivate.value!!) { 48 | showDialogLoadingPrivate.value = false 49 | } 50 | } 51 | 52 | 53 | 54 | 55 | fun showMessageDialog(dataError: DataError) { 56 | showMessageDialog.value = dataError 57 | } 58 | 59 | fun showPostMessageDialog(dataError: DataError) { 60 | showMessageDialog.value = dataError 61 | } 62 | 63 | fun hideMessageDialog(success: Success) { 64 | showMessageDialog.value = success 65 | 66 | } 67 | 68 | 69 | 70 | } 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/base/BaseViewModelRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.base 2 | 3 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 4 | 5 | 6 | open class BaseViewModelRepository(repo : T ) 7 | : BaseViewModel(repo){ 8 | 9 | 10 | fun getRepository() : T{ 11 | 12 | return anyType 13 | } 14 | 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/base/BaseViewModelUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.base 2 | 3 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 4 | 5 | 6 | open class BaseViewModelUseCase(private val useCase: T) 7 | : BaseViewModel(useCase){ 8 | 9 | 10 | fun getUseCase() : T{ 11 | 12 | return useCase 13 | } 14 | 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/dashboard/DashboardActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.dashboard 2 | 3 | import android.content.res.Configuration.UI_MODE_NIGHT_NO 4 | import androidx.activity.viewModels 5 | import androidx.compose.foundation.ExperimentalFoundationApi 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.shape.RoundedCornerShape 8 | import androidx.compose.material.* 9 | import androidx.compose.runtime.* 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.res.painterResource 12 | import androidx.compose.ui.text.style.TextAlign 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | import androidx.navigation.compose.NavHost 16 | import androidx.navigation.compose.composable 17 | import androidx.navigation.compose.currentBackStackEntryAsState 18 | import androidx.navigation.compose.rememberNavController 19 | import com.example.mvvmKotlinJetpackCompose.R 20 | import com.example.mvvmKotlinJetpackCompose.data.network.DataError 21 | import com.example.mvvmKotlinJetpackCompose.data.network.Success 22 | import com.example.mvvmKotlinJetpackCompose.data.network.model.DashboardResponse 23 | import com.example.mvvmKotlinJetpackCompose.data.others.MenuItem 24 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseComponentActivity 25 | import com.example.mvvmKotlinJetpackCompose.ui.dashboard.compose.BottomNavData 26 | import com.example.mvvmKotlinJetpackCompose.ui.dashboard.compose.TopBar 27 | import com.example.mvvmKotlinJetpackCompose.ui.login.LoginActivity 28 | import com.example.mvvmKotlinJetpackCompose.util.observe 29 | import dagger.hilt.android.AndroidEntryPoint 30 | 31 | @AndroidEntryPoint 32 | class DashboardActivity : BaseComponentActivity() { 33 | 34 | override val viewModel: DashboardViewModel by viewModels() 35 | 36 | 37 | 38 | @ExperimentalFoundationApi 39 | @Composable 40 | override fun ProvideCompose() { 41 | 42 | val dashboardData = remember { 43 | mutableStateOf(DashboardResponse.Data()) 44 | } 45 | 46 | 47 | val userId = remember { mutableStateOf("") } 48 | 49 | observe(viewModel.userIdData) { 50 | when (it) { 51 | is Success -> { 52 | userId.value = it.data!! 53 | } 54 | else -> {} 55 | } 56 | } 57 | 58 | observe(viewModel.dashboardData) { 59 | when (it) { 60 | is DataError -> { 61 | 62 | } 63 | is Success -> { 64 | it.data.let { 65 | dashboardData.value = it!!.data 66 | } 67 | } 68 | } 69 | } 70 | 71 | observe(viewModel.logoutData) { 72 | when (it.getContentIfNotHandled()) { 73 | is DataError -> println() 74 | is Success -> { 75 | startActivity() 76 | finish() 77 | } 78 | else -> {} 79 | } 80 | 81 | } 82 | 83 | val navController = rememberNavController() 84 | val navBackStackEntry by navController.currentBackStackEntryAsState() 85 | val currentRoute = navBackStackEntry?.destination?.route 86 | 87 | SetUpDashboardCompose(dashboardData.value, 88 | userId.value, 89 | currentRoute, 90 | { 91 | navController.navigate(it) { 92 | // Pop up to the start destination of the graph to 93 | // avoid building up a large stack of destinations 94 | // on the back stack as users select items 95 | navController.graph.startDestinationRoute?.let { route -> 96 | popUpTo(route) { 97 | saveState = true 98 | } 99 | } 100 | // Avoid multiple copies of the same destination when 101 | // reselecting the same item 102 | launchSingleTop = true 103 | // Restore state when reselecting a previously selected item 104 | restoreState = true 105 | } 106 | 107 | }) { 108 | 109 | NavHost(navController = navController, 110 | startDestination = BottomNavData.Home.route) { 111 | 112 | composable(route = BottomNavData.Home.route) { 113 | DashboardContent(viewModel.getMenuData()) { 114 | 115 | } 116 | 117 | } 118 | 119 | composable(route = BottomNavData.History.route) { 120 | Text(text = "History", 121 | modifier = Modifier.fillMaxWidth(), 122 | textAlign = TextAlign.Center) 123 | } 124 | 125 | composable(route = BottomNavData.Other.route) { 126 | Text(text = "Other", 127 | modifier = Modifier.fillMaxWidth(), 128 | textAlign = TextAlign.Center) 129 | 130 | } 131 | 132 | 133 | 134 | } 135 | } 136 | 137 | 138 | } 139 | 140 | @ExperimentalFoundationApi 141 | @Composable 142 | private fun SetUpDashboardCompose( 143 | dashboardData: DashboardResponse.Data, 144 | userId: String, 145 | currentRoute: String?, 146 | onItemClick: (String)->Unit={}, 147 | SetupNavHost: @Composable () -> Unit, 148 | ) { 149 | val scaffoldState = 150 | rememberScaffoldState(rememberDrawerState(DrawerValue.Closed)) 151 | val scope = rememberCoroutineScope() 152 | 153 | Scaffold(scaffoldState = scaffoldState, 154 | topBar = { 155 | TopBar(scope, 156 | scaffoldState, 157 | dashboardData.liqrToINR) 158 | }, 159 | drawerShape = RoundedCornerShape(0.dp), 160 | drawerContent = { 161 | DrawerCompose(scope, scaffoldState, 162 | dashboardData.balanceLiqr.toString(), 163 | userId) { viewModel.logout() } 164 | }, 165 | bottomBar = { 166 | BottomNavigation( 167 | backgroundColor = MaterialTheme.colors.onSecondary, 168 | contentColor = MaterialTheme.colors.onSecondary 169 | ) { 170 | 171 | val items=BottomNavData.values() 172 | items.forEach { 173 | BottomNavigationItem( 174 | icon = { 175 | Icon(painterResource(it.iconId ) , 176 | contentDescription =it.title) 177 | }, 178 | label = { Text(text = it.title) }, 179 | selectedContentColor = MaterialTheme.colors.secondary, 180 | unselectedContentColor = MaterialTheme.colors.primary 181 | , 182 | alwaysShowLabel = true, 183 | selected = currentRoute==it.route, 184 | onClick = { 185 | onItemClick(it.route) 186 | 187 | } 188 | ) 189 | } 190 | 191 | } 192 | 193 | 194 | }) { 195 | 196 | SetupNavHost() 197 | 198 | } 199 | } 200 | 201 | 202 | @ExperimentalFoundationApi 203 | @Preview(showBackground = true, 204 | uiMode = UI_MODE_NIGHT_NO) 205 | @Composable 206 | override fun ProvideComposeLightPreview() { 207 | 208 | //jetpack compose compiler sometimes shows different colors 209 | SetUpDashboardCompose(DashboardResponse.Data(), "userId", 210 | "") { 211 | 212 | DashboardContent(listOf( 213 | MenuItem(R.drawable.ic_prepaid, "Prepaid"), 214 | MenuItem(R.drawable.ic_postpaid, "PostPaid"), 215 | MenuItem(R.drawable.ic_dtf, "Dth"), 216 | MenuItem(R.drawable.ic_datacard, "DataCard"), 217 | MenuItem(R.drawable.ic_gas, "Gas"), 218 | MenuItem(R.drawable.ic_water, "Water"), 219 | MenuItem(R.drawable.ic_electricity, "Electricity"), 220 | MenuItem(R.drawable.ic_billpayment, "Bill Payment"), 221 | MenuItem(R.drawable.ic_fund_recieve, "Fun receive"), 222 | MenuItem(R.drawable.ic_bus, "bus"), 223 | MenuItem(R.drawable.ic_flight, "flight"), 224 | MenuItem(R.drawable.ic_hotel_booking, "Hotel booking"), 225 | )) 226 | } 227 | } 228 | 229 | 230 | } 231 | 232 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/dashboard/DashboardUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.dashboard 2 | 3 | import com.example.mvvmKotlinJetpackCompose.data.network.Resource 4 | import com.example.mvvmKotlinJetpackCompose.data.network.model.DashboardResponse 5 | import com.example.mvvmKotlinJetpackCompose.data.repos.DashboardRepository 6 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseUseCase 7 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.flow 10 | import kotlinx.coroutines.flow.flowOn 11 | import javax.inject.Inject 12 | 13 | class DashboardUseCase @Inject constructor( 14 | appDispatcher: DispatcherProvider, 15 | dashboardRepository: DashboardRepository 16 | ) : BaseUseCase(dashboardRepository, appDispatcher) { 17 | //add dashboard business logic 18 | suspend fun getDashboardData(): Resource { 19 | 20 | return getRepository().getDashboardData() 21 | } 22 | 23 | fun getUserId(): String { 24 | 25 | return getRepository().getUserId() 26 | } 27 | 28 | fun logout(){ 29 | getRepository().logout() 30 | 31 | 32 | } 33 | 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/dashboard/DashboardViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.dashboard 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.annotation.VisibleForTesting.Companion.PRIVATE 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.viewModelScope 8 | import com.example.mvvmKotlinJetpackCompose.R 9 | import com.example.mvvmKotlinJetpackCompose.data.network.Resource 10 | import com.example.mvvmKotlinJetpackCompose.data.network.Success 11 | import com.example.mvvmKotlinJetpackCompose.data.network.model.DashboardResponse 12 | import com.example.mvvmKotlinJetpackCompose.data.others.MenuItem 13 | import com.example.mvvmKotlinJetpackCompose.data.repos.DashboardRepository 14 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseViewModelUseCase 15 | import com.example.mvvmKotlinJetpackCompose.util.SingleEvent 16 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 17 | import dagger.hilt.android.lifecycle.HiltViewModel 18 | import kotlinx.coroutines.flow.flow 19 | import kotlinx.coroutines.flow.flowOn 20 | import kotlinx.coroutines.launch 21 | import javax.inject.Inject 22 | 23 | @HiltViewModel 24 | class DashboardViewModel @Inject constructor( 25 | dashboardUseCase: DashboardUseCase 26 | ) : BaseViewModelUseCase(dashboardUseCase) { 27 | 28 | @VisibleForTesting(otherwise = PRIVATE) 29 | val dashboardDataPrivate = MutableLiveData>() 30 | val dashboardData: LiveData> get() = dashboardDataPrivate 31 | 32 | 33 | @VisibleForTesting(otherwise = PRIVATE) 34 | val userIdDataPrivate = MutableLiveData>() 35 | val userIdData: LiveData> get() = userIdDataPrivate 36 | 37 | 38 | @VisibleForTesting(otherwise = PRIVATE) 39 | val logoutPrivate = MutableLiveData>>() 40 | val logoutData: LiveData>> get() = logoutPrivate 41 | 42 | // init { 43 | // getDashBoarData() 44 | // } 45 | 46 | fun getDashBoarData() { 47 | 48 | viewModelScope.launch(exceptionHandler) { 49 | showLoading() 50 | 51 | val dashboardData = getUseCase().getDashboardData() 52 | dashboardDataPrivate.value = dashboardData 53 | 54 | val userId = getUseCase().getUserId() 55 | userIdDataPrivate.value = Success(userId) 56 | 57 | hideLoading() 58 | 59 | } 60 | } 61 | 62 | 63 | fun getMenuData(): List = listOf( 64 | MenuItem(R.drawable.ic_prepaid, "Prepaid"), 65 | MenuItem(R.drawable.ic_postpaid, "PostPaid"), 66 | MenuItem(R.drawable.ic_dtf, "Dth"), 67 | MenuItem(R.drawable.ic_datacard, "DataCard"), 68 | MenuItem(R.drawable.ic_gas, "Gas"), 69 | MenuItem(R.drawable.ic_water, "Water"), 70 | MenuItem(R.drawable.ic_electricity, "Electricity"), 71 | MenuItem(R.drawable.ic_billpayment, "Bill Payment"), 72 | MenuItem(R.drawable.ic_fund_recieve, "Fun receive"), 73 | MenuItem(R.drawable.ic_bus, "bus"), 74 | MenuItem(R.drawable.ic_flight, "flight"), 75 | MenuItem(R.drawable.ic_hotel_booking, "Hotel booking"), 76 | ) 77 | 78 | 79 | fun logout() { 80 | 81 | viewModelScope.launch(exceptionHandler) { 82 | 83 | getUseCase().logout() 84 | logoutPrivate.value = SingleEvent(Success(true)) 85 | 86 | } 87 | 88 | } 89 | 90 | 91 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/dashboard/compose/BottomNavData.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.dashboard.compose 2 | 3 | import com.example.mvvmKotlinJetpackCompose.R 4 | 5 | const val home="Home" 6 | const val history="History" 7 | const val other="Other" 8 | 9 | enum class BottomNavData(val route:String,val iconId:Int,val title:String) { 10 | Home("home", R.drawable.ic_baseline_home_24,home), 11 | History("history", R.drawable.ic_baseline_history_24,history), 12 | Other("other", R.drawable.ic_baseline_settings_24,other) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/dashboard/compose/DashboardCompose.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.dashboard 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.* 7 | import androidx.compose.foundation.lazy.grid.GridCells 8 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 9 | import androidx.compose.foundation.lazy.items 10 | import androidx.compose.material.* 11 | import androidx.compose.runtime.* 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.platform.testTag 16 | import androidx.compose.ui.res.dimensionResource 17 | import androidx.compose.ui.res.painterResource 18 | import androidx.compose.ui.res.stringResource 19 | import com.example.mvvmKotlinJetpackCompose.R 20 | import com.example.mvvmKotlinJetpackCompose.data.others.MenuItem 21 | 22 | 23 | @ExperimentalFoundationApi 24 | @Composable 25 | fun DashboardContent(menuItems: List, openActivity: (String) -> Unit={}) { 26 | 27 | Column(horizontalAlignment = Alignment.CenterHorizontally, 28 | modifier = Modifier.testTag(stringResource(id = R.string.dashboard_content_tag))) { 29 | Menu(menuItems, openActivity) 30 | Spacer(modifier = Modifier 31 | .height(dimensionResource(R.dimen.dp_80))) 32 | Image(painter = painterResource(R.drawable.jetpack_logo), 33 | modifier = Modifier 34 | .width(dimensionResource(id = R.dimen.dp_145)) 35 | .height(dimensionResource(id = R.dimen.dp_145)), 36 | contentDescription = "") 37 | 38 | Spacer(modifier = Modifier 39 | .height(dimensionResource(R.dimen.dp_100))) 40 | } 41 | 42 | } 43 | 44 | @ExperimentalFoundationApi 45 | @Composable 46 | private fun Menu(menuItems: List, openActivity: (String) -> Unit) { 47 | 48 | 49 | LazyVerticalGrid( 50 | columns = GridCells.Fixed(4), 51 | ) { 52 | items( count = menuItems.size) { item -> 53 | MenuItemCompose(Modifier 54 | .padding(top = dimensionResource(R.dimen.dp_40)), 55 | iconId = menuItems.get(item).icon, 56 | title = menuItems.get(item).text, 57 | onClick = { 58 | openActivity(it) 59 | 60 | }, color = MaterialTheme.colors.primary) 61 | 62 | } 63 | } 64 | } 65 | 66 | @Composable 67 | fun MenuItemCompose( 68 | modifier: Modifier = Modifier, iconId: Int, 69 | title: String, 70 | onClick: (String) -> Unit, 71 | color: Color, 72 | ) { 73 | Column(modifier 74 | 75 | .clickable { onClick(title) }, 76 | horizontalAlignment = Alignment.CenterHorizontally) 77 | { 78 | Image(modifier = Modifier 79 | .width(dimensionResource(R.dimen.dp_40)) 80 | .height(dimensionResource(R.dimen.dp_40)), 81 | painter = painterResource(iconId), 82 | contentDescription = "") 83 | Spacer(modifier = Modifier.height(dimensionResource(R.dimen.dp_5))) 84 | Text( 85 | text = title.uppercase(), 86 | color = color, 87 | style = MaterialTheme.typography.caption, 88 | ) 89 | 90 | } 91 | 92 | } 93 | 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/dashboard/compose/DrawerCompose.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.dashboard 2 | 3 | import android.content.res.Configuration.UI_MODE_NIGHT_NO 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.clickable 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.material.* 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.rememberCoroutineScope 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.layout.ContentScale 14 | import androidx.compose.ui.platform.testTag 15 | import androidx.compose.ui.res.dimensionResource 16 | import androidx.compose.ui.res.painterResource 17 | import androidx.compose.ui.res.stringResource 18 | import androidx.compose.ui.text.style.TextAlign 19 | import androidx.compose.ui.tooling.preview.Preview 20 | import androidx.compose.ui.unit.dp 21 | import com.example.mvvmKotlinJetpackCompose.R 22 | import com.example.mvvmKotlinJetpackCompose.ui.theme.CoinTheme 23 | import kotlinx.coroutines.CoroutineScope 24 | import kotlinx.coroutines.launch 25 | 26 | 27 | @Composable 28 | fun DrawerCompose( 29 | scope: CoroutineScope, 30 | scaffoldState: ScaffoldState, 31 | totalLiqrCoin: String, 32 | userId: String, 33 | logout: (String) -> Unit, 34 | ) { 35 | 36 | 37 | Column( 38 | Modifier 39 | .testTag(stringResource(R.string.drawer_test_tag)) 40 | .fillMaxSize() 41 | .background(MaterialTheme.colors.onSecondary)) { 42 | Box(contentAlignment = Alignment.Center) { 43 | 44 | 45 | Column(horizontalAlignment = Alignment.CenterHorizontally, 46 | modifier = Modifier 47 | .fillMaxWidth() 48 | .background(MaterialTheme.colors.secondary)) { 49 | 50 | Image(modifier = Modifier 51 | .width(100.dp) 52 | .height(100.dp), 53 | painter = painterResource(R.drawable.ic_baseline_person_24), 54 | contentScale = ContentScale.FillBounds, 55 | contentDescription = "") 56 | Text( 57 | text = userId, 58 | textAlign = TextAlign.Center, 59 | color = MaterialTheme.colors.onSecondary, 60 | style = MaterialTheme.typography.body1, 61 | ) 62 | Text( 63 | text = "Total = "+totalLiqrCoin, 64 | textAlign = TextAlign.Center, 65 | color = MaterialTheme.colors.onSecondary, 66 | style = MaterialTheme.typography.body1, 67 | ) 68 | Spacer(modifier = Modifier.height(dimensionResource(R.dimen.dp_10))) 69 | 70 | } 71 | 72 | } 73 | 74 | Column(Modifier.padding(dimensionResource(R.dimen.dp_20))) { 75 | DrawerItem(R.drawable.ic_home, 76 | R.string.dashboard, 77 | { 78 | scope.launch { 79 | scaffoldState.drawerState.close() 80 | } 81 | 82 | }) 83 | 84 | DrawerItem(R.drawable.ic_logout, 85 | R.string.logout, 86 | { 87 | logout("logout") 88 | }) 89 | 90 | 91 | } 92 | } 93 | 94 | } 95 | 96 | 97 | @Composable 98 | fun DrawerItem(icon: Int, title: Int, onClick: () -> Unit) { 99 | Row( 100 | Modifier 101 | .fillMaxWidth() 102 | .height(dimensionResource(R.dimen.dp_60)) 103 | .clickable { onClick() }, 104 | verticalAlignment = Alignment.CenterVertically) { 105 | Image(modifier = Modifier 106 | .width(dimensionResource(R.dimen.dp_25)) 107 | .height(dimensionResource(R.dimen.dp_25)), 108 | painter = painterResource(icon), 109 | contentDescription = "") 110 | Spacer(modifier = Modifier.width(dimensionResource(R.dimen.dp_15))) 111 | Text( 112 | text = stringResource(title), 113 | style = MaterialTheme.typography.h6, 114 | ) 115 | 116 | } 117 | 118 | Divider(color = MaterialTheme.colors.primary) 119 | 120 | 121 | } 122 | 123 | @Preview(uiMode = UI_MODE_NIGHT_NO) 124 | @Composable 125 | fun Preview() { 126 | CoinTheme { 127 | val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed)) 128 | val scope = rememberCoroutineScope() 129 | 130 | DrawerCompose(scope, scaffoldState, 131 | "23","123", {}) 132 | } 133 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/dashboard/compose/NavDrawerItem.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.dashboard.compose 2 | 3 | import com.example.mvvmKotlinJetpackCompose.R 4 | 5 | sealed class NavDrawerItem(var route: String, var icon: Int, var title: Int) { 6 | 7 | object Dashboard : NavDrawerItem( 8 | "dashboard", 9 | R.drawable.ic_home, 10 | R.string.dashboard 11 | ) 12 | 13 | object Logout : NavDrawerItem( 14 | "logout", 15 | R.drawable.ic_logout, 16 | R.string.logout 17 | ) 18 | 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/dashboard/compose/TopBarCompose.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.dashboard.compose 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material.* 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.filled.Menu 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.rememberCoroutineScope 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.res.dimensionResource 12 | import androidx.compose.ui.text.style.TextAlign 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | import com.example.mvvmKotlinJetpackCompose.R 16 | import com.example.mvvmKotlinJetpackCompose.ui.theme.CoinTheme 17 | import kotlinx.coroutines.CoroutineScope 18 | import kotlinx.coroutines.launch 19 | 20 | @Composable 21 | fun TopBar( 22 | scope: CoroutineScope, 23 | scaffoldState: ScaffoldState, 24 | coin: Double, 25 | ) { 26 | 27 | TopAppBar(title = { 28 | 29 | Text(modifier = Modifier 30 | .fillMaxWidth() 31 | .padding(end = dimensionResource(id = R.dimen.dp_15)), 32 | textAlign = TextAlign.End, 33 | text = "1 coin = "+coin+" INR", 34 | color = MaterialTheme.colors.onSecondary, 35 | style = MaterialTheme.typography.subtitle2) 36 | 37 | }, 38 | navigationIcon = { 39 | IconButton(onClick = { 40 | scope.launch { scaffoldState.drawerState.open() } 41 | }) { 42 | Icon(Icons.Default.Menu, "", 43 | tint = MaterialTheme.colors.onSecondary) 44 | } 45 | }, 46 | backgroundColor = MaterialTheme.colors.secondary, 47 | elevation = 0.dp 48 | ) 49 | 50 | 51 | } 52 | 53 | @Preview(showBackground = true, 54 | uiMode = Configuration.UI_MODE_NIGHT_NO) 55 | @Composable 56 | fun ProvideComposeLightPreview() { 57 | CoinTheme { 58 | val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed)) 59 | val scope = rememberCoroutineScope() 60 | TopBar(scope, scaffoldState, 20.0) 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/login/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.login 2 | 3 | 4 | import android.content.Intent 5 | import android.content.res.Configuration 6 | import androidx.activity.viewModels 7 | import androidx.compose.foundation.Image 8 | import androidx.compose.foundation.background 9 | import androidx.compose.foundation.layout.* 10 | import androidx.compose.foundation.rememberScrollState 11 | import androidx.compose.foundation.verticalScroll 12 | import androidx.compose.material.* 13 | import androidx.compose.runtime.* 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.platform.testTag 18 | import androidx.compose.ui.res.dimensionResource 19 | import androidx.compose.ui.res.painterResource 20 | import androidx.compose.ui.res.stringResource 21 | import androidx.compose.ui.text.input.PasswordVisualTransformation 22 | import androidx.compose.ui.text.style.TextAlign 23 | import androidx.compose.ui.tooling.preview.Preview 24 | import com.example.mvvmKotlinJetpackCompose.R 25 | import com.example.mvvmKotlinJetpackCompose.data.network.DataError 26 | import com.example.mvvmKotlinJetpackCompose.data.network.Success 27 | import com.example.mvvmKotlinJetpackCompose.data.repos.LoginRepository 28 | import com.example.mvvmKotlinJetpackCompose.di.login.LoginComponentManager 29 | import com.example.mvvmKotlinJetpackCompose.di.login.LoginEntryPoint 30 | import com.example.mvvmKotlinJetpackCompose.ui.ViewModelFactory 31 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseComponentActivity 32 | import com.example.mvvmKotlinJetpackCompose.ui.dashboard.DashboardActivity 33 | import com.example.mvvmKotlinJetpackCompose.ui.theme.CoinTheme 34 | import com.example.mvvmKotlinJetpackCompose.ui.theme.Shapes 35 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 36 | import com.example.mvvmKotlinJetpackCompose.util.observe 37 | import dagger.hilt.EntryPoints 38 | import dagger.hilt.android.AndroidEntryPoint 39 | import javax.inject.Inject 40 | 41 | @AndroidEntryPoint 42 | class LoginActivity : BaseComponentActivity() { 43 | 44 | @Inject 45 | lateinit var loginComponentManager: LoginComponentManager 46 | 47 | private lateinit var loginRepository: LoginRepository 48 | 49 | override val viewModel: LoginViewModel by viewModels { 50 | loginRepository = EntryPoints.get( 51 | loginComponentManager.getComponent(), 52 | LoginEntryPoint::class.java 53 | ).getLoginRepo() 54 | ViewModelFactory(loginRepository) 55 | 56 | } 57 | 58 | 59 | 60 | @Composable 61 | override fun ProvideCompose() { 62 | 63 | observe(viewModel.loginResponse) { 64 | when (it) { 65 | is DataError -> { 66 | } 67 | is Success -> { 68 | loginComponentManager.destroyLoginComponent() 69 | startDashboardAcitivty() 70 | finish() 71 | } 72 | 73 | 74 | } 75 | } 76 | 77 | LoginCompose { 78 | TopImageAndText() 79 | var txtAccountNo by remember { 80 | mutableStateOf("suorizwansayyed786@gmail.com") } 81 | 82 | TextFieldUserName(txtAccountNo) { txtAccountNo = it } 83 | var txtPass by remember { mutableStateOf("SuoRizwan") } 84 | 85 | TextFieldPassword(txtPass) { 86 | txtPass = it 87 | } 88 | 89 | RegistrationButton { 90 | viewModel.onSignInBtnClick(txtAccountNo.trim(), 91 | txtPass.trim()) 92 | } 93 | 94 | 95 | } 96 | 97 | } 98 | 99 | @Composable 100 | private fun RegistrationButton(onClick: () -> Unit={}) { 101 | 102 | Button(modifier = Modifier 103 | .testTag(stringResource(id = R.string.sign_in)) 104 | .width(dimensionResource(R.dimen.dp_150)), 105 | colors = ButtonDefaults 106 | .buttonColors(MaterialTheme.colors.secondary), 107 | onClick = { onClick() }) { 108 | 109 | Text(text = stringResource(id = R.string.sign_in).uppercase()) 110 | } 111 | 112 | Spacer(modifier = Modifier 113 | .height(dimensionResource(R.dimen.dp_30))) 114 | 115 | 116 | } 117 | 118 | 119 | @Composable 120 | private fun TextFieldPassword(txtPass: String, setPass: (String) -> Unit={}) { 121 | 122 | 123 | TextField(modifier = Modifier 124 | .testTag(stringResource(R.string.password)) 125 | .fillMaxWidth() 126 | .wrapContentHeight() 127 | .background( 128 | shape = Shapes.medium, 129 | color = MaterialTheme.colors.secondary 130 | ), 131 | maxLines = 1, 132 | singleLine = true, 133 | value = txtPass, 134 | visualTransformation = PasswordVisualTransformation(), 135 | onValueChange = { setPass(it) }, 136 | placeholder = { 137 | Text(text = stringResource(R.string.password), 138 | color = MaterialTheme.colors.onSecondary) 139 | }, 140 | colors = TextFieldDefaults.textFieldColors( 141 | textColor = MaterialTheme.colors.onSecondary, 142 | disabledTextColor = Color.Transparent, 143 | backgroundColor = Color.Transparent, 144 | focusedIndicatorColor = Color.Transparent, 145 | unfocusedIndicatorColor = Color.Transparent, 146 | disabledIndicatorColor = Color.Transparent, 147 | cursorColor = MaterialTheme.colors.onSecondary 148 | ), 149 | leadingIcon = { 150 | Icon(painter = painterResource(R.drawable.ic_baseline_lock_24), 151 | contentDescription = "", tint = MaterialTheme.colors.onSecondary) 152 | }) 153 | 154 | Spacer(modifier = Modifier 155 | .height(dimensionResource(id = R.dimen.dp_40))) 156 | 157 | 158 | } 159 | 160 | @Composable 161 | private fun TopImageAndText() { 162 | Image(modifier = Modifier 163 | .padding(top = dimensionResource(id = R.dimen.dp_100)) 164 | .size(dimensionResource(id = R.dimen.dp_100)), 165 | painter = painterResource(id = R.drawable.jetpack_logo), 166 | contentDescription = "") 167 | 168 | Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.dp_10))) 169 | 170 | Text(modifier = Modifier.width(IntrinsicSize.Max), 171 | textAlign = TextAlign.Center, 172 | text = stringResource(R.string.login_title), 173 | style = MaterialTheme.typography.button, 174 | color = MaterialTheme.colors.secondary) 175 | 176 | Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.dp_100))) 177 | 178 | 179 | } 180 | 181 | @Composable 182 | private fun TextFieldUserName(txtAccountNo: String, setAcc: (String) -> Unit={}) { 183 | 184 | 185 | TextField(modifier = Modifier 186 | .testTag(stringResource(R.string.email_address)) 187 | .fillMaxWidth() 188 | .wrapContentHeight() 189 | .background( 190 | shape = Shapes.medium, 191 | color = MaterialTheme.colors.secondary 192 | ), 193 | maxLines = 1, 194 | singleLine = true, 195 | value = txtAccountNo, 196 | onValueChange = { setAcc(it) }, 197 | placeholder = { 198 | Text(text = stringResource(R.string.email_address), 199 | color = MaterialTheme.colors.onSecondary) 200 | }, 201 | colors = TextFieldDefaults.textFieldColors( 202 | textColor = MaterialTheme.colors.onSecondary, 203 | disabledTextColor = Color.Transparent, 204 | backgroundColor = Color.Transparent, 205 | focusedIndicatorColor = Color.Transparent, 206 | unfocusedIndicatorColor = Color.Transparent, 207 | disabledIndicatorColor = Color.Transparent, 208 | cursorColor = MaterialTheme.colors.onSecondary 209 | 210 | ), 211 | leadingIcon = { 212 | Icon(painter = painterResource(R.drawable.ic_baseline_person_24), 213 | contentDescription = "", tint = MaterialTheme.colors.onSecondary) 214 | }) 215 | Spacer(modifier = Modifier 216 | .height(dimensionResource(id = R.dimen.dp_20))) 217 | 218 | 219 | } 220 | 221 | private fun startDashboardAcitivty() { 222 | startActivity(Intent(this, DashboardActivity::class.java)) 223 | } 224 | 225 | @Composable 226 | fun LoginCompose(childrenCompose: @Composable () -> Unit) { 227 | 228 | 229 | Column( 230 | Modifier 231 | .verticalScroll(rememberScrollState()) 232 | .fillMaxSize() 233 | .padding(dimensionResource(R.dimen.dp_20)), 234 | horizontalAlignment = Alignment.CenterHorizontally, 235 | ) { 236 | childrenCompose() 237 | } 238 | 239 | 240 | 241 | 242 | } 243 | 244 | @Preview( 245 | showBackground = true, 246 | uiMode = Configuration.UI_MODE_NIGHT_NO 247 | ) 248 | @Composable 249 | override fun ProvideComposeLightPreview() { 250 | CoinTheme { 251 | LoginCompose { 252 | 253 | TopImageAndText() 254 | TextFieldUserName("") 255 | TextFieldPassword("") 256 | RegistrationButton() 257 | 258 | } 259 | 260 | } 261 | } 262 | 263 | 264 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/login/LoginViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.login 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.annotation.VisibleForTesting.Companion.PRIVATE 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.viewModelScope 8 | import com.example.mvvmKotlinJetpackCompose.data.network.DataError 9 | import com.example.mvvmKotlinJetpackCompose.data.network.Resource 10 | import com.example.mvvmKotlinJetpackCompose.data.network.model.LoginResponse 11 | import com.example.mvvmKotlinJetpackCompose.data.repos.LoginRepository 12 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseViewModel 13 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseViewModelRepository 14 | import com.example.mvvmKotlinJetpackCompose.util.ENTER_EMAIL_ID 15 | import com.example.mvvmKotlinJetpackCompose.util.ENTER_PASSWORD 16 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 17 | import kotlinx.coroutines.flow.flowOn 18 | import kotlinx.coroutines.launch 19 | 20 | 21 | class LoginViewModel ( 22 | loginRepository: LoginRepository, 23 | ) : BaseViewModelRepository(loginRepository) { 24 | 25 | 26 | 27 | @VisibleForTesting(otherwise = PRIVATE) 28 | val loginResponsePrivate = MutableLiveData>() 29 | val loginResponse: LiveData> get() = loginResponsePrivate 30 | 31 | 32 | 33 | fun onSignInBtnClick(email: String, password: String) { 34 | 35 | when { 36 | email.isEmpty() -> { 37 | showMessageDialog(DataError(ENTER_EMAIL_ID)) 38 | } 39 | password.isEmpty() -> { 40 | showMessageDialog(DataError(ENTER_PASSWORD)) 41 | 42 | } 43 | else -> { 44 | showLoading() 45 | 46 | viewModelScope.launch(exceptionHandler) { 47 | 48 | getRepository().login(email, password) 49 | .collect { loginResult -> 50 | hideLoading() 51 | 52 | if (loginResult.data != null) { 53 | loginResponsePrivate.value = loginResult 54 | } else { 55 | showMessageDialog(loginResult as DataError) 56 | } 57 | 58 | } 59 | 60 | } 61 | 62 | 63 | 64 | } 65 | } 66 | 67 | } 68 | 69 | 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/splash/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.splash 2 | 3 | import android.content.res.Configuration 4 | import androidx.activity.viewModels 5 | import androidx.compose.foundation.ExperimentalFoundationApi 6 | import androidx.compose.foundation.Image 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.material.CircularProgressIndicator 9 | import androidx.compose.material.MaterialTheme 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.* 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.platform.testTag 15 | import androidx.compose.ui.res.dimensionResource 16 | import androidx.compose.ui.res.painterResource 17 | import androidx.compose.ui.res.stringResource 18 | import androidx.compose.ui.text.style.TextAlign 19 | import androidx.compose.ui.tooling.preview.Preview 20 | import com.example.mvvmKotlinJetpackCompose.R 21 | import com.example.mvvmKotlinJetpackCompose.data.network.Success 22 | import com.example.mvvmKotlinJetpackCompose.di.login.LoginComponentManager 23 | import com.example.mvvmKotlinJetpackCompose.di.login.LoginEntryPoint 24 | import com.example.mvvmKotlinJetpackCompose.ui.ViewModelFactory 25 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseComponentActivity 26 | import com.example.mvvmKotlinJetpackCompose.ui.dashboard.DashboardActivity 27 | import com.example.mvvmKotlinJetpackCompose.ui.login.LoginActivity 28 | import com.example.mvvmKotlinJetpackCompose.data.repos.LoginRepository 29 | import com.example.mvvmKotlinJetpackCompose.ui.theme.CoinTheme 30 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 31 | import com.example.mvvmKotlinJetpackCompose.util.observeEvent 32 | import dagger.hilt.EntryPoints 33 | import dagger.hilt.android.AndroidEntryPoint 34 | import javax.inject.Inject 35 | 36 | @ExperimentalFoundationApi 37 | @AndroidEntryPoint 38 | class SplashActivity : BaseComponentActivity() { 39 | 40 | override val wantToShowCustomLoading=true 41 | 42 | @Inject 43 | lateinit var loginComponentManager: LoginComponentManager 44 | 45 | private lateinit var loginRepository: LoginRepository 46 | 47 | override val viewModel: SplashViewModel by viewModels { 48 | loginRepository = EntryPoints.get( 49 | loginComponentManager.getComponent(), 50 | LoginEntryPoint::class.java 51 | ).getLoginRepo() 52 | ViewModelFactory(loginRepository) 53 | 54 | } 55 | 56 | 57 | @Composable 58 | override fun ProvideCompose() { 59 | viewModel.decideActivity() 60 | 61 | SplashCompose { 62 | ImageAndAppName { 63 | val loadingValue = viewModel.isLoading() 64 | 65 | observeEvent(viewModel.singleEventOpenActivity) { 66 | when (val result = it.getContentIfNotHandled()) { 67 | is Success -> { 68 | if (result.data == 1) { 69 | startActivity() 70 | } else { 71 | startActivity() 72 | 73 | } 74 | 75 | } 76 | 77 | else -> {} 78 | } 79 | } 80 | if (loadingValue) { 81 | 82 | CircularProgressIndicator(Modifier.testTag( 83 | getString(R.string.test_tag_circular_progress) 84 | )) 85 | } 86 | 87 | } 88 | 89 | } 90 | } 91 | 92 | @Composable 93 | private fun SplashCompose(ChildrenCompose: @Composable () -> Unit) { 94 | 95 | Box(Modifier.fillMaxSize(), 96 | contentAlignment = Alignment.TopCenter) { 97 | ChildrenCompose() 98 | 99 | } 100 | } 101 | 102 | 103 | @Composable 104 | private fun ImageAndAppName(showLoading: @Composable () -> Unit) { 105 | 106 | Column(horizontalAlignment = Alignment.CenterHorizontally) { 107 | Image(painter = painterResource(R.drawable.jetpack_logo), 108 | modifier = Modifier 109 | .padding(top = dimensionResource(id = R.dimen.dp_100)) 110 | .width(dimensionResource(id = R.dimen.dp_120)) 111 | .height(dimensionResource(id = R.dimen.dp_120)), 112 | contentDescription = "") 113 | 114 | Text(modifier = Modifier.width(IntrinsicSize.Max), 115 | text = stringResource(id = R.string.app_name), 116 | textAlign = TextAlign.Center, 117 | color = MaterialTheme.colors.secondary, 118 | style = MaterialTheme.typography.h6) 119 | 120 | Spacer(modifier = Modifier.height(dimensionResource(R.dimen.dp_20))) 121 | showLoading() 122 | 123 | } 124 | } 125 | 126 | 127 | @Preview( 128 | showBackground = true, 129 | uiMode = Configuration.UI_MODE_NIGHT_NO 130 | ) 131 | @Composable 132 | override fun ProvideComposeLightPreview() { 133 | CoinTheme { 134 | 135 | SplashCompose { 136 | ImageAndAppName { 137 | CircularProgressIndicator() 138 | 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/splash/SplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.splash 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.annotation.VisibleForTesting.Companion.PRIVATE 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.viewModelScope 8 | import com.example.mvvmKotlinJetpackCompose.data.network.Resource 9 | import com.example.mvvmKotlinJetpackCompose.data.network.Success 10 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseViewModel 11 | import com.example.mvvmKotlinJetpackCompose.data.repos.LoginRepository 12 | import com.example.mvvmKotlinJetpackCompose.ui.base.BaseViewModelRepository 13 | import com.example.mvvmKotlinJetpackCompose.util.LoggedInMode 14 | import com.example.mvvmKotlinJetpackCompose.util.SingleEvent 15 | import com.example.mvvmKotlinJetpackCompose.util.coroutines.DispatcherProvider 16 | import kotlinx.coroutines.FlowPreview 17 | import kotlinx.coroutines.flow.flowOn 18 | import kotlinx.coroutines.launch 19 | 20 | 21 | class SplashViewModel ( 22 | loginRepository: LoginRepository, 23 | ) : BaseViewModelRepository(loginRepository) { 24 | 25 | @VisibleForTesting(otherwise = PRIVATE) 26 | val privateSingleEventOpenActivity = 27 | MutableLiveData>>()// if we keep this private 28 | //then it will not be visible for test package, hence its public, annotation helped us to make our code more 29 | //readable 30 | 31 | val singleEventOpenActivity: LiveData>> get() = privateSingleEventOpenActivity//activity 32 | //should observe livedata because live data is immutable you cannot set its value , in this way we restrict 33 | //activity to directly manipulate our live data ,activity should only observe the data and should not try to update 34 | //the data, doing this we reduced the dependency between our activity and view model , it becomes loosely coupled 35 | //we must try to make our classes communication loosely coupled , we can reuse our activity in another app 36 | //with minimal changes if it has a same design 37 | 38 | @FlowPreview 39 | fun decideActivity() { 40 | showLoading() 41 | viewModelScope.launch(exceptionHandler) { 42 | 43 | getRepository().isUserLoggedIn() 44 | .collect { 45 | hideLoading() 46 | if (it == LoggedInMode.LOGGED_IN_MODE_SERVER.type) { 47 | privateSingleEventOpenActivity.value = SingleEvent(Success(1)) 48 | 49 | } else { 50 | privateSingleEventOpenActivity.value = SingleEvent(Success(2)) 51 | 52 | } 53 | } 54 | 55 | } 56 | 57 | } 58 | 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | 6 | val DarkGray = Color(0xFF838285) 7 | val Gray = Color(0xFFDBDBDB) 8 | val LightPurple = Color(0xFF900370) 9 | val White = Color.White 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.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(8.dp), 10 | large = RoundedCornerShape(12.dp) 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.theme 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.material.lightColors 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.graphics.Color.Companion.Black 7 | 8 | 9 | private val LightColorPalette = lightColors( 10 | primary = DarkGray, 11 | primaryVariant = Gray, 12 | secondary = LightPurple, 13 | onPrimary = Black, 14 | onSecondary = White, 15 | 16 | /* Other default colors to override 17 | background = Color.White, 18 | surface = Color.White, 19 | onPrimary = Color.White, 20 | onSecondary = Color.Black, 21 | onBackground = Color.Black, 22 | onSurface = Color.Black, 23 | */ 24 | ) 25 | 26 | 27 | 28 | @Composable 29 | fun CoinTheme(content: @Composable() () -> Unit) { 30 | 31 | 32 | MaterialTheme( 33 | colors = LightColorPalette, 34 | typography = Typography, 35 | shapes = Shapes, 36 | content = content 37 | ) 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.Font 6 | import androidx.compose.ui.text.font.FontFamily 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.unit.sp 9 | import com.example.mvvmKotlinJetpackCompose.R 10 | 11 | // Set of Material typography styles to start with 12 | 13 | private val Montserrat = FontFamily( 14 | Font(R.font.averta_regular, FontWeight.Light), 15 | Font(R.font.averta_regular, FontWeight.Normal), 16 | Font(R.font.averta_regular, FontWeight.Medium), 17 | Font(R.font.averta_regular, FontWeight.SemiBold) 18 | ) 19 | 20 | val Typography = Typography( 21 | h1 = TextStyle( 22 | fontFamily = Montserrat, 23 | fontSize = 96.sp, 24 | fontWeight = FontWeight.Light, 25 | lineHeight = 117.sp, 26 | letterSpacing = (-1.5).sp 27 | ), 28 | h2 = TextStyle( 29 | fontFamily = Montserrat, 30 | fontSize = 60.sp, 31 | fontWeight = FontWeight.Light, 32 | lineHeight = 73.sp, 33 | letterSpacing = (-0.5).sp 34 | ), 35 | h3 = TextStyle( 36 | fontFamily = Montserrat, 37 | fontSize = 48.sp, 38 | fontWeight = FontWeight.Normal, 39 | lineHeight = 59.sp 40 | ), 41 | h4 = TextStyle( 42 | fontFamily = Montserrat, 43 | fontSize = 30.sp, 44 | fontWeight = FontWeight.SemiBold, 45 | lineHeight = 37.sp 46 | ), 47 | h5 = TextStyle( 48 | fontFamily = Montserrat, 49 | fontSize = 24.sp, 50 | fontWeight = FontWeight.SemiBold, 51 | lineHeight = 29.sp 52 | ), 53 | h6 = TextStyle( 54 | fontFamily = Montserrat, 55 | fontSize = 20.sp, 56 | fontWeight = FontWeight.SemiBold, 57 | lineHeight = 24.sp 58 | ), 59 | subtitle1 = TextStyle( 60 | fontFamily = Montserrat, 61 | fontSize = 16.sp, 62 | fontWeight = FontWeight.SemiBold, 63 | lineHeight = 20.sp, 64 | letterSpacing = 0.5.sp 65 | ), 66 | subtitle2 = TextStyle( 67 | fontFamily = Montserrat, 68 | fontSize = 14.sp, 69 | fontWeight = FontWeight.Medium, 70 | lineHeight = 17.sp, 71 | letterSpacing = 0.1.sp 72 | ), 73 | body1 = TextStyle( 74 | fontFamily = Montserrat, 75 | fontSize = 16.sp, 76 | fontWeight = FontWeight.Bold, 77 | lineHeight = 20.sp, 78 | letterSpacing = 0.15.sp 79 | ), 80 | body2 = TextStyle( 81 | fontFamily = Montserrat, 82 | fontSize = 14.sp, 83 | fontWeight = FontWeight.SemiBold, 84 | lineHeight = 20.sp, 85 | letterSpacing = 0.25.sp 86 | ), 87 | button = TextStyle( 88 | fontFamily = Montserrat, 89 | fontSize = 14.sp, 90 | fontWeight = FontWeight.SemiBold, 91 | lineHeight = 16.sp, 92 | letterSpacing = 1.25.sp 93 | ), 94 | caption = TextStyle( 95 | fontFamily = Montserrat, 96 | fontSize = 10.sp, 97 | fontWeight = FontWeight.ExtraBold, 98 | lineHeight = 16.sp, 99 | letterSpacing = 0.sp 100 | ), 101 | overline = TextStyle( 102 | fontFamily = Montserrat, 103 | fontSize = 12.sp, 104 | fontWeight = FontWeight.Normal, 105 | ) 106 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/util/AppConstants.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.util 2 | 3 | 4 | const val PREF_NAME = "MvvmKotlin_JetpackCompose_pref" 5 | const val PREF_KEY_USER_LOGGED_IN_MODE = "PREF_KEY_USER_LOGGED_IN_MODE" 6 | const val PREF_KEY_CURRENT_USER_ID = "PREF_KEY_CURRENT_USER_ID" 7 | const val PREF_KEY_CURRENT_USER_NAME = "PREF_KEY_CURRENT_USER_NAME" 8 | const val PREF_KEY_CURRENT_USER_EMAIL = "PREF_KEY_CURRENT_USER_EMAIL" 9 | const val PREF_KEY_CURRENT_USER_PROFILE_PIC_URL = "PREF_KEY_CURRENT_USER_PROFILE_PIC_URL" 10 | const val PREF_KEY_ACCESS_TOKEN = "PREF_KEY_ACCESS_TOKEN" 11 | const val NULL_INDEX = "-1" 12 | 13 | const val NO_INTERNET_CONNECTION = "No network Connection !" 14 | const val NETWORK_ERROR = -2 15 | const val ENTER_PASSWORD = "Please enter Password." 16 | const val ENTER_EMAIL_ID = "Please enter Email Id." 17 | const val SOMETHING_WENT_WRONG ="Something went Wrong" 18 | const val CLIENT_SIDE_ERROR ="Client side error" 19 | const val SERVER_SIDE_ERROR ="Server side error" 20 | 21 | 22 | 23 | enum class LoggedInMode(val type: Int) { 24 | LOGGED_IN_MODE_LOGGED_OUT(0), 25 | LOGGED_IN_MODE_GOOGLE(1), 26 | LOGGED_IN_MODE_FB(2), 27 | LOGGED_IN_MODE_SERVER(3); 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/util/LiveDataExt.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.util 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.Observer 6 | 7 | fun LifecycleOwner.observe(liveData: LiveData, action: (t: T) -> Unit) { 8 | liveData.observe(this, Observer { 9 | it?.let { 10 | t -> action(t) 11 | } 12 | }) 13 | } 14 | 15 | fun LifecycleOwner.observeEvent( 16 | liveData: LiveData>, 17 | action: (t: SingleEvent) -> Unit 18 | ) { 19 | liveData.observe(this, Observer { it?.let { t -> action(t) } }) 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/util/NetworkUtils.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.util 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkCapabilities 6 | import android.os.Build 7 | 8 | class NetworkUtils { 9 | 10 | companion object{ 11 | fun isNetworkAvailable(context: Context): Boolean { 12 | val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 14 | val nw = connectivityManager.activeNetwork ?: return false 15 | val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false 16 | return when { 17 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true 18 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true 19 | //for other device how are able to connect with Ethernet 20 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true 21 | //for check internet over Bluetooth 22 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true 23 | else -> false 24 | } 25 | } else { 26 | return connectivityManager.activeNetworkInfo?.isConnected ?: false 27 | } 28 | } 29 | } 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/util/SingleEvent.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.util 2 | 3 | /** 4 | * Used as a wrapper for data that is exposed via a LiveData that represents an event. 5 | */ 6 | open class SingleEvent(private val content: T) { 7 | 8 | var hasBeenHandled = false 9 | private set // Allow external read but not write 10 | 11 | /** 12 | * Returns the content and prevents its use again. 13 | */ 14 | fun getContentIfNotHandled(): T? { 15 | return if (hasBeenHandled) { 16 | null 17 | } else { 18 | hasBeenHandled = true 19 | content 20 | } 21 | } 22 | 23 | /** 24 | * Returns the content, even if it's already been handled. 25 | */ 26 | fun peekContent(): T = content 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/util/coroutines/AppDispatcherProvider.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.util.coroutines 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | import javax.inject.Inject 6 | 7 | class AppDispatcherProvider @Inject constructor() : DispatcherProvider -------------------------------------------------------------------------------- /app/src/main/java/com/example/mvvmKotlinJetpackCompose/util/coroutines/DispatcherProvider.kt: -------------------------------------------------------------------------------- 1 | package com.example.mvvmKotlinJetpackCompose.util.coroutines 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | interface DispatcherProvider { 7 | 8 | fun computation (): CoroutineDispatcher=Dispatchers.Default 9 | fun io ():CoroutineDispatcher=Dispatchers.IO 10 | fun main ():CoroutineDispatcher=Dispatchers.Main 11 | 12 | } -------------------------------------------------------------------------------- /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_history_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_home_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_lock_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_person_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_settings_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_billpayment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_billpayment.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_bus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_datacard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_datacard.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_deposit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_deposit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dtf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_dtf.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_electricity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_electricity.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_flight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_flight.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fund_recieve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_fund_recieve.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_gas.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hotel_booking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_hotel_booking.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_kbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_kbit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_logout.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_passbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_passbook.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_postpaid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_postpaid.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_prepaid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_prepaid.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_qr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_qr_code.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_upi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_upi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/ic_water.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/jetpack_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/drawable/jetpack_logo.png -------------------------------------------------------------------------------- /app/src/main/res/font/averta_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/font/averta_regular.ttf -------------------------------------------------------------------------------- /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.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SHAMSRIZWAN/MvvmKotlinJetpackCompose/c9204168db4d0c078107fa45dc17f990c730d58e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF370c71 4 | #FF520457 5 | #FF900370 6 | #FF920277 7 | #FF000000 8 | #FFFFFFFF 9 | #FF29B6F6 10 | #FF039BE5 11 | #FFBDBDBD 12 | #FF757575 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 100dp 4 | 120dp 5 | 150dp 6 | 200dp 7 | 10dp 8 | 50dp 9 | 20dp 10 | 40dp 11 | 30dp 12 | 12sp 13 | 15dp 14 | 80dp 15 | 70dp 16 | 75dp 17 | 130dp 18 | 35dp 19 | 145dp 20 | 5dp 21 | 60dp 22 | 25dp 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MvvmKotlinJetpackCompose 3 | MainActivity 4 | SplashActivity 5 | A demo App using kotlin, Mvvm, jetpack Compose, Unit testing. 6 | Email Address 7 | Sign In 8 | Password 9 | Forgot Password ? Registration 10 | 11 | Please check your internet connection 12 | Network error, could not get data,please try again! 13 | Not a valid Email ! 14 | Not a valid Password ! 15 | please Enter valid username and password 16 | Message 17 | Dashboard 18 | Scan 19 | ComingSoonActivity 20 | Upi 21 | Kbit 22 | passbook 23 | deposit 24 | Dashboard 25 | Logout 26 | Upi Id 27 | Amount 28 | Next 29 | Invalid Upi 30 | Invalid Amount 31 | Details 32 | LiqrDeduction 33 | Service Charge 34 | Total Deduction 35 | Insufficient Amount, Please add liqr in Wallet. 36 | Send Otp 37 | Please Enter Otp 38 | Something went wrong, please try again later ! 39 | Enter Otp 40 | Pay 41 | Transaction Failed 42 | Transaction Processed ! 43 | Transaction Pending ! 44 | Invalid Otp 45 | progress_tag 46 | drawer_test_tag 47 | dashboard_content_tag 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 |