├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── muflidevs │ │ └── dicodingevent │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── muflidevs │ │ │ └── dicodingevent │ │ │ ├── data │ │ │ ├── database │ │ │ │ └── local │ │ │ │ │ ├── entity │ │ │ │ │ └── DetailDataEntity.kt │ │ │ │ │ ├── repository │ │ │ │ │ └── EventRepository.kt │ │ │ │ │ └── room │ │ │ │ │ ├── EventDAO.kt │ │ │ │ │ └── EventRoomDatabase.kt │ │ │ └── remote │ │ │ │ ├── response │ │ │ │ └── EventsResponse.kt │ │ │ │ └── retrofit │ │ │ │ ├── ApiConfig.kt │ │ │ │ └── ApiService.kt │ │ │ ├── fragments │ │ │ ├── FavoriteFragment.kt │ │ │ ├── FinishedFragment.kt │ │ │ ├── HomeFragment.kt │ │ │ └── UpcomingFragments.kt │ │ │ ├── networking │ │ │ └── Network.kt │ │ │ └── ui │ │ │ ├── DetailActivity.kt │ │ │ ├── DetailFavoriteActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── adapter │ │ │ ├── FavoriteListAdapter.kt │ │ │ ├── HorizontalListAdapter.kt │ │ │ ├── SpaceItemDecoration.kt │ │ │ └── VerticalListAdapter.kt │ │ │ ├── settings │ │ │ ├── EventWorkerNotification.kt │ │ │ ├── SettingPreferences.kt │ │ │ └── SettingsActivity.kt │ │ │ └── viewmodel │ │ │ ├── MainSettingsModel.kt │ │ │ ├── MainViewModel.kt │ │ │ ├── MainViewModelFavorite.kt │ │ │ ├── MainViewModelFinish.kt │ │ │ ├── ViewModelFactory.kt │ │ │ └── ViewModelSettingsFactory.kt │ └── res │ │ ├── drawable │ │ ├── back.xml │ │ ├── border_shape.xml │ │ ├── event_finished.xml │ │ ├── event_upcoming.xml │ │ ├── favorite.xml │ │ ├── favorite_added.xml │ │ ├── home.xml │ │ ├── horizontal_shape.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── rounded_header.xml │ │ ├── second_border_shape.xml │ │ └── settings.xml │ │ ├── font │ │ ├── poppins.xml │ │ ├── poppins_bold.xml │ │ ├── poppins_extrabold.xml │ │ ├── poppins_light.xml │ │ └── poppins_thin.xml │ │ ├── layout │ │ ├── activity_detail.xml │ │ ├── activity_detail_favorite.xml │ │ ├── activity_main.xml │ │ ├── activity_settings.xml │ │ ├── fragment_favorite.xml │ │ ├── fragment_finished.xml │ │ ├── fragment_home.xml │ │ ├── fragment_upcoming_fragments.xml │ │ ├── item_horizontal.xml │ │ └── item_vertical.xml │ │ ├── menu │ │ └── bottom_navigation_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ ├── colors.xml │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── font_certs.xml │ │ ├── preloaded_fonts.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── muflidevs │ └── dicodingevent │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | id("kotlin-parcelize") 5 | id("com.google.devtools.ksp") 6 | } 7 | 8 | android { 9 | namespace = "com.muflidevs.dicodingevent" 10 | compileSdk = 34 11 | 12 | defaultConfig { 13 | applicationId = "com.muflidevs.dicodingevent" 14 | minSdk = 24 15 | targetSdk = 34 16 | versionCode = 1 17 | versionName = "1.0" 18 | 19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 20 | } 21 | buildFeatures { 22 | viewBinding = true 23 | } 24 | buildTypes { 25 | release { 26 | isMinifyEnabled = false 27 | proguardFiles( 28 | getDefaultProguardFile("proguard-android-optimize.txt"), 29 | "proguard-rules.pro" 30 | ) 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_1_8 35 | targetCompatibility = JavaVersion.VERSION_1_8 36 | } 37 | kotlinOptions { 38 | jvmTarget = "1.8" 39 | } 40 | } 41 | 42 | dependencies { 43 | //Retrofit 44 | implementation(libs.okhttp3.logging.interceptor) 45 | implementation(libs.retrofit) 46 | implementation(libs.converter.gson) 47 | implementation(libs.glide) 48 | 49 | //room 50 | implementation(libs.androidx.room.runtime) 51 | implementation(libs.room.ktx) 52 | ksp(libs.room.compiler) 53 | 54 | // Data store && coroutine 55 | implementation(libs.androidx.datastore.preferences) 56 | implementation(libs.androidx.lifecycle.viewmodel.ktx) 57 | implementation(libs.jetbrains.kotlinx.coroutines.core) 58 | implementation(libs.kotlinx.coroutines.android.v173) 59 | implementation(libs.androidx.lifecycle.runtime.ktx) 60 | implementation(libs.androidx.lifecycle.livedata.ktx) 61 | 62 | //workmanager 63 | implementation(libs.androidx.work.runtime.ktx) 64 | 65 | implementation(libs.androidx.core) 66 | implementation(libs.androidx.cardview) 67 | implementation(libs.androidx.core.ktx) 68 | implementation(libs.androidx.appcompat) 69 | implementation(libs.material) 70 | implementation(libs.androidx.activity) 71 | implementation(libs.androidx.constraintlayout) 72 | implementation(libs.androidx.navigation.fragment.ktx) 73 | implementation(libs.androidx.navigation.ui.ktx) 74 | testImplementation(libs.junit) 75 | androidTestImplementation(libs.androidx.junit) 76 | androidTestImplementation(libs.androidx.espresso.core) 77 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/muflidevs/dicodingevent/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.muflidevs.dicodingevent", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 18 | 21 | 24 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/data/database/local/entity/DetailDataEntity.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.data.database.local.entity 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | import kotlinx.parcelize.Parcelize 8 | 9 | @Entity(tableName = "detaildata") 10 | @Parcelize 11 | data class DetailDataEntity( 12 | @PrimaryKey 13 | var id: Int? = null, 14 | 15 | @ColumnInfo(name = "name") 16 | var name: String? = null, 17 | 18 | @ColumnInfo(name = "summary") 19 | var summary: String? = null, 20 | 21 | @ColumnInfo(name = "description") 22 | var description: String? = null, 23 | 24 | @ColumnInfo(name = "image_logo") 25 | var imageLogo: String? = null, 26 | 27 | @ColumnInfo(name = "media_cover") 28 | var mediaCover: String? = null, 29 | 30 | @ColumnInfo(name = "category") 31 | var category: String? = null, 32 | 33 | @ColumnInfo(name = "owner_name") 34 | var ownerName: String? = null, 35 | 36 | @ColumnInfo(name = "city_name") 37 | var cityName: String? = null, 38 | 39 | @ColumnInfo(name = "quota") 40 | var quota: Int = 0, 41 | 42 | @ColumnInfo(name = "registrants") 43 | var registrants: Int = 0, 44 | 45 | @ColumnInfo(name = "begin_time") 46 | var beginTime: String? = null, 47 | 48 | @ColumnInfo(name = "end_time") 49 | var endTime: String? = null, 50 | 51 | @ColumnInfo(name = "link") 52 | var link: String? = null 53 | ) : Parcelable 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/data/database/local/repository/EventRepository.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.data.database.local.repository 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.LiveData 5 | import com.muflidevs.dicodingevent.data.database.local.entity.DetailDataEntity 6 | import com.muflidevs.dicodingevent.data.database.local.room.EventDAO 7 | import com.muflidevs.dicodingevent.data.database.local.room.EventRoomDatabase 8 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.withContext 11 | 12 | class EventRepository(application: Application) { 13 | private val mEventDao: EventDAO 14 | 15 | init { 16 | val db = EventRoomDatabase.getDatabase(application) 17 | mEventDao = db.eventDAO() 18 | } 19 | 20 | fun getAllEvent(): LiveData> = mEventDao.getAllEvents() 21 | 22 | suspend fun insert(detailData: DetailDataEntity) { 23 | withContext(Dispatchers.IO) { 24 | mEventDao.insert(detailData) 25 | } 26 | } 27 | 28 | suspend fun delete(detailData: DetailDataEntity) { 29 | withContext(Dispatchers.IO) { 30 | mEventDao.delete(detailData) 31 | } 32 | } 33 | 34 | fun mapRemoteToLocal(detailData: DetailData): DetailDataEntity { 35 | return DetailDataEntity( 36 | id = detailData.id, 37 | name = detailData.name, 38 | summary = detailData.summary, 39 | description = detailData.description, 40 | imageLogo = detailData.imageLogo, 41 | category = detailData.category, 42 | cityName = detailData.cityName, 43 | mediaCover = detailData.mediaCover, 44 | beginTime = detailData.beginTime, 45 | registrants = detailData.registrants, 46 | endTime = detailData.endTime, 47 | link = detailData.link 48 | ) 49 | } 50 | 51 | fun mapRemoteToLocal(detailData: DetailDataEntity): DetailDataEntity { 52 | return DetailDataEntity( 53 | id = detailData.id, 54 | name = detailData.name, 55 | summary = detailData.summary, 56 | description = detailData.description, 57 | imageLogo = detailData.imageLogo, 58 | category = detailData.category, 59 | cityName = detailData.cityName, 60 | mediaCover = detailData.mediaCover, 61 | beginTime = detailData.beginTime, 62 | registrants = detailData.registrants, 63 | endTime = detailData.endTime, 64 | link = detailData.link 65 | ) 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/data/database/local/room/EventDAO.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.data.database.local.room 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Delete 6 | import androidx.room.Insert 7 | import androidx.room.OnConflictStrategy 8 | import androidx.room.Query 9 | import com.muflidevs.dicodingevent.data.database.local.entity.DetailDataEntity 10 | 11 | @Dao 12 | interface EventDAO { 13 | @Insert(onConflict = OnConflictStrategy.IGNORE) 14 | suspend fun insert(detail: DetailDataEntity) 15 | 16 | @Delete 17 | suspend fun delete(detail: DetailDataEntity) 18 | 19 | @Query("SELECT * FROM detaildata") 20 | fun getAllEvents(): LiveData> 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/data/database/local/room/EventRoomDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.data.database.local.room 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import com.muflidevs.dicodingevent.data.database.local.entity.DetailDataEntity 8 | 9 | @Database(entities = [DetailDataEntity::class], version = 2) 10 | abstract class EventRoomDatabase : RoomDatabase() { 11 | abstract fun eventDAO(): EventDAO 12 | 13 | companion object { 14 | @Volatile 15 | private var INSTANCE: EventRoomDatabase? = null 16 | 17 | @JvmStatic 18 | fun getDatabase(context: Context): EventRoomDatabase { 19 | return INSTANCE ?: synchronized(this) { 20 | val instance = Room.databaseBuilder( 21 | context.applicationContext, 22 | EventRoomDatabase::class.java, "event_database" 23 | ) 24 | .fallbackToDestructiveMigration() 25 | .build() 26 | INSTANCE = instance 27 | instance 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/data/remote/response/EventsResponse.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.data.remote.response 2 | 3 | import android.os.Parcelable 4 | import com.google.gson.annotations.SerializedName 5 | import kotlinx.parcelize.Parcelize 6 | 7 | data class EventsResponse( 8 | @field:SerializedName("error") 9 | val errorMsg: String, 10 | 11 | @field:SerializedName("message") 12 | val message: String, 13 | 14 | @field:SerializedName("listEvents") 15 | val data: List 16 | ) 17 | 18 | @Parcelize 19 | data class DetailData( 20 | @field:SerializedName("id") 21 | var id: Int? = null, 22 | 23 | @field:SerializedName("name") 24 | var name: String? = null, 25 | 26 | @field:SerializedName("summary") 27 | var summary: String? = null, 28 | 29 | @field:SerializedName("description") 30 | var description: String? = null, 31 | 32 | @field:SerializedName("imageLogo") 33 | var imageLogo: String? = null, 34 | 35 | @field:SerializedName("mediaCover") 36 | var mediaCover: String? = null, 37 | 38 | @field:SerializedName("category") 39 | var category: String? = null, 40 | 41 | @field:SerializedName("ownerName") 42 | var ownerName: String? = null, 43 | 44 | @field:SerializedName("cityName") 45 | var cityName: String? = null, 46 | 47 | @field:SerializedName("quota") 48 | var quota: Int = 0, 49 | 50 | @field:SerializedName("registrants") 51 | var registrants: Int = 0, 52 | 53 | @field:SerializedName("beginTime") 54 | var beginTime: String? = null, 55 | 56 | @field:SerializedName("endTime") 57 | var endTime: String? = null, 58 | 59 | @field:SerializedName("link") 60 | var link: String? = null 61 | ) : Parcelable 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/data/remote/retrofit/ApiConfig.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.data.remote.retrofit 2 | 3 | import okhttp3.OkHttpClient 4 | import okhttp3.logging.HttpLoggingInterceptor 5 | import retrofit2.Retrofit 6 | import retrofit2.converter.gson.GsonConverterFactory 7 | 8 | class ApiConfig { 9 | companion object { 10 | private const val BASE_URL = "https://event-api.dicoding.dev/" 11 | fun getApiService(): ApiService { 12 | val loggingInterceptor = 13 | HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) 14 | val client = OkHttpClient.Builder() 15 | .addInterceptor(loggingInterceptor).build() 16 | val retrofit = Retrofit.Builder().baseUrl(BASE_URL) 17 | .addConverterFactory(GsonConverterFactory.create()).client(client).build() 18 | 19 | return retrofit.create(ApiService::class.java) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/data/remote/retrofit/ApiService.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.data.remote.retrofit 2 | 3 | import com.muflidevs.dicodingevent.data.remote.response.EventsResponse 4 | import retrofit2.http.GET 5 | import retrofit2.http.Query 6 | 7 | interface ApiService { 8 | @GET("events") 9 | suspend fun getEvents( 10 | @Query("active") active: Int = 1, 11 | @Query("limit") limit: Int = 40, 12 | @Query("q") query: String? = null 13 | ): EventsResponse 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/fragments/FavoriteFragment.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.fragments 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.fragment.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.lifecycle.ViewModelProvider 10 | import androidx.recyclerview.widget.LinearLayoutManager 11 | import com.muflidevs.dicodingevent.databinding.FragmentFavoriteBinding 12 | import com.muflidevs.dicodingevent.ui.DetailFavoriteActivity 13 | import com.muflidevs.dicodingevent.ui.settings.SettingsActivity 14 | import com.muflidevs.dicodingevent.ui.adapter.FavoriteListAdapter 15 | import com.muflidevs.dicodingevent.ui.adapter.SpaceItemDecoration 16 | import com.muflidevs.dicodingevent.ui.viewmodel.MainViewModelFavorite 17 | import com.muflidevs.dicodingevent.ui.viewmodel.ViewModelFactory 18 | 19 | 20 | class FavoriteFragment : Fragment() { 21 | private var _binding: FragmentFavoriteBinding? = null 22 | private lateinit var viewModel: MainViewModelFavorite 23 | private lateinit var adapter: FavoriteListAdapter 24 | private val binding get() = _binding!! 25 | 26 | 27 | override fun onCreateView( 28 | inflater: LayoutInflater, container: ViewGroup?, 29 | savedInstanceState: Bundle? 30 | ): View { 31 | _binding = FragmentFavoriteBinding.inflate(inflater, container, false) 32 | return binding.root 33 | } 34 | 35 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 36 | super.onViewCreated(view, savedInstanceState) 37 | 38 | viewModel = obtainViewModel() 39 | 40 | adapter = FavoriteListAdapter { detailDataEntity -> 41 | val intent = Intent(requireContext(), DetailFavoriteActivity::class.java) 42 | intent.putExtra("EXTRA_ENTITY_DETAIL", detailDataEntity) 43 | startActivity(intent) 44 | } 45 | 46 | binding.rvFavorite.adapter = adapter 47 | binding.rvFavorite.layoutManager = LinearLayoutManager(requireContext()) 48 | binding.rvFavorite.addItemDecoration(SpaceItemDecoration(24)) 49 | 50 | this.viewModel.getAllEvents().observe(viewLifecycleOwner) { events -> 51 | if (events != null) adapter.submitList(events) 52 | } 53 | 54 | binding.settings.setOnClickListener { 55 | val intent = Intent(requireContext(), SettingsActivity::class.java) 56 | startActivity(intent) 57 | } 58 | } 59 | 60 | override fun onDestroyView() { 61 | super.onDestroyView() 62 | _binding = null 63 | } 64 | 65 | private fun obtainViewModel(): MainViewModelFavorite { 66 | val factory = ViewModelFactory.getInstance(requireActivity().application) 67 | return ViewModelProvider(this, factory)[MainViewModelFavorite::class.java] 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/fragments/FinishedFragment.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.fragments 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.fragment.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.View.VISIBLE 9 | import android.view.ViewGroup 10 | import android.widget.Toast 11 | import androidx.lifecycle.ViewModelProvider 12 | import androidx.recyclerview.widget.LinearLayoutManager 13 | import com.google.android.material.search.SearchBar 14 | import com.google.android.material.search.SearchView 15 | import com.muflidevs.dicodingevent.networking.Network 16 | import com.muflidevs.dicodingevent.R 17 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 18 | import com.muflidevs.dicodingevent.databinding.FragmentFinishedBinding 19 | import com.muflidevs.dicodingevent.ui.DetailActivity 20 | import com.muflidevs.dicodingevent.ui.settings.SettingsActivity 21 | import com.muflidevs.dicodingevent.ui.adapter.SpaceItemDecoration 22 | import com.muflidevs.dicodingevent.ui.viewmodel.MainViewModelFinish 23 | import com.muflidevs.dicodingevent.ui.adapter.VerticalListAdapter 24 | 25 | 26 | class FinishedFragment : Fragment(), View.OnClickListener { 27 | private lateinit var binding: FragmentFinishedBinding 28 | private lateinit var searchBar: SearchBar 29 | private lateinit var searcView: SearchView 30 | private lateinit var rvVerticalAdapter: VerticalListAdapter 31 | override fun onCreateView( 32 | inflater: LayoutInflater, container: ViewGroup?, 33 | savedInstanceState: Bundle? 34 | ): View { 35 | binding = FragmentFinishedBinding.inflate(inflater, container, false) 36 | return binding.root 37 | } 38 | 39 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 40 | super.onViewCreated(view, savedInstanceState) 41 | 42 | searcView = binding.searchView 43 | searchBar = binding.searchBar 44 | searchBar.setOnClickListener(this) 45 | setupRecyclerView() 46 | val vertical = ViewModelProvider( 47 | this, 48 | ViewModelProvider.NewInstanceFactory() 49 | )[MainViewModelFinish::class.java] 50 | vertical.listEvent.observe(viewLifecycleOwner) { value -> 51 | setEventDataVertical(value) 52 | } 53 | vertical.isLoading.observe(viewLifecycleOwner) { 54 | showLoading(it) 55 | } 56 | if (!Network.isNetworkAvailable(context)) { 57 | Toast.makeText( 58 | context, 59 | "No internet connection. Please turn on your network", 60 | Toast.LENGTH_LONG 61 | ).show() 62 | vertical.isLoading.observe(viewLifecycleOwner) { 63 | showLoading(it) 64 | } 65 | } 66 | 67 | } 68 | 69 | private fun searchFinishedEvents(query: String?) { 70 | val vertical = ViewModelProvider( 71 | this, 72 | ViewModelProvider.NewInstanceFactory() 73 | )[MainViewModelFinish::class.java] 74 | 75 | vertical.searchFinishedEvents(0, 20, query) 76 | } 77 | 78 | private fun setEventDataVertical(event: List) { 79 | rvVerticalAdapter.submitList(event) 80 | } 81 | 82 | private fun setupRecyclerView() { 83 | rvVerticalAdapter = VerticalListAdapter(requireContext()) { detailData -> 84 | val intent = Intent(context, DetailActivity::class.java) 85 | intent.putExtra("EXTRA_DETAIL", detailData) 86 | startActivity(intent) 87 | } 88 | binding.verticalOnly.layoutManager = 89 | LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) 90 | binding.verticalOnly.adapter = rvVerticalAdapter 91 | binding.verticalOnly.addItemDecoration( 92 | SpaceItemDecoration(24) 93 | ) 94 | binding.verticalOnly.clipToPadding = false 95 | 96 | binding.settings.setOnClickListener { 97 | val intent = Intent(requireContext(), SettingsActivity::class.java) 98 | startActivity(intent) 99 | } 100 | } 101 | 102 | override fun onClick(view: View?) { 103 | when (view?.id) { 104 | R.id.searchBar -> { 105 | searcView.visibility = VISIBLE 106 | with(binding) { 107 | searchView.setupWithSearchBar(searchBar) 108 | searchView 109 | .editText 110 | .setOnEditorActionListener { _, _, _ -> 111 | val query = searcView.text.toString() 112 | searchBar.setText(query) 113 | searchView.hide() 114 | 115 | if (query.isNotEmpty()) { 116 | searchFinishedEvents(query) 117 | } else { 118 | searchFinishedEvents(null) 119 | } 120 | false 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | private fun showLoading(isLoading: Boolean) { 128 | binding.progressBar.visibility = if (isLoading) VISIBLE else View.GONE 129 | } 130 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/fragments/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.fragments 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.fragment.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.Toast 10 | import androidx.lifecycle.ViewModelProvider 11 | import androidx.recyclerview.widget.GridLayoutManager 12 | import androidx.recyclerview.widget.LinearLayoutManager 13 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 14 | import com.muflidevs.dicodingevent.databinding.FragmentHomeBinding 15 | import com.muflidevs.dicodingevent.networking.Network 16 | import com.muflidevs.dicodingevent.ui.DetailActivity 17 | import com.muflidevs.dicodingevent.ui.settings.SettingsActivity 18 | import com.muflidevs.dicodingevent.ui.viewmodel.MainViewModel 19 | import com.muflidevs.dicodingevent.ui.adapter.HorizontalListAdapter 20 | import com.muflidevs.dicodingevent.ui.adapter.SpaceItemDecoration 21 | import com.muflidevs.dicodingevent.ui.adapter.VerticalListAdapter 22 | 23 | class HomeFragment : Fragment() { 24 | private lateinit var binding: FragmentHomeBinding 25 | private lateinit var rvHorizontalAdapter: HorizontalListAdapter 26 | private lateinit var rvVerticalAdapter: VerticalListAdapter 27 | private var position: Int = 0 28 | 29 | override fun onCreateView( 30 | inflater: LayoutInflater, container: ViewGroup?, 31 | savedInstanceState: Bundle? 32 | ): View { 33 | binding = FragmentHomeBinding.inflate(inflater, container, false) 34 | 35 | arguments?.let { 36 | position = it.getInt("EXTRA_ID", 0) 37 | } 38 | return binding.root 39 | } 40 | 41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 42 | super.onViewCreated(view, savedInstanceState) 43 | setupRecyclerView() 44 | val mainViewModel = ViewModelProvider(this)[MainViewModel::class.java] 45 | 46 | mainViewModel.listEvent.observe(viewLifecycleOwner) { value -> 47 | setEventData(value) 48 | 49 | } 50 | mainViewModel.listEventVertical.observe(viewLifecycleOwner) { value -> 51 | setEventDataVertical(value) 52 | } 53 | mainViewModel.isLoading.observe(viewLifecycleOwner) { 54 | showLoading(it) 55 | } 56 | if (!Network.isNetworkAvailable(context)) { 57 | Toast.makeText( 58 | context, 59 | "No internet connection. Please turn on your network", 60 | Toast.LENGTH_LONG 61 | ).show() 62 | mainViewModel.isLoading.observe(viewLifecycleOwner) { 63 | showLoading(it) 64 | } 65 | } 66 | binding.settings.setOnClickListener { 67 | val intent = Intent(requireContext(), SettingsActivity::class.java) 68 | startActivity(intent) 69 | } 70 | } 71 | 72 | private fun setEventData(event: List) { 73 | rvHorizontalAdapter.submitList(event) 74 | } 75 | 76 | private fun setEventDataVertical(event: List) { 77 | rvVerticalAdapter.submitList(event) 78 | } 79 | 80 | private fun setupRecyclerView() { 81 | rvHorizontalAdapter = HorizontalListAdapter(requireContext()) { detailData -> 82 | val intent = Intent(context, DetailActivity::class.java) 83 | intent.putExtra("EXTRA_DETAIL", detailData) 84 | startActivity(intent) 85 | } 86 | 87 | binding.horizontalOnly.layoutManager = 88 | LinearLayoutManager(context, GridLayoutManager.HORIZONTAL, false) 89 | binding.horizontalOnly.adapter = rvHorizontalAdapter 90 | 91 | rvVerticalAdapter = VerticalListAdapter(requireContext()) { detailData -> 92 | val intent = Intent(context, DetailActivity::class.java) 93 | intent.putExtra("EXTRA_DETAIL", detailData) 94 | startActivity(intent) 95 | } 96 | 97 | binding.verticalOnly.layoutManager = 98 | LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) 99 | binding.verticalOnly.adapter = rvVerticalAdapter 100 | binding.verticalOnly.addItemDecoration(SpaceItemDecoration(24)) 101 | } 102 | 103 | 104 | private fun showLoading(isLoading: Boolean) { 105 | binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE 106 | binding.progressBar2.visibility = if (isLoading) View.VISIBLE else View.GONE 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/fragments/UpcomingFragments.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.fragments 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.fragment.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.Toast 10 | import androidx.lifecycle.ViewModelProvider 11 | import androidx.recyclerview.widget.LinearLayoutManager 12 | import com.muflidevs.dicodingevent.networking.Network 13 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 14 | import com.muflidevs.dicodingevent.databinding.FragmentUpcomingFragmentsBinding 15 | import com.muflidevs.dicodingevent.ui.DetailActivity 16 | import com.muflidevs.dicodingevent.ui.settings.SettingsActivity 17 | import com.muflidevs.dicodingevent.ui.adapter.SpaceItemDecoration 18 | import com.muflidevs.dicodingevent.ui.viewmodel.MainViewModel 19 | import com.muflidevs.dicodingevent.ui.adapter.VerticalListAdapter 20 | 21 | class UpcomingFragments : Fragment() { 22 | private lateinit var rvVerticalAdapter: VerticalListAdapter 23 | private lateinit var binding: FragmentUpcomingFragmentsBinding 24 | 25 | override fun onCreateView( 26 | inflater: LayoutInflater, container: ViewGroup?, 27 | savedInstanceState: Bundle? 28 | ): View { 29 | binding = FragmentUpcomingFragmentsBinding.inflate(inflater, container, false) 30 | return binding.root 31 | } 32 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | super.onViewCreated(view, savedInstanceState) 35 | setupRecyclerView() 36 | val mainViewVertical = ViewModelProvider( 37 | this, 38 | ViewModelProvider.NewInstanceFactory() 39 | )[MainViewModel::class.java] 40 | mainViewVertical.listEvent.observe(viewLifecycleOwner) { value -> 41 | setEventDataVertical(value) 42 | } 43 | mainViewVertical.isLoading.observe(viewLifecycleOwner) { 44 | showLoading(it) 45 | } 46 | if (!Network.isNetworkAvailable(context)) { 47 | Toast.makeText( 48 | context, 49 | "No internet connection. Please turn on your network", 50 | Toast.LENGTH_LONG 51 | ).show() 52 | mainViewVertical.isLoading.observe(viewLifecycleOwner) { 53 | showLoading(it) 54 | } 55 | } 56 | binding.settings.setOnClickListener { 57 | val intent = Intent(requireContext(), SettingsActivity::class.java) 58 | startActivity(intent) 59 | } 60 | } 61 | 62 | private fun setEventDataVertical(event: List) { 63 | rvVerticalAdapter.submitList(event) 64 | } 65 | 66 | private fun setupRecyclerView() { 67 | 68 | rvVerticalAdapter = VerticalListAdapter(requireContext()) { detailData -> 69 | val intent = Intent(context, DetailActivity::class.java) 70 | intent.putExtra("EXTRA_DETAIL", detailData) 71 | startActivity(intent) 72 | } 73 | binding.verticalOnly.layoutManager = LinearLayoutManager( 74 | context, 75 | LinearLayoutManager.VERTICAL, false 76 | ) 77 | binding.verticalOnly.adapter = rvVerticalAdapter 78 | binding.verticalOnly.addItemDecoration(SpaceItemDecoration(24)) 79 | } 80 | 81 | private fun showLoading(isLoading: Boolean) { 82 | binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/networking/Network.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.networking 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkCapabilities 6 | 7 | class Network { 8 | companion object { 9 | fun isNetworkAvailable(context: Context?): Boolean { 10 | val connectivityManager = 11 | context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 12 | val network = connectivityManager.activeNetwork ?: return false 13 | val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false 14 | return when { 15 | activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true 16 | activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true 17 | activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true 18 | else -> false 19 | } 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/DetailActivity.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui 2 | 3 | import android.content.Intent 4 | import android.content.res.ColorStateList 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import android.text.Html 8 | import android.widget.Toast 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.core.content.ContextCompat 11 | import androidx.lifecycle.ViewModelProvider 12 | import com.bumptech.glide.Glide 13 | import com.muflidevs.dicodingevent.R 14 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 15 | import com.muflidevs.dicodingevent.databinding.ActivityDetailBinding 16 | import com.muflidevs.dicodingevent.ui.viewmodel.MainViewModelFavorite 17 | import com.muflidevs.dicodingevent.ui.viewmodel.ViewModelFactory 18 | 19 | 20 | class DetailActivity : AppCompatActivity() { 21 | private lateinit var binding: ActivityDetailBinding 22 | private lateinit var detailData: DetailData 23 | private lateinit var eventInsertViewModel: MainViewModelFavorite 24 | private var favorite = false 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | binding = ActivityDetailBinding.inflate(layoutInflater) 28 | setContentView(binding.root) 29 | 30 | eventInsertViewModel = obtainViewModel(this@DetailActivity) 31 | @Suppress("DEPRECATION") 32 | detailData = intent.getParcelableExtra("EXTRA_DETAIL") ?: return 33 | 34 | 35 | displayEventDetails(detailData) 36 | isFavorite() 37 | 38 | binding.filledButton.setOnClickListener { 39 | val link: String = detailData.link.toString() 40 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) 41 | startActivity(intent) 42 | } 43 | 44 | binding.back.setOnClickListener { 45 | finish() 46 | } 47 | 48 | binding.btnFavorite.setOnClickListener { 49 | if (!favorite) saveFavoriteEvent() 50 | else deleteFavoriteEvent() 51 | } 52 | 53 | } 54 | 55 | 56 | private fun displayEventDetails(detailData: DetailData) { 57 | binding.name.text = detailData.name 58 | binding.ownerName.text = detailData.ownerName 59 | binding.quota.text = (detailData.quota - detailData.registrants).toString() 60 | binding.beginTime.text = detailData.beginTime 61 | binding.category.text = detailData.category 62 | binding.description.text = Html.fromHtml(detailData.description, Html.FROM_HTML_MODE_LEGACY) 63 | Glide.with(this) 64 | .load(detailData.imageLogo) 65 | .into(binding.image) 66 | } 67 | 68 | 69 | private fun obtainViewModel(activity: AppCompatActivity): MainViewModelFavorite { 70 | val factory = ViewModelFactory.getInstance(activity.application) 71 | return ViewModelProvider(activity, factory)[MainViewModelFavorite::class.java] 72 | } 73 | 74 | private fun deleteFavoriteEvent() { 75 | eventInsertViewModel.delete(detailData) 76 | showToast("Event berhasil dihapus dari daftar favorit") 77 | } 78 | 79 | private fun saveFavoriteEvent() { 80 | val title = binding.name.text.toString() 81 | val ownerName = binding.ownerName.text.toString() 82 | val quota = detailData.quota 83 | val beginTime = binding.beginTime.text.toString() 84 | val category = binding.category.text.toString() 85 | val description = binding.description.text.toString() 86 | 87 | detailData.apply { 88 | name = title 89 | this.ownerName = ownerName 90 | this.quota = quota 91 | this.beginTime = beginTime 92 | this.category = category 93 | this.description = description 94 | } 95 | 96 | eventInsertViewModel.insert(detailData) 97 | 98 | showToast("Event berhasil ditambahkan ke favorit") 99 | } 100 | 101 | private fun showToast(message: String) { 102 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 103 | } 104 | 105 | private fun isFavorite() { 106 | eventInsertViewModel.getAllEvents().observe(this) { favoriteEvents -> 107 | favorite = favoriteEvents.any { it.id == detailData.id } 108 | updateIcon() 109 | } 110 | } 111 | 112 | private fun updateIcon() { 113 | if (favorite) { 114 | binding.btnFavorite.setImageResource(R.drawable.favorite_added) 115 | binding.btnFavorite.backgroundTintList = 116 | ColorStateList.valueOf(ContextCompat.getColor(this, R.color.favorite)) 117 | binding.btnFavorite.imageTintList = 118 | ColorStateList.valueOf(ContextCompat.getColor(this, R.color.white)) 119 | } else { 120 | binding.btnFavorite.setImageResource(R.drawable.favorite) 121 | binding.btnFavorite.backgroundTintList = 122 | ColorStateList.valueOf(ContextCompat.getColor(this, R.color.white)) 123 | binding.btnFavorite.imageTintList = 124 | ColorStateList.valueOf(ContextCompat.getColor(this, R.color.favorite)) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/DetailFavoriteActivity.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui 2 | 3 | import android.content.Intent 4 | import android.content.res.ColorStateList 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import android.text.Html 8 | import android.widget.Toast 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.core.content.ContextCompat 11 | import androidx.lifecycle.ViewModelProvider 12 | import com.bumptech.glide.Glide 13 | import com.muflidevs.dicodingevent.R 14 | import com.muflidevs.dicodingevent.data.database.local.entity.DetailDataEntity 15 | import com.muflidevs.dicodingevent.databinding.ActivityDetailFavoriteBinding 16 | import com.muflidevs.dicodingevent.ui.viewmodel.MainViewModelFavorite 17 | import com.muflidevs.dicodingevent.ui.viewmodel.ViewModelFactory 18 | 19 | class DetailFavoriteActivity : AppCompatActivity() { 20 | private lateinit var binding: ActivityDetailFavoriteBinding 21 | private lateinit var eventInsertViewModel: MainViewModelFavorite 22 | private lateinit var detailDataEntity: DetailDataEntity 23 | private var favorite = false 24 | private val keyIntent = "EXTRA_ENTITY_DETAIL" 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | binding = ActivityDetailFavoriteBinding.inflate(layoutInflater) 28 | setContentView(binding.root) 29 | eventInsertViewModel = obtainViewModel(this@DetailFavoriteActivity) 30 | 31 | @Suppress("DEPRECATION") 32 | detailDataEntity = intent.getParcelableExtra(keyIntent)!! 33 | 34 | displayEventDetails(detailDataEntity) 35 | isFavorite() 36 | 37 | binding.filledButton.setOnClickListener { 38 | val link: String = detailDataEntity.link.toString() 39 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) 40 | startActivity(intent) 41 | } 42 | 43 | binding.back.setOnClickListener { 44 | finish() 45 | } 46 | 47 | binding.btnFavorite.setOnClickListener { 48 | if (!favorite) saveFavoriteEvent() 49 | else deleteFavoriteEvent() 50 | } 51 | } 52 | 53 | private fun displayEventDetails(detailData: DetailDataEntity) { 54 | binding.name.text = detailData.name 55 | binding.ownerName.text = detailData.ownerName 56 | binding.quota.text = (detailData.quota - detailData.registrants).toString() 57 | binding.beginTime.text = detailData.beginTime 58 | binding.category.text = detailData.category 59 | binding.description.text = Html.fromHtml(detailData.description, Html.FROM_HTML_MODE_LEGACY) 60 | Glide.with(this) 61 | .load(detailData.imageLogo) 62 | .into(binding.image) 63 | } 64 | 65 | private fun obtainViewModel(activity: AppCompatActivity): MainViewModelFavorite { 66 | val factory = ViewModelFactory.getInstance(activity.application) 67 | return ViewModelProvider(activity, factory)[MainViewModelFavorite::class.java] 68 | } 69 | 70 | private fun deleteFavoriteEvent() { 71 | eventInsertViewModel.delete(detailDataEntity) 72 | showToast("Event berhasil di hapus dari daftar favorit") 73 | } 74 | 75 | private fun saveFavoriteEvent() { 76 | val title = binding.name.text.toString() 77 | val ownerName = binding.ownerName.text.toString() 78 | val quota = detailDataEntity.quota 79 | val beginTime = binding.beginTime.text.toString() 80 | val category = binding.category.text.toString() 81 | val description = binding.description.text.toString() 82 | 83 | detailDataEntity.apply { 84 | name = title 85 | this.ownerName = ownerName 86 | this.quota = quota 87 | this.beginTime = beginTime 88 | this.category = category 89 | this.description = description 90 | } 91 | 92 | eventInsertViewModel.insert(detailDataEntity) 93 | showToast("Event berhasil ditambahkan ke daftar favorit") 94 | } 95 | 96 | private fun showToast(message: String) { 97 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 98 | } 99 | 100 | private fun isFavorite() { 101 | eventInsertViewModel.getAllEvents().observe(this) { favoriteEvents -> 102 | favorite = favoriteEvents.any { it.id == detailDataEntity.id } 103 | updateIcon() 104 | } 105 | } 106 | 107 | private fun updateIcon() { 108 | if (favorite) { 109 | binding.btnFavorite.setImageResource(R.drawable.favorite_added) 110 | binding.btnFavorite.backgroundTintList = 111 | ColorStateList.valueOf(ContextCompat.getColor(this, R.color.favorite)) 112 | binding.btnFavorite.imageTintList = 113 | ColorStateList.valueOf(ContextCompat.getColor(this, R.color.white)) 114 | } else { 115 | binding.btnFavorite.setImageResource(R.drawable.favorite) 116 | binding.btnFavorite.backgroundTintList = 117 | ColorStateList.valueOf(ContextCompat.getColor(this, R.color.white)) 118 | binding.btnFavorite.imageTintList = 119 | ColorStateList.valueOf(ContextCompat.getColor(this, R.color.favorite)) 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui 2 | 3 | import android.os.Bundle 4 | import androidx.activity.enableEdgeToEdge 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.appcompat.app.AppCompatDelegate 7 | import androidx.lifecycle.lifecycleScope 8 | import com.google.android.material.navigation.NavigationBarView 9 | import com.muflidevs.dicodingevent.R 10 | import com.muflidevs.dicodingevent.databinding.ActivityMainBinding 11 | import com.muflidevs.dicodingevent.fragments.FavoriteFragment 12 | import com.muflidevs.dicodingevent.fragments.FinishedFragment 13 | import com.muflidevs.dicodingevent.fragments.HomeFragment 14 | import com.muflidevs.dicodingevent.fragments.UpcomingFragments 15 | import com.muflidevs.dicodingevent.ui.settings.SettingPreferences 16 | import com.muflidevs.dicodingevent.ui.settings.dataStore 17 | import kotlinx.coroutines.flow.first 18 | import kotlinx.coroutines.launch 19 | 20 | class MainActivity : AppCompatActivity() { 21 | private lateinit var binding: ActivityMainBinding 22 | private lateinit var navigationBar: NavigationBarView 23 | 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | 27 | super.onCreate(savedInstanceState) 28 | enableEdgeToEdge() 29 | binding = ActivityMainBinding.inflate(layoutInflater) 30 | 31 | val pref = SettingPreferences.getInstance(dataStore) 32 | 33 | lifecycleScope.launch { 34 | val isDarkModeActive = pref.getThemeSetting().first() 35 | if (isDarkModeActive) { 36 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) 37 | } else { 38 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) 39 | } 40 | } 41 | setContentView(binding.root) 42 | navigationBar = binding.bottomNavigation 43 | navBarClick() 44 | } 45 | 46 | private fun navBarClick() { 47 | navigationBar = binding.bottomNavigation 48 | 49 | val fragmentManager = supportFragmentManager 50 | val finishedFragment = FinishedFragment() 51 | val homeFragment = HomeFragment() 52 | val upcomingFragment = UpcomingFragments() 53 | val favoriteFragment = FavoriteFragment() 54 | fragmentManager.beginTransaction() 55 | .replace(binding.frameContainer.id, homeFragment, HomeFragment::class.java.simpleName) 56 | .commit() 57 | navigationBar.setOnItemSelectedListener { item -> 58 | when (item.itemId) { 59 | R.id.upcoming -> { 60 | fragmentManager.beginTransaction() 61 | .replace( 62 | binding.frameContainer.id, 63 | upcomingFragment, 64 | UpcomingFragments::class.java.simpleName 65 | ) 66 | .commit() 67 | true 68 | } 69 | 70 | R.id.finished -> { 71 | fragmentManager.beginTransaction() 72 | .replace( 73 | binding.frameContainer.id, finishedFragment, 74 | FinishedFragment::class.java.simpleName 75 | ) 76 | .commit() 77 | true 78 | } 79 | 80 | R.id.home -> { 81 | fragmentManager.beginTransaction() 82 | .replace( 83 | binding.frameContainer.id, 84 | homeFragment, 85 | HomeFragment::class.java.simpleName 86 | ) 87 | .commit() 88 | true 89 | } 90 | 91 | R.id.favorite -> { 92 | fragmentManager.beginTransaction() 93 | .replace( 94 | binding.frameContainer.id, 95 | favoriteFragment, 96 | FavoriteFragment::class.java.simpleName 97 | ).commit() 98 | true 99 | } 100 | 101 | else -> false 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/adapter/FavoriteListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.bumptech.glide.Glide 9 | import com.muflidevs.dicodingevent.data.database.local.entity.DetailDataEntity 10 | import com.muflidevs.dicodingevent.databinding.ItemVerticalBinding 11 | 12 | class FavoriteListAdapter(private val onItemClicked: (DetailDataEntity) -> Unit) : 13 | ListAdapter( 14 | DIFF_CALLBACK 15 | ) { 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavoriteViewHolder { 18 | val binding = 19 | ItemVerticalBinding.inflate(LayoutInflater.from(parent.context), parent, false) 20 | return FavoriteViewHolder(binding) 21 | } 22 | 23 | override fun onBindViewHolder(holder: FavoriteViewHolder, position: Int) { 24 | val detailData = getItem(position) 25 | holder.bind(detailData) 26 | } 27 | 28 | inner class FavoriteViewHolder(private val binding: ItemVerticalBinding) : 29 | RecyclerView.ViewHolder(binding.root) { 30 | fun bind(detailData: DetailDataEntity) { 31 | binding.judul.text = detailData.name 32 | binding.penyelenggara.text = detailData.ownerName 33 | binding.waktu.text = getWaktu(detailData) 34 | Glide.with(itemView.context) 35 | .load(detailData.imageLogo) 36 | .into(binding.image) 37 | 38 | itemView.setOnClickListener { 39 | onItemClicked(detailData) 40 | } 41 | } 42 | } 43 | 44 | private fun getWaktu(detail: DetailDataEntity): String { 45 | return "Waktu Mulai : ${detail.beginTime}\nWaktu Selesai : ${detail.endTime}" 46 | } 47 | 48 | companion object { 49 | val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { 50 | override fun areItemsTheSame( 51 | oldItem: DetailDataEntity, 52 | newItem: DetailDataEntity 53 | ): Boolean { 54 | return oldItem == newItem 55 | } 56 | 57 | override fun areContentsTheSame( 58 | oldItem: DetailDataEntity, 59 | newItem: DetailDataEntity 60 | ): Boolean { 61 | return oldItem == newItem 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/adapter/HorizontalListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.adapter 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.DiffUtil 8 | import androidx.recyclerview.widget.ListAdapter 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.bumptech.glide.Glide 11 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 12 | import com.muflidevs.dicodingevent.databinding.ItemHorizontalBinding 13 | import com.muflidevs.dicodingevent.ui.DetailActivity 14 | 15 | class HorizontalListAdapter( 16 | private val context: Context, 17 | private val onItemClicked: (DetailData) -> Unit 18 | ) : ListAdapter(DIFF_CALLBACK) { 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { 22 | val binding = 23 | ItemHorizontalBinding.inflate(LayoutInflater.from(parent.context), parent, false) 24 | return MyViewHolder(binding) 25 | } 26 | 27 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) { 28 | val detailData = getItem(position) 29 | val intent = Intent(context, DetailActivity::class.java) 30 | intent.putExtra("EXTRA_ID", position) 31 | holder.bind(detailData, onItemClicked) 32 | } 33 | 34 | inner class MyViewHolder(private val binding: ItemHorizontalBinding) : 35 | RecyclerView.ViewHolder(binding.root) { 36 | 37 | fun bind(detail: DetailData, onItemClicked: (DetailData) -> Unit) { 38 | Glide.with(context) 39 | .load(detail.imageLogo) 40 | .into(binding.headerImage) 41 | binding.title.text = detail.name 42 | binding.root.setOnClickListener { 43 | onItemClicked(detail) 44 | } 45 | } 46 | } 47 | 48 | companion object { 49 | val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { 50 | override fun areItemsTheSame(oldItem: DetailData, newItem: DetailData): Boolean { 51 | return oldItem == newItem 52 | } 53 | 54 | override fun areContentsTheSame(oldItem: DetailData, newItem: DetailData): Boolean { 55 | return oldItem == newItem 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/adapter/SpaceItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.adapter 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | class SpaceItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() { 8 | override fun getItemOffsets( 9 | outRect: Rect, 10 | view: View, 11 | parent: RecyclerView, 12 | state: RecyclerView.State 13 | ) { 14 | outRect.left = space 15 | outRect.right = space 16 | outRect.bottom = space 17 | 18 | if (parent.getChildAdapterPosition(view) == 0) { 19 | outRect.top = space 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/adapter/VerticalListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.ListAdapter 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.bumptech.glide.Glide 10 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 11 | import com.muflidevs.dicodingevent.databinding.ItemVerticalBinding 12 | 13 | class VerticalListAdapter( 14 | private val context: Context, 15 | private val onItemClicked: (DetailData) -> Unit 16 | ) : ListAdapter(DIFF_CALLBACK) { 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { 20 | val binding = ItemVerticalBinding.inflate(LayoutInflater.from(parent.context)) 21 | return MyViewHolder(binding) 22 | } 23 | 24 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) { 25 | val detailData = getItem(position) 26 | holder.bind(detailData) 27 | 28 | holder.itemView.setOnClickListener { 29 | onItemClicked(detailData) 30 | } 31 | } 32 | 33 | inner class MyViewHolder(private val binding: ItemVerticalBinding) : 34 | RecyclerView.ViewHolder(binding.root) { 35 | fun bind(detail: DetailData) { 36 | Glide.with(context) 37 | .load(detail.imageLogo) 38 | .into(binding.image) 39 | binding.judul.text = "${detail.name}" 40 | binding.penyelenggara.text = "${detail.ownerName}" 41 | binding.waktu.text = getWaktu(detail) 42 | 43 | binding.root.setOnClickListener { 44 | onItemClicked(detail) 45 | } 46 | } 47 | 48 | private fun getWaktu(detail: DetailData): String { 49 | return "Waktu Mulai : ${detail.beginTime}\nWaktu Selesai : ${detail.endTime}" 50 | } 51 | } 52 | 53 | companion object { 54 | val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { 55 | override fun areContentsTheSame(oldItem: DetailData, newItem: DetailData): Boolean { 56 | return oldItem == newItem 57 | } 58 | 59 | override fun areItemsTheSame(oldItem: DetailData, newItem: DetailData): Boolean { 60 | return oldItem == newItem 61 | } 62 | } 63 | } 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/settings/EventWorkerNotification.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.settings 2 | 3 | import android.Manifest 4 | import android.app.NotificationChannel 5 | import android.app.NotificationManager 6 | import android.app.PendingIntent 7 | import android.content.Context 8 | import android.content.Intent 9 | import android.content.pm.PackageManager 10 | import android.os.Build 11 | import android.util.Log 12 | import androidx.core.app.ActivityCompat 13 | import androidx.core.app.NotificationCompat 14 | import androidx.core.app.NotificationManagerCompat 15 | import androidx.work.CoroutineWorker 16 | import androidx.work.WorkerParameters 17 | import com.muflidevs.dicodingevent.R 18 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 19 | import com.muflidevs.dicodingevent.data.remote.retrofit.ApiConfig 20 | import com.muflidevs.dicodingevent.ui.DetailActivity 21 | import kotlinx.coroutines.Dispatchers 22 | import kotlinx.coroutines.withContext 23 | 24 | class EventWorkerNotification(ctx: Context, params: WorkerParameters) : 25 | CoroutineWorker(ctx, params) { 26 | 27 | override suspend fun doWork(): Result { 28 | return try { 29 | fetchAndNotify(applicationContext) 30 | Result.success() 31 | } catch (e: Exception) { 32 | Result.failure() 33 | } 34 | } 35 | 36 | private suspend fun fetchAndNotify(context: Context) { 37 | withContext(Dispatchers.IO) { 38 | try { 39 | val response = ApiConfig.getApiService().getEvents(active = -1, limit = 1) 40 | val eventList = response.data 41 | 42 | if (eventList.isNotEmpty()) { 43 | val event = eventList[0] 44 | showNotification(context, event) 45 | } else { 46 | Log.e("fetchAndNotify", "Error mengambil event data") 47 | } 48 | } catch (e: Exception) { 49 | Log.e("fetchAndNotify", "Error mengambil event data: ${e.message}") 50 | } 51 | } 52 | } 53 | 54 | private fun showNotification(context: Context, detailData: DetailData) { 55 | val channelId = "event_channel" 56 | val notificationId = 1 57 | 58 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 59 | val channel = NotificationChannel( 60 | channelId, 61 | "Event Notifications", 62 | NotificationManager.IMPORTANCE_DEFAULT 63 | ) 64 | channel.description = detailData.description 65 | val notificationManager: NotificationManager = 66 | context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 67 | notificationManager.createNotificationChannel(channel) 68 | } 69 | 70 | val intent = Intent(context, DetailActivity::class.java).apply { 71 | flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK 72 | putExtra("EXTRA_DETAIL", detailData) 73 | } 74 | 75 | val pendingIntent = PendingIntent.getActivity( 76 | context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE 77 | ) 78 | val waktu = "Waktu Event dimulai ${detailData.beginTime} sampai dengan ${detailData.endTime}" 79 | val notificationBuilder = NotificationCompat.Builder(context, channelId) 80 | .setContentTitle(detailData.name) 81 | .setContentText(waktu) 82 | .setSmallIcon(R.drawable.ic_launcher_foreground) 83 | .setContentIntent(pendingIntent) 84 | .setAutoCancel(true) 85 | .setPriority(NotificationCompat.PRIORITY_DEFAULT) 86 | 87 | with(NotificationManagerCompat.from(context)) { 88 | if (ActivityCompat.checkSelfPermission( 89 | context, 90 | Manifest.permission.POST_NOTIFICATIONS 91 | ) != PackageManager.PERMISSION_GRANTED 92 | ) { 93 | return@with 94 | } 95 | notify(notificationId, notificationBuilder.build()) 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/settings/SettingPreferences.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.settings 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.core.booleanPreferencesKey 7 | import androidx.datastore.preferences.core.edit 8 | import androidx.datastore.preferences.preferencesDataStore 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.map 11 | 12 | val Context.dataStore: DataStore by preferencesDataStore(name = "settings") 13 | 14 | class SettingPreferences private constructor(private val dataStore: DataStore) { 15 | 16 | private val themeKey = booleanPreferencesKey("theme_setting") 17 | private val notificationKey = booleanPreferencesKey("notification_setting") 18 | 19 | fun getThemeSetting(): Flow { 20 | return dataStore.data.map { preferences -> 21 | preferences[themeKey 22 | ] ?: false 23 | } 24 | } 25 | 26 | fun getNotificationSetting(): Flow { 27 | return dataStore.data.map { preferences -> 28 | preferences[notificationKey] ?: false 29 | } 30 | } 31 | 32 | suspend fun saveThemeSetting(isDarkModeActive: Boolean) { 33 | dataStore.edit { preferences -> 34 | preferences[themeKey 35 | ] = isDarkModeActive 36 | } 37 | } 38 | 39 | suspend fun saveNotificationSetting(isNotificationActive: Boolean) { 40 | dataStore.edit { preferences -> 41 | preferences[notificationKey] = isNotificationActive 42 | } 43 | } 44 | 45 | companion object { 46 | @Volatile 47 | private var INSTANCE: SettingPreferences? = null 48 | 49 | fun getInstance(dataStore: DataStore): SettingPreferences { 50 | return INSTANCE ?: synchronized(this) { 51 | val instance = SettingPreferences(dataStore) 52 | INSTANCE = instance 53 | instance 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/settings/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.settings 2 | 3 | import android.os.Bundle 4 | import android.widget.CompoundButton 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.appcompat.app.AppCompatDelegate 7 | import androidx.lifecycle.ViewModelProvider 8 | import androidx.work.PeriodicWorkRequestBuilder 9 | import androidx.work.WorkManager 10 | import com.google.android.material.switchmaterial.SwitchMaterial 11 | import com.muflidevs.dicodingevent.R 12 | import com.muflidevs.dicodingevent.databinding.ActivitySettingsBinding 13 | import com.muflidevs.dicodingevent.ui.viewmodel.MainSettingsModel 14 | import com.muflidevs.dicodingevent.ui.viewmodel.ViewModelSettingsFactory 15 | import java.util.concurrent.TimeUnit 16 | 17 | class SettingsActivity : AppCompatActivity() { 18 | private lateinit var binding: ActivitySettingsBinding 19 | private lateinit var mainSettingsModel: MainSettingsModel 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | binding = ActivitySettingsBinding.inflate(layoutInflater) 24 | setContentView(binding.root) 25 | 26 | val switchTheme = findViewById(R.id.switch_theme) 27 | val switchNotif = findViewById(R.id.switch_notification) 28 | 29 | val pref = SettingPreferences.getInstance(application.dataStore) 30 | mainSettingsModel = 31 | ViewModelProvider(this, ViewModelSettingsFactory(pref))[MainSettingsModel::class.java] 32 | 33 | mainSettingsModel.getThemeSettings().observe(this) { isDarkModeActive: Boolean -> 34 | if (isDarkModeActive) { 35 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) 36 | switchTheme.isChecked = true 37 | } else { 38 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) 39 | switchTheme.isChecked = false 40 | } 41 | } 42 | mainSettingsModel.getNotificationSettings().observe(this) { isNotificationActive: Boolean -> 43 | if (isNotificationActive) { 44 | startPeriodicEventWorker() 45 | switchNotif.isChecked = true 46 | } else { 47 | stopPeriodicEventWorker() 48 | switchNotif.isChecked = false 49 | } 50 | } 51 | binding.switchTheme.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> 52 | mainSettingsModel.saveThemeSettings(isChecked) 53 | } 54 | 55 | binding.switchNotification.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> 56 | if (isChecked) { 57 | startPeriodicEventWorker() 58 | mainSettingsModel.saveNotificationSettings(true) 59 | } else { 60 | stopPeriodicEventWorker() 61 | mainSettingsModel.saveNotificationSettings(false) 62 | } 63 | } 64 | 65 | binding.back.setOnClickListener { 66 | finish() 67 | } 68 | } 69 | 70 | private fun startPeriodicEventWorker() { 71 | val eventWorkRequest = PeriodicWorkRequestBuilder(1, TimeUnit.DAYS) 72 | .build() 73 | WorkManager.getInstance(this).enqueue(eventWorkRequest) 74 | } 75 | 76 | private fun stopPeriodicEventWorker() { 77 | WorkManager.getInstance(this).cancelAllWorkByTag("event_work") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/viewmodel/MainSettingsModel.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.asLiveData 6 | import androidx.lifecycle.viewModelScope 7 | import com.muflidevs.dicodingevent.ui.settings.SettingPreferences 8 | import kotlinx.coroutines.launch 9 | 10 | class MainSettingsModel(private val pref: SettingPreferences) : ViewModel() { 11 | 12 | fun getThemeSettings(): LiveData { 13 | return pref.getThemeSetting().asLiveData() 14 | } 15 | 16 | fun getNotificationSettings(): LiveData { 17 | return pref.getNotificationSetting().asLiveData() 18 | } 19 | 20 | fun saveThemeSettings(isDarkModeActive: Boolean) { 21 | viewModelScope.launch { 22 | pref.saveThemeSetting(isDarkModeActive) 23 | } 24 | } 25 | 26 | fun saveNotificationSettings(isNotificationActive: Boolean) { 27 | viewModelScope.launch { 28 | pref.saveNotificationSetting(isNotificationActive) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/viewmodel/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.viewmodel 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 9 | import com.muflidevs.dicodingevent.data.remote.retrofit.ApiConfig 10 | import kotlinx.coroutines.launch 11 | 12 | 13 | class MainViewModel : ViewModel() { 14 | 15 | private val _listEvents = MutableLiveData>() 16 | val listEvent: LiveData> = _listEvents 17 | 18 | private val _listEventsVertical = MutableLiveData>() 19 | val listEventVertical: LiveData> = _listEventsVertical 20 | 21 | private val _isLoading = MutableLiveData() 22 | val isLoading: LiveData = _isLoading 23 | 24 | companion object { 25 | const val TAG = "MainViewModel" 26 | } 27 | 28 | init { 29 | findEventHorizontal() 30 | findEventVertical() 31 | } 32 | 33 | private fun findEventHorizontal() { 34 | viewModelScope.launch { 35 | try { 36 | _isLoading.value = true 37 | val response = ApiConfig.getApiService().getEvents(1, 5) 38 | 39 | _listEvents.value = response.data 40 | 41 | } catch (e: Exception) { 42 | Log.e(TAG, "onFailure : ${e.message}") 43 | } finally { 44 | _isLoading.value = false 45 | } 46 | 47 | } 48 | 49 | } 50 | 51 | private fun findEventVertical() { 52 | viewModelScope.launch { 53 | _isLoading.value = true 54 | try { 55 | _isLoading.value = true 56 | val response = ApiConfig.getApiService().getEvents(0, 5) 57 | 58 | _listEventsVertical.value = response.data 59 | 60 | } catch (e: Exception) { 61 | Log.e(TAG, "onFailure : ${e.message}") 62 | } finally { 63 | _isLoading.value = false 64 | } 65 | 66 | } 67 | 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/viewmodel/MainViewModelFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.viewmodel 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.muflidevs.dicodingevent.data.database.local.entity.DetailDataEntity 8 | import com.muflidevs.dicodingevent.data.database.local.repository.EventRepository 9 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 10 | import kotlinx.coroutines.launch 11 | 12 | class MainViewModelFavorite(application: Application) : ViewModel() { 13 | 14 | private val mEventRepository = EventRepository(application) 15 | 16 | fun insert(detailData: DetailData) { 17 | viewModelScope.launch { 18 | val detailDataEntity = mEventRepository.mapRemoteToLocal(detailData) 19 | mEventRepository.insert(detailDataEntity) 20 | } 21 | } 22 | 23 | fun insert(detailData: DetailDataEntity) { 24 | viewModelScope.launch { 25 | val detailDataEntity = mEventRepository.mapRemoteToLocal(detailData) 26 | mEventRepository.insert(detailDataEntity) 27 | } 28 | } 29 | 30 | fun delete(detailData: DetailData) { 31 | viewModelScope.launch { 32 | val detailDataEntity = mEventRepository.mapRemoteToLocal(detailData) 33 | mEventRepository.delete(detailDataEntity) 34 | } 35 | } 36 | 37 | fun delete(detailData: DetailDataEntity) { 38 | viewModelScope.launch { 39 | val detailDataEntity = mEventRepository.mapRemoteToLocal(detailData) 40 | mEventRepository.delete(detailDataEntity) 41 | } 42 | } 43 | 44 | fun getAllEvents(): LiveData> = mEventRepository.getAllEvent() 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/viewmodel/MainViewModelFinish.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.viewmodel 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import com.muflidevs.dicodingevent.data.remote.response.DetailData 9 | import com.muflidevs.dicodingevent.data.remote.retrofit.ApiConfig 10 | import kotlinx.coroutines.launch 11 | 12 | 13 | class MainViewModelFinish : ViewModel() { 14 | 15 | private val _listEvents = MutableLiveData>() 16 | val listEvent: LiveData> = _listEvents 17 | 18 | private val _isLoading = MutableLiveData() 19 | val isLoading: LiveData = _isLoading 20 | 21 | init { 22 | findEventVertical() 23 | } 24 | 25 | fun searchFinishedEvents(active: Int, limit: Int, query: String?) { 26 | viewModelScope.launch { 27 | try { 28 | _isLoading.value = true 29 | val response = ApiConfig.getApiService() 30 | .getEvents(active = active, limit = limit, query = query) 31 | _listEvents.value = response.data 32 | 33 | } catch (e: Exception) { 34 | Log.e(MainViewModel.TAG, "onFailure : ${e.message}") 35 | } finally { 36 | _isLoading.value = false 37 | } 38 | 39 | } 40 | 41 | } 42 | 43 | private fun findEventVertical() { 44 | viewModelScope.launch { 45 | try { 46 | _isLoading.value = true 47 | val response = ApiConfig.getApiService().getEvents(0) 48 | 49 | _listEvents.value = response.data 50 | 51 | } catch (e: Exception) { 52 | Log.e(MainViewModel.TAG, "onFailure : ${e.message}") 53 | } finally { 54 | _isLoading.value = false 55 | } 56 | 57 | } 58 | 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/viewmodel/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.viewmodel 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.ViewModelProvider 6 | 7 | class ViewModelFactory private constructor(private val mApplication: Application) : 8 | ViewModelProvider.NewInstanceFactory() { 9 | 10 | companion object { 11 | @Volatile 12 | private var INSTANCE: ViewModelFactory? = null 13 | 14 | @JvmStatic 15 | fun getInstance(application: Application): ViewModelFactory { 16 | return INSTANCE ?: synchronized(this) { 17 | INSTANCE ?: ViewModelFactory(application).also { INSTANCE = it } 18 | } 19 | } 20 | } 21 | 22 | @Suppress("UNCHECKED_CAST") 23 | override fun create(modelClass: Class): T { 24 | return when { 25 | modelClass.isAssignableFrom(MainViewModelFavorite::class.java) -> { 26 | MainViewModelFavorite(mApplication) as T 27 | } 28 | 29 | else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}") 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/muflidevs/dicodingevent/ui/viewmodel/ViewModelSettingsFactory.kt: -------------------------------------------------------------------------------- 1 | package com.muflidevs.dicodingevent.ui.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import com.muflidevs.dicodingevent.ui.settings.SettingPreferences 6 | 7 | class ViewModelSettingsFactory(private val pref: SettingPreferences) : 8 | ViewModelProvider.NewInstanceFactory() { 9 | 10 | @Suppress("UNCHECKED_CAST") 11 | override fun create(modelClass: Class): T { 12 | if (modelClass.isAssignableFrom(MainSettingsModel::class.java)) { 13 | return MainSettingsModel(pref) as T 14 | } 15 | throw IllegalArgumentException("Unknown ViewModel class : " + modelClass.name) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/border_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/event_finished.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/event_upcoming.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/favorite.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/favorite_added.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/home.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/horizontal_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/second_border_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/font/poppins.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_bold.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_extrabold.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_thin.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | 22 | 23 | 35 | 36 | 46 | 47 | 59 | 60 | 69 | 70 | 79 | 80 | 85 | 86 | 93 | 94 | 101 | 102 | 103 | 104 | 109 | 110 | 118 | 119 | 126 | 127 | 128 | 129 | 130 | 135 | 136 | 143 | 144 | 151 | 152 | 153 | 154 | 161 | 162 | 168 | 169 | 170 | 171 |