├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── power.png │ │ │ │ ├── explore.png │ │ │ │ ├── greetings.png │ │ │ │ ├── ic_calendar.xml │ │ │ │ ├── ic_bolt.xml │ │ │ │ ├── ic_placeholder.xml │ │ │ │ ├── ic_cake.xml │ │ │ │ ├── ic_network_error.xml │ │ │ │ ├── ic_logo.xml │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ └── ic_search_document.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── themes.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-night │ │ │ │ └── ic_placeholder.xml │ │ │ └── values-night │ │ │ │ └── themes.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ │ └── dev │ │ │ │ └── amal │ │ │ │ └── borutoapp │ │ │ │ ├── Application.kt │ │ │ │ ├── domain │ │ │ │ ├── repository │ │ │ │ │ ├── LocalDataSource.kt │ │ │ │ │ ├── DataStoreOperations.kt │ │ │ │ │ └── RemoteDataSource.kt │ │ │ │ ├── model │ │ │ │ │ ├── ApiResponse.kt │ │ │ │ │ ├── HeroRemoteKeys.kt │ │ │ │ │ ├── Hero.kt │ │ │ │ │ └── OnBoardingPage.kt │ │ │ │ └── use_cases │ │ │ │ │ ├── read_onboarding │ │ │ │ │ └── ReadOnBoardingUseCase.kt │ │ │ │ │ ├── save_onboarding │ │ │ │ │ └── SaveOnBoardingUseCase.kt │ │ │ │ │ ├── get_selected_hero │ │ │ │ │ └── GetSelectedHeroUseCase.kt │ │ │ │ │ ├── get_all_heroes │ │ │ │ │ └── GetAllHeroesUseCase.kt │ │ │ │ │ ├── search_heroes │ │ │ │ │ └── SearchHeroesUseCase.kt │ │ │ │ │ └── UseCases.kt │ │ │ │ ├── ui │ │ │ │ └── theme │ │ │ │ │ ├── Shape.kt │ │ │ │ │ ├── Dimensions.kt │ │ │ │ │ ├── Type.kt │ │ │ │ │ ├── Theme.kt │ │ │ │ │ └── Color.kt │ │ │ │ ├── navigation │ │ │ │ ├── Screen.kt │ │ │ │ └── NavGraph.kt │ │ │ │ ├── presentation │ │ │ │ ├── screens │ │ │ │ │ ├── home │ │ │ │ │ │ ├── HomeViewModel.kt │ │ │ │ │ │ ├── HomeTopBar.kt │ │ │ │ │ │ └── HomeScreen.kt │ │ │ │ │ ├── welcome │ │ │ │ │ │ ├── WelcomeViewModel.kt │ │ │ │ │ │ └── WelcomeScreen.kt │ │ │ │ │ ├── splash │ │ │ │ │ │ ├── SplashViewModel.kt │ │ │ │ │ │ └── SplashScreen.kt │ │ │ │ │ ├── search │ │ │ │ │ │ ├── SearchViewModel.kt │ │ │ │ │ │ ├── SearchScreen.kt │ │ │ │ │ │ └── SearchTopBar.kt │ │ │ │ │ └── details │ │ │ │ │ │ ├── DetailsViewModel.kt │ │ │ │ │ │ ├── DetailsScreen.kt │ │ │ │ │ │ └── DetailsContent.kt │ │ │ │ ├── components │ │ │ │ │ ├── OrderedList.kt │ │ │ │ │ ├── InfoBox.kt │ │ │ │ │ ├── ShimmerEffect.kt │ │ │ │ │ └── RatingWidget.kt │ │ │ │ └── common │ │ │ │ │ ├── EmptyScreen.kt │ │ │ │ │ └── ListContent.kt │ │ │ │ ├── data │ │ │ │ ├── remote │ │ │ │ │ └── BorutoApi.kt │ │ │ │ ├── repository │ │ │ │ │ ├── LocalDataSourceImpl.kt │ │ │ │ │ ├── Repository.kt │ │ │ │ │ ├── RemoteDataSourceImpl.kt │ │ │ │ │ └── DataStoreOperationsImpl.kt │ │ │ │ ├── local │ │ │ │ │ ├── BorutoDatabase.kt │ │ │ │ │ ├── DatabaseConverter.kt │ │ │ │ │ └── dao │ │ │ │ │ │ ├── HeroRemoteKeysDao.kt │ │ │ │ │ │ └── HeroDao.kt │ │ │ │ └── paging_source │ │ │ │ │ ├── SearchHeroesSource.kt │ │ │ │ │ └── HeroRemoteMediator.kt │ │ │ │ ├── util │ │ │ │ ├── Constants.kt │ │ │ │ └── PaletteGenerator.kt │ │ │ │ ├── di │ │ │ │ ├── DatabaseModule.kt │ │ │ │ ├── RepositoryModule.kt │ │ │ │ └── NetworkModule.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── dev │ │ │ └── amal │ │ │ └── borutoapp │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── dev │ │ └── amal │ │ └── borutoapp │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md ├── .gitignore ├── settings.gradle ├── local.properties ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/drawable/power.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/explore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/drawable/explore.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/greetings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/drawable/greetings.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustAmalll/BorutoApp/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build Modern Android App with REST API and Ktor Server - APP 2 | 3 |

