Added app language support (I think); fragments rely on Bundles instead of constructor parameters to get data, mitigating a source of crashes

This commit is contained in:
denizk0461
2023-04-30 22:47:59 +02:00
parent 7243df0f06
commit 079e66ad50
23 changed files with 619 additions and 472 deletions
@@ -1,12 +1,12 @@
package com.denizk0461.studip.activity
import android.annotation.SuppressLint
import android.app.Activity
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.denizk0461.studip.R
import com.denizk0461.studip.data.showErrorSnackBar
@@ -19,7 +19,7 @@ import java.net.URLDecoder
* Activity for allowing the user to log in to access and fetch their schedule from their Stud.IP
* profile. Launched through a button in the app's settings.
*/
class FetcherActivity : Activity() {
class FetcherActivity : AppCompatActivity() {
// View binding
private lateinit var binding: ActivityFetcherBinding
@@ -0,0 +1,32 @@
package com.denizk0461.studip.activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import com.denizk0461.studip.R
import com.denizk0461.studip.databinding.ActivityImageBinding
/**
* Activity used solely to display a single image. Not a critical part of the app.
*/
class ImageActivity : AppCompatActivity() {
// View binding
private lateinit var binding: ActivityImageBinding
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
// Inflate view binding and bind to this activity
binding = ActivityImageBinding.inflate(layoutInflater)
setContentView(binding.root)
// Set the image of the view
binding.imageView.setImageResource(when (intent.extras?.getString("img") ?: "") {
"bojack" -> { R.drawable.man }
"garbage" -> { R.drawable.garbage }
else -> { R.drawable.engineering }
})
}
}
@@ -41,20 +41,6 @@ class MainActivity : FragmentActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 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.contentMain.navView.selectedItemId = R.id.food
currentFragment = "canteen"
} else {
// Open event fragment
binding.contentMain.navView.selectedItemId = R.id.plan
currentFragment = "event"
}
// Set up navigation view bar to launch fragments
binding.contentMain.navView.setOnItemSelectedListener { item ->
// Launch fragment based on the item that has been clicked
@@ -65,8 +51,24 @@ class MainActivity : FragmentActivity() {
})
}
// Launch the fragment defined in currentFragment
loadFragment(currentFragment)
// 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.contentMain.navView.selectedItemId = R.id.food
currentFragment = "canteen"
} else {
// Open event fragment
binding.contentMain.navView.selectedItemId = R.id.plan
currentFragment = "event"
}
// Launch the fragment defined in currentFragment
loadFragment(currentFragment)
}
}
/**
@@ -1,19 +1,22 @@
package com.denizk0461.studip.adapter
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.denizk0461.studip.fragment.CanteenPageFragment
/**
* Custom ViewPager adapter for managing multiple pages of canteen offers.
*
* @param fragmentActivity parent fragment activity
* @param childFragmentManager child fragment manager of the parent fragment
* @param lifecycle parent fragment lifecycle
*/
class CanteenOfferPageAdapter(
fragmentActivity: FragmentActivity,
) : FragmentStateAdapter(fragmentActivity) {
childFragmentManager: FragmentManager,
lifecycle: Lifecycle,
) : FragmentStateAdapter(childFragmentManager, lifecycle) {
private var count: Int = 0
@@ -21,9 +24,11 @@ class CanteenOfferPageAdapter(
override fun getItemCount(): Int = count
override fun createFragment(position: Int): Fragment =
CanteenPageFragment(
position,
)
CanteenPageFragment().also { f ->
val bundle = Bundle()
bundle.putInt("currentDay", position)
f.arguments = bundle
}
fun setItemCount(newCount: Int) {
count = newCount
@@ -1,18 +1,22 @@
package com.denizk0461.studip.adapter
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.denizk0461.studip.fragment.EventPageFragment
/**
* Custom ViewPager adapter for managing multiple pages of Stud.IP events.
*
* @param fragmentActivity parent fragment activity
* @param childFragmentManager child fragment manager of the parent fragment
* @param lifecycle parent fragment lifecycle
*/
class StudIPEventPageAdapter(
fragmentActivity: FragmentActivity,
) : FragmentStateAdapter(fragmentActivity) {
childFragmentManager: FragmentManager,
lifecycle: Lifecycle,
) : FragmentStateAdapter(childFragmentManager, lifecycle) {
/*
* Assume that there will always be 7 pages, for 7 days - Monday through Sunday.
@@ -20,5 +24,9 @@ class StudIPEventPageAdapter(
override fun getItemCount(): Int = 7
override fun createFragment(position: Int): Fragment =
EventPageFragment(position)
EventPageFragment().also { f ->
val bundle = Bundle()
bundle.putInt("currentDay", position)
f.arguments = bundle
}
}
@@ -1,5 +1,6 @@
package com.denizk0461.studip.fragment
import android.content.Context
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.denizk0461.studip.sheet.AppSheet
@@ -10,6 +11,19 @@ import com.denizk0461.studip.sheet.AppSheet
*/
open class AppFragment : Fragment() {
// Internal context object
private lateinit var _context: Context
/**
* Get non-null context. Only valid after onAttach().
*/
override fun getContext(): Context = _context
override fun onAttach(context: Context) {
super.onAttach(context)
_context = context
}
/**
* Opens a bottom sheet. Sheet must be a subclass of [com.denizk0461.studip.sheet.AppSheet].
*
@@ -5,7 +5,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.viewModels
import com.denizk0461.studip.R
import com.denizk0461.studip.adapter.CanteenOfferPageAdapter
@@ -137,7 +136,10 @@ class CanteenFragment : AppFragment() {
}
// Set up the view pager's adapter
viewPagerAdapter = CanteenOfferPageAdapter(activity as FragmentActivity)
viewPagerAdapter = CanteenOfferPageAdapter(
childFragmentManager,
lifecycle,
)
// Date count is observed to correctly set the amount of pages and the corresponding tabs
viewModel.getDates().observe(viewLifecycleOwner) { newDates ->
@@ -225,7 +227,7 @@ class CanteenFragment : AppFragment() {
binding.swipeRefreshLayout.isRefreshing = false
}, onError = {
context?.theme?.showErrorSnackBar(
context.theme?.showErrorSnackBar(
binding.snackbarContainer,
getString(R.string.canteen_fetch_error)
)
@@ -19,12 +19,8 @@ import com.denizk0461.studip.viewmodel.CanteenPageViewModel
/**
* Fragment that is instantiated by [StudIPEventPageAdapter] to display individual days' pages and
* their events.
*
* @param currentDay day to display
*/
class CanteenPageFragment(
private val currentDay: Int,
) : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
class CanteenPageFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
// Nullable view binding reference
private var _binding: RecyclerViewBinding? = null
@@ -42,8 +38,18 @@ class CanteenPageFragment(
private val offerList: MutableList<CanteenOffer> = mutableListOf()
/**
* Current day that is used to show only events of a given day (0 = Monday, 4 = Friday)
*/
private var currentDay: Int = -1
// Instantiate the view binding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// Retrieve current day to fetch items specifically for that day
currentDay = arguments?.getInt("currentDay") ?: -1
// Inflate view binding
_binding = RecyclerViewBinding.inflate(inflater, container, false)
return binding.root
}
@@ -1,11 +1,9 @@
package com.denizk0461.studip.fragment
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.viewModels
import com.denizk0461.studip.R
import com.denizk0461.studip.adapter.StudIPEventPageAdapter
@@ -40,11 +38,6 @@ class EventFragment : AppFragment() {
// Titles for the view pager's tabs
private lateinit var weekdays: Array<String>
// private var viewPagerScrollPosition: Parcelable? = null
private var viewPagerPosition = -1
private var hasFragmentStarted = false
// Instantiate the view binding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentEventBinding.inflate(inflater, container, false)
@@ -55,7 +48,7 @@ class EventFragment : AppFragment() {
super.onViewCreated(view, savedInstanceState)
// Get localised weekday names
weekdays = context?.resources?.getStringArray(R.array.weekdays) ?: arrayOf()
weekdays = context.resources?.getStringArray(R.array.weekdays) ?: arrayOf()
// Determine the current day
dayOfWeek = when (Calendar.getInstance().get(Calendar.DAY_OF_WEEK)) {
@@ -69,7 +62,10 @@ class EventFragment : AppFragment() {
}
// Set up the view pager's adapter
viewPagerAdapter = StudIPEventPageAdapter(activity as FragmentActivity)
viewPagerAdapter = StudIPEventPageAdapter(
childFragmentManager,
lifecycle,
)
// Assign the adapter to the view pager
binding.viewPager.adapter = viewPagerAdapter
@@ -78,75 +74,33 @@ class EventFragment : AppFragment() {
TabLayoutMediator(binding.dayTabLayout, binding.viewPager) { tab, position ->
tab.text = weekdays[position]
}.attach()
// Set up LiveData observer to refresh the view on update
// viewModel.allEvents.observe(viewLifecycleOwner) { events ->
//
// Log.d("AAAbA?", events.toString())
//
// // Update the item list in the view pager's adapter
// viewPagerAdapter.setNewItems(events)
//
// if (!hasFragmentStarted) {
// // Scroll to the current day, if no page has been stored to be scrolled to
// switchToCurrentDayView()
// hasFragmentStarted = true
//// } else {
//// /*
//// * If a page was previously saved, scroll to that one instead. This is meant to
//// * prevent jumping from
//// */
//// binding.viewPager.currentItem = viewPagerPosition
// }
// }
}
override fun onPause() {
// viewPagerPosition = binding.viewPager.currentItem
// Log.d("AAA?", "onpause: $viewPagerPosition")
super.onPause()
}
override fun onResume() {
super.onResume()
binding.viewPager.currentItem = viewPagerPosition
Log.d("AAA?", "onresume: pos: $viewPagerPosition; vpp: ${binding.viewPager.currentItem}")
// Scroll to the current day
// switchToCurrentDayView()
}
/**
* Called when the layout changes (e.g. device rotation) but NOT when the fragment is selected
* from the bottom navigation view.
*/
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Log.d("AAA?", "${binding.viewPager.currentItem}")
outState.putInt(
"viewPagerCurrentPage",
binding.viewPager.currentItem
)
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
val restoredPage = savedInstanceState?.getInt("viewPagerCurrentPage") ?: 0
viewPagerPosition = restoredPage
}
// override fun onSaveInstanceState(outState: Bundle) {
// super.onSaveInstanceState(outState)
//
// outState.putInt(
// "viewPagerCurrentPage",
// binding.viewPager.currentItem
// )
// }
//
// override fun onViewStateRestored(savedInstanceState: Bundle?) {
// super.onViewStateRestored(savedInstanceState)
//
// // Retrieve
// val restoredPage = savedInstanceState?.getInt("viewPagerCurrentPage") ?: 0
//
//// binding.viewPager.currentItem = restoredPage
// }
// Invalidate the view binding
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
/**
* Scroll to the current day. Current day is determined after the fragment's view has been
* created.
*/
private fun switchToCurrentDayView() {
binding.viewPager.currentItem = dayOfWeek
}
}
@@ -16,13 +16,8 @@ import com.denizk0461.studip.viewmodel.EventPageViewModel
/**
* Fragment that is instantiated by [StudIPEventPageAdapter] to display individual days' pages and
* their events.
*
* @param currentDay current day that is used to show only events of a given day (0 = Monday,
* 4 = Friday)
*/
class EventPageFragment(
private val currentDay: Int,
) : AppFragment(), StudIPEventItemAdapter.OnClickListener {
class EventPageFragment : AppFragment(), StudIPEventItemAdapter.OnClickListener {
// Nullable view binding reference
private var _binding: RecyclerViewBinding? = null
@@ -39,10 +34,23 @@ class EventPageFragment(
/**
* Adapter that manages the page's items
*/
private val eventAdapter = StudIPEventItemAdapter(currentDay, this)
private lateinit var eventAdapter: StudIPEventItemAdapter
/**
* Current day that is used to show only events of a given day (0 = Monday, 4 = Friday)
*/
private var currentDay = -1
// Instantiate the view binding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// Retrieve current day to fetch items specifically for that day
currentDay = arguments?.getInt("currentDay") ?: -1
// Instantiate event adapter with the current day
eventAdapter = StudIPEventItemAdapter(currentDay, this)
// Inflate view binding
_binding = RecyclerViewBinding.inflate(inflater, container, false)
return binding.root
}
@@ -11,6 +11,8 @@ import androidx.fragment.app.viewModels
import com.denizk0461.studip.BuildConfig
import com.denizk0461.studip.R
import com.denizk0461.studip.activity.FetcherActivity
import com.denizk0461.studip.activity.ImageActivity
import com.denizk0461.studip.data.showToast
import com.denizk0461.studip.databinding.FragmentSettingsBinding
import com.denizk0461.studip.sheet.DevCodeSheet
import com.denizk0461.studip.sheet.TextSheet
@@ -36,7 +38,7 @@ class SettingsFragment : AppFragment() {
private val viewModel: SettingsViewModel by viewModels()
// Click counter on the app version button
private var appVersionClick = 0
private var appVersionClick = 1
// 222
private val mysteryLink = "https://www.youtube.com/watch?v=nhIQMCXJzLI"
@@ -129,7 +131,39 @@ class SettingsFragment : AppFragment() {
// Set click listener for the app version button
binding.buttonAppVersion.setOnClickListener {
when (appVersionClick) {
22 -> {
3 -> {
startActivity(Intent(context, ImageActivity::class.java).also { intent ->
val bundle = Bundle()
bundle.putString("img", "garbage")
intent.putExtras(bundle)
})
appVersionClick += 1
}
10 -> {
showToast(context, "ahhh yes")
appVersionClick += 1
}
15 -> {
showToast(context, "keep clicking")
appVersionClick += 1
}
20 -> {
showToast(context, "^_^")
appVersionClick += 1
}
30 -> {
showToast(context, "^_____^")
appVersionClick += 1
}
121 -> {
showToast(context, "Shouldn't you study for your exam?")
appVersionClick += 1
}
200 -> {
showToast(context, "\\__*")
appVersionClick += 1
}
222 -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(mysteryLink)))
appVersionClick = 0
}
@@ -145,7 +179,8 @@ class SettingsFragment : AppFragment() {
binding.appVersionText.text =
"${BuildConfig.VERSION_NAME}-${
if (BuildConfig.DEBUG)
"dev-[${SimpleDateFormat("yyyy-MM-dd, HH:mm:ss.SSS", Locale.GERMANY).format(Date(BuildConfig.TIMESTAMP))}]"
"dev-[${SimpleDateFormat("yyyy-MM-dd, HH:mm:ss.SSS", Locale.GERMANY)
.format(Date(BuildConfig.TIMESTAMP))}]"
else
"release"
}"
@@ -6,6 +6,7 @@ import android.os.Bundle
import android.util.Base64
import android.view.View
import com.denizk0461.studip.R
import com.denizk0461.studip.activity.ImageActivity
import com.denizk0461.studip.data.showToast
import com.denizk0461.studip.data.viewBinding
import com.denizk0461.studip.databinding.SheetDevCodeBinding
@@ -63,6 +64,14 @@ class DevCodeSheet(
"X0RSV05f".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9SUVpUUy1CWjhtcw==".d64)
"TUFSR0Uh".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS8tbHFxRHZXRjQ1dw==".d64)
"UE9XRUxM".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9XUGMtVkVxQlBISQ==".d64)
"Q0FMTE1F".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS8xbTV1WnJNMnktMA==".d64)
"Qk9KQUNL".d64 -> {
startActivity(Intent(context, ImageActivity::class.java).also { intent ->
val bundle = Bundle()
bundle.putString("img", "bojack")
intent.putExtras(bundle)
})
}
"NEVENT" -> { // nuke events
nukeEvents()
showToast(context, "Nuked all events")