├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── praveen │ │ └── androidcleanarchitecture │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── praveen │ │ │ └── androidcleanarchitecture │ │ │ ├── CoinApplication.kt │ │ │ ├── common │ │ │ ├── Constant.kt │ │ │ └── Resource.kt │ │ │ ├── data │ │ │ ├── di │ │ │ │ └── AppModule.kt │ │ │ ├── remote │ │ │ │ ├── CryptoCoinAPI.kt │ │ │ │ └── dto │ │ │ │ │ ├── CoinDetailDto.kt │ │ │ │ │ ├── CoinDto.kt │ │ │ │ │ ├── Links.kt │ │ │ │ │ ├── LinksExtended.kt │ │ │ │ │ ├── Stats.kt │ │ │ │ │ ├── Tag.kt │ │ │ │ │ ├── TeamMember.kt │ │ │ │ │ ├── TweetDto.kt │ │ │ │ │ └── Whitepaper.kt │ │ │ └── repository │ │ │ │ └── CoinRepositioryImpl.kt │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── Coin.kt │ │ │ │ ├── CoinDetail.kt │ │ │ │ └── Tweet.kt │ │ │ ├── repository │ │ │ │ └── CoinRepository.kt │ │ │ └── use_case │ │ │ │ ├── get_coin │ │ │ │ └── GetCoinDetailUseCase.kt │ │ │ │ ├── get_coins │ │ │ │ └── GetCoinsUseCase.kt │ │ │ │ └── get_tweets │ │ │ │ └── GetCoinTweetsUseCase.kt │ │ │ └── presentation │ │ │ ├── MainActivity.kt │ │ │ ├── Screen.kt │ │ │ ├── coindetail │ │ │ ├── CoinDetailScreen.kt │ │ │ ├── CoinDetailState.kt │ │ │ ├── CoinDetailViewModel.kt │ │ │ └── component │ │ │ │ ├── CoinTag.kt │ │ │ │ └── TeamListItem.kt │ │ │ ├── coinlist │ │ │ ├── CoinListScreen.kt │ │ │ ├── CoinListState.kt │ │ │ ├── CoinListViewModel.kt │ │ │ └── component │ │ │ │ └── CoinListItem.kt │ │ │ ├── component │ │ │ ├── CircularImage.kt │ │ │ └── CommonComponents.kt │ │ │ ├── tweets │ │ │ ├── CoinTweetScreen.kt │ │ │ ├── CoinTweetState.kt │ │ │ ├── CoinTweetViewModel.kt │ │ │ └── component │ │ │ │ └── TweetUi.kt │ │ │ └── ui │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── praveen │ └── androidcleanarchitecture │ └── ExampleUnitTest.kt ├── 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/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.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/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Clean Architecture That Screams (MVVM + JetPack Compose UI + Flow + State) 2 | 3 | Clean architecture that screams using MVVM 4 | 5 | A good architectures produce systems that are: 6 | 7 | - Independent of Frameworks. The architecture does not depend on the existence of some library of feature laden software. This allows you to use such frameworks as tools, rather than having to cram your system into their limited constraints. 8 | - Testable. The business rules can be tested without the UI, Database, Web Server, or any other external element. 9 | - Independent of UI. The UI can change easily, without changing the rest of the system. A Web UI could be replaced with a console UI, for example, without changing the business rules. 10 | - Independent of Database. You can swap out Oracle or SQL Server, for Mongo, BigTable, CouchDB, or something else. Your business rules are not bound to the database. 11 | - Independent of any external agency. In fact your business rules simply don’t know anything at all about the outside world. 12 | 13 | In this application i have use coinPaprika API to fetch cryptoCoins and their tweets. 14 | - I have used Screaming architecture with MVVM. 15 | - I have used Jetpack compose UI. 16 | - Instead of live data i have used StateFlow. 17 | 18 | Reference - https://github.com/coinpaprika/coinpaprika-api-kotlin-client 19 | -------------------------------------------------------------------------------- /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 31 10 | 11 | defaultConfig { 12 | applicationId "com.praveen.androidcleanarchitecture" 13 | minSdk 23 14 | targetSdk 31 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 | useIR = true 37 | } 38 | buildFeatures { 39 | compose true 40 | } 41 | composeOptions { 42 | kotlinCompilerExtensionVersion compose_version 43 | kotlinCompilerVersion '1.5.21' 44 | } 45 | packagingOptions { 46 | resources { 47 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 48 | } 49 | } 50 | } 51 | 52 | dependencies { 53 | 54 | implementation 'androidx.core:core-ktx:1.6.0' 55 | implementation 'androidx.appcompat:appcompat:1.3.1' 56 | implementation 'com.google.android.material:material:1.4.0' 57 | implementation "androidx.compose.ui:ui:$compose_version" 58 | implementation "androidx.compose.material:material:$compose_version" 59 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 60 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' 61 | implementation 'androidx.activity:activity-compose:1.3.1' 62 | testImplementation 'junit:junit:4.13.2' 63 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 64 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 65 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 66 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 67 | 68 | // Compose dependencies 69 | implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0-rc01" 70 | implementation "androidx.navigation:navigation-compose:2.4.0-alpha10" 71 | implementation "com.google.accompanist:accompanist-flowlayout:0.17.0" 72 | implementation 'androidx.compose.animation:animation:1.1.0-beta01' 73 | implementation 'androidx.compose.material:material-icons-extended:1.1.0-beta01' 74 | implementation("io.coil-kt:coil-compose:1.4.0") 75 | 76 | // Coroutines 77 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1' 78 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1' 79 | 80 | // Coroutine Lifecycle Scopes 81 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" 82 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1" 83 | 84 | //Dagger - Hilt 85 | implementation "com.google.dagger:hilt-android:2.38.1" 86 | kapt "com.google.dagger:hilt-android-compiler:2.37" 87 | implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" 88 | kapt "androidx.hilt:hilt-compiler:1.0.0" 89 | implementation 'androidx.hilt:hilt-navigation-compose:1.0.0-alpha03' 90 | 91 | // Retrofit 92 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 93 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 94 | implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2" 95 | implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2" 96 | } -------------------------------------------------------------------------------- /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/praveen/androidcleanarchitecture/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture 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.praveen.androidcleanarchitecture", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/CoinApplication.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class CoinApplication : Application() { 8 | 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/common/Constant.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.common 2 | 3 | object Constant { 4 | 5 | const val BASE_URL = "https://api.coinpaprika.com/" 6 | 7 | const val PARAM_COIN_ID = "coinId" 8 | const val ACTIVE = "active" 9 | const val IN_ACTIVE = "InActive" 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/common/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.common 2 | 3 | sealed class Resource(val data: T? = null, val message: String? = null) { 4 | class Success(data: T) : Resource(data) 5 | class Error(message: String, data: T? = null) : Resource(data, message) 6 | class Loading(data: T? = null) : Resource(data) 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.di 2 | 3 | import com.praveen.androidcleanarchitecture.common.Constant 4 | import com.praveen.androidcleanarchitecture.data.remote.CryptoCoinAPI 5 | import com.praveen.androidcleanarchitecture.data.repository.CoinRepositoryImpl 6 | import com.praveen.androidcleanarchitecture.domain.repository.CoinRepository 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | import okhttp3.logging.HttpLoggingInterceptor 12 | import retrofit2.Retrofit 13 | import retrofit2.converter.gson.GsonConverterFactory 14 | import javax.inject.Singleton 15 | import okhttp3.OkHttpClient 16 | 17 | 18 | @Module 19 | @InstallIn(SingletonComponent::class) 20 | object AppModule { 21 | 22 | @Provides 23 | @Singleton 24 | fun providePaprikaApi(): CryptoCoinAPI { 25 | val logging = HttpLoggingInterceptor() 26 | // set your desired log level 27 | // set your desired log level 28 | logging.setLevel(HttpLoggingInterceptor.Level.BODY) 29 | 30 | val httpClient = OkHttpClient.Builder() 31 | // add your other interceptors … 32 | 33 | // add logging as last interceptor 34 | // add your other interceptors … 35 | 36 | // add logging as last interceptor 37 | httpClient.addInterceptor(logging) // <-- this is the important line! 38 | 39 | return Retrofit.Builder() 40 | .baseUrl(Constant.BASE_URL) 41 | .addConverterFactory(GsonConverterFactory.create()) 42 | .client(httpClient.build()) 43 | .build() 44 | .create(CryptoCoinAPI::class.java) 45 | } 46 | 47 | @Provides 48 | @Singleton 49 | fun provideCoinRepository(api: CryptoCoinAPI): CoinRepository { 50 | return CoinRepositoryImpl(api) 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/CryptoCoinAPI.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote 2 | 3 | import com.praveen.androidcleanarchitecture.data.remote.dto.CoinDetailDto 4 | import com.praveen.androidcleanarchitecture.data.remote.dto.CoinDto 5 | import com.praveen.androidcleanarchitecture.data.remote.dto.TweetDto 6 | import retrofit2.http.GET 7 | import retrofit2.http.Path 8 | 9 | interface CryptoCoinAPI { 10 | 11 | @GET("/v1/coins") 12 | suspend fun getCoins(): List 13 | 14 | @GET("/v1/coins/{coinId}") 15 | suspend fun getCoinById(@Path("coinId") coinId: String): CoinDetailDto 16 | 17 | @GET("/v1/coins/{coinId}/twitter") 18 | suspend fun getCoinTweets(@Path("coinId") coinId: String): List 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/dto/CoinDetailDto.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote.dto 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.praveen.androidcleanarchitecture.domain.model.CoinDetail 5 | 6 | data class CoinDetailDto( 7 | val description: String, 8 | @SerializedName("development_status") 9 | val developmentStatus: String, 10 | @SerializedName("first_data_at") 11 | val firstDataAt: String, 12 | @SerializedName("hardware_wallet") 13 | val hardwareWallet: Boolean, 14 | @SerializedName("hash_algorithm") 15 | val hashAlgorithm: String, 16 | val id: String, 17 | @SerializedName("is_active") 18 | val isActive: Boolean, 19 | @SerializedName("is_new") 20 | val isNew: Boolean, 21 | @SerializedName("last_data_at") 22 | val lastDataAt: String, 23 | val links: Links, 24 | @SerializedName("links_extended") 25 | val linksExtended: List, 26 | val message: String, 27 | val name: String, 28 | @SerializedName("open_source") 29 | val openSource: Boolean, 30 | @SerializedName("org_structure") 31 | val orgStructure: String, 32 | @SerializedName("proof_type") 33 | val proofType: String, 34 | val rank: Int, 35 | @SerializedName("started_at") 36 | val startedAt: String, 37 | val symbol: String, 38 | val tags: List, 39 | val team: List, 40 | val type: String, 41 | val whitepaper: Whitepaper 42 | ) 43 | 44 | fun CoinDetailDto.toCoinDetail(): CoinDetail { 45 | return CoinDetail( 46 | id = id, 47 | description = description, 48 | name = name, 49 | isActive = isActive, 50 | rank= rank, 51 | tags = tags.map { it.name }, 52 | team = team, 53 | symbol = symbol 54 | ) 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/dto/CoinDto.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote.dto 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.praveen.androidcleanarchitecture.domain.model.Coin 5 | 6 | data class CoinDto( 7 | val id: String, 8 | @SerializedName("is_active") 9 | val isActive: Boolean, 10 | @SerializedName("is_new") 11 | val isNew: Boolean, 12 | val name: String, 13 | val rank: Int, 14 | val symbol: String, 15 | val type: String 16 | ) 17 | 18 | fun CoinDto.toCoin(): Coin { 19 | return Coin( 20 | id= id, 21 | isActive = isActive, 22 | rank = rank, 23 | name = name, 24 | symbol = symbol 25 | ) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/dto/Links.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote.dto 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class Links( 6 | val explorer: List, 7 | val facebook: List, 8 | val reddit: List, 9 | @SerializedName("source_code") 10 | val sourceCode: List, 11 | val website: List, 12 | val youtube: List 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/dto/LinksExtended.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote.dto 2 | 3 | data class LinksExtended( 4 | val stats: Stats, 5 | val type: String, 6 | val url: String 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/dto/Stats.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote.dto 2 | 3 | data class Stats( 4 | val contributors: Int, 5 | val followers: Int, 6 | val stars: Int, 7 | val subscribers: Int 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/dto/Tag.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote.dto 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class Tag( 6 | @SerializedName("coin_counter") 7 | val coinCounter: Int, 8 | @SerializedName("ico_counter") 9 | val icoCounter: Int, 10 | val id: String, 11 | val name: String 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/dto/TeamMember.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote.dto 2 | 3 | data class TeamMember( 4 | val id: String, 5 | val name: String, 6 | val position: String 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/dto/TweetDto.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote.dto 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | import com.praveen.androidcleanarchitecture.domain.model.Tweet 6 | 7 | data class TweetDto( 8 | @SerializedName("date") 9 | val date: String?, 10 | @SerializedName("is_retweet") 11 | val isRetweet: Boolean?, 12 | @SerializedName("like_count") 13 | val likeCount: Int, 14 | @SerializedName("media_link") 15 | val mediaLink: String?, 16 | @SerializedName("retweet_count") 17 | val retweetCount: Int, 18 | @SerializedName("status") 19 | val status: String?, 20 | @SerializedName("status_id") 21 | val statusId: String?, 22 | @SerializedName("status_link") 23 | val statusLink: String?, 24 | @SerializedName("user_image_link") 25 | val userImageLink: String?, 26 | @SerializedName("user_name") 27 | val userName: String?, 28 | @SerializedName("youtube_link") 29 | val youtubeLink: String? 30 | ) 31 | 32 | fun TweetDto.toTweet(): Tweet { 33 | return Tweet( 34 | date = date, 35 | isRetweet = isRetweet ?: false, 36 | likeCount = likeCount, 37 | mediaLink = mediaLink, 38 | retweetCount = retweetCount, 39 | status = status, 40 | statusId = statusId, 41 | statusLink= statusLink, 42 | userImageLink = userImageLink, 43 | userName = userName, 44 | youtubeLink = youtubeLink 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/remote/dto/Whitepaper.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.remote.dto 2 | 3 | data class Whitepaper( 4 | val link: String, 5 | val thumbnail: String 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/data/repository/CoinRepositioryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.data.repository 2 | 3 | import com.praveen.androidcleanarchitecture.data.remote.CryptoCoinAPI 4 | import com.praveen.androidcleanarchitecture.data.remote.dto.CoinDetailDto 5 | import com.praveen.androidcleanarchitecture.data.remote.dto.CoinDto 6 | import com.praveen.androidcleanarchitecture.data.remote.dto.TweetDto 7 | import com.praveen.androidcleanarchitecture.domain.repository.CoinRepository 8 | import javax.inject.Inject 9 | 10 | class CoinRepositoryImpl @Inject constructor(private val api: CryptoCoinAPI): CoinRepository{ 11 | override suspend fun getCoins(): List { 12 | return api.getCoins() 13 | } 14 | 15 | override suspend fun getCoinById(id: String): CoinDetailDto { 16 | return api.getCoinById(id) 17 | } 18 | 19 | override suspend fun getCoinTweetsById(id: String): List { 20 | return api.getCoinTweets(id) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/domain/model/Coin.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.domain.model 2 | 3 | data class Coin ( 4 | val id: String, 5 | val isActive: Boolean, 6 | val name: String, 7 | val rank: Int, 8 | val symbol: String, 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/domain/model/CoinDetail.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.domain.model 2 | 3 | import com.praveen.androidcleanarchitecture.data.remote.dto.TeamMember 4 | 5 | data class CoinDetail( 6 | val id: String, 7 | val description: String?, 8 | val name: String?, 9 | val isActive: Boolean, 10 | val rank: Int, 11 | val tags: List?, 12 | val team: List?, 13 | val symbol: String? 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/domain/model/Tweet.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.domain.model 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.* 5 | 6 | data class Tweet( 7 | val date: String?, 8 | val isRetweet: Boolean, 9 | val likeCount: Int?, 10 | val mediaLink: String?, 11 | val retweetCount: Int?, 12 | val status: String?, 13 | val statusId: String?, 14 | val statusLink: String?, 15 | val userImageLink: String?, 16 | val userName: String?, 17 | val youtubeLink: String? 18 | ) { 19 | fun timeAgo(): String { 20 | //2021-10-27T10:55:48Z 21 | val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") 22 | sdf.timeZone = TimeZone.getTimeZone("GMT") 23 | val dateObj: Date = sdf.parse(date) 24 | val currentTime = Date().time; 25 | val timeDiff = currentTime - dateObj.time; 26 | when { 27 | timeDiff >= (1000 * 60 * 60 * 24) -> { 28 | // Days 29 | return "${timeDiff / (1000 * 60 * 60 * 24)}d"; 30 | } 31 | timeDiff >= (1000 * 60 * 60) -> { 32 | // Hours 33 | return "${timeDiff / (1000 * 60 * 60)}h"; 34 | } 35 | timeDiff >= (1000 * 60) -> { 36 | // Minutes 37 | return "${timeDiff / (1000 * 60)}m"; 38 | } 39 | timeDiff >= 1000 -> { 40 | // Seconds 41 | return "${timeDiff / 1000}s"; 42 | } 43 | else -> return "0s" 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/domain/repository/CoinRepository.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.domain.repository 2 | 3 | import com.praveen.androidcleanarchitecture.data.remote.dto.CoinDetailDto 4 | import com.praveen.androidcleanarchitecture.data.remote.dto.CoinDto 5 | import com.praveen.androidcleanarchitecture.data.remote.dto.TweetDto 6 | 7 | interface CoinRepository { 8 | 9 | suspend fun getCoins(): List 10 | 11 | suspend fun getCoinById(id: String): CoinDetailDto 12 | 13 | suspend fun getCoinTweetsById(id: String): List 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/domain/use_case/get_coin/GetCoinDetailUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.domain.use_case.get_coin 2 | 3 | import com.praveen.androidcleanarchitecture.common.Resource 4 | import com.praveen.androidcleanarchitecture.data.remote.dto.toCoinDetail 5 | import com.praveen.androidcleanarchitecture.domain.model.CoinDetail 6 | import com.praveen.androidcleanarchitecture.domain.repository.CoinRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import java.io.IOException 10 | import javax.inject.Inject 11 | 12 | class GetCoinDetailUseCase @Inject constructor(private val repository: CoinRepository) { 13 | 14 | operator fun invoke(coinId: String): Flow> = flow { 15 | try { 16 | emit(Resource.Loading()) 17 | val coin = repository.getCoinById(coinId).toCoinDetail() 18 | emit(Resource.Success(coin)) 19 | catch (e: IOException) { 20 | emit(Resource.Error(e.localizedMessage ?: "Network Error Occurred")) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/domain/use_case/get_coins/GetCoinsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.domain.use_case.get_coins 2 | 3 | import com.praveen.androidcleanarchitecture.common.Resource 4 | import com.praveen.androidcleanarchitecture.data.remote.dto.toCoin 5 | import com.praveen.androidcleanarchitecture.domain.model.Coin 6 | import com.praveen.androidcleanarchitecture.domain.repository.CoinRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import java.io.IOException 10 | import javax.inject.Inject 11 | 12 | class GetCoinsUseCase @Inject constructor(private val repository: CoinRepository) { 13 | 14 | operator fun invoke(): Flow>> = flow { 15 | try { 16 | emit(Resource.Loading>()) 17 | val coins = repository.getCoins().map { it.toCoin() } 18 | emit(Resource.Success>(coins)) 19 | } catch (e: IOException) { 20 | emit(Resource.Error>(e.localizedMessage ?: "Network Error Occurred")) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/domain/use_case/get_tweets/GetCoinTweetsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.domain.use_case.get_tweets 2 | 3 | import com.praveen.androidcleanarchitecture.common.Resource 4 | import com.praveen.androidcleanarchitecture.data.remote.dto.toTweet 5 | import com.praveen.androidcleanarchitecture.domain.model.Tweet 6 | import com.praveen.androidcleanarchitecture.domain.repository.CoinRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import java.io.IOException 10 | import javax.inject.Inject 11 | 12 | class GetCoinTweetsUseCase @Inject constructor(private val repository: CoinRepository) { 13 | 14 | operator fun invoke(coinId: String): Flow>> = flow { 15 | try { 16 | emit(Resource.Loading>()) 17 | val tweets = repository.getCoinTweetsById(coinId).map { it.toTweet() } 18 | emit(Resource.Success>(tweets)) 19 | catch (e: IOException) { 20 | emit(Resource.Error>(e.localizedMessage ?: "Network Error Occurred")) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.material.MaterialTheme 7 | import androidx.compose.material.Surface 8 | import androidx.navigation.compose.NavHost 9 | import androidx.navigation.compose.composable 10 | import androidx.navigation.compose.rememberNavController 11 | import com.praveen.androidcleanarchitecture.presentation.coindetail.CoinDetailScreen 12 | import com.praveen.androidcleanarchitecture.presentation.coinlist.CoinListScreen 13 | import com.praveen.androidcleanarchitecture.presentation.tweets.CoinTweetScreen 14 | import com.praveen.androidcleanarchitecture.presentation.ui.theme.AndroidCleanArchitectureTheme 15 | import dagger.hilt.android.AndroidEntryPoint 16 | 17 | @AndroidEntryPoint 18 | class MainActivity : ComponentActivity() { 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | setContent { 22 | AndroidCleanArchitectureTheme { 23 | // A surface container using the 'background' color from the theme 24 | Surface(color = MaterialTheme.colors.background) { 25 | val navController = rememberNavController() 26 | NavHost( 27 | navController = navController, 28 | startDestination = Screen.CoinListScreen.route 29 | ) { 30 | composable( 31 | route = Screen.CoinListScreen.route 32 | ) { 33 | CoinListScreen(navController) 34 | } 35 | composable( 36 | route = Screen.CoinTweetsScreen.route + "/{coinId}" 37 | ) { 38 | CoinTweetScreen() 39 | } 40 | /*composable( 41 | route = Screen.CoinDetailScreen.route + "/{coinId}" 42 | ) { 43 | CoinDetailScreen() 44 | }*/ 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation 2 | 3 | sealed class Screen(val route: String) { 4 | object CoinListScreen : Screen("coin_list_screen") 5 | object CoinDetailScreen : Screen("coin_detail_screen") 6 | object CoinTweetsScreen : Screen("coin_tweets_screen") 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/coindetail/CoinDetailScreen.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.coindetail 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.foundation.lazy.LazyColumn 5 | import androidx.compose.foundation.lazy.items 6 | import androidx.compose.material.CircularProgressIndicator 7 | import androidx.compose.material.Divider 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Alignment.Companion.CenterVertically 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.text.font.FontStyle 16 | import androidx.compose.ui.text.style.TextAlign 17 | import androidx.compose.ui.unit.dp 18 | import androidx.hilt.navigation.compose.hiltViewModel 19 | import com.google.accompanist.flowlayout.FlowRow 20 | import com.praveen.androidcleanarchitecture.presentation.coindetail.component.CoinTag 21 | import com.praveen.androidcleanarchitecture.presentation.coindetail.component.TeamListItem 22 | 23 | @Composable 24 | fun CoinDetailScreen( 25 | viewModel: CoinDetailViewModel = hiltViewModel() 26 | ) { 27 | val state = viewModel.state.value 28 | Box(modifier = Modifier.fillMaxSize()) { 29 | state.coinDetail?.let { coin -> 30 | LazyColumn( 31 | modifier = Modifier.fillMaxSize(), 32 | contentPadding = PaddingValues(20.dp) 33 | ) { 34 | item { 35 | Row( 36 | modifier = Modifier.fillMaxWidth(), 37 | horizontalArrangement = Arrangement.SpaceBetween 38 | ) { 39 | Text( 40 | text = "${coin.name} (${coin.symbol})", 41 | style = MaterialTheme.typography.h4, 42 | modifier = Modifier.weight(8f) 43 | ) 44 | Text( 45 | text = if (coin.isActive) "active" else "inactive", 46 | color = if (coin.isActive) Color.Green else Color.Red, 47 | fontStyle = FontStyle.Italic, 48 | textAlign = TextAlign.End, 49 | modifier = Modifier 50 | .align(CenterVertically) 51 | .weight(2f) 52 | ) 53 | } 54 | Spacer(modifier = Modifier.height(15.dp)) 55 | coin.description?.let { 56 | Text( 57 | text = it, 58 | style = MaterialTheme.typography.body2 59 | ) 60 | } 61 | Spacer(modifier = Modifier.height(15.dp)) 62 | Text( 63 | text = "Tags", 64 | style = MaterialTheme.typography.h5 65 | ) 66 | Spacer(modifier = Modifier.height(15.dp)) 67 | FlowRow( 68 | mainAxisSpacing = 10.dp, 69 | crossAxisSpacing = 10.dp, 70 | modifier = Modifier.fillMaxWidth() 71 | ) { 72 | coin.tags?.forEach { tag -> 73 | CoinTag(tag = tag) 74 | } 75 | } 76 | Spacer(modifier = Modifier.height(15.dp)) 77 | if (!coin.team.isNullOrEmpty()) { 78 | Text( 79 | text = "Team members", 80 | style = MaterialTheme.typography.h5 81 | ) 82 | Spacer(modifier = Modifier.height(15.dp)) 83 | } 84 | } 85 | coin.team?.let { 86 | items(coin.team) { teamMember -> 87 | TeamListItem( 88 | teamMember = teamMember, 89 | modifier = Modifier 90 | .fillMaxWidth() 91 | .padding(10.dp) 92 | ) 93 | Divider() 94 | } 95 | } 96 | } 97 | } 98 | if (state.error.isNotBlank()) { 99 | Text( 100 | text = state.error, 101 | color = MaterialTheme.colors.error, 102 | textAlign = TextAlign.Center, 103 | modifier = Modifier 104 | .fillMaxWidth() 105 | .padding(horizontal = 20.dp) 106 | .align(Alignment.Center) 107 | ) 108 | } 109 | if (state.isLoading) { 110 | CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/coindetail/CoinDetailState.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.coindetail 2 | 3 | import com.praveen.androidcleanarchitecture.domain.model.CoinDetail 4 | 5 | data class CoinDetailState( 6 | val isLoading: Boolean = false, 7 | val coinDetail: CoinDetail? = null, 8 | val error: String = "" 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/coindetail/CoinDetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.coindetail 2 | 3 | import androidx.compose.runtime.State 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.lifecycle.SavedStateHandle 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import com.praveen.androidcleanarchitecture.common.Constant 9 | import com.praveen.androidcleanarchitecture.common.Resource 10 | import com.praveen.androidcleanarchitecture.domain.use_case.get_coin.GetCoinDetailUseCase 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.flow.launchIn 13 | import kotlinx.coroutines.flow.onEach 14 | import javax.inject.Inject 15 | 16 | @HiltViewModel 17 | class CoinDetailViewModel @Inject constructor( 18 | private val getCoinUseCase: GetCoinDetailUseCase, 19 | savedStateHandle: SavedStateHandle 20 | ) : 21 | ViewModel() { 22 | 23 | private val _state = mutableStateOf(CoinDetailState()) 24 | val state: State = _state 25 | 26 | init { 27 | savedStateHandle.get(Constant.PARAM_COIN_ID) 28 | ?.let { coinId -> getCoinDetail(coinId) } 29 | 30 | } 31 | 32 | private fun getCoinDetail(coinId: String) { 33 | getCoinUseCase(coinId = coinId).onEach { result -> 34 | when (result) { 35 | is Resource.Success -> { 36 | _state.value = CoinDetailState(coinDetail = result.data) 37 | } 38 | is Resource.Error -> { 39 | _state.value = CoinDetailState(error = result.message ?: "Error Occurred") 40 | } 41 | is Resource.Loading -> { 42 | _state.value = CoinDetailState(isLoading = true) 43 | } 44 | } 45 | }.launchIn(viewModelScope) 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/coindetail/component/CoinTag.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.coindetail.component 2 | 3 | import androidx.compose.foundation.border 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.shape.RoundedCornerShape 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.text.style.TextAlign 12 | import androidx.compose.ui.unit.dp 13 | 14 | @Composable 15 | fun CoinTag(tag: String) { 16 | Box( 17 | modifier = Modifier 18 | .border( 19 | width = 1.dp, 20 | color = MaterialTheme.colors.primary, 21 | shape = RoundedCornerShape(100.dp) 22 | ) 23 | .padding(10.dp) 24 | ) { 25 | Text( 26 | text = tag, 27 | color = MaterialTheme.colors.primary, 28 | textAlign = TextAlign.Center, 29 | style = MaterialTheme.typography.body2 30 | ) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/coindetail/component/TeamListItem.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.coindetail.component 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.text.font.FontStyle 12 | import androidx.compose.ui.unit.dp 13 | import com.praveen.androidcleanarchitecture.data.remote.dto.TeamMember 14 | 15 | @Composable 16 | fun TeamListItem( 17 | teamMember: TeamMember, 18 | modifier: Modifier = Modifier 19 | ) { 20 | 21 | Column( 22 | modifier = modifier, 23 | verticalArrangement = Arrangement.Center, content = { 24 | Text( 25 | text = teamMember.name, 26 | style = MaterialTheme.typography.h5 27 | ) 28 | Spacer(modifier = Modifier.height((4.dp))) 29 | Text( 30 | text = teamMember.position, 31 | style = MaterialTheme.typography.body2, 32 | fontStyle = FontStyle.Italic 33 | ) 34 | } 35 | ) 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/coinlist/CoinListScreen.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.coinlist 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.lazy.LazyColumn 8 | import androidx.compose.foundation.lazy.items 9 | import androidx.compose.material.CircularProgressIndicator 10 | import androidx.compose.material.MaterialTheme 11 | import androidx.compose.material.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.text.style.TextAlign 16 | import androidx.compose.ui.unit.dp 17 | import androidx.hilt.navigation.compose.hiltViewModel 18 | import androidx.navigation.NavController 19 | import com.praveen.androidcleanarchitecture.presentation.Screen 20 | import com.praveen.androidcleanarchitecture.presentation.coinlist.component.CoinListItem 21 | 22 | @Composable 23 | fun CoinListScreen( 24 | navController: NavController, 25 | coinListViewModel: CoinListViewModel = hiltViewModel() 26 | ) { 27 | val state = coinListViewModel.state.value 28 | Box(modifier = Modifier.fillMaxSize()) { 29 | Box(modifier = Modifier.fillMaxSize()) { 30 | LazyColumn(modifier = Modifier.fillMaxSize()) { 31 | items(state.coins) { coin -> 32 | CoinListItem(coin = coin, onItemClick = { 33 | navController.navigate(Screen.CoinTweetsScreen.route + "/${coin.id}") 34 | }) 35 | } 36 | } 37 | 38 | if (state.error.isNotBlank()) { 39 | Text( 40 | text = state.error, 41 | color = MaterialTheme.colors.error, 42 | textAlign = TextAlign.Center, 43 | modifier = Modifier 44 | .fillMaxWidth() 45 | .padding(horizontal = 20.dp) 46 | .align(Alignment.Center) 47 | ) 48 | } 49 | if (state.isLoading) { 50 | CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/coinlist/CoinListState.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.coinlist 2 | 3 | import com.praveen.androidcleanarchitecture.domain.model.Coin 4 | 5 | data class CoinListState( 6 | val isLoading: Boolean = false, 7 | val coins: List = emptyList(), 8 | val error: String ="" 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/coinlist/CoinListViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.coinlist 2 | 3 | import androidx.compose.runtime.State 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.praveen.androidcleanarchitecture.common.Resource 8 | import com.praveen.androidcleanarchitecture.domain.use_case.get_coins.GetCoinsUseCase 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.launchIn 11 | import kotlinx.coroutines.flow.onEach 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class CoinListViewModel @Inject constructor(private val getCoinUseCase: GetCoinsUseCase) : 16 | ViewModel() { 17 | 18 | private val _state = mutableStateOf(CoinListState()) 19 | val state: State = _state 20 | 21 | init { 22 | getCoin() 23 | } 24 | 25 | private fun getCoin() { 26 | getCoinUseCase().onEach { result -> 27 | when (result) { 28 | is Resource.Success -> { 29 | _state.value = CoinListState(coins = result.data ?: emptyList()) 30 | } 31 | is Resource.Error -> { 32 | _state.value = CoinListState(error = result.message ?: "Error Occurred") 33 | } 34 | is Resource.Loading -> { 35 | _state.value = CoinListState(isLoading = true) 36 | } 37 | } 38 | }.launchIn(viewModelScope) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/coinlist/component/CoinListItem.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.coinlist.component 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment.Companion.CenterVertically 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.text.font.FontStyle 15 | import androidx.compose.ui.text.style.TextAlign 16 | import androidx.compose.ui.text.style.TextOverflow 17 | import androidx.compose.ui.unit.dp 18 | import com.praveen.androidcleanarchitecture.common.Constant 19 | import com.praveen.androidcleanarchitecture.domain.model.Coin 20 | 21 | @Composable 22 | fun CoinListItem(coin: Coin, onItemClick: (Coin) -> Unit) { 23 | Row( 24 | modifier = Modifier 25 | .fillMaxWidth() 26 | .clickable { onItemClick(coin) } 27 | .padding(20.dp), 28 | horizontalArrangement = Arrangement.SpaceBetween 29 | ) { 30 | Text( 31 | text = "${coin.rank} ${coin.name} (${coin.symbol})", 32 | style = MaterialTheme.typography.body1, 33 | overflow = TextOverflow.Ellipsis 34 | ) 35 | Text( 36 | text = if (coin.isActive) Constant.ACTIVE else Constant.IN_ACTIVE, 37 | color = if (coin.isActive) Color.Green else Color.Red, 38 | fontStyle = FontStyle.Italic, 39 | textAlign = TextAlign.End, 40 | style = MaterialTheme.typography.body2, 41 | modifier = Modifier.align(CenterVertically) 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/component/CircularImage.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.component 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.shape.CircleShape 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.draw.clip 9 | import androidx.compose.ui.graphics.ImageBitmap 10 | import androidx.compose.ui.layout.ContentScale 11 | 12 | @Composable 13 | fun CircularImage(modifier: Modifier, image: ImageBitmap) { 14 | Image( 15 | bitmap = image, 16 | 17 | modifier = Modifier 18 | .clickable(onClick = {}) 19 | .clip(CircleShape) 20 | then modifier, 21 | contentScale = ContentScale.Crop, 22 | contentDescription = "CircularImage" 23 | ) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/component/CommonComponents.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.component 2 | 3 | import androidx.compose.foundation.layout.Spacer 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.height 6 | import androidx.compose.material.Divider 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.unit.Dp 10 | import androidx.compose.ui.unit.dp 11 | 12 | @Composable 13 | fun SeparatorSpacer(modifier: Modifier = Modifier) { 14 | Spacer(modifier = modifier.height(10.dp)) 15 | } 16 | 17 | @Composable 18 | fun SeparatorDivider(modifier: Modifier = Modifier, thickness: Dp = 1.dp) { 19 | SeparatorSpacer() 20 | Divider( 21 | modifier = modifier.fillMaxWidth(), 22 | thickness = thickness, 23 | ) 24 | SeparatorSpacer() 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/tweets/CoinTweetScreen.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.tweets 2 | 3 | 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.foundation.lazy.LazyColumn 6 | import androidx.compose.foundation.lazy.items 7 | import androidx.compose.material.CircularProgressIndicator 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.text.style.TextAlign 14 | import androidx.compose.ui.unit.dp 15 | import androidx.hilt.navigation.compose.hiltViewModel 16 | import com.praveen.androidcleanarchitecture.presentation.tweets.component.TweetComponent 17 | 18 | @Composable 19 | fun CoinTweetScreen( 20 | viewModel: CoinTweetViewModel = hiltViewModel() 21 | ) { 22 | val state = viewModel.state.value 23 | LazyColumn( 24 | modifier = Modifier.fillMaxSize(), 25 | contentPadding = PaddingValues(20.dp) 26 | ) { 27 | items(state.tweets) { tweets -> 28 | TweetComponent(tweets = tweets) 29 | } 30 | } 31 | 32 | if (state.error.isNotBlank()) { 33 | Text( 34 | text = state.error, 35 | color = MaterialTheme.colors.error, 36 | textAlign = TextAlign.Center, 37 | modifier = Modifier 38 | .fillMaxWidth() 39 | .padding(horizontal = 20.dp) 40 | ) 41 | } 42 | if (state.isLoading) { 43 | CircularProgressIndicator(modifier = Modifier) 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/tweets/CoinTweetState.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.tweets 2 | 3 | import com.praveen.androidcleanarchitecture.domain.model.Tweet 4 | 5 | data class CoinTweetState( 6 | val isLoading: Boolean = false, 7 | val tweets: List = emptyList(), 8 | val error: String = "" 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/tweets/CoinTweetViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.tweets 2 | 3 | import androidx.compose.runtime.State 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.lifecycle.SavedStateHandle 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import com.praveen.androidcleanarchitecture.common.Constant 9 | import com.praveen.androidcleanarchitecture.common.Resource 10 | import com.praveen.androidcleanarchitecture.domain.use_case.get_tweets.GetCoinTweetsUseCase 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.flow.launchIn 13 | import kotlinx.coroutines.flow.onEach 14 | import javax.inject.Inject 15 | 16 | @HiltViewModel 17 | class CoinTweetViewModel @Inject constructor( 18 | private val getCoinTweetsUseCase: GetCoinTweetsUseCase, 19 | savedStateHandle: SavedStateHandle 20 | ) : 21 | ViewModel() { 22 | 23 | private val _state = mutableStateOf(CoinTweetState()) 24 | val state: State = _state 25 | 26 | init { 27 | savedStateHandle.get(Constant.PARAM_COIN_ID) 28 | ?.let { coinId -> getTweets(coinId) } 29 | } 30 | 31 | private fun getTweets(coinId: String) { 32 | getCoinTweetsUseCase(coinId = coinId).onEach { result -> 33 | when (result) { 34 | is Resource.Success -> { 35 | _state.value = CoinTweetState(tweets = result.data ?: emptyList()) 36 | } 37 | is Resource.Error -> { 38 | _state.value = CoinTweetState(error = result.message ?: "Error Occurred") 39 | } 40 | is Resource.Loading -> { 41 | _state.value = CoinTweetState(isLoading = true) 42 | } 43 | } 44 | }.launchIn(viewModelScope) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/tweets/component/TweetUi.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.tweets.component 2 | 3 | import android.util.Log 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.material.Icon 7 | import androidx.compose.material.Text 8 | import androidx.compose.material.icons.Icons 9 | import androidx.compose.material.icons.outlined.Replay 10 | import androidx.compose.material.icons.outlined.Reply 11 | import androidx.compose.material.icons.outlined.Share 12 | import androidx.compose.material.icons.outlined.Star 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.vector.ImageVector 17 | import androidx.compose.ui.layout.ContentScale 18 | import androidx.compose.ui.text.font.FontWeight 19 | import androidx.compose.ui.text.style.TextOverflow 20 | import androidx.compose.ui.unit.dp 21 | import androidx.compose.ui.unit.sp 22 | import coil.compose.rememberImagePainter 23 | import coil.transform.CircleCropTransformation 24 | import com.praveen.androidcleanarchitecture.domain.model.Tweet 25 | import com.praveen.androidcleanarchitecture.presentation.component.SeparatorDivider 26 | import com.praveen.androidcleanarchitecture.presentation.component.SeparatorSpacer 27 | 28 | @Composable 29 | fun TweetComponent(tweets: Tweet) { 30 | Column( 31 | modifier = Modifier 32 | .padding( 33 | top = 10.dp, 34 | start = 10.dp, 35 | end = 10.dp 36 | ) 37 | .fillMaxWidth() 38 | .wrapContentHeight() 39 | ) { 40 | Row( 41 | modifier = Modifier 42 | .fillMaxWidth() 43 | .wrapContentHeight() 44 | ) { 45 | Log.d("Tweets","user image link ${tweets.userImageLink}") 46 | // coil-compose 47 | Image( 48 | painter = rememberImagePainter( 49 | data = tweets.userImageLink, 50 | builder = { 51 | crossfade(true) 52 | transformations(CircleCropTransformation()) 53 | } 54 | ), 55 | contentDescription = "user", 56 | modifier = Modifier 57 | .width(60.dp) 58 | .height(60.dp) 59 | ) 60 | SeparatorSpacer(modifier = Modifier.width(8.dp)) 61 | Column( 62 | modifier = Modifier 63 | .fillMaxWidth() 64 | .wrapContentHeight() 65 | ) { 66 | Row( 67 | modifier = Modifier 68 | .fillMaxWidth() 69 | .wrapContentHeight() 70 | ) { 71 | tweets.userName.let { 72 | Text( 73 | text = tweets.userName ?: "", 74 | fontWeight = FontWeight.Bold, 75 | fontSize = 14.sp 76 | ) 77 | 78 | SeparatorSpacer(modifier = Modifier.width(2.dp)) 79 | Text( 80 | text = tweets.timeAgo(), 81 | fontWeight = FontWeight.Normal, 82 | fontSize = 14.sp 83 | ) 84 | } 85 | } 86 | if (!tweets.status.isNullOrBlank()) { 87 | SeparatorSpacer(modifier = Modifier.height(8.dp)) 88 | Text( 89 | text = tweets.status, 90 | fontWeight = FontWeight.Normal, 91 | fontSize = 16.sp 92 | ) 93 | } 94 | if (tweets.mediaLink != null) { 95 | SeparatorSpacer(modifier = Modifier.height(8.dp)) 96 | Image( 97 | painter = rememberImagePainter( 98 | data = tweets.mediaLink, 99 | ), 100 | contentScale = ContentScale.FillWidth, 101 | contentDescription = "tweet", 102 | modifier = Modifier 103 | .fillMaxWidth() 104 | .height(180.dp) 105 | ) 106 | } 107 | SeparatorSpacer(modifier = Modifier.height(8.dp)) 108 | Row( 109 | modifier = Modifier.fillMaxWidth(), 110 | horizontalArrangement = Arrangement.SpaceBetween 111 | ) { 112 | TweetOptions( 113 | icon = Icons.Outlined.Reply, 114 | count = "0" 115 | ) 116 | TweetOptions( 117 | icon = Icons.Outlined.Replay, 118 | count = tweets.retweetCount.toString() 119 | ) 120 | TweetOptions( 121 | icon = Icons.Outlined.Star, 122 | count = tweets.likeCount.toString() 123 | ) 124 | TweetOptions( 125 | icon = Icons.Outlined.Share, 126 | count = null 127 | ) 128 | } 129 | } 130 | } 131 | SeparatorDivider() 132 | } 133 | } 134 | 135 | @Composable 136 | fun TweetOptions(icon: ImageVector, count: String?) { 137 | Row( 138 | verticalAlignment = Alignment.CenterVertically 139 | ) { 140 | Icon( 141 | imageVector = icon, 142 | modifier = Modifier, 143 | contentDescription = "icon" 144 | ) 145 | SeparatorSpacer(modifier = Modifier.width(5.dp)) 146 | if (count != null) { 147 | Text( 148 | text = count, 149 | maxLines = 1, 150 | fontSize = 14.sp, 151 | overflow = TextOverflow.Ellipsis 152 | ) 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple200 = Color(0xFFBB86FC) 6 | val Purple500 = Color(0xFF6200EE) 7 | val Purple700 = Color(0xFF3700B3) 8 | val Teal200 = Color(0xFF03DAC5) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | 9 | private val DarkColorPalette = darkColors( 10 | primary = Purple200, 11 | primaryVariant = Purple700, 12 | secondary = Teal200 13 | ) 14 | 15 | private val LightColorPalette = lightColors( 16 | primary = Purple500, 17 | primaryVariant = Purple700, 18 | secondary = Teal200 19 | 20 | /* Other default colors to override 21 | background = Color.White, 22 | surface = Color.White, 23 | onPrimary = Color.White, 24 | onSecondary = Color.Black, 25 | onBackground = Color.Black, 26 | onSurface = Color.Black, 27 | */ 28 | ) 29 | 30 | @Composable 31 | fun AndroidCleanArchitectureTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { 32 | val colors = if (darkTheme) { 33 | DarkColorPalette 34 | } else { 35 | LightColorPalette 36 | } 37 | 38 | MaterialTheme( 39 | colors = colors, 40 | typography = Typography, 41 | shapes = Shapes, 42 | content = content 43 | ) 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/praveen/androidcleanarchitecture/presentation/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.praveen.androidcleanarchitecture.presentation.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | /* Other default text styles to override 17 | button = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.W500, 20 | fontSize = 14.sp 21 | ), 22 | caption = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 12.sp 26 | ) 27 | */ 28 | ) -------------------------------------------------------------------------------- /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_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharmapraveen91/AndroidCleanArchitecture/ecc673af2d775a11eb20c8fb9afc89dd6d6b7e8d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidCleanArchitecture 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 |