diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ae388c2 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..44ca2d9 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..0fc3113 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..79a8a28 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,110 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.apollographql.apollo3") version "3.8.2" +} + +apollo { + + service("service") { + packageName.set("com.rznkolds") + } +} + +android { + namespace = "com.rznkolds.geniusricks" + compileSdk = 34 + + defaultConfig { + applicationId = "com.rznkolds.geniusricks" + versionName = "1.0" + versionCode = 1 + targetSdk = 34 + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + + release { + isMinifyEnabled = false + + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.4.3" + } + + packaging { + + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + + // CORE AND LIFECYCLE + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + + // JETPACK COMPOSE + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-graphics") + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.activity:activity-compose:1.7.2") + implementation(platform("androidx.compose:compose-bom:2023.03.00")) + + // DEBUG + debugImplementation("androidx.compose.ui:ui-tooling") + debugImplementation("androidx.compose.ui:ui-test-manifest") + + // TEST + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.compose.ui:ui-test-junit4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00")) + + // KOIN + implementation ("io.insert-koin:koin-androidx-navigation:3.2.0-beta-1") + implementation ("io.insert-koin:koin-androidx-compose:3.2.0-beta-1") + implementation ("io.insert-koin:koin-android:3.2.0-beta-1") + + // KOIN TEST + testImplementation ("io.insert-koin:koin-test-junit4:3.2.0-beta-1") + + //APOLLO GRAPHQL + implementation("com.apollographql.apollo3:apollo-runtime:3.8.2") + + // SCOPIES + implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") + implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + + // COIL + implementation ("io.coil-kt:coil-compose:1.3.0") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/rznkolds/geniusricks/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/rznkolds/geniusricks/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..2ee5f23 --- /dev/null +++ b/app/src/androidTest/java/com/rznkolds/geniusricks/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.rznkolds.geniusricks + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.rznkolds.geniusricks", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fd736cb --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/graphql/RickCharacter.graphql b/app/src/main/graphql/RickCharacter.graphql new file mode 100644 index 0000000..4c03b9f --- /dev/null +++ b/app/src/main/graphql/RickCharacter.graphql @@ -0,0 +1,10 @@ +query RickCharacter($page: Int!) { + + characters(page: $page) { + results { + name + id + image + } + } +} \ No newline at end of file diff --git a/app/src/main/graphql/schema.graphqls b/app/src/main/graphql/schema.graphqls new file mode 100644 index 0000000..0e59f2d --- /dev/null +++ b/app/src/main/graphql/schema.graphqls @@ -0,0 +1,208 @@ +"""""" +directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT | INTERFACE + +"""""" +enum CacheControlScope { + """""" + PRIVATE + + """""" + PUBLIC +} + +"""""" +type Character { + """Time at which the character was created in the database.""" + created: String + + """Episodes in which this character appeared.""" + episode: [Episode]! + + """ + The gender of the character ('Female', 'Male', 'Genderless' or 'unknown'). + """ + gender: String + + """The id of the character.""" + id: ID + + """ + Link to the character's image. + All images are 300x300px and most are medium shots or portraits since they are intended to be used as avatars. + """ + image: String + + """The character's last known location""" + location: Location + + """The name of the character.""" + name: String + + """The character's origin location""" + origin: Location + + """The species of the character.""" + species: String + + """The status of the character ('Alive', 'Dead' or 'unknown').""" + status: String + + """The type or subspecies of the character.""" + type: String +} + +"""""" +type Characters { + """""" + info: Info + + """""" + results: [Character] +} + +"""""" +type Episode { + """The air date of the episode.""" + air_date: String + + """List of characters who have been seen in the episode.""" + characters: [Character]! + + """Time at which the episode was created in the database.""" + created: String + + """The code of the episode.""" + episode: String + + """The id of the episode.""" + id: ID + + """The name of the episode.""" + name: String +} + +"""""" +type Episodes { + """""" + info: Info + + """""" + results: [Episode] +} + +"""""" +input FilterCharacter { + """""" + name: String + + """""" + status: String + + """""" + species: String + + """""" + type: String + + """""" + gender: String +} + +"""""" +input FilterEpisode { + """""" + name: String + + """""" + episode: String +} + +"""""" +input FilterLocation { + """""" + name: String + + """""" + type: String + + """""" + dimension: String +} + +"""""" +type Info { + """The length of the response.""" + count: Int + + """Number of the next page (if it exists)""" + next: Int + + """The amount of pages.""" + pages: Int + + """Number of the previous page (if it exists)""" + prev: Int +} + +"""""" +type Location { + """Time at which the location was created in the database.""" + created: String + + """The dimension in which the location is located.""" + dimension: String + + """The id of the location.""" + id: ID + + """The name of the location.""" + name: String + + """List of characters who have been last seen in the location.""" + residents: [Character]! + + """The type of the location.""" + type: String +} + +"""""" +type Locations { + """""" + info: Info + + """""" + results: [Location] +} + +"""""" +type Query { + """Get a specific character by ID""" + character(id: ID!): Character + + """Get the list of all characters""" + characters(filter: FilterCharacter, page: Int): Characters + + """Get a list of characters selected by ids""" + charactersByIds(ids: [ID!]!): [Character] + + """Get a specific episode by ID""" + episode(id: ID!): Episode + + """Get the list of all episodes""" + episodes(filter: FilterEpisode, page: Int): Episodes + + """Get a list of episodes selected by ids""" + episodesByIds(ids: [ID!]!): [Episode] + + """Get a specific locations by ID""" + location(id: ID!): Location + + """Get the list of all locations""" + locations(filter: FilterLocation, page: Int): Locations + + """Get a list of locations selected by ids""" + locationsByIds(ids: [ID!]!): [Location] +} + +"""The `Upload` scalar type represents a file upload.""" +scalar Upload diff --git a/app/src/main/java/com/rznkolds/geniusricks/GeniusRicks.kt b/app/src/main/java/com/rznkolds/geniusricks/GeniusRicks.kt new file mode 100644 index 0000000..d7f0272 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/GeniusRicks.kt @@ -0,0 +1,29 @@ +package com.rznkolds.geniusricks + +import android.app.Application +import com.rznkolds.geniusricks.di.apolloModule +import com.rznkolds.geniusricks.di.repositoryModule +import com.rznkolds.geniusricks.di.useCaseModule +import com.rznkolds.geniusricks.di.viewModelModule +import com.rznkolds.geniusricks.presentation.MainViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.context.GlobalContext.startKoin +import org.koin.dsl.module + +class GeniusRicks: Application() { + + override fun onCreate() { + super.onCreate() + + startKoin { + modules( + listOf( + apolloModule, + repositoryModule, + useCaseModule, + viewModelModule + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/common/Resource.kt b/app/src/main/java/com/rznkolds/geniusricks/common/Resource.kt new file mode 100644 index 0000000..4c66932 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/common/Resource.kt @@ -0,0 +1,7 @@ +package com.rznkolds.geniusricks.common + +sealed class Resource { + data class Success(val data: T?) : Resource() + data class Error(val message: String) : Resource() + object Loading : Resource() +} diff --git a/app/src/main/java/com/rznkolds/geniusricks/data/dto/Character.kt b/app/src/main/java/com/rznkolds/geniusricks/data/dto/Character.kt new file mode 100644 index 0000000..16d9f01 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/data/dto/Character.kt @@ -0,0 +1,18 @@ +package com.rznkolds.geniusricks.data.dto + +import com.rznkolds.RickCharacterQuery + +data class Character( + val id:String?, + val name:String?, + val image:String? +) + +fun RickCharacterQuery.Result.mapToCharacter(): Character { + + return Character( + id = id ?: "", + name = name ?: "", + image = image ?: "" + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/data/repository/RickRepository.kt b/app/src/main/java/com/rznkolds/geniusricks/data/repository/RickRepository.kt new file mode 100644 index 0000000..2102d96 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/data/repository/RickRepository.kt @@ -0,0 +1,9 @@ +package com.rznkolds.geniusricks.data.repository + +import com.rznkolds.geniusricks.data.dto.Character + +interface RickRepository { + + suspend fun getCharacters(): List? +} + diff --git a/app/src/main/java/com/rznkolds/geniusricks/data/repository/RickRepositoryImpl.kt b/app/src/main/java/com/rznkolds/geniusricks/data/repository/RickRepositoryImpl.kt new file mode 100644 index 0000000..0607aee --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/data/repository/RickRepositoryImpl.kt @@ -0,0 +1,23 @@ +package com.rznkolds.geniusricks.data.repository + +import com.apollographql.apollo3.ApolloClient +import com.rznkolds.RickCharacterQuery +import com.rznkolds.geniusricks.data.dto.Character +import com.rznkolds.geniusricks.data.dto.mapToCharacter + +class RickRepositoryImpl( + private val api:ApolloClient +) : RickRepository { + + override suspend fun getCharacters(): List? { + + return api.query(RickCharacterQuery(1)) + .execute() + .data + ?.characters + ?.results + ?.map { + it!!.mapToCharacter() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/di/ApolloModule.kt b/app/src/main/java/com/rznkolds/geniusricks/di/ApolloModule.kt new file mode 100644 index 0000000..b5dc075 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/di/ApolloModule.kt @@ -0,0 +1,14 @@ +package com.rznkolds.geniusricks.di + +import com.apollographql.apollo3.ApolloClient +import org.koin.dsl.module + +val apolloModule = module { + + single { + + ApolloClient.Builder() + .serverUrl("https://rickandmortyapi.com/graphql") + .build() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/di/RepositoryModule.kt b/app/src/main/java/com/rznkolds/geniusricks/di/RepositoryModule.kt new file mode 100644 index 0000000..71c6811 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/di/RepositoryModule.kt @@ -0,0 +1,13 @@ +package com.rznkolds.geniusricks.di + +import com.rznkolds.geniusricks.data.repository.RickRepository +import com.rznkolds.geniusricks.data.repository.RickRepositoryImpl +import org.koin.dsl.module + +val repositoryModule = module { + + factory { + + RickRepositoryImpl(get()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/di/UseCaseModule.kt b/app/src/main/java/com/rznkolds/geniusricks/di/UseCaseModule.kt new file mode 100644 index 0000000..56f95c4 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/di/UseCaseModule.kt @@ -0,0 +1,13 @@ +package com.rznkolds.geniusricks.di + +import com.rznkolds.geniusricks.domain.usecase.GetCharactersUseCase +import com.rznkolds.geniusricks.domain.usecase.GetCharactersUseCaseImpl +import org.koin.dsl.module + +val useCaseModule = module { + + factory { + + GetCharactersUseCaseImpl(get()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/di/ViewModelModule.kt b/app/src/main/java/com/rznkolds/geniusricks/di/ViewModelModule.kt new file mode 100644 index 0000000..7a5554f --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/di/ViewModelModule.kt @@ -0,0 +1,13 @@ +package com.rznkolds.geniusricks.di + +import com.rznkolds.geniusricks.presentation.MainViewModel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val viewModelModule = module { + + viewModel { + + MainViewModel(get()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/domain/usecase/GetCharactersUseCase.kt b/app/src/main/java/com/rznkolds/geniusricks/domain/usecase/GetCharactersUseCase.kt new file mode 100644 index 0000000..362940d --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/domain/usecase/GetCharactersUseCase.kt @@ -0,0 +1,10 @@ +package com.rznkolds.geniusricks.domain.usecase + +import com.rznkolds.geniusricks.common.Resource +import com.rznkolds.geniusricks.data.dto.Character +import kotlinx.coroutines.flow.Flow + +interface GetCharactersUseCase { + + operator fun invoke(): Flow>> +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/domain/usecase/GetCharactersUseCaseImpl.kt b/app/src/main/java/com/rznkolds/geniusricks/domain/usecase/GetCharactersUseCaseImpl.kt new file mode 100644 index 0000000..8724a24 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/domain/usecase/GetCharactersUseCaseImpl.kt @@ -0,0 +1,29 @@ +package com.rznkolds.geniusricks.domain.usecase + +import com.rznkolds.geniusricks.common.Resource +import com.rznkolds.geniusricks.data.dto.Character +import com.rznkolds.geniusricks.data.repository.RickRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import okio.IOException + +class GetCharactersUseCaseImpl( + private val repository: RickRepository +) : GetCharactersUseCase { + + override operator fun invoke(): Flow>> = flow { + + try { + emit(Resource.Loading) + + emit(Resource.Success(repository.getCharacters())) + + } catch (e: IOException) { + + emit(Resource.Error("Couldn't reach server.")) + } + + }.flowOn(Dispatchers.IO) +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/presentation/MainActivity.kt b/app/src/main/java/com/rznkolds/geniusricks/presentation/MainActivity.kt new file mode 100644 index 0000000..d90a015 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/presentation/MainActivity.kt @@ -0,0 +1,119 @@ +package com.rznkolds.geniusricks.presentation + +import android.annotation.SuppressLint +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.BlendMode.Companion.Screen +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import coil.annotation.ExperimentalCoilApi +import coil.compose.rememberImagePainter +import com.rznkolds.geniusricks.R +import com.rznkolds.geniusricks.presentation.ui.theme.GeniusRicksTheme +import org.koin.androidx.compose.getViewModel +import org.koin.androidx.compose.viewModel + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + GeniusRicksTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + GetCharacterScreen() + } + } + } + } +} + +@SuppressLint("StateFlowValueCalledInComposition") +@OptIn(ExperimentalCoilApi::class) +@Composable +fun GetCharacterScreen( + mainViewModel: MainViewModel = getViewModel() +) { + val state by mainViewModel.state.collectAsState() + + state.apply { + + this.characters?.let { v -> + + LazyColumn { + + items(v) { + + Card( + Modifier.fillMaxWidth().padding(4.dp), + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + + Image( + painter = rememberImagePainter( + data = it.image, + builder = { + placeholder(R.drawable.ic_launcher_foreground) + crossfade(true) + } + ), + modifier = Modifier + .height(100.dp) + .width(100.dp) + .padding(10.dp) + .fillMaxWidth() + .clip( + RoundedCornerShape(10.dp) + ), + contentDescription = "Game Picture", + contentScale = ContentScale.Crop + ) + + Text(it.name.toString()) + } + } + } + } + } + + this.loading?.let { + Toast.makeText(LocalContext.current, "Loading", Toast.LENGTH_SHORT).show() + } + + this.error?.let { + Toast.makeText(LocalContext.current, "Failed to load", Toast.LENGTH_SHORT).show() + } + } +} diff --git a/app/src/main/java/com/rznkolds/geniusricks/presentation/MainUIState.kt b/app/src/main/java/com/rznkolds/geniusricks/presentation/MainUIState.kt new file mode 100644 index 0000000..a4d857f --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/presentation/MainUIState.kt @@ -0,0 +1,9 @@ +package com.rznkolds.geniusricks.presentation + +import com.rznkolds.geniusricks.data.dto.Character + +data class MainUIState( + val characters: List? = null, + val loading: String? = null, + val error: String? = null, +) diff --git a/app/src/main/java/com/rznkolds/geniusricks/presentation/MainViewModel.kt b/app/src/main/java/com/rznkolds/geniusricks/presentation/MainViewModel.kt new file mode 100644 index 0000000..1b809bb --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/presentation/MainViewModel.kt @@ -0,0 +1,44 @@ +package com.rznkolds.geniusricks.presentation + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.rznkolds.geniusricks.common.Resource +import com.rznkolds.geniusricks.domain.usecase.GetCharactersUseCase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +class MainViewModel( + private val getCharactersUseCase: GetCharactersUseCase +) : ViewModel() { + + private val _state = MutableStateFlow(MainUIState()) + val state: StateFlow = _state.asStateFlow() + + init { + getCharacters() + } + + private fun getCharacters() { + + getCharactersUseCase().onEach { v -> + + when (v) { + is Resource.Loading -> { + _state.value = _state.value.copy(loading = "") + } + + is Resource.Error -> { + _state.value = _state.value.copy(error = "") + } + + is Resource.Success -> { + v.data.let { _state.value = _state.value.copy(characters = it) } + } + } + + }.launchIn(viewModelScope) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/presentation/ui/theme/Color.kt b/app/src/main/java/com/rznkolds/geniusricks/presentation/ui/theme/Color.kt new file mode 100644 index 0000000..732cd54 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/presentation/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.rznkolds.geniusricks.presentation.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/presentation/ui/theme/Theme.kt b/app/src/main/java/com/rznkolds/geniusricks/presentation/ui/theme/Theme.kt new file mode 100644 index 0000000..55e619f --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/presentation/ui/theme/Theme.kt @@ -0,0 +1,70 @@ +package com.rznkolds.geniusricks.presentation.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun GeniusRicksTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/rznkolds/geniusricks/presentation/ui/theme/Type.kt b/app/src/main/java/com/rznkolds/geniusricks/presentation/ui/theme/Type.kt new file mode 100644 index 0000000..1145176 --- /dev/null +++ b/app/src/main/java/com/rznkolds/geniusricks/presentation/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.rznkolds.geniusricks.presentation.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..31d5691 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + GeniusRicks + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..a7e6466 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +