Replaced fragment switching functionality in MainActivity.kt with Navigation component, simplifying logic significantly; upgraded to AGP 8.0.1

This commit is contained in:
denizk0461
2023-05-02 20:52:56 +02:00
parent 51c4c9120b
commit 36a7ae0035
12 changed files with 73 additions and 119 deletions
@@ -3,15 +3,10 @@ package com.denizk0461.studip.activity
import android.os.Bundle import android.os.Bundle
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentTransaction import androidx.navigation.fragment.NavHostFragment
import androidx.lifecycle.Lifecycle import androidx.navigation.fragment.findNavController
import com.denizk0461.studip.R import androidx.navigation.ui.setupWithNavController
import com.denizk0461.studip.databinding.ActivityMainBinding import com.denizk0461.studip.databinding.ActivityMainBinding
import com.denizk0461.studip.db.AppRepository
import com.denizk0461.studip.fragment.EventFragment
import com.denizk0461.studip.fragment.CanteenFragment
import com.denizk0461.studip.fragment.SettingsFragment
import com.denizk0461.studip.model.SettingsPreferences
/** /**
* Main activity that handles all common fragments. This is opened on app launch. * Main activity that handles all common fragments. This is opened on app launch.
@@ -21,91 +16,17 @@ class MainActivity : FragmentActivity() {
// View binding // View binding
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
/*
* Tag for the currently active fragment; set on activity launch to the fragment that will be
* shown first.
*/
private var currentFragment: String = "event"
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
/*
* Instantiate repository object that is accessed by the fragments' view models to retrieve
* data.
*/
// Dependencies.repo = AppRepository(application)
// Inflate view binding and bind to this activity // Inflate view binding and bind to this activity
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
// Set up navigation view bar to launch fragments // Set up bottom navigation view to navigate between fragments
binding.navView.setOnItemSelectedListener { item -> binding.navView.setupWithNavController(
// Launch fragment based on the item that has been clicked binding.navHostFragmentContentMain.getFragment<NavHostFragment>().findNavController()
loadFragment(when (item.itemId) { )
R.id.food -> "canteen"
R.id.settings -> "settings"
else -> "event"
})
}
// Set up bottom nav bar and the initial fragment when the app launches
if (savedInstanceState == null) {
// Open the fragment that the user specified to open on app start
if (AppRepository.getRepositoryInstance(application)
.getBooleanPreference(SettingsPreferences.LAUNCH_CANTEEN_ON_START)
) {
// Open canteen fragment
binding.navView.selectedItemId = R.id.food
currentFragment = "canteen"
} else {
// Open event fragment
binding.navView.selectedItemId = R.id.plan
currentFragment = "event"
}
// Launch the fragment defined in currentFragment
loadFragment(currentFragment)
}
}
/**
* Loads a given fragment.
*
* @param type tag for the fragment that is to be opened
* @return true on success, false if another fragment is currently instantiating
*/
private fun loadFragment(type: String): Boolean {
// Find the fragment that is to be opened, or create it if it hasn't been instantiated yet
val fragment = supportFragmentManager.findFragmentByTag(type) ?: getFragment(type)
// Don't interrupt the current fragment's lifecycle if it isn't fully initialised
if (fragment.lifecycle.currentState != Lifecycle.State.INITIALIZED) {
return false
}
// Start the transaction to show the new fragment
supportFragmentManager.beginTransaction()
.replace(R.id.nav_host_fragment_content_main, fragment, type)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit()
// Success
return true
}
/**
* Retrieve fragment by its tag.
*
* @param type tag for the corresponding fragment
* @return the corresponding fragment
*/
private fun getFragment(type: String) = when (type) {
"canteen" -> CanteenFragment() // CanteenFragment.kt
"settings" -> SettingsFragment() // SettingsFragment.kt
else -> EventFragment() // on unknown value or "event", launch EventFragment.kt
} }
} }
@@ -24,6 +24,7 @@ import java.util.*
*/ */
class StudIPEventItemAdapter( class StudIPEventItemAdapter(
private val currentDay: Int, private val currentDay: Int,
private val highlightNextCourse: Boolean,
private val onClickListener: OnClickListener, private val onClickListener: OnClickListener,
) : RecyclerView.Adapter<StudIPEventItemAdapter.EventViewHolder>() { ) : RecyclerView.Adapter<StudIPEventItemAdapter.EventViewHolder>() {
@@ -80,10 +81,11 @@ class StudIPEventItemAdapter(
val theme = holder.binding.root.context.theme val theme = holder.binding.root.context.theme
/* /*
* Highlight the next upcoming course of the day if the day of the adapter matches the * Highlight the next upcoming course of the day if the user selected the option, if the day
* current day, and if no other course has been highlighted, to avoid double highlighting. * of the adapter matches the current day, and if no other course has been highlighted, to
* avoid double highlighting.
*/ */
if (!isAnyCourseHighlighted && currentItem.isCurrentCourse(currentCalendar)) { if (highlightNextCourse && !isAnyCourseHighlighted && currentItem.isCurrentCourse(currentCalendar)) {
// Ensure that no other course will be highlighted // Ensure that no other course will be highlighted
isAnyCourseHighlighted = true isAnyCourseHighlighted = true
@@ -124,17 +124,6 @@ class CanteenFragment : AppFragment() {
chip.setOnCheckedChangeListener { buttonView, newValue -> chip.setOnCheckedChangeListener { buttonView, newValue ->
// Save the preference change to persistent storage // Save the preference change to persistent storage
setPreference(pref, newValue) setPreference(pref, newValue)
/*
* Force an animation on click. This is a subpar method from StackOverflow. Since
* it removes the view before immediately adding it back, this method causes a
* flicker.
* TODO implement a more efficient / better-looking method
* TODO order the chips alphabetically?
*/
// val index = binding.chipsPreference.indexOfChild(buttonView)
// binding.chipsPreference.removeView(buttonView)
// binding.chipsPreference.addView(buttonView, index)
} }
} }
@@ -152,6 +141,8 @@ class CanteenFragment : AppFragment() {
// Let the adapter know of the new amount of dates (pages) to display // Let the adapter know of the new amount of dates (pages) to display
viewPagerAdapter.itemCount = dates.size viewPagerAdapter.itemCount = dates.size
openingHours = viewModel.getCanteenOpeningHours()
} }
// Assign the adapter to the view pager // Assign the adapter to the view pager
@@ -48,7 +48,11 @@ class EventPageFragment : AppFragment(), StudIPEventItemAdapter.OnClickListener
currentDay = arguments?.getInt("currentDay") ?: -1 currentDay = arguments?.getInt("currentDay") ?: -1
// Instantiate event adapter with the current day // Instantiate event adapter with the current day
eventAdapter = StudIPEventItemAdapter(currentDay, this) eventAdapter = StudIPEventItemAdapter(
currentDay,
viewModel.preferenceCourseHighlighting,
this,
)
// Inflate view binding // Inflate view binding
_binding = RecyclerViewBinding.inflate(inflater, container, false) _binding = RecyclerViewBinding.inflate(inflater, container, false)
@@ -43,7 +43,6 @@ class CanteenPageViewModel(app: Application) : AppViewModel(app) {
/** /**
* This value determines which canteen the user has selected. * This value determines which canteen the user has selected.
*/ */
var preferenceColour: Boolean val preferenceColour: Boolean
get() = repo.getBooleanPreference(SettingsPreferences.COLOUR_PREFS) get() = repo.getBooleanPreference(SettingsPreferences.COLOUR_PREFS, defaultValue = true)
set(newValue) { repo.setPreference(SettingsPreferences.COLOUR_PREFS, newValue) }
} }
@@ -2,6 +2,7 @@ package com.denizk0461.studip.viewmodel
import android.app.Application import android.app.Application
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.denizk0461.studip.model.SettingsPreferences
import com.denizk0461.studip.model.StudIPEvent import com.denizk0461.studip.model.StudIPEvent
/** /**
@@ -17,4 +18,14 @@ class EventPageViewModel(app: Application) : AppViewModel(app) {
* @return Stud.IP events for a certain day exposed through a LiveData object * @return Stud.IP events for a certain day exposed through a LiveData object
*/ */
fun getEventsForDay(day: Int): LiveData<List<StudIPEvent>> = repo.getEventsForDay(day) fun getEventsForDay(day: Int): LiveData<List<StudIPEvent>> = repo.getEventsForDay(day)
/**
* This value determines whether the user wants to have the next course in their schedule
* highlighted.
*/
val preferenceCourseHighlighting: Boolean
get() = repo.getBooleanPreference(
SettingsPreferences.COURSE_HIGHLIGHTING,
defaultValue = true
)
} }
@@ -15,7 +15,10 @@ class SettingsViewModel(app: Application) : AppViewModel(app) {
* highlighted. * highlighted.
*/ */
var preferenceCourseHighlighting: Boolean var preferenceCourseHighlighting: Boolean
get() = repo.getBooleanPreference(SettingsPreferences.COURSE_HIGHLIGHTING) get() = repo.getBooleanPreference(
SettingsPreferences.COURSE_HIGHLIGHTING,
defaultValue = true
)
set(newValue) { repo.setPreference(SettingsPreferences.COURSE_HIGHLIGHTING, newValue) } set(newValue) { repo.setPreference(SettingsPreferences.COURSE_HIGHLIGHTING, newValue) }
/** /**
@@ -36,7 +39,7 @@ class SettingsViewModel(app: Application) : AppViewModel(app) {
* This value determines which canteen the user has selected. * This value determines which canteen the user has selected.
*/ */
var preferenceColour: Boolean var preferenceColour: Boolean
get() = repo.getBooleanPreference(SettingsPreferences.COLOUR_PREFS) get() = repo.getBooleanPreference(SettingsPreferences.COLOUR_PREFS, defaultValue = true)
set(newValue) { repo.setPreference(SettingsPreferences.COLOUR_PREFS, newValue) } set(newValue) { repo.setPreference(SettingsPreferences.COLOUR_PREFS, newValue) }
/** /**
@@ -13,6 +13,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:defaultNavHost="true" app:defaultNavHost="true"
app:navGraph="@navigation/main_nav_graph"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/nav_view" app:layout_constraintBottom_toTopOf="@+id/nav_view"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
+9 -9
View File
@@ -44,6 +44,15 @@
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
<TextView
android:id="@+id/app_title_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/title_food"
android:textSize="32sp"
android:fontFamily="@font/lato_bolditalic"
android:layout_marginHorizontal="16dp"/>
<HorizontalScrollView <HorizontalScrollView
android:id="@+id/scroll_view_chip" android:id="@+id/scroll_view_chip"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -181,15 +190,6 @@
</HorizontalScrollView> </HorizontalScrollView>
<TextView
android:id="@+id/app_title_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/title_food"
android:textSize="32sp"
android:fontFamily="@font/lato_bolditalic"
android:layout_marginHorizontal="16dp"/>
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
<com.google.android.material.tabs.TabLayout <com.google.android.material.tabs.TabLayout
+2 -2
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:id="@+id/plan" android:id="@+id/schedule"
android:icon="@drawable/design" android:icon="@drawable/design"
android:title="@string/nav_schedule" android:title="@string/nav_schedule"
android:enabled="true"/> android:enabled="true"/>
<item <item
android:id="@+id/food" android:id="@+id/canteen"
android:icon="@drawable/restaurant" android:icon="@drawable/restaurant"
android:title="@string/nav_food" android:title="@string/nav_food"
android:enabled="true"/> android:enabled="true"/>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_nav_graph"
app:startDestination="@id/schedule">
<fragment
android:id="@+id/schedule"
android:name="com.denizk0461.studip.fragment.EventFragment"
android:label="EventFragment"
tools:layout="@layout/fragment_event"/>
<fragment
android:id="@+id/canteen"
android:name="com.denizk0461.studip.fragment.CanteenFragment"
android:label="CanteenFragment"/>
<fragment
android:id="@+id/settings"
android:name="com.denizk0461.studip.fragment.SettingsFragment"
android:label="SettingsFragment"
tools:layout="@layout/fragment_settings"/>
</navigation>
+2 -2
View File
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id 'com.android.application' version '8.0.0' apply false id 'com.android.application' version '8.0.1' apply false
id 'com.android.library' version '8.0.0' apply false id 'com.android.library' version '8.0.1' apply false
id 'org.jetbrains.kotlin.android' version '1.8.20' apply false id 'org.jetbrains.kotlin.android' version '1.8.20' apply false
} }