Archived
Added comments and cleaned up code in activities and StudIPEvent.kt
This commit is contained in:
@@ -14,9 +14,15 @@ import com.denizk0461.studip.databinding.ActivityFetcherBinding
|
|||||||
import com.denizk0461.studip.viewmodel.FetcherViewModel
|
import com.denizk0461.studip.viewmodel.FetcherViewModel
|
||||||
import java.net.URLDecoder
|
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 : Activity() {
|
||||||
|
|
||||||
|
// View binding
|
||||||
private lateinit var binding: ActivityFetcherBinding
|
private lateinit var binding: ActivityFetcherBinding
|
||||||
|
// View model
|
||||||
private lateinit var viewModel: FetcherViewModel
|
private lateinit var viewModel: FetcherViewModel
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -27,39 +33,51 @@ class FetcherActivity : Activity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// Instantiate view model
|
||||||
viewModel = ViewModelProvider.AndroidViewModelFactory(this.application).create(FetcherViewModel::class.java)
|
viewModel = ViewModelProvider.AndroidViewModelFactory(this.application).create(FetcherViewModel::class.java)
|
||||||
|
// Inflate view binding and bind to this activity
|
||||||
binding = ActivityFetcherBinding.inflate(layoutInflater)
|
binding = ActivityFetcherBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
// Enabling JavaScript. See comment above lint suppression for reason why this is done.
|
||||||
binding.webview.settings.apply {
|
binding.webview.settings.apply {
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
}
|
}
|
||||||
binding.webview.webViewClient = object : WebViewClient() {
|
binding.webview.webViewClient = object : WebViewClient() {
|
||||||
|
// Disallow redirecting to prevent the app from launching Chrome after the user logs in
|
||||||
override fun shouldOverrideUrlLoading(
|
override fun shouldOverrideUrlLoading(
|
||||||
view: WebView,
|
view: WebView,
|
||||||
request: WebResourceRequest
|
request: WebResourceRequest
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return false
|
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.webview.loadUrl("https://elearning.uni-bremen.de/index.php?again=yes")
|
||||||
|
|
||||||
binding.fab.setOnClickListener { view ->
|
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(
|
binding.webview.evaluateJavascript(
|
||||||
"(function(){return encodeURI(document.getElementsByTagName('html')[0].innerHTML)})();"
|
"(function(){return encodeURI(document.getElementsByTagName('html')[0].innerHTML)})();"
|
||||||
) { p0 ->
|
) { p0 ->
|
||||||
|
// Delete all previously fetched elements
|
||||||
viewModel.nukeEvents()
|
viewModel.nukeEvents()
|
||||||
|
// Decode HTML and parse it into a list of StudIPEvent.kt
|
||||||
StudIPParser().parse(URLDecoder.decode(p0, "UTF-8")) { events ->
|
StudIPParser().parse(URLDecoder.decode(p0, "UTF-8")) { events ->
|
||||||
|
// Insert the list into the database
|
||||||
viewModel.insertEvents(events)
|
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()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package com.denizk0461.studip.activity
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.navigation.findNavController
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.ui.AppBarConfiguration
|
|
||||||
import androidx.navigation.ui.navigateUp
|
|
||||||
import androidx.fragment.app.FragmentTransaction
|
import androidx.fragment.app.FragmentTransaction
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import com.denizk0461.studip.R
|
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.CanteenFragment
|
||||||
import com.denizk0461.studip.fragment.SettingsFragment
|
import com.denizk0461.studip.fragment.SettingsFragment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main activity that handles all common fragments. This is opened on app launch.
|
||||||
|
*/
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
// View binding
|
||||||
private lateinit var binding: ActivityMainBinding
|
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?) {
|
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 = EventRepository(application)
|
Dependencies.repo = EventRepository(application)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
* TODO: why do fragments launch slowly when quickly clicking between them?
|
||||||
|
*/
|
||||||
binding.contentMain.navView.setOnItemSelectedListener { item ->
|
binding.contentMain.navView.setOnItemSelectedListener { item ->
|
||||||
loadFragment(bottomIdToType(item.itemId))
|
loadFragment(item.itemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Launch the fragment defined in currentFragment
|
||||||
loadFragment(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)
|
* Loads a given fragment.
|
||||||
val fragment = getFragment(type)
|
*
|
||||||
|
* @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) {
|
if (fragment.lifecycle.currentState != Lifecycle.State.INITIALIZED) {
|
||||||
|
// Return false to avoid interrupting the fragment's lifecycle
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (supportFragmentManager.fragments.size > 1) {
|
// Prepare and commit a transaction between the current and the next fragment
|
||||||
//
|
|
||||||
// val currentFragment = supportFragmentManager.findFragmentByTag(currentFragment.type)
|
|
||||||
// if (currentFragment != null) {
|
|
||||||
// supportFragmentManager.beginTransaction().remove(currentFragment).commit()
|
|
||||||
// supportFragmentManager.popBackStack()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// supportFragmentManager.popBackStack()
|
|
||||||
supportFragmentManager.beginTransaction()
|
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)
|
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||||
.commit()
|
.commit()
|
||||||
|
|
||||||
binding.contentMain.appTitleBar.text = getTitleString(type)
|
// Set text of the app bar accordingly
|
||||||
|
binding.contentMain.appTitleBar.text = getTitleString(id)
|
||||||
currentFragment = type
|
// Note down the new current fragment
|
||||||
|
currentFragment = id
|
||||||
|
|
||||||
|
// Transaction successful
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
/**
|
||||||
// // Inflate the menu; this adds items to the action bar if it is present.
|
* Retrieve fragment by the ID of the bottom navigation view's button.
|
||||||
// menuInflater.inflate(R.menu.menu_main, menu)
|
*
|
||||||
// return true
|
* @param id the ID of the bottom navigation button
|
||||||
// }
|
* @return the corresponding fragment
|
||||||
|
*/
|
||||||
// override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
private fun getFragment(id: Int) = when (id) {
|
||||||
// // Handle action bar item clicks here. The action bar will
|
R.id.food -> fragments[1] // CanteenFragment.kt
|
||||||
// // automatically handle clicks on the Home/Up button, so long
|
R.id.settings -> fragments[2] // SettingsFragment.kt
|
||||||
// // as you specify a parent activity in AndroidManifest.xml.
|
else -> fragments[0] // on unknown value or R.id.plan, launch EventFragment.kt
|
||||||
// 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private fun getFragmentByType(type: FragmentType): Fragment {
|
/**
|
||||||
//// Log.d("AAA!", "help")
|
* Retrieve app bar text by the ID of the bottom navigation view's button.
|
||||||
// return when (type) {
|
*
|
||||||
// FragmentType.SCHEDULE -> EventFragment()
|
* @param id the ID of the bottom navigation button
|
||||||
// FragmentType.FOOD -> FoodFragment()
|
* @return the corresponding fragment's title
|
||||||
// FragmentType.SETTINGS -> SettingsFragment()
|
*/
|
||||||
// }
|
private fun getTitleString(id: Int) = when (id) {
|
||||||
// }
|
R.id.food -> getString(R.string.title_food)
|
||||||
|
R.id.settings -> getString(R.string.title_settings)
|
||||||
private fun bottomIdToType(id: Int): FragmentType = when (id) {
|
else -> getString(R.string.title_schedule) // on unknown value or R.id.plan, get text for EventFragment.kt
|
||||||
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"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,10 +20,9 @@ class CanteenFragment : Fragment() {
|
|||||||
|
|
||||||
// This property is only valid between onCreateView and
|
// This property is only valid between onCreateView and
|
||||||
// onDestroyView.
|
// onDestroyView.
|
||||||
|
// BUG: if the fragment is changed before the refresh is finished, the app crashes with a NPE
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private lateinit var liveData: LiveData<List<CanteenOffer>>
|
|
||||||
|
|
||||||
private lateinit var viewPagerAdapter: CanteenOfferPageAdapter
|
private lateinit var viewPagerAdapter: CanteenOfferPageAdapter
|
||||||
private val viewModel: CanteenViewModel by viewModels()
|
private val viewModel: CanteenViewModel by viewModels()
|
||||||
|
|
||||||
|
|||||||
@@ -3,23 +3,40 @@ package com.denizk0461.studip.model
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
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")
|
@Entity(tableName = "events")
|
||||||
data class StudIPEvent(
|
data class StudIPEvent(
|
||||||
@PrimaryKey val id: Int,
|
@PrimaryKey val id: Int,
|
||||||
val title: String, // the name of the event
|
val title: String,
|
||||||
val lecturer: String, // the lecturer(s)
|
val lecturer: String,
|
||||||
val room: String, // the room the event takes place in
|
val room: String,
|
||||||
val day: Int, // 0 = Monday, 1 = Tuesday, 2 = Wednesday, 3 = Thursday, 4 = Friday
|
val day: Int,
|
||||||
val timeslotStart: String, // the time the course starts
|
val timeslotStart: String,
|
||||||
val timeslotEnd: String, // the time the course ends
|
val timeslotEnd: String,
|
||||||
val colour: String = "000000", // user-defined colour, TODO implement this
|
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"
|
fun timeslot(): String = "$timeslotStart – $timeslotEnd"
|
||||||
|
|
||||||
|
// Auto-generated methods
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
@@ -39,6 +56,7 @@ data class StudIPEvent(
|
|||||||
result = 31 * result + day
|
result = 31 * result + day
|
||||||
result = 31 * result + timeslotStart.hashCode()
|
result = 31 * result + timeslotStart.hashCode()
|
||||||
result = 31 * result + timeslotEnd.hashCode()
|
result = 31 * result + timeslotEnd.hashCode()
|
||||||
|
result = 31 * result + colour.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user