diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml
new file mode 100644
index 0000000..1d3774c
--- /dev/null
+++ b/.idea/checkstyle-idea.xml
@@ -0,0 +1,16 @@
+
+
+
+ 10.3.1
+ JavaOnly
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index bd44bdc..37ed9c1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,7 +3,6 @@ plugins {
id 'org.jetbrains.kotlin.android'
id 'androidx.navigation.safeargs'
id 'kotlin-kapt'
- id 'dagger.hilt.android.plugin'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.10'
}
@@ -12,7 +11,7 @@ android {
defaultConfig {
applicationId "com.ilein.thecocktailapp"
- minSdk 21
+ minSdk 26
targetSdk 32
versionCode 1
versionName "1.0"
@@ -54,7 +53,7 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
def nav_version = "2.4.2"
- def ktor_version = "1.6.8"
+ def ktor_version = "2.0.3"
def room_version = "2.4.2"
def koin_version = "3.2.0"
@@ -74,16 +73,6 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
- // hilt
- implementation "com.google.dagger:hilt-android:2.40.5"
- kapt "com.google.dagger:hilt-compiler:2.40.5"
-
- // retrofit
- implementation 'com.squareup.retrofit2:retrofit:2.9.0'
- implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
- implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
- implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
-
// gson
implementation 'com.google.code.gson:gson:2.9.0'
@@ -98,12 +87,12 @@ dependencies {
implementation("io.ktor:ktor-server-netty:$ktor_version")
implementation("ch.qos.logback:logback-classic:1.2.5")
implementation("io.ktor:ktor-client-logging:$ktor_version")
- implementation("io.ktor:ktor-client-serialization:$ktor_version")
+ implementation("io.ktor:ktor-client-content-negotiation:$ktor_version")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
// Room
- implementation "androidx.room:room-runtime:$room_version"
- implementation "androidx.room:room-rxjava3:$room_version"
+ implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// Koin
@@ -116,7 +105,6 @@ dependencies {
// map
implementation "org.osmdroid:osmdroid-android:6.1.13"
-
implementation 'com.google.android.gms:play-services-location:17.1.0'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 198cc6a..f6a5ae0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.TheCocktailApp">
diff --git a/app/src/main/java/com/ilein/thecocktailapp/App.kt b/app/src/main/java/com/ilein/thecocktailapp/App.kt
index bc1c38c..51de837 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/App.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/App.kt
@@ -1,15 +1,14 @@
package com.ilein.thecocktailapp
import android.app.Application
-import com.ilein.thecocktailapp.koin.databaseModule
-import com.ilein.thecocktailapp.koin.networkModule
-import com.ilein.thecocktailapp.koin.vmModule
-import dagger.hilt.android.HiltAndroidApp
+import com.ilein.thecocktailapp.di.databaseModule
+import com.ilein.thecocktailapp.di.networkModule
+import com.ilein.thecocktailapp.di.useCaseModule
+import com.ilein.thecocktailapp.di.vmModule
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
-@HiltAndroidApp
class App: Application() {
override fun onCreate() {
@@ -18,7 +17,7 @@ class App: Application() {
startKoin(){
androidLogger()
androidContext(this@App)
- modules(databaseModule, networkModule, vmModule)
+ modules(databaseModule, networkModule, useCaseModule, vmModule)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/data/DrinkRepositoryImpl.kt b/app/src/main/java/com/ilein/thecocktailapp/data/DrinkRepositoryImpl.kt
new file mode 100644
index 0000000..dfefc60
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/DrinkRepositoryImpl.kt
@@ -0,0 +1,37 @@
+package com.ilein.thecocktailapp.data
+
+import com.ilein.thecocktailapp.data.db.LocalDataSource
+import com.ilein.thecocktailapp.data.network.RemoteDataSource
+import com.ilein.thecocktailapp.domain.Drink
+import com.ilein.thecocktailapp.domain.FavoriteDrink
+import com.ilein.thecocktailapp.domain.DrinkRepository
+import kotlinx.coroutines.flow.Flow
+
+class DrinkRepositoryImpl(
+ private val localDataSource: LocalDataSource,
+ private val remoteDataSource: RemoteDataSource
+): DrinkRepository {
+ override suspend fun insertDrinkFavorites(drink: FavoriteDrink) =
+ localDataSource.insertDrinkFavorites(drink.toEntity())
+
+ override suspend fun getDrinkFavorites(): List =
+ localDataSource.getDrinkFavorites().map { it.toDomain() }
+
+ override suspend fun getDrinkFavoritesDesc(): List =
+ localDataSource.getDrinkFavoritesDesc().map { it.toDomain() }
+
+ override suspend fun getFavoriteDrink(drinkId: Int): FavoriteDrink? =
+ localDataSource.getDrink(drinkId)?.toDomain()
+
+ override suspend fun deleteFavoriteDrink(drinkId: Int) =
+ localDataSource.deleteDrink(drinkId)
+
+ override suspend fun getDrink(drinkId: Int): Flow =
+ remoteDataSource.getDrink(drinkId)
+
+ override suspend fun getOneDrink(drinkId: Int): Drink =
+ remoteDataSource.getOneDrink(drinkId)
+
+ override suspend fun searchDrinks(query: String): Flow> =
+ remoteDataSource.searchDrinks(query)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/data/MapperExt.kt b/app/src/main/java/com/ilein/thecocktailapp/data/MapperExt.kt
new file mode 100644
index 0000000..3e56603
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/MapperExt.kt
@@ -0,0 +1,31 @@
+package com.ilein.thecocktailapp.data
+
+import com.ilein.thecocktailapp.data.db.DrinkEntity
+import com.ilein.thecocktailapp.data.network.model.DrinkModel
+import com.ilein.thecocktailapp.data.network.model.DrinksModel
+import com.ilein.thecocktailapp.domain.Drink
+import com.ilein.thecocktailapp.domain.FavoriteDrink
+
+fun FavoriteDrink.toEntity() = DrinkEntity(
+ id = this.id,
+ name = this.name,
+ image = this.image,
+ createDate = this.createDate,
+ note = this.note
+)
+
+fun DrinkEntity.toDomain() = FavoriteDrink(
+ id = this.id,
+ name = this.name,
+ image = this.image,
+ createDate = this.createDate,
+ note = this.note
+)
+
+fun DrinkModel.toDomain() = Drink(
+ id = this.idDrink,
+ name = this.strDrink,
+ image = this.strDrinkThumb,
+ ingredients = this.getIngredientsText(),
+ instruction = this.strInstructions
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/db/Converters.kt b/app/src/main/java/com/ilein/thecocktailapp/data/db/Converters.kt
similarity index 93%
rename from app/src/main/java/com/ilein/thecocktailapp/db/Converters.kt
rename to app/src/main/java/com/ilein/thecocktailapp/data/db/Converters.kt
index bb64bf4..fe0a609 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/db/Converters.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/db/Converters.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.db
+package com.ilein.thecocktailapp.data.db
import android.os.Build
import androidx.annotation.RequiresApi
diff --git a/app/src/main/java/com/ilein/thecocktailapp/db/DrinkLikeEntity.kt b/app/src/main/java/com/ilein/thecocktailapp/data/db/DrinkEntity.kt
similarity index 85%
rename from app/src/main/java/com/ilein/thecocktailapp/db/DrinkLikeEntity.kt
rename to app/src/main/java/com/ilein/thecocktailapp/data/db/DrinkEntity.kt
index d12c929..3a54eff 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/db/DrinkLikeEntity.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/db/DrinkEntity.kt
@@ -1,12 +1,12 @@
-package com.ilein.thecocktailapp.db
+package com.ilein.thecocktailapp.data.db
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.time.LocalDateTime
-@Entity(tableName = DrinkLikeDb.DRINK_LIKE_TABLE)
-data class DrinkLikeEntity (
+@Entity(tableName = DrinkFavoriteDb.DRINK_LIKE_TABLE)
+data class DrinkEntity (
@PrimaryKey
@ColumnInfo(name = COLUMN_ID)
val id: Int,
diff --git a/app/src/main/java/com/ilein/thecocktailapp/data/db/DrinkFavoriteDao.kt b/app/src/main/java/com/ilein/thecocktailapp/data/db/DrinkFavoriteDao.kt
new file mode 100644
index 0000000..68dd2cc
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/db/DrinkFavoriteDao.kt
@@ -0,0 +1,24 @@
+package com.ilein.thecocktailapp.data.db
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+
+@Dao
+abstract class DrinkFavoriteDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ abstract suspend fun insertDrink(drinkFavorite: DrinkEntity)
+
+ @Query("SELECT * FROM drink_like")
+ abstract suspend fun getDrinkFavorites(): List
+
+ @Query("SELECT * FROM drink_like order by update_date desc")
+ abstract suspend fun getDrinkFavoritesDesc(): List
+
+ @Query("SELECT * FROM drink_like WHERE id = :drinkId")
+ abstract suspend fun getDrink(drinkId: Int): DrinkEntity?
+
+ @Query("DELETE FROM drink_like WHERE id = :drinkId")
+ abstract suspend fun deleteDrink(drinkId: Int)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/db/DrinkLikeDb.kt b/app/src/main/java/com/ilein/thecocktailapp/data/db/DrinkFavoriteDb.kt
similarity index 66%
rename from app/src/main/java/com/ilein/thecocktailapp/db/DrinkLikeDb.kt
rename to app/src/main/java/com/ilein/thecocktailapp/data/db/DrinkFavoriteDb.kt
index 093fc97..9af2045 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/db/DrinkLikeDb.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/db/DrinkFavoriteDb.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.db
+package com.ilein.thecocktailapp.data.db
import android.content.Context
import androidx.room.Database
@@ -10,27 +10,27 @@ import androidx.room.TypeConverters
version = 1,
exportSchema = false,
entities = [
- DrinkLikeEntity::class
+ DrinkEntity::class
]
)
@TypeConverters(Converters::class)
-abstract class DrinkLikeDb: RoomDatabase() {
+abstract class DrinkFavoriteDb: RoomDatabase() {
companion object {
private const val databaseName = "drink_database"
const val DRINK_LIKE_TABLE = "drink_like"
- fun createDatabaseInstance(context: Context): DrinkLikeDb {
+ fun createDatabaseInstance(context: Context): DrinkFavoriteDb {
return Room
.databaseBuilder(
context,
- DrinkLikeDb::class.java,
+ DrinkFavoriteDb::class.java,
databaseName
)
.build()
}
}
- abstract fun drinkLikeDao(): DrinkLikeDao
+ abstract fun drinkFavoriteDao(): DrinkFavoriteDao
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/data/db/LocalDataSource.kt b/app/src/main/java/com/ilein/thecocktailapp/data/db/LocalDataSource.kt
new file mode 100644
index 0000000..6c4644c
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/db/LocalDataSource.kt
@@ -0,0 +1,9 @@
+package com.ilein.thecocktailapp.data.db
+
+interface LocalDataSource {
+ suspend fun insertDrinkFavorites(drinkLike: DrinkEntity)
+ suspend fun getDrinkFavorites(): List
+ suspend fun getDrinkFavoritesDesc(): List
+ suspend fun getDrink(drinkId: Int): DrinkEntity?
+ suspend fun deleteDrink(drinkId: Int)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/data/db/LocalDataSourceImpl.kt b/app/src/main/java/com/ilein/thecocktailapp/data/db/LocalDataSourceImpl.kt
new file mode 100644
index 0000000..1f4328f
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/db/LocalDataSourceImpl.kt
@@ -0,0 +1,25 @@
+package com.ilein.thecocktailapp.data.db
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+class LocalDataSourceImpl(
+ private val dao: DrinkFavoriteDao,
+ private val ioDispatcher: CoroutineDispatcher
+): LocalDataSource {
+ override suspend fun insertDrinkFavorites(drinkLike: DrinkEntity) =
+ withContext(ioDispatcher) { dao.insertDrink(drinkLike) }
+
+ override suspend fun getDrinkFavorites(): List =
+ withContext(ioDispatcher) { dao.getDrinkFavorites()}
+
+ override suspend fun getDrinkFavoritesDesc(): List =
+ withContext(ioDispatcher) { dao.getDrinkFavoritesDesc() }
+
+ override suspend fun getDrink(drinkId: Int): DrinkEntity? =
+ withContext(ioDispatcher) { dao.getDrink(drinkId) }
+
+ override suspend fun deleteDrink(drinkId: Int) =
+ withContext(ioDispatcher) { dao.deleteDrink(drinkId) }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/data/network/ClientProvider.kt b/app/src/main/java/com/ilein/thecocktailapp/data/network/ClientProvider.kt
new file mode 100644
index 0000000..44c4411
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/network/ClientProvider.kt
@@ -0,0 +1,57 @@
+package com.ilein.thecocktailapp.data.network
+
+import com.ilein.thecocktailapp.BuildConfig
+import com.ilein.thecocktailapp.data.network.model.DrinksModel
+import io.ktor.client.*
+import io.ktor.client.call.*
+import io.ktor.client.plugins.cache.*
+import io.ktor.client.plugins.contentnegotiation.*
+import io.ktor.client.plugins.logging.*
+import io.ktor.client.request.*
+import io.ktor.serialization.kotlinx.json.*
+import kotlinx.serialization.json.Json
+
+class ClientProvider {
+ private val baseUrl = BuildConfig.API_BASE_URL
+ private val drinks = "api/json/${BuildConfig.API_VERSION}/${BuildConfig.API_KEY}/search.php"
+ private val drink = "api/json/${BuildConfig.API_VERSION}/${BuildConfig.API_KEY}/lookup.php"
+
+
+ private val ktorClient = HttpClient() {
+ install(Logging) {
+ level = LogLevel.ALL
+ }
+
+ install(ContentNegotiation) {
+ json(Json{ ignoreUnknownKeys = true
+ prettyPrint = true
+ isLenient = true})
+ }
+ install(HttpCache)
+ }
+
+ suspend fun getDrink(drinkId: Int): DrinksModel = ktorClient.use {
+ it.get { host = baseUrl
+ url(port = 8080, path = buildPath(drink, mapOf(Pair("i", drinkId)))) }.body()
+ }
+
+ suspend fun searchDrinks(searchText: String): DrinksModel = ktorClient.use {
+ it.get { host = baseUrl
+ url(port = 8080, path = buildPath(drinks, mapOf(Pair("s", searchText)))) }.body()
+ }
+
+ private fun buildPath(
+ endpoint: String,
+ query: Map = emptyMap(),
+ ): String =
+ buildString {
+ append("$endpoint?")
+ append(
+ query.entries.joinToString(
+ separator = "&"
+ ) { (key, value) ->
+ "$key=$value"
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/network/DrinksReq.kt b/app/src/main/java/com/ilein/thecocktailapp/data/network/DrinksReq.kt
similarity index 67%
rename from app/src/main/java/com/ilein/thecocktailapp/network/DrinksReq.kt
rename to app/src/main/java/com/ilein/thecocktailapp/data/network/DrinksReq.kt
index 9566248..4762545 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/network/DrinksReq.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/network/DrinksReq.kt
@@ -1,6 +1,6 @@
-package com.ilein.thecocktailapp.network
+package com.ilein.thecocktailapp.data.network
-import com.ilein.thecocktailapp.network.model.Drinks
+import com.ilein.thecocktailapp.data.network.model.DrinksModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -10,13 +10,13 @@ import kotlinx.coroutines.flow.retryWhen
class DrinksReq {
- fun requestDrinks(query: String): Flow {
+ fun requestDrinks(query: String): Flow {
return requestDrinksDr(query)
.flowOn(Dispatchers.IO)
.retryWithDelay()
}
- fun requestDrink(drinkId: Int): Flow {
+ fun requestDrink(drinkId: Int): Flow {
return requestDrinkDr(drinkId)
.flowOn(Dispatchers.IO)
.retryWithDelay()
@@ -24,18 +24,18 @@ class DrinksReq {
/* additional */
- private fun requestDrinksDr(query: String): Flow {
+ private fun requestDrinksDr(query: String): Flow {
return flow {
emit(
- Api().getDrinks(query)
+ ClientProvider().searchDrinks(query)
)
}
}
- private fun requestDrinkDr(drinkId: Int): Flow {
+ private fun requestDrinkDr(drinkId: Int): Flow {
return flow {
emit(
- Api().getDrink(drinkId)
+ ClientProvider().getDrink(drinkId)
)
}
}
diff --git a/app/src/main/java/com/ilein/thecocktailapp/network/NetworkUtil.kt b/app/src/main/java/com/ilein/thecocktailapp/data/network/NetworkUtil.kt
similarity index 96%
rename from app/src/main/java/com/ilein/thecocktailapp/network/NetworkUtil.kt
rename to app/src/main/java/com/ilein/thecocktailapp/data/network/NetworkUtil.kt
index 81abb3a..edb6623 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/network/NetworkUtil.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/network/NetworkUtil.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.network
+package com.ilein.thecocktailapp.data.network
import android.content.Context
import android.net.ConnectivityManager
diff --git a/app/src/main/java/com/ilein/thecocktailapp/data/network/RemoteDataSource.kt b/app/src/main/java/com/ilein/thecocktailapp/data/network/RemoteDataSource.kt
new file mode 100644
index 0000000..e41b034
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/network/RemoteDataSource.kt
@@ -0,0 +1,10 @@
+package com.ilein.thecocktailapp.data.network
+
+import com.ilein.thecocktailapp.domain.Drink
+import kotlinx.coroutines.flow.Flow
+
+interface RemoteDataSource {
+ fun searchDrinks(query: String): Flow>
+ fun getDrink(drinkId: Int): Flow
+ suspend fun getOneDrink(drinkId: Int): Drink
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/data/network/RemoteDataSourceImpl.kt b/app/src/main/java/com/ilein/thecocktailapp/data/network/RemoteDataSourceImpl.kt
new file mode 100644
index 0000000..950935c
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/network/RemoteDataSourceImpl.kt
@@ -0,0 +1,50 @@
+package com.ilein.thecocktailapp.data.network
+
+import com.ilein.thecocktailapp.data.toDomain
+import com.ilein.thecocktailapp.domain.Drink
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.retryWhen
+
+class RemoteDataSourceImpl(
+ private val client: ClientProvider,
+ private val ioDispatcher: CoroutineDispatcher
+): RemoteDataSource {
+
+ override fun searchDrinks(query: String): Flow> {
+ return flow { emit(client.searchDrinks(query).results.map { t -> t.toDomain() }) }
+ .flowOn(ioDispatcher)
+ .retryWithDelay()
+ }
+
+ override fun getDrink(drinkId: Int): Flow {
+ return flow { emit(client.getDrink(drinkId).results.first().toDomain()) }
+ .flowOn(ioDispatcher)
+ .retryWithDelay()
+ }
+
+ override suspend fun getOneDrink(drinkId: Int): Drink {
+ return client.getDrink(drinkId).results.first().toDomain()
+ }
+
+ private fun Flow.retryWithDelay(
+ delay: Long = DEFAULT_DELAY,
+ filter: (Throwable) -> Boolean = { true }
+ ): Flow {
+ return retryWhen { cause, _ ->
+ if (filter(cause)) {
+ delay(delay)
+ true
+ } else {
+ throw cause
+ }
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_DELAY = 2000L
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/network/model/Drink.kt b/app/src/main/java/com/ilein/thecocktailapp/data/network/model/DrinkModel.kt
similarity index 66%
rename from app/src/main/java/com/ilein/thecocktailapp/network/model/Drink.kt
rename to app/src/main/java/com/ilein/thecocktailapp/data/network/model/DrinkModel.kt
index 1ee703c..70ba909 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/network/model/Drink.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/network/model/DrinkModel.kt
@@ -1,10 +1,10 @@
-package com.ilein.thecocktailapp.network.model
+package com.ilein.thecocktailapp.data.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
-data class Drink (
+data class DrinkModel (
@SerialName("idDrink")
val idDrink: Int,
@SerialName("strDrink")
@@ -56,26 +56,20 @@ data class Drink (
@SerialName("strMeasure10")
val strMeasure10: String? = null,
) {
- fun getIngredientsText(): String {
- val ingredientList = listOf(Pair(strIngredient1, strMeasure1),
- Pair(strIngredient2, strMeasure2),
- Pair(strIngredient3, strMeasure3),
- Pair(strIngredient4, strMeasure4),
- Pair(strIngredient5, strMeasure5),
- Pair(strIngredient6, strMeasure6),
- Pair(strIngredient7, strMeasure7),
- Pair(strIngredient8, strMeasure8),
- Pair(strIngredient9, strMeasure9),
- Pair(strIngredient10, strMeasure10)
- )
-
- var i = 0
- val text: String = ingredientList.joinToString(separator = "") { itPair ->
- itPair.first?.let {
- i++
- return@let "${i}. $it ${itPair.second?.let {"- $it"} ?: ""}\n"
- } ?: ""
+ fun getIngredientsText(): String = listOf(
+ Pair(strIngredient1, strMeasure1),
+ Pair(strIngredient2, strMeasure2),
+ Pair(strIngredient3, strMeasure3),
+ Pair(strIngredient4, strMeasure4),
+ Pair(strIngredient5, strMeasure5),
+ Pair(strIngredient6, strMeasure6),
+ Pair(strIngredient7, strMeasure7),
+ Pair(strIngredient8, strMeasure8),
+ Pair(strIngredient9, strMeasure9),
+ Pair(strIngredient10, strMeasure10))
+ .filter { it.first.orEmpty().isNotEmpty() }
+ .mapIndexed { index, pair ->
+ "${index+1}. ${pair.first} ${pair.second?.let {"- $it"} ?: ""}"
}
- return text
- }
+ .joinToString(separator = "\n")
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/data/network/model/DrinksModel.kt b/app/src/main/java/com/ilein/thecocktailapp/data/network/model/DrinksModel.kt
new file mode 100644
index 0000000..bd82c33
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/data/network/model/DrinksModel.kt
@@ -0,0 +1,22 @@
+package com.ilein.thecocktailapp.data.network.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.builtins.ListSerializer
+import kotlinx.serialization.json.JsonArray
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonTransformingSerializer
+
+@Serializable
+data class DrinksModel(
+ @SerialName("drinks")
+ @Serializable(with = DrinkListSerializer::class)
+ val results: List = ArrayList()
+) {
+
+ object DrinkListSerializer : JsonTransformingSerializer>(ListSerializer(DrinkModel.serializer())) {
+ // If response is not an array, then it is an empty array
+ override fun transformDeserialize(element: JsonElement): JsonElement =
+ if (element !is JsonArray) JsonArray(listOf()) else element
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/db/DrinkLikeDao.kt b/app/src/main/java/com/ilein/thecocktailapp/db/DrinkLikeDao.kt
deleted file mode 100644
index 3a5990c..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/db/DrinkLikeDao.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.ilein.thecocktailapp.db
-
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
-import io.reactivex.rxjava3.core.Completable
-import io.reactivex.rxjava3.core.Maybe
-import io.reactivex.rxjava3.core.Observable
-
-@Dao
-abstract class DrinkLikeDao {
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- abstract fun insertDrinkLike(drinkLike: DrinkLikeEntity): Completable
-
- @Query("SELECT * FROM drink_like")
- abstract fun observeDrinkLike(): Observable>
-
- @Query("SELECT * FROM drink_like order by update_date desc")
- abstract fun observeDrinkLikeDesc(): Observable>
-
- @Query("SELECT * FROM drink_like WHERE id = :drinkId")
- abstract fun getDrinkLike(drinkId: Int): Maybe
-
- @Query("DELETE FROM drink_like WHERE id = :drinkId")
- abstract fun deleteDrinkLike(drinkId: Int): Completable
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/di/Module.kt b/app/src/main/java/com/ilein/thecocktailapp/di/Module.kt
new file mode 100644
index 0000000..cb5c19f
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/di/Module.kt
@@ -0,0 +1,76 @@
+package com.ilein.thecocktailapp.di
+
+import com.ilein.thecocktailapp.data.DrinkRepositoryImpl
+import com.ilein.thecocktailapp.data.db.DrinkFavoriteDao
+import com.ilein.thecocktailapp.data.db.DrinkFavoriteDb
+import com.ilein.thecocktailapp.data.db.LocalDataSource
+import com.ilein.thecocktailapp.data.db.LocalDataSourceImpl
+import com.ilein.thecocktailapp.data.network.ClientProvider
+import com.ilein.thecocktailapp.data.network.DrinksReq
+import com.ilein.thecocktailapp.data.network.NetworkUtil
+import com.ilein.thecocktailapp.data.network.RemoteDataSource
+import com.ilein.thecocktailapp.data.network.RemoteDataSourceImpl
+import com.ilein.thecocktailapp.domain.DrinkRepository
+import com.ilein.thecocktailapp.domain.usecase.DeleteDrinkUseCase
+import com.ilein.thecocktailapp.domain.usecase.GetDrinkFavoriteUseCase
+import com.ilein.thecocktailapp.domain.usecase.GetDrinkFavoritesUseCase
+import com.ilein.thecocktailapp.domain.usecase.GetDrinkUseCase
+import com.ilein.thecocktailapp.domain.usecase.GetOneDrinkUseCase
+import com.ilein.thecocktailapp.domain.usecase.InsertDrinkUseCase
+import com.ilein.thecocktailapp.domain.usecase.SearchDrinkUseCase
+import com.ilein.thecocktailapp.presentation.ui.detail.DrinkItemDetailViewModel
+import com.ilein.thecocktailapp.presentation.ui.detail.DrinkLikeItemDetailViewModel
+import com.ilein.thecocktailapp.presentation.ui.liked.LikedDrinksViewModel
+import com.ilein.thecocktailapp.presentation.ui.search.SearchDrinksViewModel
+import kotlinx.coroutines.Dispatchers
+import org.koin.android.ext.koin.androidContext
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.dsl.module
+
+val databaseModule = module {
+ single { DrinkFavoriteDb.createDatabaseInstance(context = androidContext()) }
+
+ single { get().drinkFavoriteDao() }
+ single { LocalDataSourceImpl(get(), Dispatchers.IO) }
+
+ single { ClientProvider() }
+ single { RemoteDataSourceImpl(get(), Dispatchers.IO) }
+
+ single { DrinkRepositoryImpl(get() as LocalDataSource, get() as RemoteDataSource) }
+}
+
+val useCaseModule = module {
+ single { InsertDrinkUseCase(get() as DrinkRepository, Dispatchers.IO) }
+ single { DeleteDrinkUseCase(get() as DrinkRepository, Dispatchers.IO) }
+ single { SearchDrinkUseCase(get() as DrinkRepository, Dispatchers.IO) }
+ single { GetDrinkFavoritesUseCase(get() as DrinkRepository, Dispatchers.IO) }
+ single { GetDrinkFavoriteUseCase(get() as DrinkRepository, Dispatchers.IO) }
+ single { GetDrinkUseCase(get() as DrinkRepository, Dispatchers.IO) }
+ single { GetOneDrinkUseCase(get() as DrinkRepository, Dispatchers.IO) }
+
+}
+
+val networkModule = module {
+ single { DrinksReq() }
+
+ single { NetworkUtil(context = androidContext()) }
+
+ single { get().isOnline() }
+}
+
+val vmModule = module {
+ viewModel {
+ SearchDrinksViewModel(get() as SearchDrinkUseCase, get() as GetDrinkFavoritesUseCase,
+ get() as InsertDrinkUseCase, get() as DeleteDrinkUseCase, get() as NetworkUtil)
+ }
+ viewModel {
+ LikedDrinksViewModel(get() as DeleteDrinkUseCase, get() as GetDrinkFavoritesUseCase)
+ }
+ viewModel {
+ DrinkItemDetailViewModel(get() as DrinkFavoriteDao, get() as DrinksReq, get() as NetworkUtil)
+ }
+ viewModel {
+ DrinkLikeItemDetailViewModel(get() as InsertDrinkUseCase, get() as GetDrinkFavoriteUseCase,
+ get() as GetDrinkUseCase, get() as GetOneDrinkUseCase, get() as NetworkUtil)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/Drink.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/Drink.kt
new file mode 100644
index 0000000..4ead256
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/Drink.kt
@@ -0,0 +1,9 @@
+package com.ilein.thecocktailapp.domain
+
+data class Drink (
+ val id: Int,
+ val name: String?,
+ val image: String?,
+ val instruction: String?,
+ val ingredients: String?
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/DrinkRepository.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/DrinkRepository.kt
new file mode 100644
index 0000000..a39f617
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/DrinkRepository.kt
@@ -0,0 +1,15 @@
+package com.ilein.thecocktailapp.domain
+
+import kotlinx.coroutines.flow.Flow
+
+
+interface DrinkRepository {
+ suspend fun insertDrinkFavorites(drink: FavoriteDrink)
+ suspend fun getDrinkFavorites(): List
+ suspend fun getDrinkFavoritesDesc(): List
+ suspend fun getFavoriteDrink(drinkId: Int): FavoriteDrink?
+ suspend fun deleteFavoriteDrink(drinkId: Int)
+ suspend fun getDrink(drinkId: Int): Flow
+ suspend fun getOneDrink(drinkId: Int): Drink
+ suspend fun searchDrinks(query: String): Flow>
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/FavoriteDrink.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/FavoriteDrink.kt
new file mode 100644
index 0000000..a9fa996
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/FavoriteDrink.kt
@@ -0,0 +1,12 @@
+package com.ilein.thecocktailapp.domain
+
+
+import java.time.LocalDateTime
+
+data class FavoriteDrink(
+ val id: Int,
+ val name: String?,
+ val image: String?,
+ val createDate: LocalDateTime?,
+ val note: String?,
+)
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/base/FlowUseCaseBase.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/base/FlowUseCaseBase.kt
new file mode 100644
index 0000000..1169a21
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/base/FlowUseCaseBase.kt
@@ -0,0 +1,15 @@
+package com.ilein.thecocktailapp.domain.base
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.flowOn
+
+abstract class FlowUseCaseBase(private val dispatcher: CoroutineDispatcher) {
+
+ operator fun invoke(arg: P): Flow> = execute(arg)
+ .catch { e -> emit(Result.Error(e)) }
+ .flowOn(dispatcher)
+
+ protected abstract fun execute(arg: P): Flow>
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/base/Result.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/base/Result.kt
new file mode 100644
index 0000000..70648bd
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/base/Result.kt
@@ -0,0 +1,12 @@
+package com.ilein.thecocktailapp.domain.base
+
+sealed class Result {
+ data class Success(val data: T) : Result()
+ data class Error(val throwable: Throwable) : Result()
+}
+
+val Result.data: T?
+ get() = (this as? Result.Success)?.data
+
+val Result.error: Throwable?
+ get() = (this as? Result.Error)?.throwable
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/base/SuspendUseCaseBase.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/base/SuspendUseCaseBase.kt
new file mode 100644
index 0000000..30deb21
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/base/SuspendUseCaseBase.kt
@@ -0,0 +1,22 @@
+package com.ilein.thecocktailapp.domain.base
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+abstract class SuspendUseCaseBase(private val dispatcher: CoroutineDispatcher) {
+
+ /** Выполняет UseCase асинхронно и возвращает [Result] с типом [R] */
+ suspend operator fun invoke(arg: P): Result {
+ return try {
+ withContext(dispatcher) {
+ execute(arg).let { Result.Success(it) }
+ }
+ } catch (throwable: Throwable) {
+ Result.Error(throwable)
+ }
+ }
+
+ /** Переопределить этод метод в наследнике для выполнения UseСase - а */
+ @Throws(RuntimeException::class)
+ protected abstract suspend fun execute(arg: P): R
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/DeleteDrinkUseCase.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/DeleteDrinkUseCase.kt
new file mode 100644
index 0000000..0620a50
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/DeleteDrinkUseCase.kt
@@ -0,0 +1,14 @@
+package com.ilein.thecocktailapp.domain.usecase
+
+import com.ilein.thecocktailapp.domain.DrinkRepository
+import com.ilein.thecocktailapp.domain.base.SuspendUseCaseBase
+import kotlinx.coroutines.CoroutineDispatcher
+
+class DeleteDrinkUseCase(
+ private val repository: DrinkRepository,
+ dispatcher: CoroutineDispatcher
+): SuspendUseCaseBase(dispatcher) {
+ override suspend fun execute(arg: Int) {
+ repository.deleteFavoriteDrink(arg)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetDrinkFavoriteUseCase.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetDrinkFavoriteUseCase.kt
new file mode 100644
index 0000000..b6eb0e5
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetDrinkFavoriteUseCase.kt
@@ -0,0 +1,15 @@
+package com.ilein.thecocktailapp.domain.usecase
+
+import com.ilein.thecocktailapp.domain.FavoriteDrink
+import com.ilein.thecocktailapp.domain.DrinkRepository
+import com.ilein.thecocktailapp.domain.base.SuspendUseCaseBase
+import kotlinx.coroutines.CoroutineDispatcher
+
+class GetDrinkFavoriteUseCase(
+ private val repository: DrinkRepository,
+ dispatcher: CoroutineDispatcher
+): SuspendUseCaseBase(dispatcher) {
+ override suspend fun execute(arg: Int): FavoriteDrink? {
+ return repository.getFavoriteDrink(arg)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetDrinkFavoritesUseCase.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetDrinkFavoritesUseCase.kt
new file mode 100644
index 0000000..8ccb7c7
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetDrinkFavoritesUseCase.kt
@@ -0,0 +1,15 @@
+package com.ilein.thecocktailapp.domain.usecase
+
+import com.ilein.thecocktailapp.domain.FavoriteDrink
+import com.ilein.thecocktailapp.domain.DrinkRepository
+import com.ilein.thecocktailapp.domain.base.SuspendUseCaseBase
+import kotlinx.coroutines.CoroutineDispatcher
+
+class GetDrinkFavoritesUseCase(
+ private val repository: DrinkRepository,
+ dispatcher: CoroutineDispatcher
+): SuspendUseCaseBase>(dispatcher) {
+ override suspend fun execute(arg: String): List {
+ return repository.getDrinkFavorites()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetDrinkUseCase.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetDrinkUseCase.kt
new file mode 100644
index 0000000..b2ab3fd
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetDrinkUseCase.kt
@@ -0,0 +1,16 @@
+package com.ilein.thecocktailapp.domain.usecase
+
+import com.ilein.thecocktailapp.domain.Drink
+import com.ilein.thecocktailapp.domain.DrinkRepository
+import com.ilein.thecocktailapp.domain.base.SuspendUseCaseBase
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+
+class GetDrinkUseCase (
+ private val repository: DrinkRepository,
+ dispatcher: CoroutineDispatcher
+): SuspendUseCaseBase>(dispatcher) {
+ override suspend fun execute(arg: Int): Flow {
+ return repository.getDrink(arg)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetOneDrinkUseCase.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetOneDrinkUseCase.kt
new file mode 100644
index 0000000..933f5b1
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/GetOneDrinkUseCase.kt
@@ -0,0 +1,15 @@
+package com.ilein.thecocktailapp.domain.usecase
+
+import com.ilein.thecocktailapp.domain.Drink
+import com.ilein.thecocktailapp.domain.DrinkRepository
+import com.ilein.thecocktailapp.domain.base.SuspendUseCaseBase
+import kotlinx.coroutines.CoroutineDispatcher
+
+class GetOneDrinkUseCase(
+ private val repository: DrinkRepository,
+ dispatcher: CoroutineDispatcher
+): SuspendUseCaseBase(dispatcher) {
+ override suspend fun execute(arg: Int): Drink? {
+ return repository.getOneDrink(arg)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/InsertDrinkUseCase.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/InsertDrinkUseCase.kt
new file mode 100644
index 0000000..e504852
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/InsertDrinkUseCase.kt
@@ -0,0 +1,15 @@
+package com.ilein.thecocktailapp.domain.usecase
+
+import com.ilein.thecocktailapp.domain.FavoriteDrink
+import com.ilein.thecocktailapp.domain.DrinkRepository
+import com.ilein.thecocktailapp.domain.base.SuspendUseCaseBase
+import kotlinx.coroutines.CoroutineDispatcher
+
+class InsertDrinkUseCase(
+ private val repository: DrinkRepository,
+ dispatcher: CoroutineDispatcher
+): SuspendUseCaseBase(dispatcher) {
+ override suspend fun execute(arg: FavoriteDrink) {
+ repository.insertDrinkFavorites(arg)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/SearchDrinkUseCase.kt b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/SearchDrinkUseCase.kt
new file mode 100644
index 0000000..1a18393
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/domain/usecase/SearchDrinkUseCase.kt
@@ -0,0 +1,16 @@
+package com.ilein.thecocktailapp.domain.usecase
+
+import com.ilein.thecocktailapp.domain.Drink
+import com.ilein.thecocktailapp.domain.DrinkRepository
+import com.ilein.thecocktailapp.domain.base.SuspendUseCaseBase
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+
+class SearchDrinkUseCase (
+ private val repository: DrinkRepository,
+ dispatcher: CoroutineDispatcher
+): SuspendUseCaseBase>>(dispatcher) {
+ override suspend fun execute(arg: String): Flow> {
+ return repository.searchDrinks(arg)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/koin/Module.kt b/app/src/main/java/com/ilein/thecocktailapp/koin/Module.kt
deleted file mode 100644
index 8ad19dc..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/koin/Module.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.ilein.thecocktailapp.koin
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import com.ilein.thecocktailapp.db.DrinkLikeDao
-import com.ilein.thecocktailapp.db.DrinkLikeDb
-import com.ilein.thecocktailapp.network.DrinksReq
-import com.ilein.thecocktailapp.network.NetworkUtil
-import com.ilein.thecocktailapp.ui.detail.DrinkItemDetailViewModel
-import com.ilein.thecocktailapp.ui.detail.DrinkLikeItemDetailViewModel
-import com.ilein.thecocktailapp.ui.liked.LikedDrinksViewModel
-import com.ilein.thecocktailapp.ui.search.SearchDrinksViewModel
-import org.koin.android.ext.koin.androidContext
-import org.koin.androidx.viewmodel.dsl.viewModel
-import org.koin.dsl.module
-
-val databaseModule = module {
- single { DrinkLikeDb.createDatabaseInstance(context = androidContext()) }
-
- single { get().drinkLikeDao() }
-}
-
-@RequiresApi(Build.VERSION_CODES.M)
-val networkModule = module {
- single { DrinksReq() }
-
- single { NetworkUtil(context = androidContext()) }
-
- single { get().isOnline() }
-}
-
-val vmModule = module {
- viewModel {
- SearchDrinksViewModel(get() as DrinkLikeDao, get() as DrinksReq, get() as NetworkUtil)
- }
- viewModel {
- LikedDrinksViewModel(get() as DrinkLikeDao)
- }
- viewModel {
- DrinkItemDetailViewModel(get() as DrinkLikeDao, get() as DrinksReq, get() as NetworkUtil)
- }
- viewModel {
- DrinkLikeItemDetailViewModel(get() as DrinkLikeDao, get() as DrinksReq, get() as NetworkUtil)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/network/Api.kt b/app/src/main/java/com/ilein/thecocktailapp/network/Api.kt
deleted file mode 100644
index 95e9723..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/network/Api.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.ilein.thecocktailapp.network
-
-import com.ilein.thecocktailapp.BuildConfig
-import com.ilein.thecocktailapp.network.model.Drinks
-import io.ktor.client.*
-import io.ktor.client.features.json.*
-import io.ktor.client.features.json.serializer.*
-import io.ktor.client.features.logging.*
-import io.ktor.client.request.*
-
-class Api {
- private val baseUrl = BuildConfig.API_BASE_URL
- private val drinks = "api/json/${BuildConfig.API_VERSION}/${BuildConfig.API_KEY}/search.php"
- private val drink = "api/json/${BuildConfig.API_VERSION}/${BuildConfig.API_KEY}/lookup.php"
-
-
- private val ktorClient = HttpClient() {
- install(Logging) {
- level = LogLevel.BODY
- }
-
- install(JsonFeature) {
- serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
- prettyPrint = true
- isLenient = true
- ignoreUnknownKeys = true
- })
- }
- }
-
- suspend fun getDrink(drinkId: Int): Drinks = ktorClient.use {
- it.get(
- host = baseUrl,
- path = buildPath(drink, mapOf(Pair("i", drinkId)))
- )
- }
-
- suspend fun getDrinks(searchText: String): Drinks = ktorClient.use {
- it.get(
- host = baseUrl,
- path = buildPath(drinks, mapOf(Pair("s", searchText)))
- )
- }
-
- private fun buildPath(
- endpoint: String,
- query: Map = emptyMap(),
- ): String =
- buildString {
- append("$endpoint?")
- append(
- query.entries.joinToString(
- separator = "&"
- ) { (key, value) ->
- "$key=$value"
- }
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/network/model/Drinks.kt b/app/src/main/java/com/ilein/thecocktailapp/network/model/Drinks.kt
deleted file mode 100644
index 7f38e5f..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/network/model/Drinks.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.ilein.thecocktailapp.network.model
-
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.builtins.ListSerializer
-import kotlinx.serialization.json.JsonArray
-import kotlinx.serialization.json.JsonElement
-import kotlinx.serialization.json.JsonTransformingSerializer
-
-@Serializable
-data class Drinks(
- @SerialName("drinks")
- @Serializable(with = DrinkListSerializer::class)
- val results: List = ArrayList()
-)
-
-object DrinkListSerializer : JsonTransformingSerializer>(ListSerializer(Drink.serializer())) {
- // If response is not an array, then it is an empty array
- override fun transformDeserialize(element: JsonElement): JsonElement =
- if (element !is JsonArray) JsonArray(listOf()) else element
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/MainActivity.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/MainActivity.kt
similarity index 82%
rename from app/src/main/java/com/ilein/thecocktailapp/MainActivity.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/MainActivity.kt
index 84f11f4..e729c6a 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/MainActivity.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/MainActivity.kt
@@ -1,8 +1,9 @@
-package com.ilein.thecocktailapp
+package com.ilein.thecocktailapp.presentation
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.Window
+import com.ilein.thecocktailapp.R
class MainActivity : AppCompatActivity() {
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkItemDetailFragment.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkItemDetailFragment.kt
similarity index 89%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkItemDetailFragment.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkItemDetailFragment.kt
index 69f79e7..7a259f1 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkItemDetailFragment.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkItemDetailFragment.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.ui.detail
+package com.ilein.thecocktailapp.presentation.ui.detail
import android.content.Intent
import android.os.Bundle
@@ -13,7 +13,7 @@ import coil.transform.RoundedCornersTransformation
import com.google.android.material.button.MaterialButton
import com.ilein.thecocktailapp.R
import com.ilein.thecocktailapp.databinding.FragmentDrinkItemDetailBinding
-import com.ilein.thecocktailapp.ui.state.DrinkItemState
+import com.ilein.thecocktailapp.presentation.ui.state.DrinkItemState
import org.koin.androidx.viewmodel.ext.android.viewModel
/**
@@ -89,13 +89,13 @@ class DrinkItemDetailFragment : Fragment() {
if (state.isOnline) {
state.item?.let {
- binding.poster.contentDescription = it.drink.strDrinkThumb
- binding.poster.load("${it.drink.strDrinkThumb}") {
+ binding.poster.contentDescription = it.drink.image
+ binding.poster.load("${it.drink.image}") {
transformations(RoundedCornersTransformation(16f))
}
- binding.drinkTitle.text = it.drink.strDrink
- binding.instructions.text = it.drink.strInstructions
- binding.ingredients.text = it.drink.getIngredientsText()
+ binding.drinkTitle.text = it.drink.name
+ binding.instructions.text = it.drink.instruction
+ binding.ingredients.text = it.drink.ingredients
}
} else {
val toast = Toast.makeText(context, "No Internet Connection!!!", Toast.LENGTH_LONG)
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkItemDetailViewModel.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkItemDetailViewModel.kt
similarity index 64%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkItemDetailViewModel.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkItemDetailViewModel.kt
index fff033c..f4be765 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkItemDetailViewModel.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkItemDetailViewModel.kt
@@ -1,18 +1,19 @@
-package com.ilein.thecocktailapp.ui.detail
+package com.ilein.thecocktailapp.presentation.ui.detail
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.ilein.thecocktailapp.db.DrinkLikeDao
-import com.ilein.thecocktailapp.network.DrinksReq
-import com.ilein.thecocktailapp.network.NetworkUtil
-import com.ilein.thecocktailapp.ui.state.DrinkData
-import com.ilein.thecocktailapp.ui.state.DrinkItemState
+import com.ilein.thecocktailapp.data.db.DrinkFavoriteDao
+import com.ilein.thecocktailapp.data.network.DrinksReq
+import com.ilein.thecocktailapp.data.network.NetworkUtil
+import com.ilein.thecocktailapp.data.toDomain
+import com.ilein.thecocktailapp.presentation.ui.state.DrinkData
+import com.ilein.thecocktailapp.presentation.ui.state.DrinkItemState
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
-class DrinkItemDetailViewModel(private val drinkLikeDao: DrinkLikeDao,
+class DrinkItemDetailViewModel(private val drinkFavoriteDao: DrinkFavoriteDao,
private val drinksReq: DrinksReq,
private val networkUtils: NetworkUtil
): ViewModel() {
@@ -24,8 +25,8 @@ class DrinkItemDetailViewModel(private val drinkLikeDao: DrinkLikeDao,
.onStart { state.postValue(DrinkItemState(null, true, isOnline = true)) }
.onEach { drinks ->
run {
- val drink = drinks.results[0]
- state.postValue(DrinkItemState(DrinkData(drink, isLike), false, isOnline = true))
+ val drink = drinks.results.first()
+ state.postValue(DrinkItemState(DrinkData(drink.toDomain(), isLike), false, isOnline = true))
}
}
.launchIn(viewModelScope)
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkLikeItemDetailFragment.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkLikeItemDetailFragment.kt
similarity index 91%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkLikeItemDetailFragment.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkLikeItemDetailFragment.kt
index 21858e7..d34139a 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkLikeItemDetailFragment.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkLikeItemDetailFragment.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.ui.detail
+package com.ilein.thecocktailapp.presentation.ui.detail
import android.content.Intent
import android.os.Bundle
@@ -14,7 +14,7 @@ import coil.transform.RoundedCornersTransformation
import com.google.android.material.button.MaterialButton
import com.ilein.thecocktailapp.R
import com.ilein.thecocktailapp.databinding.FragmentDrinkLikeItemDetailBinding
-import com.ilein.thecocktailapp.ui.state.DrinkItemLikeState
+import com.ilein.thecocktailapp.presentation.ui.state.DrinkItemLikeState
import org.koin.androidx.viewmodel.ext.android.viewModel
@@ -90,13 +90,13 @@ class DrinkLikeItemDetailFragment : Fragment() {
if (state.isOnline) {
state.drink?.let {
- binding.posterLike.contentDescription = state.drink.strDrinkThumb
- binding.posterLike.load("${state.drink.strDrinkThumb}") {
+ binding.posterLike.contentDescription = state.drink.image
+ binding.posterLike.load("${state.drink.image}") {
transformations(RoundedCornersTransformation(16f))
}
- binding.drinkTitleLike.text = state.drink.strDrink
- binding.instructionsLike.text = state.drink.strInstructions
- binding.ingredientsLike.text = state.drink.getIngredientsText()
+ binding.drinkTitleLike.text = state.drink.name
+ binding.instructionsLike.text = state.drink.instruction
+ binding.ingredientsLike.text = state.drink.ingredients
}
} else {
val toast = Toast.makeText(context, "No Internet Connection", Toast.LENGTH_LONG)
diff --git a/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkLikeItemDetailViewModel.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkLikeItemDetailViewModel.kt
new file mode 100644
index 0000000..37c1dc5
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/detail/DrinkLikeItemDetailViewModel.kt
@@ -0,0 +1,73 @@
+package com.ilein.thecocktailapp.presentation.ui.detail
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.ilein.thecocktailapp.data.network.NetworkUtil
+import com.ilein.thecocktailapp.domain.base.Result
+import com.ilein.thecocktailapp.domain.usecase.GetDrinkFavoriteUseCase
+import com.ilein.thecocktailapp.domain.usecase.GetDrinkUseCase
+import com.ilein.thecocktailapp.domain.usecase.GetOneDrinkUseCase
+import com.ilein.thecocktailapp.domain.usecase.InsertDrinkUseCase
+import com.ilein.thecocktailapp.presentation.ui.state.DrinkItemLikeState
+import kotlinx.coroutines.launch
+
+class DrinkLikeItemDetailViewModel(private val insertDrinkUseCase: InsertDrinkUseCase,
+ private val getDrinkFavoriteUseCase: GetDrinkFavoriteUseCase,
+ private val getDrinkUseCase: GetDrinkUseCase,
+ private val getOneDrinkUseCase: GetOneDrinkUseCase,
+ private val networkUtil: NetworkUtil
+): ViewModel() {
+ val state = MutableLiveData()
+
+ fun init(drinkId: Int) {
+ if (networkUtil.isOnline()) {
+ viewModelScope.launch {
+ when(val result = getDrinkFavoriteUseCase(drinkId)) {
+ is Result.Success -> {
+ when(val result2 = getOneDrinkUseCase(drinkId)) {
+ is Result.Success -> {
+ state.postValue(DrinkItemLikeState(result2.data, result.data, false))
+ }
+ is Result.Error -> {
+ state.postValue(DrinkItemLikeState(null, null, false, isOnline = true))
+ }
+ }
+ }
+ is Result.Error -> {
+ state.postValue(DrinkItemLikeState(null, null, false, isOnline = true))
+ }
+ }
+ }
+ } else {
+ state.postValue(DrinkItemLikeState(null, null, false, isOnline = false))
+ }
+ }
+
+ fun onRefresh(drinkId: Int) {
+ init(drinkId)
+ }
+
+ fun onNoteChange(note: String) {
+ if (state.value!!.drinkLike?.note != note) {
+ state.postValue(state.value!!.copy(
+ drinkLike = state.value!!.drinkLike?.copy(note = note)
+ ))
+ }
+ }
+
+ fun onSaveClick() {
+ state.value?.drinkLike?.let {
+ viewModelScope.launch {
+ when (val result = insertDrinkUseCase(it)) {
+ is Result.Success -> {
+
+ }
+ is Result.Error -> {
+ result.throwable
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/liked/LikedDrinkItemAdapter.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/liked/LikedDrinkItemAdapter.kt
similarity index 80%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/liked/LikedDrinkItemAdapter.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/liked/LikedDrinkItemAdapter.kt
index 1a21342..43c05e0 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/liked/LikedDrinkItemAdapter.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/liked/LikedDrinkItemAdapter.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.ui.liked
+package com.ilein.thecocktailapp.presentation.ui.liked
import android.annotation.SuppressLint
import android.os.Build
@@ -14,17 +14,17 @@ import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.RoundedCornersTransformation
import com.ilein.thecocktailapp.R
-import com.ilein.thecocktailapp.db.DrinkLikeEntity
-import com.ilein.thecocktailapp.ui.utils.DiffLikeCallback
+import com.ilein.thecocktailapp.domain.FavoriteDrink
+import com.ilein.thecocktailapp.presentation.ui.utils.DiffLikeCallback
import java.time.format.DateTimeFormatter
-internal class LikedDrinkItemAdapter (private val onItemClick: (DrinkLikeEntity) -> Unit,
- private val onDelClick: (DrinkLikeEntity) -> Unit) :
+internal class LikedDrinkItemAdapter (private val onItemClick: (FavoriteDrink) -> Unit,
+ private val onDelClick: (FavoriteDrink) -> Unit) :
RecyclerView.Adapter() {
- private val items: MutableList = mutableListOf()
+ private val items: MutableList = mutableListOf()
- fun setItems(newItems: List) {
+ fun setItems(newItems: List) {
DiffUtil.calculateDiff(DiffLikeCallback(items, newItems)).also { result ->
items.clear()
items.addAll(newItems)
@@ -41,9 +41,9 @@ internal class LikedDrinkItemAdapter (private val onItemClick: (DrinkLikeEntity)
private val image: ImageView = view.findViewById(R.id.imageLiked)
private val updateDate: TextView = view.findViewById(R.id.tvUpdateDate)
private val del: ImageButton = view.findViewById(R.id.ibDelete)
+
@SuppressLint("SetTextI18n")
- @RequiresApi(Build.VERSION_CODES.O)
- fun onBind(item: DrinkLikeEntity, onItemClick: (DrinkLikeEntity) -> Unit, onDelClick: (DrinkLikeEntity) -> Unit) {
+ fun onBind(item: FavoriteDrink, onItemClick: (FavoriteDrink) -> Unit, onDelClick: (FavoriteDrink) -> Unit) {
itemView.setOnClickListener { onItemClick(item) }
del.setOnClickListener {
onDelClick(item)
@@ -51,7 +51,6 @@ internal class LikedDrinkItemAdapter (private val onItemClick: (DrinkLikeEntity)
title.text = item.name
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
updateDate.text = "Added: ${item.createDate?.format(formatter) ?: ""}"
- //like.isChecked = item.like ?: false
image.load("${item.image}/preview") {
transformations(RoundedCornersTransformation(16f))
}
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/liked/LikedDrinksFragment.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/liked/LikedDrinksFragment.kt
similarity index 96%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/liked/LikedDrinksFragment.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/liked/LikedDrinksFragment.kt
index fc3008b..8f7b963 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/liked/LikedDrinksFragment.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/liked/LikedDrinksFragment.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.ui.liked
+package com.ilein.thecocktailapp.presentation.ui.liked
import android.os.Bundle
import androidx.fragment.app.Fragment
diff --git a/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/liked/LikedDrinksViewModel.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/liked/LikedDrinksViewModel.kt
new file mode 100644
index 0000000..cc745de
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/liked/LikedDrinksViewModel.kt
@@ -0,0 +1,57 @@
+package com.ilein.thecocktailapp.presentation.ui.liked
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.ilein.thecocktailapp.domain.FavoriteDrink
+import com.ilein.thecocktailapp.domain.base.Result
+import com.ilein.thecocktailapp.domain.usecase.DeleteDrinkUseCase
+import com.ilein.thecocktailapp.domain.usecase.GetDrinkFavoritesUseCase
+import com.ilein.thecocktailapp.presentation.ui.state.DrinksLikeState
+import kotlinx.coroutines.launch
+
+class LikedDrinksViewModel(private val deleteDrinkUseCase: DeleteDrinkUseCase,
+ private val getDrinkFavoritesUseCase: GetDrinkFavoritesUseCase) : ViewModel() {
+
+ private val _liveData: MutableLiveData = MutableLiveData(DrinksLikeState())
+
+ val state: LiveData = _liveData
+
+ init {
+ initData()
+ }
+
+ private fun initData() {
+ viewModelScope.launch {
+ when(val result = getDrinkFavoritesUseCase.invoke("")){
+ is Result.Success -> {
+ _liveData.postValue(DrinksLikeState(
+ items = result.data,
+ loading = false
+ ))
+ }
+ is Result.Error -> {
+ result.throwable
+ }
+ }
+ }
+ }
+
+ /**
+ * ACTIONS
+ */
+
+ fun onDelClick(drink: FavoriteDrink) {
+ viewModelScope.launch {
+ when(val result = deleteDrinkUseCase(drink.id)) {
+ is Result.Success -> {
+ initData()
+ }
+ is Result.Error -> {
+ result.throwable
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/main/MainFragment.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/main/MainFragment.kt
similarity index 95%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/main/MainFragment.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/main/MainFragment.kt
index 468eb90..e6da89f 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/main/MainFragment.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/main/MainFragment.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.ui.main
+package com.ilein.thecocktailapp.presentation.ui.main
import android.os.Bundle
import androidx.fragment.app.Fragment
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/main/MainViewModel.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/main/MainViewModel.kt
similarity index 67%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/main/MainViewModel.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/main/MainViewModel.kt
index 986e6e6..77dc09f 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/main/MainViewModel.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/main/MainViewModel.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.ui.main
+package com.ilein.thecocktailapp.presentation.ui.main
import androidx.lifecycle.ViewModel
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/map/MapFragment.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/map/MapFragment.kt
similarity index 98%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/map/MapFragment.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/map/MapFragment.kt
index 4b9a363..4270dc9 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/map/MapFragment.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/map/MapFragment.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.ui.map
+package com.ilein.thecocktailapp.presentation.ui.map
import android.Manifest
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/search/DrinkItemAdapter.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/search/DrinkItemAdapter.kt
similarity index 87%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/search/DrinkItemAdapter.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/search/DrinkItemAdapter.kt
index 6322beb..8ec3404 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/search/DrinkItemAdapter.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/search/DrinkItemAdapter.kt
@@ -1,4 +1,4 @@
-package com.ilein.thecocktailapp.ui.search
+package com.ilein.thecocktailapp.presentation.ui.search
import android.view.LayoutInflater
import android.view.View
@@ -11,9 +11,9 @@ import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.RoundedCornersTransformation
import com.ilein.thecocktailapp.R
-import com.ilein.thecocktailapp.network.model.Drink
-import com.ilein.thecocktailapp.ui.state.DrinkData
-import com.ilein.thecocktailapp.ui.utils.DiffCallback
+import com.ilein.thecocktailapp.domain.Drink
+import com.ilein.thecocktailapp.presentation.ui.state.DrinkData
+import com.ilein.thecocktailapp.presentation.ui.utils.DiffCallback
internal class DrinkItemAdapter(private val onItemClick: (Drink) -> Unit,
private val onLikeClick: (Drink, Boolean) -> Unit) :
@@ -43,9 +43,9 @@ internal class DrinkItemAdapter(private val onItemClick: (Drink) -> Unit,
like.setOnCheckedChangeListener { _, isChecked ->
onLikeClick(item.drink, isChecked)
}
- title.text = item.drink.strDrink
+ title.text = item.drink.id.toString()
like.isChecked = item.like ?: false
- image.load("${item.drink.strDrinkThumb}/preview") {
+ image.load("${item.drink.image}/preview") {
transformations(RoundedCornersTransformation(16f))
}
}
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/search/SearchDrinksFragment.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/search/SearchDrinksFragment.kt
similarity index 86%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/search/SearchDrinksFragment.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/search/SearchDrinksFragment.kt
index ee60969..6c56a89 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/search/SearchDrinksFragment.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/search/SearchDrinksFragment.kt
@@ -1,14 +1,12 @@
-package com.ilein.thecocktailapp.ui.search
+package com.ilein.thecocktailapp.presentation.ui.search
-import android.os.Build
import android.os.Bundle
-import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
-import androidx.annotation.RequiresApi
import androidx.core.widget.addTextChangedListener
+import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.ilein.thecocktailapp.databinding.SearchDrinksFragmentBinding
@@ -28,15 +26,13 @@ class SearchDrinksFragment : Fragment() {
return binding.root
}
- @RequiresApi(Build.VERSION_CODES.O)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.lCardDrinksRecycler.visibility = View.VISIBLE
- //binding.loadingDrinks.visibility = View.INVISIBLE
val drinksAdapter = DrinkItemAdapter ({
- findNavController().navigate(SearchDrinksFragmentDirections.toDrinkDetailWithParam(it.idDrink))
+ findNavController().navigate(SearchDrinksFragmentDirections.toDrinkDetailWithParam(it.id))
},
{ drink, isLike ->
vm.onLikeClick(drink, isLike)
@@ -47,8 +43,6 @@ class SearchDrinksFragment : Fragment() {
run {
binding.lCardDrinksRecycler.visibility =
if (state.loading) View.INVISIBLE else View.VISIBLE
- //binding.loadingDrinks.visibility =
- // if (state.loading) View.VISIBLE else View.INVISIBLE
binding.twHint.visibility =
if (state.itemsMap.isNotEmpty()) View.GONE else View.VISIBLE
diff --git a/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/search/SearchDrinksViewModel.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/search/SearchDrinksViewModel.kt
new file mode 100644
index 0000000..2e9a637
--- /dev/null
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/search/SearchDrinksViewModel.kt
@@ -0,0 +1,201 @@
+package com.ilein.thecocktailapp.presentation.ui.search
+
+import android.os.Handler
+import android.os.Looper
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.ilein.thecocktailapp.data.network.NetworkUtil
+import com.ilein.thecocktailapp.domain.Drink
+import com.ilein.thecocktailapp.domain.FavoriteDrink
+import com.ilein.thecocktailapp.domain.base.Result
+import com.ilein.thecocktailapp.domain.usecase.DeleteDrinkUseCase
+import com.ilein.thecocktailapp.domain.usecase.GetDrinkFavoritesUseCase
+import com.ilein.thecocktailapp.domain.usecase.InsertDrinkUseCase
+import com.ilein.thecocktailapp.domain.usecase.SearchDrinkUseCase
+import com.ilein.thecocktailapp.presentation.ui.state.DrinkData
+import com.ilein.thecocktailapp.presentation.ui.state.DrinksSearchState
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onEmpty
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import java.time.LocalDateTime
+import java.util.Collections.emptyList
+
+@OptIn(ExperimentalCoroutinesApi::class, kotlinx.coroutines.FlowPreview::class)
+class SearchDrinksViewModel(private val searchDrinksUseCase: SearchDrinkUseCase,
+ private val getDrinkFavoritesUseCase: GetDrinkFavoritesUseCase,
+ private val insertDrinkUseCase: InsertDrinkUseCase,
+ private val deleteDrinkUseCase: DeleteDrinkUseCase,
+ private val networkUtils: NetworkUtil) : ViewModel() {
+
+ private val _liveData: MutableLiveData = MutableLiveData(DrinksSearchState())
+
+ val state: LiveData = _liveData
+
+ private val sharedFlow = MutableSharedFlow(1, 1)
+
+ private val drinkLikes = mutableListOf()
+
+ init {
+ init()
+ }
+
+ private fun init() {
+ viewModelScope.launch {
+ when (val result = getDrinkFavoritesUseCase("")) {
+ is Result.Success -> {
+ result.data.forEach {
+ if (it.id !in drinkLikes) {
+ drinkLikes.add(it.id)
+ }
+ }
+ }
+ is Result.Error -> {
+ result.throwable
+ }
+ }
+ }
+
+ viewModelScope.launch {
+ sharedFlow
+ .filter { it.isNotBlank() }
+ .debounce(500)
+ .flatMapLatest { query ->
+ if (query.isBlank()) flowOf(emptyList()) else
+ if (networkUtils.isOnline()) {
+
+ when (val result = searchDrinksUseCase(query)) {
+ is Result.Success -> {
+ result.data
+ .onStart {
+ updateState {
+ it.copy(loading = true)
+ }
+ }
+ .onEmpty {
+ updateState {
+ it.copy(loading = false)
+ }
+ }
+ .onCompletion {
+ updateState {
+ it.copy(loading = false)
+ }
+ }
+ return@flatMapLatest result.data
+ }
+ is Result.Error -> {
+ result.throwable
+ flowOf(emptyList())
+ }
+ }
+ } else {
+ updateState {
+ it.copy(loading = false, isOnline = false)
+ }
+ flowOf(emptyList())
+ }
+ }
+ .flowOn(Dispatchers.IO)
+ .onEach(::onResponse)
+ .launchIn(viewModelScope)
+ }
+ }
+
+
+ private fun updateState(update: (DrinksSearchState) -> DrinksSearchState) {
+ Handler(Looper.getMainLooper()).post {
+ _liveData.value?.also {
+ _liveData.value = update(it)
+ }
+ }
+ }
+
+ private fun onResponse(drinks: List) {
+ updateState { it ->
+ it.copy(
+ itemsMap = drinks.associate {
+ it.id to DrinkData(
+ it,
+ drinkLikes.contains(it.id)
+ )
+ },
+ loading = false
+ )
+ }
+ }
+
+ /**
+ * ACTIONS
+ */
+
+ fun onTextInput(text: String) {
+ viewModelScope.launch {
+ sharedFlow.emit(text)
+ }
+ }
+
+ fun onRefresh() {
+ init()
+ }
+
+
+ fun onLikeClick(drink: Drink, isLike: Boolean) {
+ if (!isLike) {
+ viewModelScope.launch {
+ when(val result = deleteDrinkUseCase(drink.id)) {
+ is Result.Success -> {
+ drinkLikes.remove(drink.id)
+ updateState {
+ val copyVal = _liveData.value?.copy()
+ if (copyVal != null) {
+ copyVal.itemsMap[drink.id]?.like = false
+ }
+ return@updateState copyVal!!
+ }
+ }
+ is Result.Error -> {
+ result.throwable
+ }
+ }
+ }
+ } else {
+ viewModelScope.launch {
+ val favoriteDrink = FavoriteDrink(
+ id = drink.id,
+ name = drink.name,
+ image = drink.image,
+ createDate = LocalDateTime.now(),
+ note = null
+ )
+ when(val result = insertDrinkUseCase(favoriteDrink)) {
+ is Result.Success -> {
+ if (drink.id !in drinkLikes) {drinkLikes.add(drink.id)}
+ updateState {
+ val copyVal = _liveData.value?.copy()
+ if (copyVal != null) {
+ copyVal.itemsMap[drink.id]?.like = true
+ }
+ return@updateState copyVal!!
+ }
+ }
+ is Result.Error -> {
+ result.throwable
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/state/DrinksState.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/state/DrinksState.kt
similarity index 70%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/state/DrinksState.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/state/DrinksState.kt
index 0dbccd2..86ffb5c 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/state/DrinksState.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/state/DrinksState.kt
@@ -1,7 +1,7 @@
-package com.ilein.thecocktailapp.ui.state
+package com.ilein.thecocktailapp.presentation.ui.state
-import com.ilein.thecocktailapp.db.DrinkLikeEntity
-import com.ilein.thecocktailapp.network.model.Drink
+import com.ilein.thecocktailapp.domain.Drink
+import com.ilein.thecocktailapp.domain.FavoriteDrink
data class DrinksSearchState (
val input: String = "",
@@ -18,7 +18,7 @@ data class DrinkItemState (
data class DrinkItemLikeState (
val drink: Drink?,
- val drinkLike: DrinkLikeEntity?,
+ val drinkLike: FavoriteDrink?,
val loading: Boolean = false,
val isOnline: Boolean = true
)
@@ -29,6 +29,6 @@ data class DrinkData (
)
data class DrinksLikeState (
- val items: List = emptyList(),
+ val items: List = emptyList(),
val loading: Boolean = false
)
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/utils/DiffCallback.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/utils/DiffCallback.kt
similarity index 72%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/utils/DiffCallback.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/utils/DiffCallback.kt
index 045f787..4ae0a01 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/utils/DiffCallback.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/utils/DiffCallback.kt
@@ -1,7 +1,7 @@
-package com.ilein.thecocktailapp.ui.utils
+package com.ilein.thecocktailapp.presentation.ui.utils
import androidx.recyclerview.widget.DiffUtil
-import com.ilein.thecocktailapp.ui.state.DrinkData
+import com.ilein.thecocktailapp.presentation.ui.state.DrinkData
class DiffCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() {
@@ -10,7 +10,7 @@ class DiffCallback(private val oldList: List, private val newList: Li
override fun getNewListSize(): Int = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
- oldList[oldItemPosition].drink.idDrink == newList[newItemPosition].drink.idDrink
+ oldList[oldItemPosition].drink.id == newList[newItemPosition].drink.id
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] == newList[newItemPosition]
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/utils/DrinkLikeCallback.kt b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/utils/DrinkLikeCallback.kt
similarity index 66%
rename from app/src/main/java/com/ilein/thecocktailapp/ui/utils/DrinkLikeCallback.kt
rename to app/src/main/java/com/ilein/thecocktailapp/presentation/ui/utils/DrinkLikeCallback.kt
index 0e485de..2e29e0a 100644
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/utils/DrinkLikeCallback.kt
+++ b/app/src/main/java/com/ilein/thecocktailapp/presentation/ui/utils/DrinkLikeCallback.kt
@@ -1,9 +1,9 @@
-package com.ilein.thecocktailapp.ui.utils
+package com.ilein.thecocktailapp.presentation.ui.utils
import androidx.recyclerview.widget.DiffUtil
-import com.ilein.thecocktailapp.db.DrinkLikeEntity
+import com.ilein.thecocktailapp.domain.FavoriteDrink
-class DiffLikeCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() {
+class DiffLikeCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/command/CommandsLiveData.kt b/app/src/main/java/com/ilein/thecocktailapp/ui/command/CommandsLiveData.kt
deleted file mode 100644
index a093c39..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/command/CommandsLiveData.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.ilein.thecocktailapp.ui.command
-
-import androidx.lifecycle.MutableLiveData
-import java.util.*
-import java.util.concurrent.ConcurrentLinkedQueue
-
-class CommandsLiveData : MutableLiveData>() {
-
- fun onNext(value: T) {
- val commands = getValue() ?: ConcurrentLinkedQueue()
- commands.add(value)
- setValue(commands)
- }
-}
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/command/LifecycleExt.kt b/app/src/main/java/com/ilein/thecocktailapp/ui/command/LifecycleExt.kt
deleted file mode 100644
index 00277b6..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/command/LifecycleExt.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.ilein.thecocktailapp.ui.command
-
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LiveData
-
-inline fun LifecycleOwner.observe(liveData: LiveData, crossinline block: (T) -> Unit) {
- liveData.observe(this) { block.invoke(it) }
-}
-
-inline fun LifecycleOwner.observe(liveData: CommandsLiveData, crossinline block: (T) -> Unit) {
- liveData.observe(this) { commands ->
- val iterator = commands.iterator()
- while (iterator.hasNext()) {
- block.invoke(iterator.next())
- iterator.remove()
- }
- }
-}
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/command/NavigationCommand.kt b/app/src/main/java/com/ilein/thecocktailapp/ui/command/NavigationCommand.kt
deleted file mode 100644
index 56eab44..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/command/NavigationCommand.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.ilein.thecocktailapp.ui.command
-
-sealed class NavigationCommand {
-
- object Back: NavigationCommand()
- data class ToDrinkLikeDetail(val drinkId: Int?): NavigationCommand()
- data class ToDrinkDetail(val drinkId: Int?): NavigationCommand()
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkLikeItemDetailViewModel.kt b/app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkLikeItemDetailViewModel.kt
deleted file mode 100644
index c9f1d18..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/detail/DrinkLikeItemDetailViewModel.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.ilein.thecocktailapp.ui.detail
-
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.ilein.thecocktailapp.db.DrinkLikeDao
-import com.ilein.thecocktailapp.network.DrinksReq
-import com.ilein.thecocktailapp.network.NetworkUtil
-import com.ilein.thecocktailapp.ui.state.DrinkItemLikeState
-import io.reactivex.rxjava3.disposables.CompositeDisposable
-import io.reactivex.rxjava3.schedulers.Schedulers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
-
-class DrinkLikeItemDetailViewModel(private val drinkLikeDao: DrinkLikeDao,
- private val drinksReq: DrinksReq,
- private val networkUtil: NetworkUtil
-): ViewModel() {
- val state = MutableLiveData()
-
- private val compositeDisposable = CompositeDisposable()
-
- fun init(drinkId: Int) {
- if (networkUtil.isOnline()) {
- drinksReq.requestDrink(drinkId)
- .onStart { state.postValue(DrinkItemLikeState(null, null, true)) }
- .onEach { drinks ->
- run {
- val drink = drinks.results[0]
- drinkLikeDao.getDrinkLike(drinkId)
- .subscribeOn(Schedulers.io())
- .observeOn(Schedulers.single())
- .subscribe { drinkLike ->
- state.postValue(DrinkItemLikeState(drink, drinkLike, false))
- }
-
- }
- }
- .launchIn(viewModelScope)
- } else {
- state.postValue(DrinkItemLikeState(null, null, false, isOnline = false))
- }
- }
-
- fun onRefresh(drinkId: Int) {
- init(drinkId)
- }
-
- fun onNoteChange(note: String) {
- if (state.value!!.drinkLike?.note != note) {
- state.postValue(state.value!!.copy(
- drinkLike = state.value!!.drinkLike?.copy(note = note)
- ))
- }
- }
-
- fun onSaveClick() {
- state.value?.drinkLike?.let {
- compositeDisposable.add(
- drinkLikeDao.insertDrinkLike(it)
- .subscribeOn(Schedulers.io())
- .observeOn(Schedulers.io())
- .subscribe()
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/liked/LikedDrinksViewModel.kt b/app/src/main/java/com/ilein/thecocktailapp/ui/liked/LikedDrinksViewModel.kt
deleted file mode 100644
index b1c586e..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/liked/LikedDrinksViewModel.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.ilein.thecocktailapp.ui.liked
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import com.ilein.thecocktailapp.db.DrinkLikeDao
-import com.ilein.thecocktailapp.db.DrinkLikeEntity
-import com.ilein.thecocktailapp.ui.state.DrinksLikeState
-import io.reactivex.rxjava3.disposables.CompositeDisposable
-import io.reactivex.rxjava3.schedulers.Schedulers
-
-class LikedDrinksViewModel(private val drinkLikeDao: DrinkLikeDao) : ViewModel() {
- private val compositeDisposable = CompositeDisposable()
-
- private val _liveData: MutableLiveData = MutableLiveData(DrinksLikeState())
-
- val state: LiveData = _liveData
-
- init {
- initData()
- }
-
- private fun initData() {
- compositeDisposable.add(
- drinkLikeDao.observeDrinkLikeDesc()
- .subscribeOn(Schedulers.io())
- .observeOn(Schedulers.single())
- .doOnSubscribe { _liveData.postValue(DrinksLikeState(
- loading = true
- )) }
- .subscribe { drinksList ->
- _liveData.postValue(DrinksLikeState(
- items = drinksList,
- loading = false
- ))
- }
- )
- }
-
- override fun onCleared() {
- compositeDisposable.dispose()
- super.onCleared()
- }
-
- /**
- * ACTIONS
- */
-
- fun onDelClick(drink: DrinkLikeEntity) {
- compositeDisposable.add(
- drinkLikeDao.deleteDrinkLike(drink.id)
- .subscribeOn(Schedulers.io())
- .observeOn(Schedulers.single())
- .subscribe {
- initData()
- }
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ilein/thecocktailapp/ui/search/SearchDrinksViewModel.kt b/app/src/main/java/com/ilein/thecocktailapp/ui/search/SearchDrinksViewModel.kt
deleted file mode 100644
index db2301a..0000000
--- a/app/src/main/java/com/ilein/thecocktailapp/ui/search/SearchDrinksViewModel.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-package com.ilein.thecocktailapp.ui.search
-
-import android.os.Build
-import android.os.Handler
-import android.os.Looper
-import androidx.annotation.RequiresApi
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.ilein.thecocktailapp.db.DrinkLikeDao
-import com.ilein.thecocktailapp.db.DrinkLikeEntity
-import com.ilein.thecocktailapp.koin.networkModule
-import com.ilein.thecocktailapp.network.DrinksReq
-import com.ilein.thecocktailapp.network.NetworkUtil
-import com.ilein.thecocktailapp.network.model.Drink
-import com.ilein.thecocktailapp.network.model.Drinks
-import com.ilein.thecocktailapp.ui.state.DrinkData
-import com.ilein.thecocktailapp.ui.state.DrinksSearchState
-import io.reactivex.rxjava3.disposables.CompositeDisposable
-import io.reactivex.rxjava3.schedulers.Schedulers
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.*
-import kotlinx.coroutines.launch
-import java.time.LocalDateTime
-
-@OptIn(ExperimentalCoroutinesApi::class, kotlinx.coroutines.FlowPreview::class)
-class SearchDrinksViewModel(private val drinkLikeDao: DrinkLikeDao,
- private val drinksReq: DrinksReq,
- private val networkUtils: NetworkUtil) : ViewModel() {
-
- private val _liveData: MutableLiveData = MutableLiveData(DrinksSearchState())
-
- val state: LiveData = _liveData
-
- private val sharedFlow = MutableSharedFlow(1, 1)
-
- private val drinkLikes = mutableListOf()
-
- private val compositeDisposable = CompositeDisposable()
-
- init {
- init()
- }
-
- private fun init() {
- compositeDisposable.add(
- drinkLikeDao.observeDrinkLike()
- .subscribeOn(Schedulers.io())
- .observeOn(Schedulers.single())
- .subscribe { drinkList ->
- drinkList.forEach { if (it.id !in drinkLikes) {drinkLikes.add(it.id)} }
- }
- )
- sharedFlow
- .filter { it.isNotBlank() }
- .debounce(500)
- .flatMapLatest { query ->
- if (query.isBlank()) flowOf(Drinks()) else
- if ( networkUtils.isOnline() ) {
- drinksReq.requestDrinks(query)
- .onStart {
- updateState {
- it.copy(loading = true)
- }
- }
- .onEmpty {
- updateState {
- it.copy(loading = false)
- }
- }
- .onCompletion {
- updateState {
- it.copy(loading = false)
- }
- }
- } else {
- updateState {
- it.copy(loading = false, isOnline = false) }
- flowOf(Drinks())
- }
- }
- .flowOn(Dispatchers.IO)
- .onEach(::onResponse)
- .launchIn(viewModelScope)
- }
-
-
- private fun updateState(update: (DrinksSearchState) -> DrinksSearchState) {
- Handler(Looper.getMainLooper()).post {
- _liveData.value?.also {
- _liveData.value = update(it)
- }
- }
- }
-
- private fun onResponse(drinks: Drinks) {
- updateState { it ->
- it.copy(
- itemsMap = drinks.results.associate {
- it.idDrink to DrinkData(
- it,
- drinkLikes.contains(it.idDrink)
- )
- },
- loading = false
- )
- }
- }
-
- override fun onCleared() {
- compositeDisposable.dispose()
- super.onCleared()
- }
-
- /**
- * ACTIONS
- */
-
- fun onTextInput(text: String) {
- viewModelScope.launch {
- sharedFlow.emit(text)
- }
- }
-
- fun onRefresh() {
- init()
- }
-
- @RequiresApi(Build.VERSION_CODES.O)
- fun onLikeClick(drinkLike: Drink, isLike: Boolean) {
- if (!isLike) {
- compositeDisposable.add(
- drinkLikeDao.deleteDrinkLike(drinkLike.idDrink)
- .subscribeOn(Schedulers.io())
- .observeOn(Schedulers.single())
- .subscribe {
- drinkLikes.remove(drinkLike.idDrink)
- updateState {
- val copyVal = _liveData.value?.copy()
- if (copyVal != null) {
- copyVal.itemsMap[drinkLike.idDrink]?.like = false
- }
- return@updateState copyVal!!
- }
- }
- )
-
- } else {
- compositeDisposable.add(
- drinkLikeDao.insertDrinkLike(
- DrinkLikeEntity(
- id = drinkLike.idDrink,
- name = drinkLike.strDrink,
- image = drinkLike.strDrinkThumb,
- createDate = LocalDateTime.now(),
- note = null
- )
- )
- .subscribeOn(Schedulers.io())
- .observeOn(Schedulers.single())
- .subscribe {
- if (drinkLike.idDrink !in drinkLikes) {drinkLikes.add(drinkLike.idDrink)}
- updateState {
- val copyVal = _liveData.value?.copy()
- if (copyVal != null) {
- copyVal.itemsMap[drinkLike.idDrink]?.like = true
- }
- return@updateState copyVal!!
- }
- }
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_drink_item_detail.xml b/app/src/main/res/layout/fragment_drink_item_detail.xml
index 6b3a3ae..543ec81 100644
--- a/app/src/main/res/layout/fragment_drink_item_detail.xml
+++ b/app/src/main/res/layout/fragment_drink_item_detail.xml
@@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
- tools:context=".ui.detail.DrinkLikeItemDetailFragment">
+ tools:context=".presentation.ui.detail.DrinkLikeItemDetailFragment">
+ tools:context=".presentation.ui.detail.DrinkLikeItemDetailFragment">
+ tools:context=".presentation.ui.map.MapFragment">
+ tools:context=".presentation.ui.liked.LikedDrinksFragment">
+ tools:context=".presentation.MainActivity">
+ tools:context=".presentation.ui.main.MainFragment">
+ tools:context=".presentation.ui.search.SearchDrinksFragment">
\ No newline at end of file