Food menu now splits up holiday messages; added documentation

This commit is contained in:
Deniz Düzgören
2019-10-01 10:38:41 +02:00
parent 56c7e8ba53
commit f6283c8221
7 changed files with 83 additions and 53 deletions
@@ -25,6 +25,7 @@ import com.denizd.substitutionplan.data.HelperFunctions
import com.denizd.substitutionplan.data.LoginWebViewClient import com.denizd.substitutionplan.data.LoginWebViewClient
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import java.lang.Exception
import kotlin.math.hypot import kotlin.math.hypot
/** /**
@@ -80,45 +81,49 @@ internal class Login : AppCompatActivity(R.layout.activity_login_webview), Login
private fun success() { private fun success() {
prefs.edit().putBoolean("successful_login", true).apply() prefs.edit().putBoolean("successful_login", true).apply()
val layoutAfterCircleReveal = findViewById<LinearLayout>(R.id.layoutAfterCircleReveal)
val x: Int = logInButton.right - logInButton.width / 2
val y: Int = logInButton.bottom - logInButton.height / 2
val endRadius = hypot(parentLayout.width.toDouble(), parentLayout.height.toDouble()).toInt()
View.inflate(this, R.layout.unlock_screen, layoutAfterCircleReveal)
val anim = ViewAnimationUtils.createCircularReveal(layoutAfterCircleReveal, x, y, 0F, endRadius.toFloat())
val handler = Handler() val handler = Handler()
val animOut = AnimationUtils.loadAnimation(this, R.anim.fade_out_short) try {
anim.duration = 600 val layoutAfterCircleReveal = findViewById<LinearLayout>(R.id.layoutAfterCircleReveal)
val colour = findViewById<LinearLayout>(R.id.colourLayout)
layoutAfterCircleReveal.visibility = View.VISIBLE val x: Int = logInButton.right - logInButton.width / 2
anim.start() val y: Int = logInButton.bottom - logInButton.height / 2
logInButton.hide() val endRadius = hypot(parentLayout.width.toDouble(), parentLayout.height.toDouble()).toInt()
View.inflate(this, R.layout.unlock_screen, layoutAfterCircleReveal)
val anim = ViewAnimationUtils.createCircularReveal(layoutAfterCircleReveal, x, y, 0F, endRadius.toFloat())
val animOut = AnimationUtils.loadAnimation(this, R.anim.fade_out_short)
anim.duration = 600
val colour = findViewById<LinearLayout>(R.id.colourLayout)
layoutAfterCircleReveal.visibility = View.VISIBLE
anim.start()
logInButton.hide()
handler.postDelayed({
colour.startAnimation(animOut)
}, 400)
animOut.setAnimationListener(object: Animation.AnimationListener {
override fun onAnimationStart(arg0: Animation) {}
override fun onAnimationRepeat(arg0: Animation) {}
override fun onAnimationEnd(arg0: Animation) {
colour.visibility = View.GONE
}
})
handler.postDelayed({
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
val mImgCheck = findViewById<ImageView>(R.id.imageView).drawable as AnimatedVectorDrawable
mImgCheck.start()
} else {
val mImgCheck = findViewById<ImageView>(R.id.imageView).drawable as AnimatedVectorDrawableCompat
mImgCheck.start()
}
}, 600)
} catch (e: Exception) {
} finally {
handler.postDelayed({
val main = Intent(this, Main::class.java)
startActivity(main)
finish()
}, 3000)
}
handler.postDelayed({
colour.startAnimation(animOut)
}, 400)
animOut.setAnimationListener(object: Animation.AnimationListener {
override fun onAnimationStart(arg0: Animation) {}
override fun onAnimationRepeat(arg0: Animation) {}
override fun onAnimationEnd(arg0: Animation) {
colour.visibility = View.GONE
}
})
handler.postDelayed({
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
val mImgCheck = findViewById<ImageView>(R.id.imageView).drawable as AnimatedVectorDrawable
mImgCheck.start()
} else {
val mImgCheck = findViewById<ImageView>(R.id.imageView).drawable as AnimatedVectorDrawableCompat
mImgCheck.start()
}
}, 600)
handler.postDelayed({
val main = Intent(this, Main::class.java)
startActivity(main)
finish()
}, 3000)
} }
} }
@@ -86,8 +86,10 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) {
val contextView = findViewById<View>(R.id.coordination) val contextView = findViewById<View>(R.id.coordination)
val window = this.window val window = this.window
/// Sets the theme explicitly to dismiss the splash screen
setTheme(R.style.AppTheme0) setTheme(R.style.AppTheme0)
/// Sets the system bar's colours according to the current Android version
val barColour = when { val barColour = when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.M -> ContextCompat.getColor(context, R.color.legacyBlack) Build.VERSION.SDK_INT < Build.VERSION_CODES.M -> ContextCompat.getColor(context, R.color.legacyBlack)
Build.VERSION.SDK_INT <= Build.VERSION_CODES.P -> ContextCompat.getColor(context, R.color.colorBackground) Build.VERSION.SDK_INT <= Build.VERSION_CODES.P -> ContextCompat.getColor(context, R.color.colorBackground)
@@ -98,6 +100,7 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) {
window.statusBarColor = barColour window.statusBarColor = barColour
} }
/// Applies theming with additional workarounds for API levels 23-28 (M-P)
when (prefs.getInt("themeInt", 0)) { when (prefs.getInt("themeInt", 0)) {
0 -> { 0 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
@@ -114,12 +117,18 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) {
} }
} }
/// Displays the date and time of the last refresh of the substitution plan in a snack bar
if (!prefs.getBoolean("autoRefresh", false) && prefs.getInt("firstTimeOpening", 0) != 0) { if (!prefs.getBoolean("autoRefresh", false) && prefs.getInt("firstTimeOpening", 0) != 0) {
try { try {
Snackbar.make(contextView, "${getString(R.string.last_updated)} ${prefs.getString("timeNew", "")}", Snackbar.LENGTH_LONG).show() Snackbar.make(contextView, "${getString(R.string.last_updated)} ${prefs.getString("timeNew", "")}", Snackbar.LENGTH_LONG).show()
} catch (e: IllegalArgumentException) {} } catch (e: IllegalArgumentException) {}
} }
/**
* Retrieves the greeting string if enabled by the user, sets it empty otherwise.
* Don't set the text view of the greeting string to View.GONE, as that will mess
* with the constraints
*/
val textViewGreeting = findViewById<TextView>(R.id.text_greeting) val textViewGreeting = findViewById<TextView>(R.id.text_greeting)
textViewGreeting.text = if (prefs.getBoolean("greeting", true) && (prefs.getString("username", "") ?: "").isNotEmpty()) { textViewGreeting.text = if (prefs.getBoolean("greeting", true) && (prefs.getString("username", "") ?: "").isNotEmpty()) {
getGreetingString() getGreetingString()
@@ -127,6 +136,7 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) {
"" ""
} }
/// Legacy function
if (prefs.getInt("firstTimeOpening", 0) == 0) { if (prefs.getInt("firstTimeOpening", 0) == 0) {
edit.putInt("firstTimeOpening", prefs.getInt("firstTimeOpening", 0) + 1).apply() edit.putInt("firstTimeOpening", prefs.getInt("firstTimeOpening", 0) + 1).apply()
} }
@@ -143,6 +153,7 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) {
} }
loadFragment(defaultFragment) loadFragment(defaultFragment)
/// Opens the corresponding fragment or the info dialog
bottomNav.setOnNavigationItemSelectedListener { item: MenuItem -> bottomNav.setOnNavigationItemSelectedListener { item: MenuItem ->
var fragmentLoading = true var fragmentLoading = true
lateinit var fragment: Fragment lateinit var fragment: Fragment
@@ -176,6 +187,7 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) {
} }
} }
/// Scrolls to the top of any currently displayed fragment
bottomNav.setOnNavigationItemReselectedListener { item: MenuItem -> bottomNav.setOnNavigationItemReselectedListener { item: MenuItem ->
when (item.itemId) { when (item.itemId) {
R.id.plan, R.id.personal -> { R.id.plan, R.id.personal -> {
@@ -231,6 +243,10 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) {
}, prefs.getString("username", "")) }, prefs.getString("username", ""))
} }
/**
* Retrieves the strings provided in the substitution plan's info boxes as well as the
* refresh date and time from Shared Preferences and displays them in a dialog
*/
private fun openInfoDialog() { private fun openInfoDialog() {
val dialog = AlertDialog.Builder(context) val dialog = AlertDialog.Builder(context)
val dialogView = View.inflate(context, R.layout.simple_dialog, null) val dialogView = View.inflate(context, R.layout.simple_dialog, null)
@@ -247,9 +263,7 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) {
return true return true
} }
/** /// Triggers FBPingService.kt to re-subscribe to the selected Firebase topics every 15 minutes
* Triggers FBPingService.kt to re-subscribe to the selected Firebase topics every 15 minutes
*/
private fun pingFirebaseTopics() { private fun pingFirebaseTopics() {
val componentName = ComponentName(this, FBPingService::class.java) val componentName = ComponentName(this, FBPingService::class.java)
val info = JobInfo.Builder(42, componentName) val info = JobInfo.Builder(42, componentName)
@@ -30,6 +30,13 @@ import java.util.*
*/ */
internal class SubstitutionAdapter(private var substitutions: List<Substitution>, private val prefs: SharedPreferences) : RecyclerView.Adapter<SubstitutionAdapter.CardViewHolder>() { internal class SubstitutionAdapter(private var substitutions: List<Substitution>, private val prefs: SharedPreferences) : RecyclerView.Adapter<SubstitutionAdapter.CardViewHolder>() {
/**
* The ViewHolder class used by SubstitutionAdapter.kt to resolve references to views in
* course_card.xml that will be inflated and populated with data in
* SubstitutionAdapter#onBindViewHolder
*
* @param view a reference to the xml that is to be inflated
*/
internal class CardViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener { internal class CardViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
var iconView: ImageView = view.findViewById(R.id.iconView) var iconView: ImageView = view.findViewById(R.id.iconView)
@@ -43,7 +50,9 @@ internal class SubstitutionAdapter(private var substitutions: List<Substitution>
var spacer: TextView = view.findViewById(R.id.spacer) var spacer: TextView = view.findViewById(R.id.spacer)
var card: MaterialCardView = view.findViewById(R.id.planCard) var card: MaterialCardView = view.findViewById(R.id.planCard)
val context: Context = card.context val context: Context = card.context
init { view.setOnClickListener(this) } init { view.setOnClickListener(this) }
override fun onClick(v: View?) { override fun onClick(v: View?) {
if (date.text.toString().length > 7 && date.text.toString().substring(3, 7) == "http") { if (date.text.toString().length > 7 && date.text.toString().substring(3, 7) == "http") {
try { try {
@@ -77,14 +86,14 @@ internal class SubstitutionAdapter(private var substitutions: List<Substitution>
} }
} }
val add = currentItem.additional.toLowerCase(Locale.ROOT) val add = currentItem.additional
val type = currentItem.type.toLowerCase(Locale.ROOT) val type = currentItem.type.toLowerCase(Locale.ROOT)
if (add.isNotEmpty()) { if (add.isNotEmpty()) {
if (HelperFunctions.checkStringForArray(add, HelperFunctions.cancellations)) { if (HelperFunctions.checkStringForArray(add, HelperFunctions.cancellations, true)) {
strikeThrough(strings) strikeThrough(strings)
} }
} else { } else {
if (HelperFunctions.checkStringForArray(type, HelperFunctions.cancellations)) { if (HelperFunctions.checkStringForArray(type, HelperFunctions.cancellations, true)) {
strikeThrough(strings) strikeThrough(strings)
} }
} }
@@ -161,6 +170,7 @@ internal class SubstitutionAdapter(private var substitutions: List<Substitution>
notifyDataSetChanged() notifyDataSetChanged()
} }
/// Strikes through the strings for course, room and teacher
private fun strikeThrough(strings: Array<SpannableString>) { private fun strikeThrough(strings: Array<SpannableString>) {
for (i in 2..4) { strings[i].setSpan(StrikethroughSpan(), 0, strings[i].length, 0) } for (i in 2..4) { strings[i].setSpan(StrikethroughSpan(), 0, strings[i].length, 0) }
} }
@@ -122,9 +122,9 @@ internal class DataFetcher(isPlan: Boolean, isMenu: Boolean, isJobService: Boole
foodRepository.deleteAll() foodRepository.deleteAll()
val indices = ArrayList<Int>() val indices = ArrayList<Int>()
val daysAndVon = arrayOf("Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "von") val daysAndVon = arrayOf("montag", "dienstag", "mittwoch", "donnerstag", "freitag", "von", "wünschen")
for (i in 0 until foodElements.size) { for (i in 0 until foodElements.size) {
if (HelperFunctions.checkStringForArray(foodElements[i].text(), daysAndVon)) indices.add(i) if (HelperFunctions.checkStringForArray(foodElements[i].text(), daysAndVon, true)) indices.add(i)
} }
indices.add(foodElements.size) indices.add(foodElements.size)
@@ -47,8 +47,8 @@ internal object HelperFunctions {
"lavender", "carnation", "brown2", "pureBlack") "lavender", "carnation", "brown2", "pureBlack")
/** /**
* A list of all phrases used to describe that a course has been cancelled. Expand this * A lowercased list of all phrases used to describe that a course has been cancelled. Expand
* if necessary. No further code changes required if this is expanded * this if necessary. No further code changes required if this is expanded
*/ */
val cancellations = arrayOf("eigenverantwortliches arbeiten", "entfall", "entfällt", "fällt aus", "freisetzung", "vtr. ohne lehrer") val cancellations = arrayOf("eigenverantwortliches arbeiten", "entfall", "entfällt", "fällt aus", "freisetzung", "vtr. ohne lehrer")
@@ -305,8 +305,9 @@ internal object HelperFunctions {
} }
} }
fun checkStringForArray(s: String, checking: Array<String>): Boolean { fun checkStringForArray(checkedString: String, checkingArray: Array<String>, lowerCased: Boolean): Boolean {
checking.forEach { check -> val s = if (lowerCased) checkedString.toLowerCase(Locale.ROOT) else checkedString
checkingArray.forEach { check ->
if (s.contains(check)) return true if (s.contains(check)) return true
} }
return false return false
@@ -330,7 +331,7 @@ internal object HelperFunctions {
val a = group.substring(0, 2).toInt() val a = group.substring(0, 2).toInt()
-a -a
} }
checkStringForArray(group.substring(0, 1), juniors) -> -101 checkStringForArray(group.substring(0, 1), juniors, false) -> -101
else -> -100 else -> -100
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -2,7 +2,7 @@ package com.denizd.substitutionplan.data
/** /**
* Enum class for declaring Firebase Cloud Messaging topics without using the raw strings * Enum class for declaring Firebase Cloud Messaging topics without using the raw strings
* throughout the project * throughout the project to avoid human error
*/ */
internal enum class Topic(val tag: String) { internal enum class Topic(val tag: String) {
ANDROID("substitutions-android"), ANDROID("substitutions-android"),
@@ -12,7 +12,7 @@ import com.google.firebase.messaging.FirebaseMessaging
* issues regarding the app being force-closed in the background and not being able to receive * issues regarding the app being force-closed in the background and not being able to receive
* notifications. * notifications.
* *
* I am unsure about its effectiveness, though notifications do seem to arrive * I am unsure about its effectiveness, though notifications do seem to arrive relatively reliably
*/ */
internal class FBPingService : JobService() { internal class FBPingService : JobService() {