diff --git a/app/src/main/java/com/denizd/substitutionplan/activities/Login.kt b/app/src/main/java/com/denizd/substitutionplan/activities/Login.kt index 50b46e7..6500970 100644 --- a/app/src/main/java/com/denizd/substitutionplan/activities/Login.kt +++ b/app/src/main/java/com/denizd/substitutionplan/activities/Login.kt @@ -25,6 +25,7 @@ import com.denizd.substitutionplan.data.HelperFunctions import com.denizd.substitutionplan.data.LoginWebViewClient import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.snackbar.Snackbar +import java.lang.Exception import kotlin.math.hypot /** @@ -80,45 +81,49 @@ internal class Login : AppCompatActivity(R.layout.activity_login_webview), Login private fun success() { prefs.edit().putBoolean("successful_login", true).apply() - - val layoutAfterCircleReveal = findViewById(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 animOut = AnimationUtils.loadAnimation(this, R.anim.fade_out_short) - anim.duration = 600 - val colour = findViewById(R.id.colourLayout) - layoutAfterCircleReveal.visibility = View.VISIBLE - anim.start() - logInButton.hide() + try { + val layoutAfterCircleReveal = findViewById(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 animOut = AnimationUtils.loadAnimation(this, R.anim.fade_out_short) + anim.duration = 600 + val colour = findViewById(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(R.id.imageView).drawable as AnimatedVectorDrawable + mImgCheck.start() + } else { + val mImgCheck = findViewById(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(R.id.imageView).drawable as AnimatedVectorDrawable - mImgCheck.start() - } else { - val mImgCheck = findViewById(R.id.imageView).drawable as AnimatedVectorDrawableCompat - mImgCheck.start() - } - }, 600) - handler.postDelayed({ - val main = Intent(this, Main::class.java) - startActivity(main) - finish() - }, 3000) } } \ No newline at end of file diff --git a/app/src/main/java/com/denizd/substitutionplan/activities/Main.kt b/app/src/main/java/com/denizd/substitutionplan/activities/Main.kt index 3c56668..e08d0ca 100644 --- a/app/src/main/java/com/denizd/substitutionplan/activities/Main.kt +++ b/app/src/main/java/com/denizd/substitutionplan/activities/Main.kt @@ -86,8 +86,10 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) { val contextView = findViewById(R.id.coordination) val window = this.window + /// Sets the theme explicitly to dismiss the splash screen setTheme(R.style.AppTheme0) + /// Sets the system bar's colours according to the current Android version val barColour = when { 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) @@ -98,6 +100,7 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) { window.statusBarColor = barColour } + /// Applies theming with additional workarounds for API levels 23-28 (M-P) when (prefs.getInt("themeInt", 0)) { 0 -> { 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) { try { Snackbar.make(contextView, "${getString(R.string.last_updated)} ${prefs.getString("timeNew", "")}", Snackbar.LENGTH_LONG).show() } 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(R.id.text_greeting) textViewGreeting.text = if (prefs.getBoolean("greeting", true) && (prefs.getString("username", "") ?: "").isNotEmpty()) { getGreetingString() @@ -127,6 +136,7 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) { "" } + /// Legacy function if (prefs.getInt("firstTimeOpening", 0) == 0) { edit.putInt("firstTimeOpening", prefs.getInt("firstTimeOpening", 0) + 1).apply() } @@ -143,6 +153,7 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) { } loadFragment(defaultFragment) + /// Opens the corresponding fragment or the info dialog bottomNav.setOnNavigationItemSelectedListener { item: MenuItem -> var fragmentLoading = true 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 -> when (item.itemId) { R.id.plan, R.id.personal -> { @@ -231,6 +243,10 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) { }, 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() { val dialog = AlertDialog.Builder(context) 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 } - /** - * 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() { val componentName = ComponentName(this, FBPingService::class.java) val info = JobInfo.Builder(42, componentName) diff --git a/app/src/main/java/com/denizd/substitutionplan/adapters/SubstitutionAdapter.kt b/app/src/main/java/com/denizd/substitutionplan/adapters/SubstitutionAdapter.kt index 1dba6a5..509d06e 100644 --- a/app/src/main/java/com/denizd/substitutionplan/adapters/SubstitutionAdapter.kt +++ b/app/src/main/java/com/denizd/substitutionplan/adapters/SubstitutionAdapter.kt @@ -30,6 +30,13 @@ import java.util.* */ internal class SubstitutionAdapter(private var substitutions: List, private val prefs: SharedPreferences) : RecyclerView.Adapter() { + /** + * 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 { var iconView: ImageView = view.findViewById(R.id.iconView) @@ -43,7 +50,9 @@ internal class SubstitutionAdapter(private var substitutions: List var spacer: TextView = view.findViewById(R.id.spacer) var card: MaterialCardView = view.findViewById(R.id.planCard) val context: Context = card.context + init { view.setOnClickListener(this) } + override fun onClick(v: View?) { if (date.text.toString().length > 7 && date.text.toString().substring(3, 7) == "http") { try { @@ -77,14 +86,14 @@ internal class SubstitutionAdapter(private var substitutions: List } } - val add = currentItem.additional.toLowerCase(Locale.ROOT) + val add = currentItem.additional val type = currentItem.type.toLowerCase(Locale.ROOT) if (add.isNotEmpty()) { - if (HelperFunctions.checkStringForArray(add, HelperFunctions.cancellations)) { + if (HelperFunctions.checkStringForArray(add, HelperFunctions.cancellations, true)) { strikeThrough(strings) } } else { - if (HelperFunctions.checkStringForArray(type, HelperFunctions.cancellations)) { + if (HelperFunctions.checkStringForArray(type, HelperFunctions.cancellations, true)) { strikeThrough(strings) } } @@ -161,6 +170,7 @@ internal class SubstitutionAdapter(private var substitutions: List notifyDataSetChanged() } + /// Strikes through the strings for course, room and teacher private fun strikeThrough(strings: Array) { for (i in 2..4) { strings[i].setSpan(StrikethroughSpan(), 0, strings[i].length, 0) } } diff --git a/app/src/main/java/com/denizd/substitutionplan/data/DataFetcher.kt b/app/src/main/java/com/denizd/substitutionplan/data/DataFetcher.kt index 8f08368..8a91440 100644 --- a/app/src/main/java/com/denizd/substitutionplan/data/DataFetcher.kt +++ b/app/src/main/java/com/denizd/substitutionplan/data/DataFetcher.kt @@ -122,9 +122,9 @@ internal class DataFetcher(isPlan: Boolean, isMenu: Boolean, isJobService: Boole foodRepository.deleteAll() val indices = ArrayList() - 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) { - 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) diff --git a/app/src/main/java/com/denizd/substitutionplan/data/HelperFunctions.kt b/app/src/main/java/com/denizd/substitutionplan/data/HelperFunctions.kt index b7f4cda..6f48969 100644 --- a/app/src/main/java/com/denizd/substitutionplan/data/HelperFunctions.kt +++ b/app/src/main/java/com/denizd/substitutionplan/data/HelperFunctions.kt @@ -47,8 +47,8 @@ internal object HelperFunctions { "lavender", "carnation", "brown2", "pureBlack") /** - * A list of all phrases used to describe that a course has been cancelled. Expand this - * if necessary. No further code changes required if this is expanded + * A lowercased list of all phrases used to describe that a course has been cancelled. Expand + * 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") @@ -305,8 +305,9 @@ internal object HelperFunctions { } } - fun checkStringForArray(s: String, checking: Array): Boolean { - checking.forEach { check -> + fun checkStringForArray(checkedString: String, checkingArray: Array, lowerCased: Boolean): Boolean { + val s = if (lowerCased) checkedString.toLowerCase(Locale.ROOT) else checkedString + checkingArray.forEach { check -> if (s.contains(check)) return true } return false @@ -330,7 +331,7 @@ internal object HelperFunctions { val a = group.substring(0, 2).toInt() -a } - checkStringForArray(group.substring(0, 1), juniors) -> -101 + checkStringForArray(group.substring(0, 1), juniors, false) -> -101 else -> -100 } } catch (e: Exception) { diff --git a/app/src/main/java/com/denizd/substitutionplan/data/Topic.kt b/app/src/main/java/com/denizd/substitutionplan/data/Topic.kt index eea79ab..169a595 100644 --- a/app/src/main/java/com/denizd/substitutionplan/data/Topic.kt +++ b/app/src/main/java/com/denizd/substitutionplan/data/Topic.kt @@ -2,7 +2,7 @@ package com.denizd.substitutionplan.data /** * 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) { ANDROID("substitutions-android"), diff --git a/app/src/main/java/com/denizd/substitutionplan/services/FBPingService.kt b/app/src/main/java/com/denizd/substitutionplan/services/FBPingService.kt index b63f870..90353d8 100644 --- a/app/src/main/java/com/denizd/substitutionplan/services/FBPingService.kt +++ b/app/src/main/java/com/denizd/substitutionplan/services/FBPingService.kt @@ -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 * 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() {