4 | 5 |

6 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/Application.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class Application : Application() -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/repository/LocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.repository 2 | 3 | import dev.amal.borutoapp.domain.model.Hero 4 | 5 | interface LocalDataSource { 6 | suspend fun getSelectedHero(heroId: Int): Hero 7 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Dec 15 15:55:30 ALMT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/ 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/repository/DataStoreOperations.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.repository 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface DataStoreOperations { 6 | suspend fun saveOnBoardingState(completed: Boolean) 7 | fun readOnBoardingState(): Flow 8 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | jcenter() // Warning: this repository is going to shut down soon 7 | } 8 | } 9 | rootProject.name = "BorutoApp" 10 | include ':app' 11 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/repository/RemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.repository 2 | 3 | import androidx.paging.PagingData 4 | import dev.amal.borutoapp.domain.model.Hero 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface RemoteDataSource { 8 | fun getAllHeroes(): Flow> 9 | fun searchHeroes(query: String): Flow> 10 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.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/dev/amal/borutoapp/domain/model/ApiResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ApiResponse( 7 | val success: Boolean, 8 | val message: String? = null, 9 | val prevPage: Int? = null, 10 | val nextPage: Int? = null, 11 | val heroes: List = emptyList(), 12 | val lastUpdated: Long? = null 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/use_cases/read_onboarding/ReadOnBoardingUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.use_cases.read_onboarding 2 | 3 | import dev.amal.borutoapp.data.repository.Repository 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | class ReadOnBoardingUseCase( 7 | private val repository: Repository 8 | ) { 9 | operator fun invoke(): Flow = 10 | repository.readOnBoardingState() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/use_cases/save_onboarding/SaveOnBoardingUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.use_cases.save_onboarding 2 | 3 | import dev.amal.borutoapp.data.repository.Repository 4 | 5 | class SaveOnBoardingUseCase( 6 | private val repository: Repository 7 | ) { 8 | suspend operator fun invoke(completed: Boolean) { 9 | repository.saveOnBoardingState(completed = completed) 10 | } 11 | } -------------------------------------------------------------------------------- /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/test/java/dev/amal/borutoapp/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/use_cases/get_selected_hero/GetSelectedHeroUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.use_cases.get_selected_hero 2 | 3 | import dev.amal.borutoapp.data.repository.Repository 4 | import dev.amal.borutoapp.domain.model.Hero 5 | 6 | class GetSelectedHeroUseCase( 7 | private val repository: Repository 8 | ) { 9 | suspend operator fun invoke(heroId: Int): Hero = 10 | repository.getSelectedHero(heroId = heroId) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/navigation/Screen.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.navigation 2 | 3 | sealed class Screen(val route: String) { 4 | object Splash : Screen("splash_screen") 5 | object Welcome : Screen("welcome_screen") 6 | object Home : Screen("home_screen") 7 | object Details : Screen("details_screen/{heroId}") { 8 | fun passHeroId(heroId: Int): String = "details_screen/$heroId" 9 | } 10 | object Search : Screen("search_screen") 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/presentation/screens/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.presentation.screens.home 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.hilt.android.lifecycle.HiltViewModel 5 | import dev.amal.borutoapp.domain.use_cases.UseCases 6 | import javax.inject.Inject 7 | 8 | @HiltViewModel 9 | class HomeViewModel @Inject constructor( 10 | useCases: UseCases 11 | ) : ViewModel() { 12 | val getAllHeroes = useCases.getAllHeroesUseCase() 13 | } -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | ## This file is automatically generated by Android Studio. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file should *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | # 7 | # Location of the SDK. This is only used by Gradle. 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | sdk.dir=C\:\\Users\\lilba\\AppData\\Local\\Android\\Sdk -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_calendar.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/model/HeroRemoteKeys.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import dev.amal.borutoapp.util.Constants.HERO_REMOTE_KEYS_DATABASE_TABLE 6 | 7 | @Entity(tableName = HERO_REMOTE_KEYS_DATABASE_TABLE) 8 | data class HeroRemoteKeys( 9 | @PrimaryKey(autoGenerate = false) 10 | val id: Int, 11 | val prevPage: Int?, 12 | val nextPage: Int?, 13 | val lastUpdated: Long? = null 14 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bolt.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/use_cases/get_all_heroes/GetAllHeroesUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.use_cases.get_all_heroes 2 | 3 | import androidx.paging.PagingData 4 | import dev.amal.borutoapp.data.repository.Repository 5 | import dev.amal.borutoapp.domain.model.Hero 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | class GetAllHeroesUseCase( 9 | private val repository: Repository 10 | ) { 11 | operator fun invoke(): Flow> = 12 | repository.getAllHeroes() 13 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/data/remote/BorutoApi.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.data.remote 2 | 3 | import dev.amal.borutoapp.domain.model.ApiResponse 4 | import retrofit2.http.GET 5 | import retrofit2.http.Query 6 | 7 | interface BorutoApi { 8 | @GET("/boruto/heroes") 9 | suspend fun getAllHeroes( 10 | @Query("page") page: Int = 1 11 | ): ApiResponse 12 | 13 | @GET("/boruto/heroes/search") 14 | suspend fun searchHeroes( 15 | @Query("name") name: String 16 | ): ApiResponse 17 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/use_cases/search_heroes/SearchHeroesUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.use_cases.search_heroes 2 | 3 | import androidx.paging.PagingData 4 | import dev.amal.borutoapp.data.repository.Repository 5 | import dev.amal.borutoapp.domain.model.Hero 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | class SearchHeroesUseCase( 9 | private val repository: Repository 10 | ) { 11 | operator fun invoke(query: String): Flow> = 12 | repository.searchHeroes(query = query) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/data/repository/LocalDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.data.repository 2 | 3 | import dev.amal.borutoapp.data.local.BorutoDatabase 4 | import dev.amal.borutoapp.domain.model.Hero 5 | import dev.amal.borutoapp.domain.repository.LocalDataSource 6 | 7 | class LocalDataSourceImpl(borutoDatabase: BorutoDatabase) : LocalDataSource { 8 | 9 | private val heroDao = borutoDatabase.heroDao() 10 | 11 | override suspend fun getSelectedHero(heroId: Int): Hero = 12 | heroDao.getSelectedHero(heroId = heroId) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/data/local/BorutoDatabase.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.data.local 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import dev.amal.borutoapp.data.local.dao.HeroDao 7 | import dev.amal.borutoapp.data.local.dao.HeroRemoteKeysDao 8 | import dev.amal.borutoapp.domain.model.Hero 9 | import dev.amal.borutoapp.domain.model.HeroRemoteKeys 10 | 11 | @Database(entities = [Hero::class, HeroRemoteKeys::class], version = 1) 12 | @TypeConverters(DatabaseConverter::class) 13 | abstract class BorutoDatabase : RoomDatabase() { 14 | abstract fun heroDao(): HeroDao 15 | abstract fun heroRemoteKeysDao(): HeroRemoteKeysDao 16 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/data/local/DatabaseConverter.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.data.local 2 | 3 | import androidx.room.TypeConverter 4 | 5 | class DatabaseConverter { 6 | 7 | private val separator = "," 8 | 9 | @TypeConverter 10 | fun convertListToString(list: List): String { 11 | val stringBuilder = StringBuilder() 12 | for (item in list) { 13 | stringBuilder.append(item).append(separator) 14 | } 15 | stringBuilder.setLength(stringBuilder.length - separator.length) 16 | return stringBuilder.toString() 17 | } 18 | 19 | @TypeConverter 20 | fun convertStringToList(string: String): List = string.split(separator) 21 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/model/Hero.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import dev.amal.borutoapp.util.Constants.HERO_DATABASE_TABLE 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | @Entity(tableName = HERO_DATABASE_TABLE) 10 | data class Hero( 11 | @PrimaryKey(autoGenerate = false) 12 | val id: Int, 13 | val name: String, 14 | val image: String, 15 | val about: String, 16 | val rating: Double, 17 | val power: Int, 18 | val month: String, 19 | val day: String, 20 | val family: List, 21 | val abilities: List, 22 | val natureTypes: List 23 | ) -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/data/local/dao/HeroRemoteKeysDao.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.data.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import dev.amal.borutoapp.domain.model.HeroRemoteKeys 8 | 9 | @Dao 10 | interface HeroRemoteKeysDao { 11 | @Query("SELECT * FROM hero_remote_keys_table WHERE id =:heroId ") 12 | suspend fun getRemoteKeys(heroId: Int): HeroRemoteKeys? 13 | 14 | @Insert(onConflict = OnConflictStrategy.REPLACE) 15 | suspend fun addAllRemoteKeys(heroRemoteKeys: List) 16 | 17 | @Query("DELETE FROM hero_remote_keys_table") 18 | suspend fun deleteAllRemoteKeys() 19 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/data/local/dao/HeroDao.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.data.local.dao 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import dev.amal.borutoapp.domain.model.Hero 9 | 10 | @Dao 11 | interface HeroDao { 12 | @Query("SELECT * FROM hero_table ORDER BY id ASC") 13 | fun getAllHeroes(): PagingSource 14 | 15 | @Query("SELECT * FROM hero_table WHERE id=:heroId") 16 | fun getSelectedHero(heroId: Int): Hero 17 | 18 | @Insert(onConflict = OnConflictStrategy.REPLACE) 19 | suspend fun addHeroes(heroes: List) 20 | 21 | @Query("DELETE FROM hero_table") 22 | suspend fun deleteAllHeroes() 23 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/presentation/screens/welcome/WelcomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.presentation.screens.welcome 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import dev.amal.borutoapp.domain.use_cases.UseCases 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.launch 9 | import javax.inject.Inject 10 | 11 | @HiltViewModel 12 | class WelcomeViewModel @Inject constructor( 13 | private val useCases: UseCases 14 | ) : ViewModel() { 15 | 16 | fun saveOnBoardingState(completed: Boolean) { 17 | viewModelScope.launch(Dispatchers.IO) { 18 | useCases.saveOnBoardingUserCase(completed = completed) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_placeholder.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_placeholder.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/ui/theme/Dimensions.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.ui.theme 2 | 3 | import androidx.compose.ui.unit.dp 4 | 5 | val EXTRA_LARGE_PADDING = 40.dp 6 | val LARGE_PADDING = 20.dp 7 | val MEDIUM_PADDING = 16.dp 8 | val DEFAULT_PADDING = 12.dp 9 | val SMALL_PADDING = 10.dp 10 | val EXTRA_SMALL_PADDING = 6.dp 11 | val SUPER_EXTRA_SMALL_PADDING = 3.dp 12 | 13 | val PAGING_INDICATOR_WIDTH = 12.dp 14 | val PAGING_INDICATOR_SPACING = 8.dp 15 | 16 | val TOP_APP_BAR_HEIGHT = 56.dp 17 | val HERO_ITEM_HEIGHT = 500.dp 18 | val NAME_PLACEHOLDER_HEIGHT = 30.dp 19 | val ABOUT_PLACEHOLDER_HEIGHT = 15.dp 20 | val RATING_PLACEHOLDER_HEIGHT = 20.dp 21 | val NETWORK_ERROR_ICON_HEIGHT = 120.dp 22 | 23 | val DEFAULT_ICON_SIZE = 32.dp 24 | 25 | val MIN_SHEET_HEIGHT = 140.dp 26 | val EXPANDED_RADIUS_LEVEL = 0.dp -------------------------------------------------------------------------------- /app/src/androidTest/java/dev/amal/borutoapp/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("dev.amal.borutoapp", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /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/main/java/dev/amal/borutoapp/domain/use_cases/UseCases.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.use_cases 2 | 3 | import dev.amal.borutoapp.domain.use_cases.get_all_heroes.GetAllHeroesUseCase 4 | import dev.amal.borutoapp.domain.use_cases.get_selected_hero.GetSelectedHeroUseCase 5 | import dev.amal.borutoapp.domain.use_cases.read_onboarding.ReadOnBoardingUseCase 6 | import dev.amal.borutoapp.domain.use_cases.save_onboarding.SaveOnBoardingUseCase 7 | import dev.amal.borutoapp.domain.use_cases.search_heroes.SearchHeroesUseCase 8 | 9 | data class UseCases( 10 | val saveOnBoardingUserCase: SaveOnBoardingUseCase, 11 | val readOnBoardingUseCase: ReadOnBoardingUseCase, 12 | val getAllHeroesUseCase: GetAllHeroesUseCase, 13 | val searchHeroesUseCase: SearchHeroesUseCase, 14 | val getSelectedHeroUseCase: GetSelectedHeroUseCase 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/util/Constants.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.util 2 | 3 | object Constants { 4 | 5 | // const val BASE_URL = "http://192.168.1.6:8080" 6 | const val BASE_URL = "https://ktor-boruto-server.herokuapp.com" 7 | 8 | const val DETAILS_ARGUMENT_KEY = "heroId" 9 | 10 | const val BORUTO_DATABASE = "boruto_database" 11 | const val HERO_DATABASE_TABLE = "hero_table" 12 | const val HERO_REMOTE_KEYS_DATABASE_TABLE = "hero_remote_keys_table" 13 | 14 | const val PREFERENCES_NAME = "boruto_preferences" 15 | const val PREFERENCES_KEY = "on_boarding_completed" 16 | 17 | const val ON_BOARDING_PAGE_COUNT = 3 18 | const val LAST_ON_BOARDING_PAGE = 2 19 | 20 | const val ITEMS_PER_PAGE = 3 21 | const val ABOUT_TEXT_MAX_LINES = 7 22 | 23 | const val MIN_BACKGROUND_IMAGE_HEIGHT = 0.5f 24 | } -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.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/ic_cake.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/domain/model/OnBoardingPage.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.domain.model 2 | 3 | import androidx.annotation.DrawableRes 4 | import dev.amal.borutoapp.R 5 | 6 | sealed class OnBoardingPage( 7 | @DrawableRes val image: Int, 8 | val title: String, 9 | val description: String 10 | ) { 11 | object First : OnBoardingPage( 12 | image = R.drawable.greetings, 13 | title = "Greetings", 14 | description = "Are you a Boruto fan? Because if you are then we have a great news for you!" 15 | ) 16 | 17 | object Second : OnBoardingPage( 18 | image = R.drawable.explore, 19 | title = "Explore", 20 | description = "Find your favourite heroes and learn some of the things that you didn't know about." 21 | ) 22 | 23 | object Third : OnBoardingPage( 24 | image = R.drawable.power, 25 | title = "Power", 26 | description = "Check out your hero's power and see how mush are they strong comparing to other." 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BorutoApp 3 | Application Logo 4 | On Boarding Image 5 | Search Icon 6 | M14,21.288,21.416,26l-1.968-8.88L26,11.145l-8.628-.771L14,2l-3.372,8.375L2,11.145,8.552,17.12,6.584,26Z 7 | Hero Image 8 | Network Error Icon 9 | Search here... 10 | Close Icon 11 | Info Icon 12 | Power 13 | Month 14 | Birthday 15 | About 16 | Family 17 | Abilities 18 | Nature Types 19 | -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/presentation/screens/splash/SplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.presentation.screens.splash 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import dev.amal.borutoapp.domain.use_cases.UseCases 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.flow.MutableStateFlow 9 | import kotlinx.coroutines.flow.StateFlow 10 | import kotlinx.coroutines.flow.stateIn 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class SplashViewModel @Inject constructor( 16 | private val useCases: UseCases 17 | ) : ViewModel() { 18 | 19 | private val _onBoardingCompleted = MutableStateFlow(false) 20 | val onBoardingCompleted: StateFlow = _onBoardingCompleted 21 | 22 | init { 23 | viewModelScope.launch(Dispatchers.IO) { 24 | _onBoardingCompleted.value = 25 | useCases.readOnBoardingUseCase().stateIn(viewModelScope).value 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/presentation/screens/home/HomeTopBar.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.presentation.screens.home 2 | 3 | import androidx.compose.material.* 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.filled.Search 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.res.stringResource 8 | import dev.amal.borutoapp.R 9 | import dev.amal.borutoapp.ui.theme.topAppBarBgColor 10 | import dev.amal.borutoapp.ui.theme.topAppBarContentColor 11 | 12 | @Composable 13 | fun HomeTopBar(onSearchClicked: () -> Unit) { 14 | TopAppBar( 15 | title = { 16 | Text( 17 | text = "Explore", 18 | color = MaterialTheme.colors.topAppBarContentColor 19 | ) 20 | }, 21 | backgroundColor = MaterialTheme.colors.topAppBarBgColor, 22 | actions = { 23 | IconButton(onClick = onSearchClicked) { 24 | Icon( 25 | imageVector = Icons.Default.Search, 26 | contentDescription = stringResource(R.string.search_icon) 27 | ) 28 | } 29 | } 30 | ) 31 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/data/repository/Repository.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.data.repository 2 | 3 | import androidx.paging.PagingData 4 | import dev.amal.borutoapp.domain.model.Hero 5 | import dev.amal.borutoapp.domain.repository.DataStoreOperations 6 | import dev.amal.borutoapp.domain.repository.LocalDataSource 7 | import dev.amal.borutoapp.domain.repository.RemoteDataSource 8 | import kotlinx.coroutines.flow.Flow 9 | import javax.inject.Inject 10 | 11 | class Repository @Inject constructor( 12 | private val local: LocalDataSource, 13 | private val remote: RemoteDataSource, 14 | private val dataStore: DataStoreOperations 15 | ) { 16 | 17 | fun getAllHeroes(): Flow> = 18 | remote.getAllHeroes() 19 | 20 | fun searchHeroes(query: String): Flow> = 21 | remote.searchHeroes(query = query) 22 | 23 | suspend fun getSelectedHero(heroId: Int): Hero = 24 | local.getSelectedHero(heroId = heroId) 25 | 26 | suspend fun saveOnBoardingState(completed: Boolean) { 27 | dataStore.saveOnBoardingState(completed = completed) 28 | } 29 | 30 | fun readOnBoardingState(): Flow = 31 | dataStore.readOnBoardingState() 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /app/src/main/java/dev/amal/borutoapp/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package dev.amal.borutoapp.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import androidx.room.RoomDatabase 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import dagger.hilt.components.SingletonComponent 11 | import dev.amal.borutoapp.data.local.BorutoDatabase 12 | import dev.amal.borutoapp.data.repository.LocalDataSourceImpl 13 | import dev.amal.borutoapp.domain.repository.LocalDataSource 14 | import dev.amal.borutoapp.util.Constants.BORUTO_DATABASE 15 | import javax.inject.Singleton 16 | 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | object DatabaseModule { 20 | 21 | @Provides 22 | @Singleton 23 | fun provideDatabase( 24 | @ApplicationContext context: Context 25 | ): BorutoDatabase = 26 | Room.databaseBuilder( 27 | context, 28 | BorutoDatabase::class.java, 29 | BORUTO_DATABASE 30 | ).build() 31 | 32 | @Provides 33 | @Singleton 34 | fun provideLocalDataSource( 35 | database: BorutoDatabase 36 | ): LocalDataSource = 37 | LocalDataSourceImpl(borutoDatabase = database) 38 | 39 | } -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 |