diff --git a/.gitignore b/.gitignore index 3ed3dcb..872eace 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,6 @@ local.properties app/version.properties # SonarQube -.sonar/ \ No newline at end of file +.sonar/ + +gradle/gradle-daemon-jvm.properties diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3b94d95 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,30 @@ +# Agent Project Guidelines & Android Skills Enforcement + +## Instruksi Penggunaan Skill Android +Setiap kali mengembangkan, memodifikasi, merombak (*refactoring*), mengoptimalkan, atau menguji proyek Android **piano-tiles** ini, Agent WAJIB secara aktif membaca dan menerapkan petunjuk dari **Skills Agent Android** yang relevan sebelum melakukan perubahan kode: + +### 1. Pengembangan & Arsitektur UI +- **`frogo-sdk`**: Gunakan panduan dan komponen Frogo SDK untuk pengembangan UI (Compose/XML), integrasi iklan (AdMob), helper RecyclerView, dan utilitas Android core. +- **`edge-to-edge`**: Terapkan panduan adaptif edge-to-edge untuk memastikan komponen UI tidak tertutup status bar, navigation bar, atau IME/keyboard inset. +- **`migrate-xml-views-to-jetpack-compose`**: Ikuti alur kerja terstruktur saat melakukan migrasi layout dari legacy XML View ke Jetpack Compose. +- **`jetpack-compose-m3`** & **`android-jetpack-compose-expert`**: Gunakan praktik terbaik Material Design 3 dan pengoptimalan performa Jetpack Compose. + +### 2. Navigasi & Monetisasi +- **`navigation-3`**: Gunakan panduan Jetpack Navigation 3 untuk manajemen navigasi halaman, deep link, multiple backstack, dan scene transitions. +- **`play-billing-library-version-upgrade`**: Ikuti panduan pembaruan Google Play Billing Library (PBL) saat menangani in-app purchase atau langganan. + +### 3. Build Optimization & CLI Operations +- **`android-cli`**: Gunakan skill `android-cli` untuk otomasi perintah Android CLI, pengujian, deployment, emulator, dan diagnosa lingkungan pengembangan. +- **`agp-9-upgrade`**: Ikuti aturan & panduan migrasi saat memperbarui Android Gradle Plugin (AGP) ke versi 9. +- **`r8-analyzer`**: Analisis file build dan Proguard/R8 keep rules untuk mengeliminasi aturan redundan dan meminimalkan ukuran APK. + +### 4. Performa & Asinkron +- **`kotlin-coroutines-expert`**: Gunakan praktik terbaik penanganan Coroutines, Flow, dan operasi asinkron tanpa memblokir Main UI Thread. +- **`diagnose-android-overheating`**: Periksa dan hindari kebocoran memori (*memory leak*) atau pemakaian CPU tinggi yang dapat menyebabkan perangkat cepat panas. + +--- + +## Prosedur Eksekusi Agent +1. **Periksa Skill**: Sebelum mulai menulis atau merombak kode, identifikasi skill Android di atas yang relevan dengan tugas. +2. **Baca `SKILL.md`**: Gunakan `view_file` pada `SKILL.md` dari skill yang relevan untuk membaca dokumentasi & petunjuk teknisnya. +3. **Patuhi Panduan**: Ikuti standar coding, arsitektur, dan keamanan yang tertera pada skill tersebut secara konsisten. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fd1f849..1c96b14 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.ksp) alias(libs.plugins.hilt) alias(libs.plugins.kotlinParcelize) + alias(libs.plugins.kotlin.compose) } ksp { @@ -21,7 +22,7 @@ base { android { - namespace = "com.frogobox.kickstart" + namespace = "io.github.amirisback.androidapp" compileSdk = ProjectSetting.PROJECT_COMPILE_SDK defaultConfig { @@ -98,6 +99,7 @@ android { viewBinding = true buildConfig = true resValues = true + compose = true } compileOptions { @@ -138,4 +140,17 @@ dependencies { ksp(libs.androidx.room.compiler) ksp(libs.github.glide.compiler) + // Compose + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui.graphics) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.coil.compose) + debugImplementation(libs.androidx.compose.ui.tooling) + } \ No newline at end of file diff --git a/app/schemas/io.github.amirisback.androidapp.domain.db.ProjectDatabase/1.json b/app/schemas/io.github.amirisback.androidapp.domain.db.ProjectDatabase/1.json new file mode 100644 index 0000000..d5eaeb2 --- /dev/null +++ b/app/schemas/io.github.amirisback.androidapp.domain.db.ProjectDatabase/1.json @@ -0,0 +1,286 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "55fd7d6098321df8bda0298c83bc17c7", + "entities": [ + { + "tableName": "meal", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`table_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `idMeal` TEXT, `strMeal` TEXT, `strDrinkAlternate` TEXT, `strCategory` TEXT, `strArea` TEXT, `strInstructions` TEXT, `strMealThumb` TEXT, `strTags` TEXT, `strYoutube` TEXT, `strIngredient1` TEXT, `strIngredient2` TEXT, `strIngredient3` TEXT, `strIngredient4` TEXT, `strIngredient5` TEXT, `strIngredient6` TEXT, `strIngredient7` TEXT, `strIngredient8` TEXT, `strIngredient9` TEXT, `strIngredient10` TEXT, `strIngredient11` TEXT, `strIngredient12` TEXT, `strIngredient13` TEXT, `strIngredient14` TEXT, `strIngredient15` TEXT, `strIngredient16` TEXT, `strIngredient17` TEXT, `strIngredient18` TEXT, `strIngredient19` TEXT, `strIngredient20` TEXT, `strMeasure1` TEXT, `strMeasure2` TEXT, `strMeasure3` TEXT, `strMeasure4` TEXT, `strMeasure5` TEXT, `strMeasure6` TEXT, `strMeasure7` TEXT, `strMeasure8` TEXT, `strMeasure9` TEXT, `strMeasure10` TEXT, `strMeasure11` TEXT, `strMeasure12` TEXT, `strMeasure13` TEXT, `strMeasure14` TEXT, `strMeasure15` TEXT, `strMeasure16` TEXT, `strMeasure17` TEXT, `strMeasure18` TEXT, `strMeasure19` TEXT, `strMeasure20` TEXT, `strSource` TEXT, `dateModified` TEXT)", + "fields": [ + { + "fieldPath": "table_id", + "columnName": "table_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "idMeal", + "columnName": "idMeal", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeal", + "columnName": "strMeal", + "affinity": "TEXT" + }, + { + "fieldPath": "strDrinkAlternate", + "columnName": "strDrinkAlternate", + "affinity": "TEXT" + }, + { + "fieldPath": "strCategory", + "columnName": "strCategory", + "affinity": "TEXT" + }, + { + "fieldPath": "strArea", + "columnName": "strArea", + "affinity": "TEXT" + }, + { + "fieldPath": "strInstructions", + "columnName": "strInstructions", + "affinity": "TEXT" + }, + { + "fieldPath": "strMealThumb", + "columnName": "strMealThumb", + "affinity": "TEXT" + }, + { + "fieldPath": "strTags", + "columnName": "strTags", + "affinity": "TEXT" + }, + { + "fieldPath": "strYoutube", + "columnName": "strYoutube", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient1", + "columnName": "strIngredient1", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient2", + "columnName": "strIngredient2", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient3", + "columnName": "strIngredient3", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient4", + "columnName": "strIngredient4", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient5", + "columnName": "strIngredient5", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient6", + "columnName": "strIngredient6", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient7", + "columnName": "strIngredient7", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient8", + "columnName": "strIngredient8", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient9", + "columnName": "strIngredient9", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient10", + "columnName": "strIngredient10", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient11", + "columnName": "strIngredient11", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient12", + "columnName": "strIngredient12", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient13", + "columnName": "strIngredient13", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient14", + "columnName": "strIngredient14", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient15", + "columnName": "strIngredient15", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient16", + "columnName": "strIngredient16", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient17", + "columnName": "strIngredient17", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient18", + "columnName": "strIngredient18", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient19", + "columnName": "strIngredient19", + "affinity": "TEXT" + }, + { + "fieldPath": "strIngredient20", + "columnName": "strIngredient20", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure1", + "columnName": "strMeasure1", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure2", + "columnName": "strMeasure2", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure3", + "columnName": "strMeasure3", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure4", + "columnName": "strMeasure4", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure5", + "columnName": "strMeasure5", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure6", + "columnName": "strMeasure6", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure7", + "columnName": "strMeasure7", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure8", + "columnName": "strMeasure8", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure9", + "columnName": "strMeasure9", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure10", + "columnName": "strMeasure10", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure11", + "columnName": "strMeasure11", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure12", + "columnName": "strMeasure12", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure13", + "columnName": "strMeasure13", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure14", + "columnName": "strMeasure14", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure15", + "columnName": "strMeasure15", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure16", + "columnName": "strMeasure16", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure17", + "columnName": "strMeasure17", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure18", + "columnName": "strMeasure18", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure19", + "columnName": "strMeasure19", + "affinity": "TEXT" + }, + { + "fieldPath": "strMeasure20", + "columnName": "strMeasure20", + "affinity": "TEXT" + }, + { + "fieldPath": "strSource", + "columnName": "strSource", + "affinity": "TEXT" + }, + { + "fieldPath": "dateModified", + "columnName": "dateModified", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "table_id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '55fd7d6098321df8bda0298c83bc17c7')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2953b0a..c1ee28f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,7 +22,7 @@ diff --git a/app/src/main/java/com/frogobox/kickstart/common/base/BaseAdapter.kt b/app/src/main/java/com/frogobox/kickstart/common/base/BaseAdapter.kt deleted file mode 100644 index e403465..0000000 --- a/app/src/main/java/com/frogobox/kickstart/common/base/BaseAdapter.kt +++ /dev/null @@ -1,117 +0,0 @@ -package com.frogobox.kickstart.common.base - -import androidx.recyclerview.widget.AsyncListDiffer -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import com.frogobox.kickstart.common.callback.OnItemClickCallback - -/** Standard BaseAdapter Handling BaseModel List and BaseViewHolder**/ - -abstract class BaseAdapter> : RecyclerView.Adapter() { - - var onItemClickCallback: OnItemClickCallback? = null - - var selectedItem = mutableListOf() - - fun setSelectedItems(items: MutableList) { - selectedItem = items - notifyDataSetChanged() - } - - protected val asyncListDiffer = AsyncListDiffer(this, object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: BM & Any, newItem: BM & Any): Boolean { - return adapterAreItemsTheSame(oldItem, newItem) - } - - override fun areContentsTheSame(oldItem: BM & Any, newItem: BM & Any): Boolean { - return adapterAreContentsTheSame(oldItem, newItem) - } - }) - - abstract fun bindVH(holder: T, position: Int) - - abstract fun adapterAreItemsTheSame(oldItem: BM & Any, newItem: BM & Any): Boolean - - abstract fun adapterAreContentsTheSame(oldItem: BM & Any, newItem: BM & Any): Boolean - - override fun getItemCount(): Int { - return asyncListDiffer.currentList.size - } - - fun getLastItemPosition(): Int { - return if (itemCount == 0) { - 0 - } else { - itemCount - 1 - } - } - - override fun onBindViewHolder(holder: T, position: Int) { - bindVH(holder, position) - } - - open fun setOnItemCallBack(onItemClickCallback: OnItemClickCallback) { - this.onItemClickCallback = onItemClickCallback - } - - open fun getItem(): MutableList = asyncListDiffer.currentList.toMutableList() - - open fun setItem(item: List) { - if (item.isEmpty()) { - asyncListDiffer.submitList(listOf()) - } else { - asyncListDiffer.submitList(item.map { it }) - } - } - - open fun clearItems() { - asyncListDiffer.submitList(null) - } - - open fun insert(item: BM, position: Int) { - val listItems = asyncListDiffer.currentList.toMutableList() - listItems.add(position + 1, item) - val changedCount = itemCount - position + 1 - asyncListDiffer.submitList(listItems) { - // Trigger onBindViewHolder for the rest of the items that moved to the end of the list - notifyItemRangeChanged(position, changedCount) - } - - } - - open fun delete(item: BM) { - val listItems = asyncListDiffer.currentList.toMutableList() - val position = listItems.indexOf(item) - listItems.remove(item) - val changedCount = itemCount - position - asyncListDiffer.submitList(listItems) { - // Triggered once deletion is done and notifyItemRangeRemoved has been called - notifyItemRangeChanged(position, changedCount) - } - } - - open fun delete(position: Int) { - val listItems = asyncListDiffer.currentList.toMutableList() - listItems.removeAt(position) - val changedCount = itemCount - position - asyncListDiffer.submitList(listItems) { - // Triggered once deletion is done and notifyItemRangeRemoved has been called - notifyItemRangeChanged(position, changedCount) - } - } - - open fun moveItem(from: Int, to: Int) { - val listItems = asyncListDiffer.currentList.toMutableList() - val fromLocation = listItems[from] - listItems.removeAt(from) - if (to < from) { - listItems.add(to + 1, fromLocation) - } else { - listItems.add(to - 1, fromLocation) - } - asyncListDiffer.submitList(listItems) { - notifyItemMoved(from, to) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/common/base/BaseViewHolder.kt b/app/src/main/java/com/frogobox/kickstart/common/base/BaseViewHolder.kt deleted file mode 100644 index 6a36709..0000000 --- a/app/src/main/java/com/frogobox/kickstart/common/base/BaseViewHolder.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.frogobox.kickstart.common.base - -import android.view.View -import androidx.recyclerview.widget.RecyclerView - -/** RV ViewHolder Base **/ -abstract class BaseViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) { - abstract fun bindData(model: BaseModel, position: Int? = -1) -} - -abstract class CheckedViewHolder(rootView: View) : - BaseViewHolder(rootView) { - abstract fun bindData(model: BaseModel, position: Int?, checked: Boolean?) -} - -abstract class EditorViewHolder(rootView: View) : - BaseViewHolder(rootView) { - abstract fun bindData(model: BaseModel, position: Int?, onChange: (String) -> Unit) -} \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/ui/about/AboutUsActivity.kt b/app/src/main/java/com/frogobox/kickstart/ui/about/AboutUsActivity.kt deleted file mode 100644 index 29eb6d7..0000000 --- a/app/src/main/java/com/frogobox/kickstart/ui/about/AboutUsActivity.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.frogobox.kickstart.ui.about - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import com.frogobox.kickstart.common.base.BaseActivity -import com.frogobox.kickstart.databinding.ActivityAboutUsBinding -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class AboutUsActivity : BaseActivity() { - - companion object { - - fun createIntent(context: Context): Intent { - return Intent(context, AboutUsActivity::class.java).apply { - - } - } - - fun launch(context: Context) { - context.startActivity(createIntent(context)) - } - - } - - override fun setupViewBinding(): ActivityAboutUsBinding { - return ActivityAboutUsBinding.inflate(layoutInflater) - } - - override fun setupViewModel() {} - - override fun onCreateExt(savedInstanceState: Bundle?) { - super.onCreateExt(savedInstanceState) - setupDetailActivity("") - } - -} diff --git a/app/src/main/java/com/frogobox/kickstart/ui/detail/DetailActivity.kt b/app/src/main/java/com/frogobox/kickstart/ui/detail/DetailActivity.kt deleted file mode 100644 index 1fc0eff..0000000 --- a/app/src/main/java/com/frogobox/kickstart/ui/detail/DetailActivity.kt +++ /dev/null @@ -1,133 +0,0 @@ -package com.frogobox.kickstart.ui.detail - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.activity.viewModels -import com.frogobox.kickstart.common.base.BaseActivity -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.databinding.ActivityDetailBinding -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.sdk.ext.getExtraExt -import com.frogobox.sdk.ext.gone -import com.frogobox.sdk.ext.setImageExt -import com.frogobox.sdk.ext.showToast -import com.frogobox.sdk.ext.toJson -import com.frogobox.sdk.ext.visible -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class DetailActivity : BaseActivity() { - - companion object { - const val EXTRA_DATA = "EXTRA_DATA" - - fun createIntent(context: Context, data: MealModel): Intent { - return Intent(context, DetailActivity::class.java).apply { - putExtra(EXTRA_DATA, data.toJson()) - } - } - - fun launch(context: Context, data: MealModel) { - context.startActivity(createIntent(context, data)) - } - - } - - private val viewModel: DetailViewModel by viewModels() - - override fun setupViewBinding(): ActivityDetailBinding { - return ActivityDetailBinding.inflate(layoutInflater) - } - - override fun setupViewModel() { - viewModel.mealsState.observe(this) { - when (it) { - is Resource.Error -> { - binding.progressView.gone() - showToast(it.message.toString()) - } - - is Resource.Loading -> { - binding.progressView.visible() - } - - is Resource.Success -> { - binding.progressView.gone() - it.data?.let { items -> - if (!items.isEmpty()) { - binding.btnInsert.gone() - binding.btnDelete.visible() - } - } - } - } - } - - viewModel.insertState.observe(this) { - when (it) { - is Resource.Error -> { - binding.progressView.gone() - showToast(it.message.toString()) - } - - is Resource.Loading -> { - binding.progressView.visible() - } - - is Resource.Success -> { - binding.progressView.gone() - binding.btnInsert.gone() - binding.btnDelete.visible() - showToast("Berhasil Menambahkan Ke Favorite ${it.data?.strMeal}") - } - } - } - - viewModel.deleteState.observe(this) { - when (it) { - is Resource.Error -> { - binding.progressView.gone() - showToast(it.message.toString()) - } - - is Resource.Loading -> { - binding.progressView.visible() - } - - is Resource.Success -> { - binding.progressView.gone() - finish() - } - } - } - } - - override fun onCreateExt(savedInstanceState: Bundle?) { - super.onCreateExt(savedInstanceState) - setupDetailActivity("Detail Meals") - - val extra = getExtraExt(EXTRA_DATA) - viewModel.mealModel = extra - viewModel.getData() - - extra?.let { - binding.apply { - ivUrl.setImageExt(it.strMealThumb) - tvSource.text = it.strArea - tvTitle.text = it.strMeal - tvContent.text = it.strCategory - - btnInsert.setOnClickListener { - viewModel.insertToDB() - } - - btnDelete.setOnClickListener { - viewModel.removeFromDb() - } - } - } - - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/ui/detail/DetailViewModel.kt b/app/src/main/java/com/frogobox/kickstart/ui/detail/DetailViewModel.kt deleted file mode 100644 index fe079a8..0000000 --- a/app/src/main/java/com/frogobox/kickstart/ui/detail/DetailViewModel.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.frogobox.kickstart.ui.detail - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import com.frogobox.kickstart.common.base.BaseViewModel -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.kickstart.domain.source.meal.usecase.MealUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import javax.inject.Inject - -/** - * Created by faisalamir on 12/07/21 - * KickStartProject - * ----------------------------------------- - * Name : Muhammad Faisal Amir - * E-mail : faisalamircs@gmail.com - * Github : github.com/amirisback - * ----------------------------------------- - * Copyright (C) 2021 Frogobox Inc. - * All rights reserved - * - */ - -@HiltViewModel -class DetailViewModel @Inject constructor( - private val useCase: MealUseCase, -) : BaseViewModel() { - - var mealModel: MealModel? = null - - private var _mealsState = MutableLiveData>>() - var mealsState: LiveData>> = _mealsState - - private var _insertState = MutableLiveData>() - var insertState: LiveData> = _insertState - - private var _deleteState = MutableLiveData>() - var deleteState: LiveData> = _deleteState - - fun getData() { - viewModelScope.launch { - mealModel?.idMeal?.let { - useCase.searchById(it).onEach { - _mealsState.postValue(it) - }.launchIn(viewModelScope) - } - } - } - - fun insertToDB() { - viewModelScope.launch { - mealModel?.let { - useCase.insertToFavorite(it).onEach { - _insertState.postValue(it) - }.launchIn(viewModelScope) - } - } - } - - fun removeFromDb() { - viewModelScope.launch { - mealModel?.idMeal?.let { - useCase.deleteFromMealId(it).onEach { - _deleteState.postValue(it) - }.launchIn(viewModelScope) - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/ui/favorite/FavoriteFragment.kt b/app/src/main/java/com/frogobox/kickstart/ui/favorite/FavoriteFragment.kt deleted file mode 100644 index 6ff89aa..0000000 --- a/app/src/main/java/com/frogobox/kickstart/ui/favorite/FavoriteFragment.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.frogobox.kickstart.ui.favorite - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels -import androidx.recyclerview.widget.LinearLayoutManager -import com.frogobox.kickstart.common.base.BaseFragment -import com.frogobox.kickstart.common.callback.OnItemClickCallback -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.databinding.FragmentFavoriteBinding -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.kickstart.ui.detail.DetailActivity -import com.frogobox.kickstart.ui.main.MainAdapter -import com.frogobox.sdk.ext.gone -import com.frogobox.sdk.ext.showLogD -import com.frogobox.sdk.ext.showToast -import com.frogobox.sdk.ext.visible -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class FavoriteFragment : BaseFragment() { - - private val viewModel : FavoriteViewModel by activityViewModels() - - private val mainAdapter: MainAdapter by lazy { - MainAdapter() - } - - override fun setupViewBinding( - inflater: LayoutInflater, - container: ViewGroup? - ): FragmentFavoriteBinding { - return FragmentFavoriteBinding.inflate(inflater, container, false) - } - - override fun setupViewModel() { - viewModel.mealsState.observe(this) { - when (it) { - is Resource.Error -> { - binding.progressView.gone() - requireContext().showToast(it.message.toString()) - } - - is Resource.Loading -> { - binding.progressView.visible() - } - - is Resource.Success -> { - binding.progressView.gone() - it.data?.let { items -> - if (items.isEmpty()) { - binding.tvEmpty.visible() - } else { - binding.tvEmpty.gone() - } - mainAdapter.setItem(items) - } - } - } - } - } - - override fun onViewCreatedExt(view: View, savedInstanceState: Bundle?) { - super.onViewCreatedExt(view, savedInstanceState) - viewModel.getData() - binding.apply { - rv.adapter = mainAdapter - rv.layoutManager = - LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) - - - mainAdapter.setOnItemCallBack(object : OnItemClickCallback { - override fun onItemClick( - view: View, - objects: Any, - position: Int?, - ) { - (objects as MealModel).let { - mActivity.startActivityResultExt(DetailActivity.createIntent(requireContext(), it)) - } - } - }) - - } - } - - - override fun onDestroy() { - super.onDestroy() - viewModel.onClearDisposable() - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/ui/favorite/FavoriteViewModel.kt b/app/src/main/java/com/frogobox/kickstart/ui/favorite/FavoriteViewModel.kt deleted file mode 100644 index 7e00a0c..0000000 --- a/app/src/main/java/com/frogobox/kickstart/ui/favorite/FavoriteViewModel.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.frogobox.kickstart.ui.favorite - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import com.frogobox.kickstart.common.base.BaseViewModel -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.kickstart.domain.source.meal.usecase.MealUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class FavoriteViewModel @Inject constructor( - private val useCase: MealUseCase, -) : BaseViewModel() { - - private var _mealsState = MutableLiveData>>() - var mealsState: LiveData>> = _mealsState - - fun getData() { - viewModelScope.launch { - useCase.getAllFavorite().onEach { - _mealsState.postValue(it) - }.launchIn(viewModelScope) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/ui/main/MainActivity.kt b/app/src/main/java/com/frogobox/kickstart/ui/main/MainActivity.kt deleted file mode 100644 index 89aa3b3..0000000 --- a/app/src/main/java/com/frogobox/kickstart/ui/main/MainActivity.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.frogobox.kickstart.ui.main - -import android.content.res.ColorStateList -import android.os.Bundle -import androidx.activity.result.ActivityResult -import androidx.activity.viewModels -import com.frogobox.kickstart.R -import com.frogobox.kickstart.common.base.BaseActivity -import com.frogobox.kickstart.databinding.ActivityMainBinding -import com.frogobox.kickstart.ui.favorite.FavoriteFragment -import com.frogobox.kickstart.ui.favorite.FavoriteViewModel -import com.frogobox.sdk.ext.getColorExt -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class MainActivity : BaseActivity() { - - private val favoriteViewModel : FavoriteViewModel by viewModels() - - override fun setupViewBinding(): ActivityMainBinding { - return ActivityMainBinding.inflate(layoutInflater) - } - - override fun setupActivityResultExt(result: ActivityResult) { - super.setupActivityResultExt(result) - favoriteViewModel.getData() - } - - override fun setupViewModel() {} - - override fun onCreateExt(savedInstanceState: Bundle?) { - super.onCreateExt(savedInstanceState) - setupToolbar() - setupBottomNav(binding.framelayoutMainContainer.id) - setupFragment(savedInstanceState) - } - - private fun setupToolbar() { - supportActionBar?.elevation = 0f - } - - private fun setupFragment(savedInstanceState: Bundle?) { - if (savedInstanceState == null) { - binding.bottomNavMainMenu.selectedItemId = R.id.bottom_menu_main - } - } - - private fun setupBottomNav(frameLayout: Int) { - binding.bottomNavMainMenu.apply { - clearAnimation() - - val iconColorStates = ColorStateList( - arrayOf( - intArrayOf(-android.R.attr.state_checked), - intArrayOf(android.R.attr.state_checked) - ), intArrayOf( - getColorExt(R.color.colorTextTitle), - getColorExt(R.color.colorPrimary), - ) - ) - - itemIconTintList = iconColorStates - itemTextColor = iconColorStates - - setOnItemSelectedListener { - when (it.itemId) { - - R.id.bottom_menu_favorite -> { - supportActionBar?.title = getString(R.string.title_fav) - setupChildFragment( - frameLayout, - FavoriteFragment() - ) - return@setOnItemSelectedListener true - } - - R.id.bottom_menu_main -> { - supportActionBar?.title = getString(R.string.title_main) - setupChildFragment( - frameLayout, - MainFragment() - ) - return@setOnItemSelectedListener true - } - } - - false - } - } - - } - -} diff --git a/app/src/main/java/com/frogobox/kickstart/ui/main/MainAdapter.kt b/app/src/main/java/com/frogobox/kickstart/ui/main/MainAdapter.kt deleted file mode 100644 index d8986f4..0000000 --- a/app/src/main/java/com/frogobox/kickstart/ui/main/MainAdapter.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.frogobox.kickstart.ui.main - -import android.view.LayoutInflater -import android.view.ViewGroup -import com.frogobox.kickstart.common.base.BaseAdapter -import com.frogobox.kickstart.common.base.BaseViewHolder -import com.frogobox.kickstart.common.callback.OnItemClickCallback -import com.frogobox.kickstart.databinding.ContentArticleVerticalBinding -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.sdk.ext.setImageExt - -/** - * Created by faisalamircs on 10/09/2025 - * ----------------------------------------- - * Name : Muhammad Faisal Amir - * E-mail : faisalamircs@gmail.com - * Github : github.com/amirisback - * ----------------------------------------- - */ - - -class MainAdapter : BaseAdapter() { - - override fun bindVH( - holder: MainHolder, - position: Int, - ) { - holder.bindData(asyncListDiffer.currentList[position], position) - } - - override fun adapterAreItemsTheSame( - oldItem: MealModel, - newItem: MealModel, - ): Boolean { - return oldItem.idMeal == newItem.idMeal - } - - override fun adapterAreContentsTheSame( - oldItem: MealModel, - newItem: MealModel, - ): Boolean { - return oldItem == newItem - } - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int, - ): MainHolder { - return MainHolder( - binding = ContentArticleVerticalBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ), - onItemClickCallback = onItemClickCallback - ) - } - - inner class MainHolder( - private val binding: ContentArticleVerticalBinding, - private val onItemClickCallback: OnItemClickCallback? = null, - ) : BaseViewHolder(binding.root) { - - override fun bindData(model: MealModel, position: Int?) { - binding.apply { - ivUrl.setImageExt(model.strMealThumb) - tvTitle.text = model.strMeal - tvDescription.text = model.strCategory - tvPublished.text = model.strArea - - root.setOnClickListener { v -> - onItemClickCallback?.onItemClick(v, model, position) - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/ui/main/MainFragment.kt b/app/src/main/java/com/frogobox/kickstart/ui/main/MainFragment.kt deleted file mode 100644 index 6981800..0000000 --- a/app/src/main/java/com/frogobox/kickstart/ui/main/MainFragment.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.frogobox.kickstart.ui.main - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.activityViewModels -import androidx.recyclerview.widget.LinearLayoutManager -import com.frogobox.kickstart.common.base.BaseFragment -import com.frogobox.kickstart.common.callback.OnItemClickCallback -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.databinding.FragmentMainBinding -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.kickstart.ui.detail.DetailActivity -import com.frogobox.sdk.ext.gone -import com.frogobox.sdk.ext.showToast -import com.frogobox.sdk.ext.visible -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class MainFragment : BaseFragment() { - - private val viewModel: MainViewModel by activityViewModels() - - private val mainAdapter: MainAdapter by lazy { - MainAdapter() - } - - override fun setupViewBinding( - inflater: LayoutInflater, - container: ViewGroup?, - ): FragmentMainBinding { - return FragmentMainBinding.inflate(inflater, container, false) - } - - override fun setupViewModel() { - viewModel.mealsState.observe(this) { - when (it) { - is Resource.Error -> { - binding.progressView.gone() - requireContext().showToast(it.message.toString()) - } - - is Resource.Loading -> { - binding.progressView.visible() - } - - is Resource.Success -> { - binding.progressView.gone() - it.data?.let { items -> - mainAdapter.setItem(items) - } - } - } - } - } - - override fun onViewCreatedExt(view: View, savedInstanceState: Bundle?) { - super.onViewCreatedExt(view, savedInstanceState) - viewModel.searchMeal("Cream") - binding.apply { - rv.adapter = mainAdapter - rv.layoutManager = - LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) - - - mainAdapter.setOnItemCallBack(object : OnItemClickCallback { - override fun onItemClick( - view: View, - objects: Any, - position: Int?, - ) { - (objects as MealModel).let { - mActivity.startActivityResultExt(DetailActivity.createIntent(requireContext(), it)) - } - } - }) - - } - } - - override fun onDestroy() { - super.onDestroy() - viewModel.onClearDisposable() - } - -} diff --git a/app/src/main/java/com/frogobox/kickstart/ui/main/MainViewModel.kt b/app/src/main/java/com/frogobox/kickstart/ui/main/MainViewModel.kt deleted file mode 100644 index ec61bb5..0000000 --- a/app/src/main/java/com/frogobox/kickstart/ui/main/MainViewModel.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.frogobox.kickstart.ui.main - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import com.frogobox.kickstart.common.base.BaseViewModel -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.domain.source.meal.usecase.MealUseCase -import com.frogobox.kickstart.domain.model.MealModel -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import javax.inject.Inject - -/** - * Created by Faisal Amir - * ----------------------------------------- - * Copyright (C) 28/04/2020. - * All rights reserved - * ----------------------------------------- - * Name : Muhammad Faisal Amir - * E-mail : faisalamircs@gmail.com - * Github : github.com/amirisback - * ----------------------------------------- - * Frogobox Inc - * - */ - -@HiltViewModel -class MainViewModel @Inject constructor( - private val useCase: MealUseCase, -) : BaseViewModel() { - - private var _mealsState = MutableLiveData>>() - var mealsState: LiveData>> = _mealsState - - fun searchMeal(name: String = "") { - viewModelScope.launch { - useCase.searchMeal( - nameMeal = name - ).onEach { - _mealsState.postValue(it) - }.launchIn(viewModelScope) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/ProjectApplication.kt b/app/src/main/java/io/github/amirisback/androidapp/ProjectApplication.kt similarity index 90% rename from app/src/main/java/com/frogobox/kickstart/ProjectApplication.kt rename to app/src/main/java/io/github/amirisback/androidapp/ProjectApplication.kt index 119f098..fa131a1 100644 --- a/app/src/main/java/com/frogobox/kickstart/ProjectApplication.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/ProjectApplication.kt @@ -1,9 +1,9 @@ -package com.frogobox.kickstart +package io.github.amirisback.androidapp import android.content.Context -import com.frogobox.kickstart.common.ext.appIsDebug import com.frogobox.sdk.FrogoApplication import dagger.hilt.android.HiltAndroidApp +import io.github.amirisback.androidapp.common.ext.appIsDebug /** * Created by Faisal Amir on 23/10/2020 diff --git a/app/src/main/java/com/frogobox/kickstart/common/base/BaseActivity.kt b/app/src/main/java/io/github/amirisback/androidapp/common/base/BaseActivity.kt similarity index 66% rename from app/src/main/java/com/frogobox/kickstart/common/base/BaseActivity.kt rename to app/src/main/java/io/github/amirisback/androidapp/common/base/BaseActivity.kt index 8e42ffd..ed94ecf 100644 --- a/app/src/main/java/com/frogobox/kickstart/common/base/BaseActivity.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/common/base/BaseActivity.kt @@ -1,7 +1,6 @@ -package com.frogobox.kickstart.common.base +package io.github.amirisback.androidapp.common.base -import androidx.viewbinding.ViewBinding -import com.frogobox.ads.ui.FrogoAdBindActivity +import com.frogobox.ads.ui.compose.FrogoAdComposeActivity /** * Created by Faisal Amir @@ -19,6 +18,4 @@ import com.frogobox.ads.ui.FrogoAdBindActivity * */ -abstract class BaseActivity : FrogoAdBindActivity(), IBaseActivity { - -} \ No newline at end of file +abstract class BaseActivity : FrogoAdComposeActivity(), IBaseActivity \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/common/base/BaseFragment.kt b/app/src/main/java/io/github/amirisback/androidapp/common/base/BaseFragment.kt similarity index 60% rename from app/src/main/java/com/frogobox/kickstart/common/base/BaseFragment.kt rename to app/src/main/java/io/github/amirisback/androidapp/common/base/BaseFragment.kt index 78e7e5c..ec9924f 100644 --- a/app/src/main/java/com/frogobox/kickstart/common/base/BaseFragment.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/common/base/BaseFragment.kt @@ -1,7 +1,6 @@ -package com.frogobox.kickstart.common.base +package io.github.amirisback.androidapp.common.base -import androidx.viewbinding.ViewBinding -import com.frogobox.sdk.view.FrogoBindFragment +import com.frogobox.compose.view.FrogoComposeFragment /** * Created by Faisal Amir @@ -21,11 +20,4 @@ import com.frogobox.sdk.view.FrogoBindFragment * */ -abstract class BaseFragment : FrogoBindFragment(), IBaseFragment { - - - protected val mActivity: BaseActivity<*> by lazy { - (activity as BaseActivity<*>) - } - -} \ No newline at end of file +abstract class BaseFragment : FrogoComposeFragment(), IBaseFragment \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/common/base/BaseViewModel.kt b/app/src/main/java/io/github/amirisback/androidapp/common/base/BaseViewModel.kt similarity index 75% rename from app/src/main/java/com/frogobox/kickstart/common/base/BaseViewModel.kt rename to app/src/main/java/io/github/amirisback/androidapp/common/base/BaseViewModel.kt index 517f471..13e4fc5 100644 --- a/app/src/main/java/com/frogobox/kickstart/common/base/BaseViewModel.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/common/base/BaseViewModel.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.common.base +package io.github.amirisback.androidapp.common.base import com.frogobox.sdk.view.FrogoViewModel @@ -20,10 +20,6 @@ import com.frogobox.sdk.view.FrogoViewModel * */ -open class BaseViewModel() : FrogoViewModel() { - - override fun onClearDisposable() { - super.onClearDisposable() - } +open class BaseViewModel : FrogoViewModel() { } \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/common/base/IBaseActivity.kt b/app/src/main/java/io/github/amirisback/androidapp/common/base/IBaseActivity.kt similarity index 82% rename from app/src/main/java/com/frogobox/kickstart/common/base/IBaseActivity.kt rename to app/src/main/java/io/github/amirisback/androidapp/common/base/IBaseActivity.kt index 7913f05..01060bd 100644 --- a/app/src/main/java/com/frogobox/kickstart/common/base/IBaseActivity.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/common/base/IBaseActivity.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.common.base +package io.github.amirisback.androidapp.common.base /** @@ -14,6 +14,4 @@ package com.frogobox.kickstart.common.base * */ -interface IBaseActivity { - -} \ No newline at end of file +interface IBaseActivity \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/common/base/IBaseFragment.kt b/app/src/main/java/io/github/amirisback/androidapp/common/base/IBaseFragment.kt similarity index 82% rename from app/src/main/java/com/frogobox/kickstart/common/base/IBaseFragment.kt rename to app/src/main/java/io/github/amirisback/androidapp/common/base/IBaseFragment.kt index f9db666..8b1164a 100644 --- a/app/src/main/java/com/frogobox/kickstart/common/base/IBaseFragment.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/common/base/IBaseFragment.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.common.base +package io.github.amirisback.androidapp.common.base /** * Created by Faisal Amir on 13/05/2020 @@ -13,6 +13,4 @@ package com.frogobox.kickstart.common.base * */ -interface IBaseFragment { - -} \ No newline at end of file +interface IBaseFragment \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/common/callback/OnItemClickCallback.kt b/app/src/main/java/io/github/amirisback/androidapp/common/callback/OnItemClickCallback.kt similarity index 65% rename from app/src/main/java/com/frogobox/kickstart/common/callback/OnItemClickCallback.kt rename to app/src/main/java/io/github/amirisback/androidapp/common/callback/OnItemClickCallback.kt index 2c6a77c..0354179 100644 --- a/app/src/main/java/com/frogobox/kickstart/common/callback/OnItemClickCallback.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/common/callback/OnItemClickCallback.kt @@ -1,10 +1,10 @@ -package com.frogobox.kickstart.common.callback +package io.github.amirisback.androidapp.common.callback import android.view.View interface OnItemClickCallback { fun onItemClick(view: View, objects: Any, position: Int?) fun onItemLongClick(view: View, objects: Any, position: Int?) { - throw kotlin.RuntimeException("Stub!"); + throw kotlin.RuntimeException("Stub!") } } \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/common/callback/Resource.kt b/app/src/main/java/io/github/amirisback/androidapp/common/callback/Resource.kt similarity index 90% rename from app/src/main/java/com/frogobox/kickstart/common/callback/Resource.kt rename to app/src/main/java/io/github/amirisback/androidapp/common/callback/Resource.kt index 05524b7..9dbcdb8 100644 --- a/app/src/main/java/com/frogobox/kickstart/common/callback/Resource.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/common/callback/Resource.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.common.callback +package io.github.amirisback.androidapp.common.callback /** * Created by faisalamircs on 09/09/2025 diff --git a/app/src/main/java/com/frogobox/kickstart/common/ext/BuildConfigExt.kt b/app/src/main/java/io/github/amirisback/androidapp/common/ext/BuildConfigExt.kt similarity index 84% rename from app/src/main/java/com/frogobox/kickstart/common/ext/BuildConfigExt.kt rename to app/src/main/java/io/github/amirisback/androidapp/common/ext/BuildConfigExt.kt index 72ea101..6b73588 100644 --- a/app/src/main/java/com/frogobox/kickstart/common/ext/BuildConfigExt.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/common/ext/BuildConfigExt.kt @@ -1,6 +1,6 @@ -package com.frogobox.kickstart.common.ext +package io.github.amirisback.androidapp.common.ext -import com.frogobox.kickstart.BuildConfig +import io.github.amirisback.androidapp.BuildConfig /** * Created by faisalamir on 21/04/22 diff --git a/app/src/main/java/com/frogobox/kickstart/common/ext/GsonExt.kt b/app/src/main/java/io/github/amirisback/androidapp/common/ext/GsonExt.kt similarity index 89% rename from app/src/main/java/com/frogobox/kickstart/common/ext/GsonExt.kt rename to app/src/main/java/io/github/amirisback/androidapp/common/ext/GsonExt.kt index 38049e7..f65be73 100644 --- a/app/src/main/java/com/frogobox/kickstart/common/ext/GsonExt.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/common/ext/GsonExt.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.common.ext +package io.github.amirisback.androidapp.common.ext import com.google.gson.Gson import com.google.gson.reflect.TypeToken diff --git a/app/src/main/java/com/frogobox/kickstart/di/DatabaseModule.kt b/app/src/main/java/io/github/amirisback/androidapp/di/DatabaseModule.kt similarity index 76% rename from app/src/main/java/com/frogobox/kickstart/di/DatabaseModule.kt rename to app/src/main/java/io/github/amirisback/androidapp/di/DatabaseModule.kt index adfccb9..29e8388 100644 --- a/app/src/main/java/com/frogobox/kickstart/di/DatabaseModule.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/di/DatabaseModule.kt @@ -1,13 +1,13 @@ -package com.frogobox.kickstart.di +package io.github.amirisback.androidapp.di import android.content.Context -import com.frogobox.kickstart.domain.db.ProjectDatabase -import com.frogobox.kickstart.domain.db.dao.MealDao import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import io.github.amirisback.androidapp.domain.db.ProjectDatabase +import io.github.amirisback.androidapp.domain.db.dao.MealDao import javax.inject.Singleton @Module diff --git a/app/src/main/java/com/frogobox/kickstart/di/NetworkModule.kt b/app/src/main/java/io/github/amirisback/androidapp/di/NetworkModule.kt similarity index 90% rename from app/src/main/java/com/frogobox/kickstart/di/NetworkModule.kt rename to app/src/main/java/io/github/amirisback/androidapp/di/NetworkModule.kt index 9a1f5c1..2bdb847 100644 --- a/app/src/main/java/com/frogobox/kickstart/di/NetworkModule.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/di/NetworkModule.kt @@ -1,17 +1,17 @@ -package com.frogobox.kickstart.di +package io.github.amirisback.androidapp.di import android.content.Context import com.chuckerteam.chucker.api.ChuckerInterceptor -import com.frogobox.kickstart.domain.source.meal.MealApiService -import com.frogobox.kickstart.common.ext.appIsDebug -import com.frogobox.kickstart.util.Constant -import com.frogobox.kickstart.domain.source.meal.MealUrl import com.google.gson.GsonBuilder import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import io.github.amirisback.androidapp.common.ext.appIsDebug +import io.github.amirisback.androidapp.domain.source.meal.MealApiService +import io.github.amirisback.androidapp.domain.source.meal.MealUrl +import io.github.amirisback.androidapp.util.Constant import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.ResponseBody diff --git a/app/src/main/java/com/frogobox/kickstart/di/RepositoryModule.kt b/app/src/main/java/io/github/amirisback/androidapp/di/RepositoryModule.kt similarity index 73% rename from app/src/main/java/com/frogobox/kickstart/di/RepositoryModule.kt rename to app/src/main/java/io/github/amirisback/androidapp/di/RepositoryModule.kt index 5ed77d8..ddae316 100644 --- a/app/src/main/java/com/frogobox/kickstart/di/RepositoryModule.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/di/RepositoryModule.kt @@ -1,11 +1,11 @@ -package com.frogobox.kickstart.di +package io.github.amirisback.androidapp.di -import com.frogobox.kickstart.domain.source.meal.repository.MealRepository -import com.frogobox.kickstart.domain.source.meal.repository.MealRepositoryImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import io.github.amirisback.androidapp.domain.source.meal.repository.MealRepository +import io.github.amirisback.androidapp.domain.source.meal.repository.MealRepositoryImpl /** * Created by faisalamircs on 09/09/2025 diff --git a/app/src/main/java/com/frogobox/kickstart/di/UseCaseModule.kt b/app/src/main/java/io/github/amirisback/androidapp/di/UseCaseModule.kt similarity index 75% rename from app/src/main/java/com/frogobox/kickstart/di/UseCaseModule.kt rename to app/src/main/java/io/github/amirisback/androidapp/di/UseCaseModule.kt index d745dff..ea0be8c 100644 --- a/app/src/main/java/com/frogobox/kickstart/di/UseCaseModule.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/di/UseCaseModule.kt @@ -1,12 +1,12 @@ -package com.frogobox.kickstart.di +package io.github.amirisback.androidapp.di -import com.frogobox.kickstart.domain.source.meal.usecase.MealInteractor -import com.frogobox.kickstart.domain.source.meal.usecase.MealUseCase import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.android.components.ViewModelComponent import dagger.hilt.android.scopes.ViewModelScoped +import io.github.amirisback.androidapp.domain.source.meal.usecase.MealInteractor +import io.github.amirisback.androidapp.domain.source.meal.usecase.MealUseCase /** * Created by faisalamircs on 09/09/2025 diff --git a/app/src/main/java/com/frogobox/kickstart/domain/db/DBConfig.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/db/DBConfig.kt similarity index 87% rename from app/src/main/java/com/frogobox/kickstart/domain/db/DBConfig.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/db/DBConfig.kt index 7130eab..0365eac 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/db/DBConfig.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/db/DBConfig.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.domain.db +package io.github.amirisback.androidapp.domain.db /** * Created by faisalamircs on 09/09/2025 diff --git a/app/src/main/java/com/frogobox/kickstart/domain/db/ProjectDatabase.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/db/ProjectDatabase.kt similarity index 85% rename from app/src/main/java/com/frogobox/kickstart/domain/db/ProjectDatabase.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/db/ProjectDatabase.kt index a9d0264..a07ad6b 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/db/ProjectDatabase.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/db/ProjectDatabase.kt @@ -1,13 +1,13 @@ -package com.frogobox.kickstart.domain.db +package io.github.amirisback.androidapp.domain.db import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase -import com.frogobox.kickstart.common.ext.appDatabaseName -import com.frogobox.kickstart.common.ext.appIsDebug -import com.frogobox.kickstart.domain.db.dao.MealDao -import com.frogobox.kickstart.domain.model.MealModel +import io.github.amirisback.androidapp.common.ext.appDatabaseName +import io.github.amirisback.androidapp.common.ext.appIsDebug +import io.github.amirisback.androidapp.domain.db.dao.MealDao +import io.github.amirisback.androidapp.domain.model.MealModel /** * Created by Faisal Amir diff --git a/app/src/main/java/com/frogobox/kickstart/domain/db/dao/MealDao.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/db/dao/MealDao.kt similarity index 82% rename from app/src/main/java/com/frogobox/kickstart/domain/db/dao/MealDao.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/db/dao/MealDao.kt index dcea455..89c6911 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/db/dao/MealDao.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/db/dao/MealDao.kt @@ -1,11 +1,10 @@ -package com.frogobox.kickstart.domain.db.dao +package io.github.amirisback.androidapp.domain.db.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.Query -import com.frogobox.kickstart.domain.db.DBConfig.TABLE_MEALS -import com.frogobox.kickstart.domain.model.MealModel -import kotlin.collections.List +import io.github.amirisback.androidapp.domain.db.DBConfig.TABLE_MEALS +import io.github.amirisback.androidapp.domain.model.MealModel /** * Created by faisalamircs on 10/09/2025 diff --git a/app/src/main/java/com/frogobox/kickstart/domain/model/AreaModel.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/model/AreaModel.kt similarity index 91% rename from app/src/main/java/com/frogobox/kickstart/domain/model/AreaModel.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/model/AreaModel.kt index 90e947e..6740300 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/model/AreaModel.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/model/AreaModel.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.domain.model +package io.github.amirisback.androidapp.domain.model import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/com/frogobox/kickstart/domain/model/CategoryModel.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/model/CategoryModel.kt similarity index 94% rename from app/src/main/java/com/frogobox/kickstart/domain/model/CategoryModel.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/model/CategoryModel.kt index 7f73c38..4b70dee 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/model/CategoryModel.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/model/CategoryModel.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.domain.model +package io.github.amirisback.androidapp.domain.model import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/com/frogobox/kickstart/domain/model/IngredientModel.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/model/IngredientModel.kt similarity index 93% rename from app/src/main/java/com/frogobox/kickstart/domain/model/IngredientModel.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/model/IngredientModel.kt index 30e18af..6c170c6 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/model/IngredientModel.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/model/IngredientModel.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.domain.model +package io.github.amirisback.androidapp.domain.model import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/com/frogobox/kickstart/domain/model/MealFilterModel.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/model/MealFilterModel.kt similarity index 93% rename from app/src/main/java/com/frogobox/kickstart/domain/model/MealFilterModel.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/model/MealFilterModel.kt index 4d40ad8..662f00f 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/model/MealFilterModel.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/model/MealFilterModel.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.domain.model +package io.github.amirisback.androidapp.domain.model import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/com/frogobox/kickstart/domain/model/MealModel.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/model/MealModel.kt similarity index 98% rename from app/src/main/java/com/frogobox/kickstart/domain/model/MealModel.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/model/MealModel.kt index f188cd7..3810076 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/model/MealModel.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/model/MealModel.kt @@ -1,10 +1,10 @@ -package com.frogobox.kickstart.domain.model +package io.github.amirisback.androidapp.domain.model import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import com.frogobox.kickstart.domain.db.DBConfig.TABLE_MEALS import com.google.gson.annotations.SerializedName +import io.github.amirisback.androidapp.domain.db.DBConfig.TABLE_MEALS /** * Created by Faisal Amir diff --git a/app/src/main/java/com/frogobox/kickstart/domain/response/CategoryResponse.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/response/CategoryResponse.kt similarity index 84% rename from app/src/main/java/com/frogobox/kickstart/domain/response/CategoryResponse.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/response/CategoryResponse.kt index 234cda6..71f51fd 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/response/CategoryResponse.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/response/CategoryResponse.kt @@ -1,7 +1,7 @@ -package com.frogobox.kickstart.domain.response +package io.github.amirisback.androidapp.domain.response -import com.frogobox.kickstart.domain.model.CategoryModel import com.google.gson.annotations.SerializedName +import io.github.amirisback.androidapp.domain.model.CategoryModel /** * Created by Faisal Amir diff --git a/app/src/main/java/com/frogobox/kickstart/domain/response/MealResponse.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/response/MealResponse.kt similarity index 91% rename from app/src/main/java/com/frogobox/kickstart/domain/response/MealResponse.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/response/MealResponse.kt index 473873c..86d34a7 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/response/MealResponse.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/response/MealResponse.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.domain.response +package io.github.amirisback.androidapp.domain.response import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealApiService.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealApiService.kt similarity index 64% rename from app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealApiService.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealApiService.kt index 3b7575c..9370566 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealApiService.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealApiService.kt @@ -1,25 +1,25 @@ -package com.frogobox.kickstart.domain.source.meal +package io.github.amirisback.androidapp.domain.source.meal -import com.frogobox.kickstart.domain.model.AreaModel -import com.frogobox.kickstart.domain.model.CategoryModel -import com.frogobox.kickstart.domain.response.CategoryResponse -import com.frogobox.kickstart.domain.model.IngredientModel -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.kickstart.domain.model.MealFilterModel -import com.frogobox.kickstart.domain.response.MealResponse -import com.frogobox.kickstart.domain.source.meal.MealConstant.PATH_API_KEY -import com.frogobox.kickstart.domain.source.meal.MealConstant.QUERY_AREA -import com.frogobox.kickstart.domain.source.meal.MealConstant.QUERY_CATEGORY -import com.frogobox.kickstart.domain.source.meal.MealConstant.QUERY_FIRST_LETTER -import com.frogobox.kickstart.domain.source.meal.MealConstant.QUERY_ID -import com.frogobox.kickstart.domain.source.meal.MealConstant.QUERY_INGREDIENT -import com.frogobox.kickstart.domain.source.meal.MealConstant.QUERY_NAME -import com.frogobox.kickstart.domain.source.meal.MealUrl.URL_CATEGORIES -import com.frogobox.kickstart.domain.source.meal.MealUrl.URL_FILTER -import com.frogobox.kickstart.domain.source.meal.MealUrl.URL_LIST -import com.frogobox.kickstart.domain.source.meal.MealUrl.URL_LOOKUP_MEAL -import com.frogobox.kickstart.domain.source.meal.MealUrl.URL_RANDOM_MEAL -import com.frogobox.kickstart.domain.source.meal.MealUrl.URL_SEARCH_MEAL +import io.github.amirisback.androidapp.domain.model.AreaModel +import io.github.amirisback.androidapp.domain.model.CategoryModel +import io.github.amirisback.androidapp.domain.model.IngredientModel +import io.github.amirisback.androidapp.domain.model.MealFilterModel +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.domain.response.CategoryResponse +import io.github.amirisback.androidapp.domain.response.MealResponse +import io.github.amirisback.androidapp.domain.source.meal.MealConstant.PATH_API_KEY +import io.github.amirisback.androidapp.domain.source.meal.MealConstant.QUERY_AREA +import io.github.amirisback.androidapp.domain.source.meal.MealConstant.QUERY_CATEGORY +import io.github.amirisback.androidapp.domain.source.meal.MealConstant.QUERY_FIRST_LETTER +import io.github.amirisback.androidapp.domain.source.meal.MealConstant.QUERY_ID +import io.github.amirisback.androidapp.domain.source.meal.MealConstant.QUERY_INGREDIENT +import io.github.amirisback.androidapp.domain.source.meal.MealConstant.QUERY_NAME +import io.github.amirisback.androidapp.domain.source.meal.MealUrl.URL_CATEGORIES +import io.github.amirisback.androidapp.domain.source.meal.MealUrl.URL_FILTER +import io.github.amirisback.androidapp.domain.source.meal.MealUrl.URL_LIST +import io.github.amirisback.androidapp.domain.source.meal.MealUrl.URL_LOOKUP_MEAL +import io.github.amirisback.androidapp.domain.source.meal.MealUrl.URL_RANDOM_MEAL +import io.github.amirisback.androidapp.domain.source.meal.MealUrl.URL_SEARCH_MEAL import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path diff --git a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealConstant.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealConstant.kt similarity index 93% rename from app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealConstant.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealConstant.kt index 01c6eea..22f005b 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealConstant.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealConstant.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.domain.source.meal +package io.github.amirisback.androidapp.domain.source.meal /** * Created by Faisal Amir diff --git a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealDaoSource.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealDaoSource.kt similarity index 91% rename from app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealDaoSource.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealDaoSource.kt index 96b4e37..ccd0e7a 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealDaoSource.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealDaoSource.kt @@ -1,8 +1,8 @@ -package com.frogobox.kickstart.domain.source.meal +package io.github.amirisback.androidapp.domain.source.meal -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.domain.db.dao.MealDao -import com.frogobox.kickstart.domain.model.MealModel +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.db.dao.MealDao +import io.github.amirisback.androidapp.domain.model.MealModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealDataSource.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealDataSource.kt similarity index 92% rename from app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealDataSource.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealDataSource.kt index 0619eea..9bfa03f 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealDataSource.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealDataSource.kt @@ -1,14 +1,13 @@ -package com.frogobox.kickstart.domain.source.meal - -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.domain.db.dao.MealDao -import com.frogobox.kickstart.domain.model.AreaModel -import com.frogobox.kickstart.domain.model.CategoryModel -import com.frogobox.kickstart.domain.response.CategoryResponse -import com.frogobox.kickstart.domain.model.IngredientModel -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.kickstart.domain.model.MealFilterModel -import com.frogobox.kickstart.domain.response.MealResponse +package io.github.amirisback.androidapp.domain.source.meal + +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.model.AreaModel +import io.github.amirisback.androidapp.domain.model.CategoryModel +import io.github.amirisback.androidapp.domain.model.IngredientModel +import io.github.amirisback.androidapp.domain.model.MealFilterModel +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.domain.response.CategoryResponse +import io.github.amirisback.androidapp.domain.response.MealResponse import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealUrl.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealUrl.kt similarity index 94% rename from app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealUrl.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealUrl.kt index f7eae40..d930948 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/MealUrl.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/MealUrl.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.domain.source.meal +package io.github.amirisback.androidapp.domain.source.meal /** * Created by faisalamir on 27/07/21 diff --git a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/repository/MealRepository.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/repository/MealRepository.kt similarity index 81% rename from app/src/main/java/com/frogobox/kickstart/domain/source/meal/repository/MealRepository.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/repository/MealRepository.kt index bececaf..71eba9e 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/repository/MealRepository.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/repository/MealRepository.kt @@ -1,11 +1,11 @@ -package com.frogobox.kickstart.domain.source.meal.repository - -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.domain.model.AreaModel -import com.frogobox.kickstart.domain.model.CategoryModel -import com.frogobox.kickstart.domain.model.IngredientModel -import com.frogobox.kickstart.domain.model.MealFilterModel -import com.frogobox.kickstart.domain.model.MealModel +package io.github.amirisback.androidapp.domain.source.meal.repository + +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.model.AreaModel +import io.github.amirisback.androidapp.domain.model.CategoryModel +import io.github.amirisback.androidapp.domain.model.IngredientModel +import io.github.amirisback.androidapp.domain.model.MealFilterModel +import io.github.amirisback.androidapp.domain.model.MealModel import kotlinx.coroutines.flow.Flow /** diff --git a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/repository/MealRepositoryImpl.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/repository/MealRepositoryImpl.kt similarity index 92% rename from app/src/main/java/com/frogobox/kickstart/domain/source/meal/repository/MealRepositoryImpl.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/repository/MealRepositoryImpl.kt index 029866c..db1f914 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/repository/MealRepositoryImpl.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/repository/MealRepositoryImpl.kt @@ -1,13 +1,13 @@ -package com.frogobox.kickstart.domain.source.meal.repository - -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.domain.model.AreaModel -import com.frogobox.kickstart.domain.model.CategoryModel -import com.frogobox.kickstart.domain.model.IngredientModel -import com.frogobox.kickstart.domain.model.MealFilterModel -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.kickstart.domain.source.meal.MealDaoSource -import com.frogobox.kickstart.domain.source.meal.MealDataSource +package io.github.amirisback.androidapp.domain.source.meal.repository + +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.model.AreaModel +import io.github.amirisback.androidapp.domain.model.CategoryModel +import io.github.amirisback.androidapp.domain.model.IngredientModel +import io.github.amirisback.androidapp.domain.model.MealFilterModel +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.domain.source.meal.MealDaoSource +import io.github.amirisback.androidapp.domain.source.meal.MealDataSource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject diff --git a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/usecase/MealInteractor.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/usecase/MealInteractor.kt similarity index 82% rename from app/src/main/java/com/frogobox/kickstart/domain/source/meal/usecase/MealInteractor.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/usecase/MealInteractor.kt index 4a5bf9f..9e8e6be 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/usecase/MealInteractor.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/usecase/MealInteractor.kt @@ -1,13 +1,13 @@ -package com.frogobox.kickstart.domain.source.meal.usecase - -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.domain.model.AreaModel -import com.frogobox.kickstart.domain.model.CategoryModel -import com.frogobox.kickstart.domain.model.IngredientModel -import com.frogobox.kickstart.domain.model.MealFilterModel -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.kickstart.domain.source.meal.MealUrl -import com.frogobox.kickstart.domain.source.meal.repository.MealRepository +package io.github.amirisback.androidapp.domain.source.meal.usecase + +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.model.AreaModel +import io.github.amirisback.androidapp.domain.model.CategoryModel +import io.github.amirisback.androidapp.domain.model.IngredientModel +import io.github.amirisback.androidapp.domain.model.MealFilterModel +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.domain.source.meal.MealUrl +import io.github.amirisback.androidapp.domain.source.meal.repository.MealRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject diff --git a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/usecase/MealUseCase.kt b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/usecase/MealUseCase.kt similarity index 80% rename from app/src/main/java/com/frogobox/kickstart/domain/source/meal/usecase/MealUseCase.kt rename to app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/usecase/MealUseCase.kt index 2eaaa44..74cd70d 100644 --- a/app/src/main/java/com/frogobox/kickstart/domain/source/meal/usecase/MealUseCase.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/domain/source/meal/usecase/MealUseCase.kt @@ -1,11 +1,11 @@ -package com.frogobox.kickstart.domain.source.meal.usecase - -import com.frogobox.kickstart.common.callback.Resource -import com.frogobox.kickstart.domain.model.AreaModel -import com.frogobox.kickstart.domain.model.CategoryModel -import com.frogobox.kickstart.domain.model.IngredientModel -import com.frogobox.kickstart.domain.model.MealModel -import com.frogobox.kickstart.domain.model.MealFilterModel +package io.github.amirisback.androidapp.domain.source.meal.usecase + +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.model.AreaModel +import io.github.amirisback.androidapp.domain.model.CategoryModel +import io.github.amirisback.androidapp.domain.model.IngredientModel +import io.github.amirisback.androidapp.domain.model.MealFilterModel +import io.github.amirisback.androidapp.domain.model.MealModel import kotlinx.coroutines.flow.Flow /** diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/components/AppTopAppBar.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/components/AppTopAppBar.kt new file mode 100644 index 0000000..1f9eba0 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/components/AppTopAppBar.kt @@ -0,0 +1,57 @@ +package io.github.amirisback.androidapp.ui.components + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import io.github.amirisback.androidapp.ui.theme.InitTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AppTopAppBar( + title: String, + modifier: Modifier = Modifier, + onBackClick: (() -> Unit)? = null, + colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) +) { + TopAppBar( + title = { Text(text = title) }, + navigationIcon = { + if (onBackClick != null) { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back" + ) + } + } + }, + colors = colors, + modifier = modifier + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview(showBackground = true) +@Composable +fun AppTopAppBarPreview() { + InitTheme { + AppTopAppBar( + title = "App Title", + onBackClick = {} + ) + } +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/components/CommonState.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/components/CommonState.kt new file mode 100644 index 0000000..4bc06b6 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/components/CommonState.kt @@ -0,0 +1,50 @@ +package io.github.amirisback.androidapp.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.github.amirisback.androidapp.R + +@Composable +fun LoadingIndicator( + modifier: Modifier = Modifier +) { + Box( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } +} + +@Composable +fun ErrorMessage( + message: String, + modifier: Modifier = Modifier +) { + Text( + text = message, + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodyLarge, + modifier = modifier.padding(16.dp) + ) +} + +@Composable +fun EmptyState( + message: String = stringResource(id = R.string.frogo_is_empty_data), + modifier: Modifier = Modifier +) { + Text( + text = message, + style = MaterialTheme.typography.bodyLarge, + modifier = modifier.padding(16.dp) + ) +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/components/MealCard.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/components/MealCard.kt new file mode 100644 index 0000000..954198f --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/components/MealCard.kt @@ -0,0 +1,98 @@ +package io.github.amirisback.androidapp.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.ui.theme.InitTheme + +@Composable +fun MealCard( + meal: MealModel, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + .clickable { onClick() }, + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(meal.strMealThumb) + .crossfade(true) + .build(), + contentDescription = meal.strMeal, + modifier = Modifier + .fillMaxWidth() + .height(128.dp), + contentScale = ContentScale.Crop + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = meal.strMeal ?: "", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = meal.strArea ?: "", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = meal.strCategory ?: "", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun MealCardPreview() { + InitTheme { + MealCard( + meal = MealModel( + idMeal = "1", + strMeal = "Creamy Chicken Pasta", + strMealThumb = "https://www.themealdb.com/images/media/meals/ustsqw1468250014.jpg", + strCategory = "Pasta description here. This is a very delicious and easy meal to make.", + strArea = "Italian" + ), + onClick = {} + ) + } +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/about/AboutUsActivity.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/about/AboutUsActivity.kt new file mode 100644 index 0000000..24226a8 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/about/AboutUsActivity.kt @@ -0,0 +1,48 @@ +package io.github.amirisback.androidapp.ui.features.about + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.enableEdgeToEdge +import androidx.compose.runtime.Composable +import dagger.hilt.android.AndroidEntryPoint +import io.github.amirisback.androidapp.common.base.BaseActivity +import io.github.amirisback.androidapp.ui.theme.InitTheme + +@AndroidEntryPoint +class AboutUsActivity : BaseActivity() { + + companion object { + + fun createIntent(context: Context): Intent { + return Intent(context, AboutUsActivity::class.java) + } + + fun launch(context: Context) { + context.startActivity(createIntent(context)) + } + + } + + override fun setupViewModel() {} + + override fun onCreateExt(savedInstanceState: Bundle?) { + super.onCreateExt(savedInstanceState) + enableEdgeToEdge() + setupToolbar() + } + + @Composable + override fun SetupCompose() { + InitTheme { + AboutUsScreen( + onBackClick = { finish() } + ) + } + } + + private fun setupToolbar() { + supportActionBar?.hide() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/about/AboutUsScreen.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/about/AboutUsScreen.kt new file mode 100644 index 0000000..bbe883f --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/about/AboutUsScreen.kt @@ -0,0 +1,82 @@ +package io.github.amirisback.androidapp.ui.features.about + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.github.amirisback.androidapp.R +import io.github.amirisback.androidapp.ui.components.AppTopAppBar +import io.github.amirisback.androidapp.ui.theme.BrandGreen +import io.github.amirisback.androidapp.ui.theme.InitTheme + +@Composable +fun AboutUsScreen( + onBackClick: () -> Unit, + modifier: Modifier = Modifier +) { + Scaffold( + topBar = { + AppTopAppBar( + title = stringResource(id = R.string.title_about_us), + onBackClick = onBackClick + ) + }, + modifier = modifier + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = R.drawable.ic_frogobox), + contentDescription = "Logo", + modifier = Modifier.size(150.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(id = R.string.about_frogobox), + color = BrandGreen, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } + + Text( + text = stringResource(id = R.string.about_copyright), + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 16.dp) + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun AboutUsScreenPreview() { + InitTheme { + AboutUsScreen(onBackClick = {}) + } +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/detail/DetailActivity.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/detail/DetailActivity.kt new file mode 100644 index 0000000..54b448f --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/detail/DetailActivity.kt @@ -0,0 +1,147 @@ +package io.github.amirisback.androidapp.ui.detail + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.frogobox.sdk.ext.getExtraExt +import com.frogobox.sdk.ext.showToast +import com.frogobox.sdk.ext.toJson +import dagger.hilt.android.AndroidEntryPoint +import io.github.amirisback.androidapp.common.base.BaseActivity +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.ui.features.detail.DetailScreen +import io.github.amirisback.androidapp.ui.theme.InitTheme +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class DetailActivity : BaseActivity() { + + companion object { + const val EXTRA_DATA = "EXTRA_DATA" + + fun createIntent(context: Context, data: MealModel): Intent { + return Intent(context, DetailActivity::class.java).apply { + putExtra(EXTRA_DATA, data.toJson()) + } + } + + fun launch(context: Context, data: MealModel) { + context.startActivity(createIntent(context, data)) + } + } + + private val viewModel: DetailViewModel by viewModels() + + private var isLoadingState by mutableStateOf(false) + private var isFavoriteState by mutableStateOf(false) + + override fun setupViewModel() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.mealsState.collect { resource -> + when (resource) { + is Resource.Error -> { + isLoadingState = false + showToast(resource.message.toString()) + } + is Resource.Loading -> { + isLoadingState = true + } + is Resource.Success -> { + isLoadingState = false + resource.data?.let { items -> + if (items.isNotEmpty()) { + isFavoriteState = true + } + } + } + } + } + } + + launch { + viewModel.insertState.collect { resource -> + when (resource) { + is Resource.Error -> { + isLoadingState = false + showToast(resource.message.toString()) + } + is Resource.Loading -> { + isLoadingState = true + } + is Resource.Success -> { + isLoadingState = false + isFavoriteState = true + showToast("Berhasil Menambahkan Ke Favorite ${resource.data?.strMeal}") + } + null -> {} + } + } + } + + launch { + viewModel.deleteState.collect { resource -> + when (resource) { + is Resource.Error -> { + isLoadingState = false + showToast(resource.message.toString()) + } + is Resource.Loading -> { + isLoadingState = true + } + is Resource.Success -> { + isLoadingState = false + finish() + } + null -> {} + } + } + } + } + } + } + + override fun onCreateExt(savedInstanceState: Bundle?) { + super.onCreateExt(savedInstanceState) + enableEdgeToEdge() + setupToolbar() + + val extra = getExtraExt(EXTRA_DATA) + if (extra == null) { + showToast("Data meal tidak ditemukan") + finish() + return + } + viewModel.mealModel = extra + viewModel.getData() + } + + @Composable + override fun SetupCompose() { + InitTheme { + DetailScreen( + meal = viewModel.mealModel, + isLoading = isLoadingState, + isFavorite = isFavoriteState, + onBackClick = { finish() }, + onInsertClick = { viewModel.insertToDB() }, + onDeleteClick = { viewModel.removeFromDb() } + ) + } + } + + private fun setupToolbar() { + supportActionBar?.hide() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/detail/DetailScreen.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/detail/DetailScreen.kt new file mode 100644 index 0000000..af7ec5b --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/detail/DetailScreen.kt @@ -0,0 +1,172 @@ +package io.github.amirisback.androidapp.ui.features.detail + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.ui.components.AppTopAppBar +import io.github.amirisback.androidapp.ui.components.LoadingIndicator +import io.github.amirisback.androidapp.ui.theme.InitTheme + +@Composable +fun DetailScreen( + meal: MealModel?, + isLoading: Boolean, + isFavorite: Boolean, + onBackClick: () -> Unit, + onInsertClick: () -> Unit, + onDeleteClick: () -> Unit, + modifier: Modifier = Modifier +) { + Scaffold( + topBar = { + AppTopAppBar( + title = "Detail Meals", + onBackClick = onBackClick + ) + }, + modifier = modifier + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + if (meal != null) { + DetailContent( + meal = meal, + isFavorite = isFavorite, + onInsertClick = onInsertClick, + onDeleteClick = onDeleteClick, + modifier = Modifier.fillMaxSize() + ) + } + + if (isLoading) { + LoadingIndicator( + modifier = Modifier.fillMaxSize() + ) + } + } + } +} + +@Composable +fun DetailContent( + meal: MealModel, + isFavorite: Boolean, + onInsertClick: () -> Unit, + onDeleteClick: () -> Unit, + modifier: Modifier = Modifier +) { + val scrollState = rememberScrollState() + + Column( + modifier = modifier + .padding(16.dp) + ) { + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(scrollState) + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(meal.strMealThumb) + .crossfade(true) + .build(), + contentDescription = meal.strMeal, + modifier = Modifier + .fillMaxWidth() + .height(350.dp), + contentScale = ContentScale.Crop + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = meal.strMeal ?: "", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = meal.strArea ?: "", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.End + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = meal.strCategory ?: "", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + if (isFavorite) { + OutlinedButton( + onClick = onDeleteClick, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.error + ) + ) { + Text(text = "Remove From Database") + } + } else { + Button( + onClick = onInsertClick, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = "Insert to Database") + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun DetailScreenPreview() { + InitTheme { + DetailScreen( + meal = MealModel( + idMeal = "1", + strMeal = "Creamy Chicken Pasta", + strMealThumb = "https://www.themealdb.com/images/media/meals/ustsqw1468250014.jpg", + strCategory = "Delicious pasta description", + strArea = "Italian" + ), + isLoading = false, + isFavorite = false, + onBackClick = {}, + onInsertClick = {}, + onDeleteClick = {} + ) + } +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/detail/DetailViewModel.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/detail/DetailViewModel.kt new file mode 100644 index 0000000..62059b0 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/detail/DetailViewModel.kt @@ -0,0 +1,56 @@ +package io.github.amirisback.androidapp.ui.detail + +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import io.github.amirisback.androidapp.common.base.BaseViewModel +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.domain.source.meal.usecase.MealUseCase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +@HiltViewModel +class DetailViewModel @Inject constructor( + private val useCase: MealUseCase, +) : BaseViewModel() { + + var mealModel: MealModel? = null + + private val _mealsState = MutableStateFlow>>(Resource.Loading()) + val mealsState: StateFlow>> = _mealsState.asStateFlow() + + private val _insertState = MutableStateFlow?>(null) + val insertState: StateFlow?> = _insertState.asStateFlow() + + private val _deleteState = MutableStateFlow?>(null) + val deleteState: StateFlow?> = _deleteState.asStateFlow() + + fun getData() { + mealModel?.idMeal?.let { id -> + useCase.searchById(id) + .onEach { _mealsState.value = it } + .launchIn(viewModelScope) + } + } + + fun insertToDB() { + mealModel?.let { model -> + useCase.insertToFavorite(model) + .onEach { _insertState.value = it } + .launchIn(viewModelScope) + } + } + + fun removeFromDb() { + mealModel?.idMeal?.let { id -> + useCase.deleteFromMealId(id) + .onEach { _deleteState.value = it } + .launchIn(viewModelScope) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/favorite/FavoriteScreen.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/favorite/FavoriteScreen.kt new file mode 100644 index 0000000..95107af --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/favorite/FavoriteScreen.kt @@ -0,0 +1,68 @@ +package io.github.amirisback.androidapp.ui.features.favorite + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.github.amirisback.androidapp.R +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.ui.components.EmptyState +import io.github.amirisback.androidapp.ui.components.ErrorMessage +import io.github.amirisback.androidapp.ui.components.LoadingIndicator +import io.github.amirisback.androidapp.ui.components.MealCard + +@Composable +fun FavoriteScreen( + viewModel: FavoriteViewModel, + onItemClick: (MealModel) -> Unit, + modifier: Modifier = Modifier +) { + // Refresh favorite list when entering screen + LaunchedEffect(Unit) { + viewModel.getData() + } + + val resourceState by viewModel.mealsState.collectAsState() + + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + when (val state = resourceState) { + is Resource.Loading -> { + LoadingIndicator() + } + is Resource.Error -> { + ErrorMessage(message = state.message ?: stringResource(id = R.string.frogo_is_empty_data)) + } + is Resource.Success -> { + val meals = state.data ?: emptyList() + if (meals.isEmpty()) { + EmptyState() + } else { + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items( + items = meals, + key = { it.idMeal ?: "" } + ) { meal -> + MealCard( + meal = meal, + onClick = { onItemClick(meal) } + ) + } + } + } + } + } + } +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/favorite/FavoriteViewModel.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/favorite/FavoriteViewModel.kt new file mode 100644 index 0000000..a9ebc2a --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/favorite/FavoriteViewModel.kt @@ -0,0 +1,32 @@ +package io.github.amirisback.androidapp.ui.features.favorite + +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import io.github.amirisback.androidapp.common.base.BaseViewModel +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.domain.source.meal.usecase.MealUseCase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +@HiltViewModel +class FavoriteViewModel @Inject constructor( + private val useCase: MealUseCase, +) : BaseViewModel() { + + private val _mealsState = MutableStateFlow>>(Resource.Loading()) + val mealsState: StateFlow>> = _mealsState.asStateFlow() + + fun getData() { + useCase.getAllFavorite() + .onEach { + _mealsState.value = it + } + .launchIn(viewModelScope) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainActivity.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainActivity.kt new file mode 100644 index 0000000..d53dc09 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainActivity.kt @@ -0,0 +1,129 @@ +package io.github.amirisback.androidapp.ui.features.main + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.ActivityResult +import androidx.activity.viewModels +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import dagger.hilt.android.AndroidEntryPoint +import io.github.amirisback.androidapp.R +import io.github.amirisback.androidapp.common.base.BaseActivity +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.ui.components.AppTopAppBar +import io.github.amirisback.androidapp.ui.detail.DetailActivity +import io.github.amirisback.androidapp.ui.features.favorite.FavoriteScreen +import io.github.amirisback.androidapp.ui.features.favorite.FavoriteViewModel +import io.github.amirisback.androidapp.ui.theme.InitTheme + +@AndroidEntryPoint +class MainActivity : BaseActivity() { + + private val favoriteViewModel: FavoriteViewModel by viewModels() + private val mainViewModel: MainViewModel by viewModels() + + enum class Tab(val titleRes: Int, val iconRes: Int) { + MAIN(R.string.title_main, R.drawable.ic_tv), + FAVORITE(R.string.title_fav, R.drawable.ic_favorite) + } + + override fun setupActivityResultExt(result: ActivityResult) { + super.setupActivityResultExt(result) + favoriteViewModel.getData() + } + + override fun setupViewModel() {} + + override fun onCreateExt(savedInstanceState: Bundle?) { + super.onCreateExt(savedInstanceState) + enableEdgeToEdge() + setupToolbar() + mainViewModel.searchMeal("Cream") + } + + @Composable + override fun SetupCompose() { + InitTheme { + MainActivityScreen( + mainViewModel = mainViewModel, + favoriteViewModel = favoriteViewModel, + onItemClick = { meal -> + startActivityResultExt(DetailActivity.createIntent(this, meal)) + } + ) + } + } + + private fun setupToolbar() { + supportActionBar?.hide() + } +} + +@Composable +fun MainActivityScreen( + mainViewModel: MainViewModel, + favoriteViewModel: FavoriteViewModel, + onItemClick: (MealModel) -> Unit +) { + var currentTab by rememberSaveable { mutableStateOf(MainActivity.Tab.MAIN) } + + Scaffold( + topBar = { + AppTopAppBar( + title = stringResource(id = currentTab.titleRes) + ) + }, + bottomBar = { + NavigationBar { + MainActivity.Tab.entries.forEach { tab -> + NavigationBarItem( + selected = currentTab == tab, + onClick = { currentTab = tab }, + label = { Text(text = stringResource(id = tab.titleRes)) }, + icon = { + Icon( + painter = painterResource(id = tab.iconRes), + contentDescription = stringResource(id = tab.titleRes) + ) + } + ) + } + } + } + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + when (currentTab) { + MainActivity.Tab.MAIN -> { + MainScreen( + viewModel = mainViewModel, + onItemClick = onItemClick + ) + } + MainActivity.Tab.FAVORITE -> { + FavoriteScreen( + viewModel = favoriteViewModel, + onItemClick = onItemClick + ) + } + } + } + } +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainScreen.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainScreen.kt new file mode 100644 index 0000000..669cbe5 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainScreen.kt @@ -0,0 +1,96 @@ +package io.github.amirisback.androidapp.ui.features.main + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import io.github.amirisback.androidapp.domain.model.MealModel +import io.github.amirisback.androidapp.ui.components.ErrorMessage +import io.github.amirisback.androidapp.ui.components.LoadingIndicator +import io.github.amirisback.androidapp.ui.components.MealCard +import io.github.amirisback.androidapp.ui.theme.InitTheme + +@Composable +fun MainScreen( + viewModel: MainViewModel, + onItemClick: (MealModel) -> Unit, + modifier: Modifier = Modifier +) { + val uiState by viewModel.uiState.collectAsState() + + MainContent( + uiState = uiState, + onItemClick = onItemClick, + modifier = modifier + ) +} + +@Composable +fun MainContent( + uiState: MainUiState, + onItemClick: (MealModel) -> Unit, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + when (uiState) { + is MainUiState.Loading -> { + LoadingIndicator() + } + is MainUiState.Error -> { + ErrorMessage(message = uiState.message) + } + is MainUiState.Success -> { + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items( + items = uiState.meals, + key = { it.idMeal ?: "" } + ) { meal -> + MealCard( + meal = meal, + onClick = { onItemClick(meal) } + ) + } + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun MainContentSuccessPreview() { + InitTheme { + MainContent( + uiState = MainUiState.Success( + meals = listOf( + MealModel( + idMeal = "1", + strMeal = "Creamy Chicken Pasta", + strMealThumb = "https://www.themealdb.com/images/media/meals/ustsqw1468250014.jpg", + strCategory = "Pasta description here. This is a very delicious and easy meal to make.", + strArea = "Italian" + ), + MealModel( + idMeal = "2", + strMeal = "Beef Wellington", + strMealThumb = "https://www.themealdb.com/images/media/meals/ustsqw1468250014.jpg", + strCategory = "Beef description here.", + strArea = "British" + ) + ) + ), + onItemClick = {} + ) + } +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainUiState.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainUiState.kt new file mode 100644 index 0000000..72b6d84 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainUiState.kt @@ -0,0 +1,9 @@ +package io.github.amirisback.androidapp.ui.features.main + +import io.github.amirisback.androidapp.domain.model.MealModel + +sealed interface MainUiState { + object Loading : MainUiState + data class Success(val meals: List) : MainUiState + data class Error(val message: String) : MainUiState +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainViewModel.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainViewModel.kt new file mode 100644 index 0000000..04bafc3 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/features/main/MainViewModel.kt @@ -0,0 +1,34 @@ +package io.github.amirisback.androidapp.ui.features.main + +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import io.github.amirisback.androidapp.common.base.BaseViewModel +import io.github.amirisback.androidapp.common.callback.Resource +import io.github.amirisback.androidapp.domain.source.meal.usecase.MealUseCase +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +@HiltViewModel +class MainViewModel @Inject constructor( + private val useCase: MealUseCase, +) : BaseViewModel() { + + private val _uiState = MutableStateFlow(MainUiState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() + + fun searchMeal(name: String = "") { + useCase.searchMeal(nameMeal = name) + .onEach { resource -> + _uiState.value = when (resource) { + is Resource.Loading -> MainUiState.Loading + is Resource.Success -> MainUiState.Success(resource.data ?: emptyList()) + is Resource.Error -> MainUiState.Error(resource.message ?: "Unknown Error") + } + } + .launchIn(viewModelScope) + } +} diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/theme/Color.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/theme/Color.kt new file mode 100644 index 0000000..f4f6628 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/theme/Color.kt @@ -0,0 +1,13 @@ +package io.github.amirisback.androidapp.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) + +val BrandGreen = Color(0xFF00C853) \ No newline at end of file diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/theme/Theme.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/theme/Theme.kt new file mode 100644 index 0000000..db8bce0 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/theme/Theme.kt @@ -0,0 +1,47 @@ +package io.github.amirisback.androidapp.ui.theme + +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 +) + +@Composable +fun InitTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/io/github/amirisback/androidapp/ui/theme/Type.kt b/app/src/main/java/io/github/amirisback/androidapp/ui/theme/Type.kt new file mode 100644 index 0000000..2afa009 --- /dev/null +++ b/app/src/main/java/io/github/amirisback/androidapp/ui/theme/Type.kt @@ -0,0 +1,18 @@ +package io.github.amirisback.androidapp.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) +) \ No newline at end of file diff --git a/app/src/main/java/com/frogobox/kickstart/util/Constant.kt b/app/src/main/java/io/github/amirisback/androidapp/util/Constant.kt similarity index 95% rename from app/src/main/java/com/frogobox/kickstart/util/Constant.kt rename to app/src/main/java/io/github/amirisback/androidapp/util/Constant.kt index 9f599e1..25d4d70 100644 --- a/app/src/main/java/com/frogobox/kickstart/util/Constant.kt +++ b/app/src/main/java/io/github/amirisback/androidapp/util/Constant.kt @@ -1,4 +1,4 @@ -package com.frogobox.kickstart.util +package io.github.amirisback.androidapp.util import android.os.Environment diff --git a/app/src/main/res/layout/activity_about_us.xml b/app/src/main/res/layout/activity_about_us.xml deleted file mode 100644 index 64df565..0000000 --- a/app/src/main/res/layout/activity_about_us.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_detail.xml b/app/src/main/res/layout/activity_detail.xml deleted file mode 100644 index c4f31da..0000000 --- a/app/src/main/res/layout/activity_detail.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - -