Added comments and cleaned up code in activities and StudIPEvent.kt

This commit is contained in:
denizk0461
2023-04-21 15:42:21 +02:00
parent 7a6926ff64
commit 2534849329
4 changed files with 117 additions and 108 deletions
@@ -14,9 +14,15 @@ import com.denizk0461.studip.databinding.ActivityFetcherBinding
import com.denizk0461.studip.viewmodel.FetcherViewModel
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() {
// View binding
private lateinit var binding: ActivityFetcherBinding
// View model
private lateinit var viewModel: FetcherViewModel
/*
@@ -27,39 +33,51 @@ class FetcherActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Instantiate view model
viewModel = ViewModelProvider.AndroidViewModelFactory(this.application).create(FetcherViewModel::class.java)
// Inflate view binding and bind to this activity
binding = ActivityFetcherBinding.inflate(layoutInflater)
setContentView(binding.root)
// Enabling JavaScript. See comment above lint suppression for reason why this is done.
binding.webview.settings.apply {
javaScriptEnabled = true
}
binding.webview.webViewClient = object : WebViewClient() {
// Disallow redirecting to prevent the app from launching Chrome after the user logs in
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean {
return false
}
override fun onPageFinished(view: WebView, url: String) {
binding.webview.loadUrl(
"javascript:window.HtmlViewer.showHTML" +
"('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');"
)
}
}
/*
* Attempt to load the user-set Stud.IP front page. Redirects to login page if user is
* not logged in.
*/
binding.webview.loadUrl("https://elearning.uni-bremen.de/index.php?again=yes")
binding.fab.setOnClickListener { view ->
/*
* Retrieve encoded HTML via JavaScript function. Encoding is done as otherwise not all
* symbols will be accurately fetched.
*/
binding.webview.evaluateJavascript(
"(function(){return encodeURI(document.getElementsByTagName('html')[0].innerHTML)})();"
) { p0 ->
// Delete all previously fetched elements
viewModel.nukeEvents()
// Decode HTML and parse it into a list of StudIPEvent.kt
StudIPParser().parse(URLDecoder.decode(p0, "UTF-8")) { events ->
// Insert the list into the database
viewModel.insertEvents(events)
Toast.makeText(this, R.string.toast_fetch_finished, Toast.LENGTH_SHORT).show()
// Notify the user that the fetch was successful. TODO: notify on error
Toast.makeText(
this, R.string.toast_fetch_finished, Toast.LENGTH_SHORT
).show()
// Close the activity
finish()
}
}
@@ -3,9 +3,7 @@ package com.denizk0461.studip.activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.Lifecycle
import com.denizk0461.studip.R
@@ -16,124 +14,100 @@ import com.denizk0461.studip.fragment.EventFragment
import com.denizk0461.studip.fragment.CanteenFragment
import com.denizk0461.studip.fragment.SettingsFragment
/**
* Main activity that handles all common fragments. This is opened on app launch.
*/
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
// View binding
private lateinit var binding: ActivityMainBinding
private var currentFragment = FragmentType.SCHEDULE
private val fragments = listOf(EventFragment(), CanteenFragment(), SettingsFragment())
/*
* ID of the bottom navigation view button that launched the currently active fragment; set on
* activity launch to the fragment that will be first shown
*/
private var currentFragment: Int = R.id.plan
// All fragments are instantiated at app launch
private val fragments: List<Fragment> =
listOf(EventFragment(), CanteenFragment(), SettingsFragment())
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
/*
* Instantiate repository object that is accessed by the fragments' view models to retrieve
* data.
*/
Dependencies.repo = EventRepository(application)
// Inflate view binding and bind to this activity
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
/*
* Set up navigation view bar to launch fragments.
* TODO: why do fragments launch slowly when quickly clicking between them?
*/
binding.contentMain.navView.setOnItemSelectedListener { item ->
loadFragment(bottomIdToType(item.itemId))
loadFragment(item.itemId)
}
// Launch the fragment defined in currentFragment
loadFragment(currentFragment)
// setSupportActionBar(binding.toolbar)
// val navController = findNavController(R.id.nav_host_fragment_content_main)
// appBarConfiguration = AppBarConfiguration(navController.graph)
// setupActionBarWithNavController(navController, appBarConfiguration)
// binding.fab.setOnClickListener { view ->
// findNavController(R.id.nav_host_fragment_content_main).navigate(R.id.action_FirstFragment_to_SecondFragment)
//// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
//// .setAnchorView(R.id.fab)
//// .setAction("Action", null).show()
// }
}
private fun loadFragment(type: FragmentType): Boolean {
// val fragment = supportFragmentManager.findFragmentByTag(type.type) ?: getFragmentByType(type)
val fragment = getFragment(type)
/**
* Loads a given fragment.
*
* @param id the ID of the bottom navigation view button that was clicked
* @return true on success, false if another fragment is currently instantiating
*/
private fun loadFragment(id: Int): Boolean {
// Get fragment to be loaded
val fragment = getFragment(id)
// Check if another fragment exists and is not fully initialised
if (fragment.lifecycle.currentState != Lifecycle.State.INITIALIZED) {
// Return false to avoid interrupting the fragment's lifecycle
return false
}
// if (supportFragmentManager.fragments.size > 1) {
//
// val currentFragment = supportFragmentManager.findFragmentByTag(currentFragment.type)
// if (currentFragment != null) {
// supportFragmentManager.beginTransaction().remove(currentFragment).commit()
// supportFragmentManager.popBackStack()
// }
// }
// supportFragmentManager.popBackStack()
// Prepare and commit a transaction between the current and the next fragment
supportFragmentManager.beginTransaction()
.replace(R.id.nav_host_fragment_content_main, fragment, type.type)
.replace(R.id.nav_host_fragment_content_main, fragment, id.toString())
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
binding.contentMain.appTitleBar.text = getTitleString(type)
currentFragment = type
// Set text of the app bar accordingly
binding.contentMain.appTitleBar.text = getTitleString(id)
// Note down the new current fragment
currentFragment = id
// Transaction successful
return true
}
// override fun onCreateOptionsMenu(menu: Menu): Boolean {
// // Inflate the menu; this adds items to the action bar if it is present.
// menuInflater.inflate(R.menu.menu_main, menu)
// return true
// }
// override fun onOptionsItemSelected(item: MenuItem): Boolean {
// // Handle action bar item clicks here. The action bar will
// // automatically handle clicks on the Home/Up button, so long
// // as you specify a parent activity in AndroidManifest.xml.
// return when (item.itemId) {
// R.id.action_settings -> true
// else -> super.onOptionsItemSelected(item)
// }
// }
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
/**
* Retrieve fragment by the ID of the bottom navigation view's button.
*
* @param id the ID of the bottom navigation button
* @return the corresponding fragment
*/
private fun getFragment(id: Int) = when (id) {
R.id.food -> fragments[1] // CanteenFragment.kt
R.id.settings -> fragments[2] // SettingsFragment.kt
else -> fragments[0] // on unknown value or R.id.plan, launch EventFragment.kt
}
// private fun getFragmentByType(type: FragmentType): Fragment {
//// Log.d("AAA!", "help")
// return when (type) {
// FragmentType.SCHEDULE -> EventFragment()
// FragmentType.FOOD -> FoodFragment()
// FragmentType.SETTINGS -> SettingsFragment()
// }
// }
private fun bottomIdToType(id: Int): FragmentType = when (id) {
R.id.food -> FragmentType.FOOD
R.id.settings -> FragmentType.SETTINGS
else -> FragmentType.SCHEDULE
}
private fun getFragment(type: FragmentType) = when (type) {
FragmentType.SCHEDULE -> fragments[0]
FragmentType.FOOD -> fragments[1]
FragmentType.SETTINGS -> fragments[2]
}
private fun getTitleString(type: FragmentType) = when (type) {
FragmentType.SCHEDULE -> getString(R.string.title_schedule)
FragmentType.FOOD -> getString(R.string.title_food)
FragmentType.SETTINGS -> getString(R.string.title_settings)
}
enum class FragmentType(val type: String) {
SCHEDULE("schedule"),
FOOD("food"),
SETTINGS("settings"),
/**
* Retrieve app bar text by the ID of the bottom navigation view's button.
*
* @param id the ID of the bottom navigation button
* @return the corresponding fragment's title
*/
private fun getTitleString(id: Int) = when (id) {
R.id.food -> getString(R.string.title_food)
R.id.settings -> getString(R.string.title_settings)
else -> getString(R.string.title_schedule) // on unknown value or R.id.plan, get text for EventFragment.kt
}
}
@@ -20,10 +20,9 @@ class CanteenFragment : Fragment() {
// This property is only valid between onCreateView and
// onDestroyView.
// BUG: if the fragment is changed before the refresh is finished, the app crashes with a NPE
private val binding get() = _binding!!
private lateinit var liveData: LiveData<List<CanteenOffer>>
private lateinit var viewPagerAdapter: CanteenOfferPageAdapter
private val viewModel: CanteenViewModel by viewModels()
@@ -3,23 +3,40 @@ package com.denizk0461.studip.model
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* Entity for storing entries of the user's Stud.IP schedule. Registered in the app database.
*
* @param id primary key that uniquely identifies the entry
* @param title title of the event
* @param lecturer lecturer(s) organising the event
* @param room room the event takes place in
* @param day day the event takes place on; 0 = Monday, 4 = Friday
* @param timeslotStart time the event starts at
* @param timeslotEnd time the event ends at
* @param colour user-defined colour the event will be shown in - UNIMPLEMENTED
*/
@Entity(tableName = "events")
data class StudIPEvent(
@PrimaryKey val id: Int,
val title: String, // the name of the event
val lecturer: String, // the lecturer(s)
val room: String, // the room the event takes place in
val day: Int, // 0 = Monday, 1 = Tuesday, 2 = Wednesday, 3 = Thursday, 4 = Friday
val timeslotStart: String, // the time the course starts
val timeslotEnd: String, // the time the course ends
val colour: String = "000000", // user-defined colour, TODO implement this
val title: String,
val lecturer: String,
val room: String,
val day: Int,
val timeslotStart: String,
val timeslotEnd: String,
val colour: String = "000000",
) {
// @Ignore private val timeSlotStartTimes: List<String> = listOf("06:15", "08:15", "10:15", "12:15", "14:15", "16:15", "18:15", "20:15")
// @Ignore private val timeSlotEndTimes: List<String> = listOf("07:45", "09:45", "11:45", "13:45", "15:45", "17:45", "19:45", "21:45")
/**
* Parse timeslotStart and timeslotEnd into an easily readable string in the following format:
* 12:15 13:45
*
* @return formatted time string
*/
fun timeslot(): String = "$timeslotStart $timeslotEnd"
// Auto-generated methods
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@@ -39,6 +56,7 @@ data class StudIPEvent(
result = 31 * result + day
result = 31 * result + timeslotStart.hashCode()
result = 31 * result + timeslotEnd.hashCode()
result = 31 * result + colour.hashCode()
return result
}
}