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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index fa4488d..0000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_article_horizontal.xml b/app/src/main/res/layout/content_article_horizontal.xml
deleted file mode 100644
index 2c3fd32..0000000
--- a/app/src/main/res/layout/content_article_horizontal.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_article_vertical.xml b/app/src/main/res/layout/content_article_vertical.xml
deleted file mode 100644
index 1c5e967..0000000
--- a/app/src/main/res/layout/content_article_vertical.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_category.xml b/app/src/main/res/layout/content_category.xml
deleted file mode 100644
index 7033a3a..0000000
--- a/app/src/main/res/layout/content_category.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_consumable.xml b/app/src/main/res/layout/fragment_consumable.xml
deleted file mode 100644
index 4824e05..0000000
--- a/app/src/main/res/layout/fragment_consumable.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_favorite.xml b/app/src/main/res/layout/fragment_favorite.xml
deleted file mode 100644
index f6e6591..0000000
--- a/app/src/main/res/layout/fragment_favorite.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
deleted file mode 100644
index 1daffcd..0000000
--- a/app/src/main/res/layout/fragment_main.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6d747c8..af8c67f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -29,6 +29,7 @@
Main
Favorite
Consumable
+ Data is empty
diff --git a/build.gradle.kts b/build.gradle.kts
index a0a6354..a7ea048 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,6 +6,7 @@ plugins {
alias(libs.plugins.kotlinParcelize) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.hilt) apply false
+ alias(libs.plugins.kotlin.compose) apply false
}
tasks.register("clean", Delete::class) {
diff --git a/buildSrc/src/main/kotlin/ProjectSetting.kt b/buildSrc/src/main/kotlin/ProjectSetting.kt
index 4c15484..359ed55 100644
--- a/buildSrc/src/main/kotlin/ProjectSetting.kt
+++ b/buildSrc/src/main/kotlin/ProjectSetting.kt
@@ -14,10 +14,10 @@
object ProjectSetting {
// Project settings
- const val NAME_APP = "Frogo Kick Start Project"
+ const val NAME_APP = "Kick Start"
- const val APP_DOMAIN = "com"
- const val APP_PLAY_CONSOLE = "frogobox"
+ const val APP_DOMAIN = "io.github"
+ const val APP_PLAY_CONSOLE = "amirisback"
// ---------------------------------------------------------------------------------------------
@@ -35,7 +35,7 @@ object ProjectSetting {
// ---------------------------------------------------------------------------------------------
const val PROJECT_MIN_SDK = 23
- const val PROJECT_COMPILE_SDK = 36
+ const val PROJECT_COMPILE_SDK = 37
const val PROJECT_TARGET_SDK = PROJECT_COMPILE_SDK
// ---------------------------------------------------------------------------------------------
diff --git a/gradle.properties b/gradle.properties
index 622ef4a..8397f1b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,13 +15,10 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
-# Automatically convert third-party libraries to use AndroidX
-android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Software Components will not be created automatically for Maven publishing from Android Gradle Plugin 8.0. To opt-in to the future behavior, set the Gradle property
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
-android.nonFinalResIds=false
\ No newline at end of file
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9f9db12..a53ac34 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,35 +1,34 @@
[versions]
-agp = "9.2.0"
-kotlin = "2.3.20"
-ksp = "2.3.6"
-hilt = "2.59.2"
-googleServices = "4.4.4"
-crashlytics = "3.0.7"
-
-constraintlayout = "2.2.1"
-liveCycle = "2.10.0"
+agp = "9.3.1"
+kotlin = "2.4.10"
+ksp = "2.3.10"
+hilt = "2.60.1"
+composeBom = "2026.06.01"
+activityCompose = "1.13.0"
+
+liveCycle = "2.11.0"
roomKtx = "2.8.4"
-work = "2.11.2"
-swiperefreshlayout = "1.2.0"
-firebaseBom = "34.12.0"
-glide = "5.0.7"
-balloon = "1.7.6"
+glide = "5.0.9"
chucker = "4.3.1"
-mixpanel = "8.5.2"
-
-frogoAndroid = "2.3.7"
+frogoAndroid = "3.0.5"
+coil = "2.7.0"
[libraries]
# Android Kit
-androidx-work-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" }
-
-androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }
-
-androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-lifecycle-compiler = { group = "androidx.lifecycle", name = "lifecycle-compiler", version.ref = "liveCycle" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "roomKtx" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
+androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "liveCycle" }
+androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "liveCycle" }
+androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
@@ -37,22 +36,15 @@ hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", v
github-chucker = { group = "com.github.chuckerteam.chucker", name = "library", version.ref = "chucker" }
github-chucker-no-op = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" }
-firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
-firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" }
-firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" }
-
# --------------------------------------------------------------------------------------------------
# Test Library
# --------------------------------------------------------------------------------------------------
# Library
-
-mixpanel = { group = "com.mixpanel.android", name = "mixpanel-android", version.ref = "mixpanel" }
-
github-glide-compiler = { group = "com.github.bumptech.glide", name = "ksp", version.ref = "glide" }
-github-balloon = { group = "com.github.skydoves", name = "balloon", version.ref = "balloon" }
frogo-android = { group = "com.github.frogobox", name = "frogo-sdk", version.ref = "frogoAndroid" }
+coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
# --------------------------------------------------------------------------------------------------
@@ -63,5 +55,4 @@ android-dynamic-feature = { id = "com.android.dynamic-feature", version.ref = "a
kotlinParcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
-gms-google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
-firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" }
+kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2c41eef..55c91d8 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
#Thu Apr 23 15:08:06 WIB 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
+distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/prompt_ai/main-to-compose.md b/prompt_ai/main-to-compose.md
new file mode 100644
index 0000000..2647f05
--- /dev/null
+++ b/prompt_ai/main-to-compose.md
@@ -0,0 +1,118 @@
+# ROLE & SKILL
+
+Anda adalah seorang AI Agent Android Developer Senior yang memiliki spesialisasi (skill) dalam memigrasikan sistem UI lama (XML/View-based) ke Jetpack Compose modern. Anda menguasai best practices Android, arsitektur MVVM/MVI, State Management, Kotlin Coroutines, dan optimasi performa UI.
+
+# TUJUAN
+
+Saya ingin memigrasikan komponen UI Android dari View System (XML) ke Jetpack Compose menggunakan skill/tools yang tersedia di sistem ini, dengan mengikuti standar arsitektur modern (MVI/MVVM), Material Design 3, serta memastikan state management terpisah dari UI.
+
+# INPUT DATA
+
+Skill yang wajib di gunakan adalah sebagai berikut:
+
+- .agents\skills\jetpack-compose-m3
+- .agents\skills\migrate-xml-views-to-jetpack-compose
+- .agents\skills\styles
+- .agents\skills\edge-to-edge
+
+Gunakan skill pembaca file / workspace untuk mengambil source code berikut:
+
+1. File Layout XML:
+
+- app\src\main\res\layout\activity_about_us.xml
+
+1. File Activity/Fragment Terkait:
+
+- app\src\main\java\io\github\amirisback\androidapp\ui\about\AboutUsActivity.kt
+
+# INSTRUKSI MIGRASI
+
+Mohon proses file di atas dan buatkan kode Jetpack Compose dengan ketentuan sebagai berikut:
+
+1. Komponen UI & Layouting:
+ - Konversikan ViewGroup (ConstraintLayout, LinearLayout, RelativeLayout) ke Composable yang setara (Box, Column, Row, LazyColumn, atau ConstraintLayout Compose jika sangat kompleks).
+ - Gunakan komponen Material Design 3 (Button, OutlinedTextField, Card, Text, dll.).
+ - ConstraintLayout XML -> ConstraintLayout Compose (hanya jika kompleks) atau optimalkan menggunakan Row/Column/Box standar.
+ - RecyclerView -> LazyColumn / LazyRow.
+ - ImageView -> AsyncImage (Coil) jika memuat gambar dari URL.
+
+2. Styling & Resources:
+ - Gunakan `stringResource()`, `painterResource()`, dan `dimensionResource()` untuk menjaga modularitas resource.
+ - Sesuaikan warna dan tipografi menggunakan objek `MaterialTheme`.
+ - Styling & Themes: Gunakan token dari `MaterialTheme` (color, typography, shapes) alih-alih hardcoded colors/dimens dari XML, kecuali jika saya sebutkan lain.
+
+3. State & Event Handling:
+ - UI harus bersifat Stateless. Pisahkan State dan Event.
+ - Buat parameter lambda untuk event handling (misal: `onButtonClicked: () -> Unit`).
+ - Integrasikan dengan StateFlow/LiveData dari ViewModel yang ada di file Activity/Fragment asal (gunakan `collectAsStateWithLifecycle()`).
+ - State Management: Ubah semua state UI yang sebelumnya diatur manual (misal: setText(), setVisibility()) menggunakan `MutableState`, `remember`, atau `collectAsStateWithLifecycle()` dari ViewModel jika ada.
+ - Unidirectional Data Flow (UDF): Pastikan Composable bersifat stateless (menggunakan State Hoisting) di mana event dikirim ke atas (callbacks) dan data mengalir ke bawah.
+
+4. Preview:
+ - Sediakan `@Preview` fungsi Composable, lengkap dengan `ShowBackground = true` dan tema default-nya.
+
+5. Performa: Hindari recomposition yang tidak perlu. Gunakan `remember` untuk objek yang berat dan `derivedStateOf` jika ada kalkulasi state turunan.
+
+# OUTPUT YANG DIHARAPKAN
+
+Berikan output berupa full code untuk file Composable baru (`.kt`), serta berikan panduan singkat jika ada dependensi Gradle baru yang perlu ditambahkan atau perubahan minor yang harus saya lakukan di sisi ViewModel/Activity.
+
+---
+
+# STRUCTURE FOLDER
+
+com.example.myapp/
+│
+├── data/ # Data Layer (Agnostik terhadap UI/Compose)
+│ ├── model/ # Data models (DTO, Entity)
+│ ├── repository/ # Implementasi Repository
+│ └── source/ # Local (Room) & Remote (Retrofit/Ktor) data sources
+│
+├── domain/ # Domain Layer (Opsional, untuk business logic kompleks)
+│ ├── model/ # Domain/Business models
+│ └── usecase/ # Use Cases / Interactors
+│
+├── ui/ # UI Layer (Tempat Jetpack Compose berada)
+│ ├── components/ # Global/Shared Composables (Reusable UI)
+│ │ ├── CustomButton.kt
+│ │ └── LoadingScreen.kt
+│ │
+│ ├── theme/ # Design System Tokens (Aksesibilitas global)
+│ │ ├── Color.kt
+│ │ ├── Theme.kt
+│ │ ├── Type.kt
+│ │ └── Shape.kt
+│ │
+│ ├── features/ # Fitur Utama Aplikasi (Feature-by-Package)
+│ │ ├── home/
+│ │ │ ├── HomeScreen.kt # Composable utama untuk fitur Home
+│ │ │ ├── HomeViewModel.kt # State holder untuk Home
+│ │ │ ├── HomeUiState.kt # Data class / Sealed interface penampung State
+│ │ │ └── components/ # Composable lokal yang hanya dipakai di Home
+│ │ │ └── HomeHeader.kt
+│ │ │
+│ │ └── detail/
+│ │ ├── DetailScreen.kt
+│ │ └── DetailViewModel.kt
+│ │
+│ └── navigation/ # Navigasi Aplikasi (Compose Navigation)
+│ ├── NavGraph.kt # Setup NavHost dan composable destinations
+│ └── Destinations.kt # Definisi rute/screen (bisa menggunakan Type-Safe Navigation)
+│
+└── MainActivity.kt # Entry point aplikasi (Entry Point untuk Scaffold/NavHost)
+
+# ANDROID APP ARCHITECTURE
+
+1. File State, ViewModel, dan Screen Berdampingan
+Di Jetpack Compose, UI digerakkan oleh State (UiState). Menyatukan HomeScreen.kt, HomeViewModel.kt, dan HomeUiState.kt di dalam folder fitur yang sama (ui/features/home/) membuat kode jauh lebih mudah dirawat (highly cohesive). Saat Anda mengerjakan fitur Home, Anda tidak perlu melompat-lompat folder dari ujung atas ke ujung bawah proyek.
+
+2. Pemisahan Komponen Global vs Lokal
+ui/components/: Berisi komponen UI generik yang digunakan di banyak layar, seperti custom button, loading spinner, atau error dialog.
+
+ui/features/[nama_fitur]/components/: Berisi komponen yang sangat spesifik dan hanya masuk akal jika berada di layar tersebut. Ini mencegah folder global menjadi terlalu penuh.
+
+1. Paket theme/ yang Sentralized
+Saat Anda membuat proyek baru di Android Studio dengan template Compose, folder ui/theme otomatis dibuat. Tetap pertahankan folder ini karena ia menyimpan konfigurasi MaterialTheme (Warna, Tipografi, dan Bentuk) yang membungkus seluruh aplikasi Anda di MainActivity.
+
+2. Layer Data dan Domain Tetap Bersih dari Compose
+Perlu diingat bahwa Jetpack Compose hanyalah toolkit UI. Folder data/ dan domain/ Anda sama sekali tidak boleh mengimpor library Compose (androidx.compose.*). Mereka murni berisi Kotlin standard/coroutine agar kode backend aplikasi Anda tetap bisa diuji (testable) secara independen.
diff --git a/prompt_ai/template/MIGRATE_COMPOSE.md b/prompt_ai/template/MIGRATE_COMPOSE.md
new file mode 100644
index 0000000..8baf84a
--- /dev/null
+++ b/prompt_ai/template/MIGRATE_COMPOSE.md
@@ -0,0 +1,114 @@
+# ROLE & SKILL
+
+Anda adalah seorang AI Agent Android Developer Senior yang memiliki spesialisasi (skill) dalam memigrasikan sistem UI lama (XML/View-based) ke Jetpack Compose modern. Anda menguasai best practices Android, arsitektur MVVM/MVI, State Management, Kotlin Coroutines, dan optimasi performa UI.
+
+# TUJUAN
+
+Saya ingin memigrasikan komponen UI Android dari View System (XML) ke Jetpack Compose menggunakan skill/tools yang tersedia di sistem ini, dengan mengikuti standar arsitektur modern (MVI/MVVM), Material Design 3, serta memastikan state management terpisah dari UI.
+
+# INPUT DATA
+
+Skill yang wajib di gunakan adalah sebagai berikut:
+
+- .agents\skills\jetpack-compose-m3
+- .agents\skills\migrate-xml-views-to-jetpack-compose
+- .agents\skills\styles
+- .agents\skills\edge-to-edge
+
+Gunakan skill pembaca file / workspace untuk mengambil source code berikut:
+
+1. File Layout XML: [PATH_KE_FILE_XML_ANDA, contoh: res/layout/activity_main.xml]
+2. File Kategori/Style (jikit ada): [PATH_KE_STYLES_XML, contoh: res/values/themes.xml]
+3. File Activity/Fragment Terkait: [PATH_KE_KOTLIN_FILE, contoh: MainActivity.kt]
+
+# INSTRUKSI MIGRASI
+
+Mohon proses file di atas dan buatkan kode Jetpack Compose dengan ketentuan sebagai berikut:
+
+1. Komponen UI & Layouting:
+ - Konversikan ViewGroup (ConstraintLayout, LinearLayout, RelativeLayout) ke Composable yang setara (Box, Column, Row, LazyColumn, atau ConstraintLayout Compose jika sangat kompleks).
+ - Gunakan komponen Material Design 3 (Button, OutlinedTextField, Card, Text, dll.).
+ - ConstraintLayout XML -> ConstraintLayout Compose (hanya jika kompleks) atau optimalkan menggunakan Row/Column/Box standar.
+ - RecyclerView -> LazyColumn / LazyRow.
+ - ImageView -> AsyncImage (Coil) jika memuat gambar dari URL.
+
+2. Styling & Resources:
+ - Gunakan `stringResource()`, `painterResource()`, dan `dimensionResource()` untuk menjaga modularitas resource.
+ - Sesuaikan warna dan tipografi menggunakan objek `MaterialTheme`.
+ - Styling & Themes: Gunakan token dari `MaterialTheme` (color, typography, shapes) alih-alih hardcoded colors/dimens dari XML, kecuali jika saya sebutkan lain.
+
+3. State & Event Handling:
+ - UI harus bersifat Stateless. Pisahkan State dan Event.
+ - Buat parameter lambda untuk event handling (misal: `onButtonClicked: () -> Unit`).
+ - Integrasikan dengan StateFlow/LiveData dari ViewModel yang ada di file Activity/Fragment asal (gunakan `collectAsStateWithLifecycle()`).
+ - State Management: Ubah semua state UI yang sebelumnya diatur manual (misal: setText(), setVisibility()) menggunakan `MutableState`, `remember`, atau `collectAsStateWithLifecycle()` dari ViewModel jika ada.
+ - Unidirectional Data Flow (UDF): Pastikan Composable bersifat stateless (menggunakan State Hoisting) di mana event dikirim ke atas (callbacks) dan data mengalir ke bawah.
+
+4. Preview:
+ - Sediakan `@Preview` fungsi Composable, lengkap dengan `ShowBackground = true` dan tema default-nya.
+
+5. Performa: Hindari recomposition yang tidak perlu. Gunakan `remember` untuk objek yang berat dan `derivedStateOf` jika ada kalkulasi state turunan.
+
+# OUTPUT YANG DIHARAPKAN
+
+Berikan output berupa full code untuk file Composable baru (`.kt`), serta berikan panduan singkat jika ada dependensi Gradle baru yang perlu ditambahkan atau perubahan minor yang harus saya lakukan di sisi ViewModel/Activity.
+
+---
+
+# STRUCTURE FOLDER
+
+com.example.myapp/
+│
+├── data/ # Data Layer (Agnostik terhadap UI/Compose)
+│ ├── model/ # Data models (DTO, Entity)
+│ ├── repository/ # Implementasi Repository
+│ └── source/ # Local (Room) & Remote (Retrofit/Ktor) data sources
+│
+├── domain/ # Domain Layer (Opsional, untuk business logic kompleks)
+│ ├── model/ # Domain/Business models
+│ └── usecase/ # Use Cases / Interactors
+│
+├── ui/ # UI Layer (Tempat Jetpack Compose berada)
+│ ├── components/ # Global/Shared Composables (Reusable UI)
+│ │ ├── CustomButton.kt
+│ │ └── LoadingScreen.kt
+│ │
+│ ├── theme/ # Design System Tokens (Aksesibilitas global)
+│ │ ├── Color.kt
+│ │ ├── Theme.kt
+│ │ ├── Type.kt
+│ │ └── Shape.kt
+│ │
+│ ├── features/ # Fitur Utama Aplikasi (Feature-by-Package)
+│ │ ├── home/
+│ │ │ ├── HomeScreen.kt # Composable utama untuk fitur Home
+│ │ │ ├── HomeViewModel.kt # State holder untuk Home
+│ │ │ ├── HomeUiState.kt # Data class / Sealed interface penampung State
+│ │ │ └── components/ # Composable lokal yang hanya dipakai di Home
+│ │ │ └── HomeHeader.kt
+│ │ │
+│ │ └── detail/
+│ │ ├── DetailScreen.kt
+│ │ └── DetailViewModel.kt
+│ │
+│ └── navigation/ # Navigasi Aplikasi (Compose Navigation)
+│ ├── NavGraph.kt # Setup NavHost dan composable destinations
+│ └── Destinations.kt # Definisi rute/screen (bisa menggunakan Type-Safe Navigation)
+│
+└── MainActivity.kt # Entry point aplikasi (Entry Point untuk Scaffold/NavHost)
+
+# ANDROID APP ARCHITECTURE
+
+1. File State, ViewModel, dan Screen Berdampingan
+Di Jetpack Compose, UI digerakkan oleh State (UiState). Menyatukan HomeScreen.kt, HomeViewModel.kt, dan HomeUiState.kt di dalam folder fitur yang sama (ui/features/home/) membuat kode jauh lebih mudah dirawat (highly cohesive). Saat Anda mengerjakan fitur Home, Anda tidak perlu melompat-lompat folder dari ujung atas ke ujung bawah proyek.
+
+2. Pemisahan Komponen Global vs Lokal
+ui/components/: Berisi komponen UI generik yang digunakan di banyak layar, seperti custom button, loading spinner, atau error dialog.
+
+ui/features/[nama_fitur]/components/: Berisi komponen yang sangat spesifik dan hanya masuk akal jika berada di layar tersebut. Ini mencegah folder global menjadi terlalu penuh.
+
+1. Paket theme/ yang Sentralized
+Saat Anda membuat proyek baru di Android Studio dengan template Compose, folder ui/theme otomatis dibuat. Tetap pertahankan folder ini karena ia menyimpan konfigurasi MaterialTheme (Warna, Tipografi, dan Bentuk) yang membungkus seluruh aplikasi Anda di MainActivity.
+
+2. Layer Data dan Domain Tetap Bersih dari Compose
+Perlu diingat bahwa Jetpack Compose hanyalah toolkit UI. Folder data/ dan domain/ Anda sama sekali tidak boleh mengimpor library Compose (androidx.compose.*). Mereka murni berisi Kotlin standard/coroutine agar kode backend aplikasi Anda tetap bisa diuji (testable) secara independen.