Archived
Started converting the app to a true MVVM architecture; fixed 7 month old bug that would cause substitution items to not be shown on the personal plan if they contained a question mark (e.g. 'INF7?inf999')
This commit is contained in:
+6
-2
@@ -10,8 +10,8 @@ android {
|
|||||||
applicationId "com.denizd.substitutionplan"
|
applicationId "com.denizd.substitutionplan"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 42
|
versionCode 43
|
||||||
versionName "2.3.4"
|
versionName "2.4.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -48,6 +48,10 @@ dependencies {
|
|||||||
def room_version = "2.1.0-alpha04"
|
def room_version = "2.1.0-alpha04"
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
kapt "androidx.room:room-compiler:$room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
|
||||||
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
|
||||||
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-beta01'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.denizd.substitutionplan.adapters
|
|||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.style.StrikethroughSpan
|
import android.text.style.StrikethroughSpan
|
||||||
@@ -26,9 +25,9 @@ import java.util.*
|
|||||||
* PlanFragment.kt and its subclasses
|
* PlanFragment.kt and its subclasses
|
||||||
*
|
*
|
||||||
* @param substitutions a list of all substitutions of data type Substitution
|
* @param substitutions a list of all substitutions of data type Substitution
|
||||||
* @param prefs a reference to the app's Shared Preferences used to get user-set colours
|
* @param colours a map of all user-defined colours for the individual courses
|
||||||
*/
|
*/
|
||||||
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 colours: Map<String, String>) : RecyclerView.Adapter<SubstitutionAdapter.CardViewHolder>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ViewHolder class used by SubstitutionAdapter.kt to resolve references to views in
|
* The ViewHolder class used by SubstitutionAdapter.kt to resolve references to views in
|
||||||
@@ -114,11 +113,7 @@ internal class SubstitutionAdapter(private var substitutions: List<Substitution>
|
|||||||
View.GONE
|
View.GONE
|
||||||
} else {
|
} else {
|
||||||
val colourString = SubstUtil.getColourString(holder.course.text.toString())
|
val colourString = SubstUtil.getColourString(holder.course.text.toString())
|
||||||
val colourPrefsInt = if (colourString.isNotEmpty()) {
|
val colourPrefsInt = colours[colourString] ?: ""
|
||||||
prefs.getString("card$colourString", "") ?: ""
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
colour = SubstUtil.getColourForString(colourPrefsInt, holder.context)
|
colour = SubstUtil.getColourForString(colourPrefsInt, holder.context)
|
||||||
cardBackgroundColour = if (colour != 0) {
|
cardBackgroundColour = if (colour != 0) {
|
||||||
colour
|
colour
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.denizd.substitutionplan.data
|
||||||
|
|
||||||
|
internal enum class Caller {
|
||||||
|
SUBSTITUTION,
|
||||||
|
FOODMENU,
|
||||||
|
JOBSERVICE,
|
||||||
|
SETTINGS
|
||||||
|
}
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
package com.denizd.substitutionplan.data
|
|
||||||
|
|
||||||
import android.app.*
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.media.RingtoneManager
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.AsyncTask
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.RemoteViews
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
||||||
import com.denizd.substitutionplan.database.FoodRepository
|
|
||||||
import com.denizd.substitutionplan.R
|
|
||||||
import com.denizd.substitutionplan.database.SubstRepository
|
|
||||||
import com.denizd.substitutionplan.activities.Main
|
|
||||||
import com.denizd.substitutionplan.models.Food
|
|
||||||
import com.denizd.substitutionplan.models.Substitution
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class gets data from the djd4rkn355.github.io domain asynchronously and persists it in the
|
|
||||||
* database. By using booleans as parameters, network usage and speed can be improved as only
|
|
||||||
* requested data will be downloaded and processed. Furthermore, the class provides a boolean
|
|
||||||
* to determine whether to send a notification to the user, if applicable.
|
|
||||||
*
|
|
||||||
* @param isPlan substitution plan will be downloaded and persisted in the database if true
|
|
||||||
* @param isMenu food menu will be downloaded and persisted in the database if true
|
|
||||||
* @param isJobService enables sending of a notification if true
|
|
||||||
* @param forced if true, times for food menu and substitution table will be overwritten
|
|
||||||
* with empty values to enable a forced refresh
|
|
||||||
* @param context the context of the application that will be stored as a WeakReference
|
|
||||||
* to avoid memory leakage
|
|
||||||
* @param application a reference to the application that will be stored as a WeakReference
|
|
||||||
* @param parentView a reference to the parent view that will be stored as a WeakReference
|
|
||||||
*/
|
|
||||||
internal class DataFetcher(
|
|
||||||
isPlan: Boolean,
|
|
||||||
isMenu: Boolean,
|
|
||||||
isJobService: Boolean = false,
|
|
||||||
forced: Boolean = false,
|
|
||||||
context: Context,
|
|
||||||
application: Application,
|
|
||||||
parentView: View?
|
|
||||||
) : AsyncTask<Void, Void, Void>() {
|
|
||||||
|
|
||||||
private var jobService = isJobService
|
|
||||||
private var plan = isPlan
|
|
||||||
private var menu = isMenu
|
|
||||||
private var forceRefresh = forced
|
|
||||||
|
|
||||||
private var mContext = WeakReference(context)
|
|
||||||
private var mApplication = application
|
|
||||||
private var mView = WeakReference(parentView)
|
|
||||||
private var notificationText = ""
|
|
||||||
private var informational = ""
|
|
||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(mContext.get())
|
|
||||||
private val edit = prefs.edit()
|
|
||||||
private var currentTime = ""
|
|
||||||
private var currentFoodTime = ""
|
|
||||||
private var substUrl = "https://djd4rkn355.github.io/avh_substitutions.html"
|
|
||||||
private var foodUrl = "https://djd4rkn355.github.io/food.html"
|
|
||||||
private var websitePriority = 0
|
|
||||||
|
|
||||||
override fun doInBackground(vararg params: Void?): Void? {
|
|
||||||
try {
|
|
||||||
if (prefs.getBoolean("testUrls", false)) {
|
|
||||||
substUrl = "https://djd4rkn355.github.io/subst_test.html"
|
|
||||||
foodUrl = "https://djd4rkn355.github.io/food_test.html"
|
|
||||||
} else if ((prefs.getString("custom_test_url", "") ?: "").isNotEmpty()) {
|
|
||||||
substUrl = "https://djd4rkn355.github.io/${prefs.getString("custom_test_url", "")}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceRefresh) {
|
|
||||||
edit.putString("timeNew", "").putString("newFoodTime", "").apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menu) {
|
|
||||||
requestFoodMenuData()
|
|
||||||
}
|
|
||||||
if (plan) {
|
|
||||||
requestSubstPlan()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plan || menu) {
|
|
||||||
mView.get()?.let { v: View ->
|
|
||||||
var snackText = "${mContext.get()?.getText(R.string.last_updated)} ${when {
|
|
||||||
plan -> currentTime
|
|
||||||
menu -> currentFoodTime
|
|
||||||
else -> "---"
|
|
||||||
}}"
|
|
||||||
snackText = if (forceRefresh) mContext.get()?.getString(R.string.force_refresh_successful) ?: "" else snackText
|
|
||||||
val snackBarView = v.findViewById<View>(R.id.coordination)
|
|
||||||
Snackbar.make(snackBarView, snackText, Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
mView.get()?.let { v: View ->
|
|
||||||
val snackBarView = v.findViewById<View>(R.id.coordination)
|
|
||||||
Snackbar.make(snackBarView, mContext.get()?.getString(R.string.no_internet_connection) ?: "", Snackbar.LENGTH_LONG).setBackgroundTint(ContextCompat.getColor(mContext.get()!!,
|
|
||||||
R.color.colorError
|
|
||||||
)).show()
|
|
||||||
}
|
|
||||||
prefs.edit().putString("debug_recent_exception", Log.getStackTraceString(e)).apply()
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostExecute(result: Void?) {
|
|
||||||
mView.get()?.let {
|
|
||||||
try {
|
|
||||||
it.findViewById<SwipeRefreshLayout>(R.id.swipe_refresh_layout).isRefreshing = false
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mView.clear()
|
|
||||||
mContext.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requestFoodMenuData() {
|
|
||||||
val docFood = Jsoup.connect(foodUrl).get()
|
|
||||||
currentFoodTime = docFood.select("h1")[0].text()
|
|
||||||
val foodRepository = FoodRepository(mApplication)
|
|
||||||
if (currentFoodTime != prefs.getString("newFoodTime", "")) {
|
|
||||||
val foodElements = docFood.select("th")
|
|
||||||
foodRepository.deleteAll()
|
|
||||||
|
|
||||||
val indices = ArrayList<Int>()
|
|
||||||
val daysAndVon = arrayOf("montag", "dienstag", "mittwoch", "donnerstag", "freitag", "von", "wünschen")
|
|
||||||
for (i in 0 until foodElements.size) {
|
|
||||||
if (SubstUtil.checkStringForArray(foodElements[i].text(), daysAndVon, true)) indices.add(i)
|
|
||||||
}
|
|
||||||
indices.add(foodElements.size)
|
|
||||||
|
|
||||||
for (l in 0 until indices.size - 1) {
|
|
||||||
var s = ""
|
|
||||||
for (i2 in indices[l] until indices[l + 1]) {
|
|
||||||
if (s.isEmpty()) {
|
|
||||||
s = foodElements[i2].text()
|
|
||||||
} else {
|
|
||||||
s += "\n${foodElements[i2].text()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foodRepository.insert(Food(s, l))
|
|
||||||
}
|
|
||||||
edit.putString("newFoodTime", currentFoodTime).apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requestSubstPlan() {
|
|
||||||
val doc = Jsoup.connect(substUrl).get()
|
|
||||||
currentTime = doc.select("h1")[0].text()
|
|
||||||
val substRepo = SubstRepository(mApplication)
|
|
||||||
if (currentTime != prefs.getString("timeNew", "")) {
|
|
||||||
val rows = doc.select("tr")
|
|
||||||
val paragraphs = doc.select("h6")
|
|
||||||
val substArray = ArrayList<Substitution>()
|
|
||||||
|
|
||||||
val coursePreference = prefs.getString("courses", "") ?: ""
|
|
||||||
val classPreference = prefs.getString("classes", "") ?: ""
|
|
||||||
|
|
||||||
for (i in 0 until paragraphs.size) {
|
|
||||||
if (i == 0) {
|
|
||||||
informational = formatElement(paragraphs[i].html())
|
|
||||||
} else {
|
|
||||||
informational += "\n\n" + formatElement(paragraphs[i].html())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edit.putString("informational", informational).apply()
|
|
||||||
|
|
||||||
substRepo.deleteAllSubst()
|
|
||||||
|
|
||||||
for (i in 0 until rows.size) {
|
|
||||||
val row = rows[i]
|
|
||||||
val cols = row.select("th")
|
|
||||||
|
|
||||||
val group = cols[0].text()
|
|
||||||
val date = cols[1].text()
|
|
||||||
val subst = Substitution(
|
|
||||||
group = group,
|
|
||||||
date = date,
|
|
||||||
time = cols[2].text(),
|
|
||||||
course = cols[3].text(),
|
|
||||||
room = cols[4].text(),
|
|
||||||
additional = cols[5].text(),
|
|
||||||
teacher = cols[6].text(),
|
|
||||||
type = cols[7].text(),
|
|
||||||
priority = SubstUtil.assignRanking(group, (date.length > 2 && date.substring(0, 3) == "psa")),
|
|
||||||
date_priority = SubstUtil.assignDatePriority(date),
|
|
||||||
website_priority = websitePriority
|
|
||||||
)
|
|
||||||
substArray.add(subst)
|
|
||||||
substRepo.insert(subst)
|
|
||||||
websitePriority += 1
|
|
||||||
}
|
|
||||||
var countOfNotificationItems = 0
|
|
||||||
var countOfMoreNotificationItems = 0
|
|
||||||
if (jobService && prefs.getBoolean("notif", true)) {
|
|
||||||
substArray.filter { substItem ->
|
|
||||||
SubstUtil.checkPersonalSubstitutions(
|
|
||||||
substItem,
|
|
||||||
coursePreference,
|
|
||||||
classPreference,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}.forEach { substItem ->
|
|
||||||
if (countOfNotificationItems < 4) {
|
|
||||||
notificationText += ("${if (notificationText.isNotEmpty()) ",\n" else ""}${substItem.course}: ${if (substItem.additional.isNotEmpty()) substItem.additional else if (substItem.type.isNotEmpty()) substItem.type else "---"}")
|
|
||||||
countOfNotificationItems += 1
|
|
||||||
} else {
|
|
||||||
countOfMoreNotificationItems += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (countOfMoreNotificationItems > 0) {
|
|
||||||
notificationText += mContext.get()?.resources?.getQuantityString(R.plurals.notification_more_messages, countOfMoreNotificationItems, countOfMoreNotificationItems)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notificationText.isNotEmpty()) {
|
|
||||||
sendNotification()
|
|
||||||
}
|
|
||||||
|
|
||||||
edit.putString("timeNew", currentTime).apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendNotification() {
|
|
||||||
mContext.get()?.let { context ->
|
|
||||||
val openApp = Intent(context, Main::class.java)
|
|
||||||
openApp.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
openApp.flags += Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
val openAppPending = PendingIntent.getActivity(context, 0, openApp, 0)
|
|
||||||
|
|
||||||
val notificationLayout = RemoteViews(context.packageName, R.layout.notification)
|
|
||||||
notificationLayout.setTextViewText(R.id.notification_title, context.getString(
|
|
||||||
R.string.substitution_plan
|
|
||||||
))
|
|
||||||
notificationLayout.setTextViewText(R.id.notification_textview, notificationText)
|
|
||||||
|
|
||||||
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
SubstUtil.getNotificationChannel(context, prefs)
|
|
||||||
}
|
|
||||||
|
|
||||||
val notification = NotificationCompat.Builder(context, SubstUtil.notificationChannelId)
|
|
||||||
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
|
|
||||||
.setCustomContentView(notificationLayout)
|
|
||||||
.setContentIntent(openAppPending)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setColor(ContextCompat.getColor(context, R.color.colorAccent))
|
|
||||||
|
|
||||||
notification.setSmallIcon(if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) R.drawable.ic_avh else R.drawable.ic_avhlogo)
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
||||||
val sound = if ((prefs.getString("ringtoneUri", "") ?: "").isNotEmpty()) {
|
|
||||||
Uri.parse(prefs.getString("ringtoneUri", "") ?: "")
|
|
||||||
} else {
|
|
||||||
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
|
||||||
}
|
|
||||||
notification.setSound(sound)
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.notify(1, notification.build())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatElement(element: String): String {
|
|
||||||
return element
|
|
||||||
.replace("<br>", "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -81,16 +81,11 @@ internal object SubstUtil {
|
|||||||
|
|
||||||
const val notificationChannelId = "general"
|
const val notificationChannelId = "general"
|
||||||
|
|
||||||
fun getEmptyGeneralSubstitution(context: Context) = arrayOf(
|
// Constants for Firebase Cloud Messaging Topics
|
||||||
Substitution("", "", "", context.getString(R.string.plan_empty), "", "", "", "", 0, 0, 0)
|
const val FB_TOPIC_ANDROID = "substitutions-android"
|
||||||
).toList()
|
const val FB_TOPIC_BROADCAST = "substitutions-broadcast"
|
||||||
|
const val FB_TOPIC_IOS = "substitutions-ios"
|
||||||
fun getEmptyPersonalSubstitution(context: Context) =
|
const val FB_TOPIC_DEVELOPMENT = "substitutions-debug"
|
||||||
Substitution("", "", "", context.getString(R.string.personal_plan_empty), "", "", "", "", 0, 0, 0)
|
|
||||||
|
|
||||||
fun getEmptyFoodMenu(context: Context) = arrayOf(
|
|
||||||
Food("\n${context.getString(R.string.food_menu_empty)}\n", 0)
|
|
||||||
).toList()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function returns an array that contains all data relevant to tinting individual entries
|
* This function returns an array that contains all data relevant to tinting individual entries
|
||||||
@@ -248,39 +243,6 @@ internal object SubstUtil {
|
|||||||
edit.apply()
|
edit.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This function serves for checking whether a course on the substitution plan is relevant to
|
|
||||||
* the user and should be shown on their personal plan as well as in their notifications
|
|
||||||
*
|
|
||||||
* @param substitution the substitution item
|
|
||||||
* @param coursePreference a string that represents the courses the user is enrolled in
|
|
||||||
* @param classPreference a string that represents the group the user is in
|
|
||||||
* @param psa decide whether or not any PSA items should be included in the filter
|
|
||||||
*
|
|
||||||
* @return true if the substitution is relevant to the user, false otherwise
|
|
||||||
*/
|
|
||||||
fun checkPersonalSubstitutions(substitution: Substitution, coursePreference: String, classPreference: String, psa: Boolean): Boolean {
|
|
||||||
val group = substitution.group
|
|
||||||
val course = substitution.course
|
|
||||||
if (psa && substitution.date.isNotEmpty() && substitution.date.substring(0, 3) == "psa") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (coursePreference.isEmpty() && classPreference.isNotEmpty()) {
|
|
||||||
if (group.isNotEmpty()) {
|
|
||||||
if (classPreference.contains(group) || group.contains(classPreference)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (classPreference.isNotEmpty() && coursePreference.isNotEmpty()) {
|
|
||||||
if (group != "" && course != "") {
|
|
||||||
if ((classPreference.contains(group) || group.contains(classPreference)) && coursePreference.contains(course)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is used throughout some classes to set the theme according to the device's
|
* This function is used throughout some classes to set the theme according to the device's
|
||||||
* Android version before an app theme may be picked in the settings. As Main.kt as well as
|
* Android version before an app theme may be picked in the settings. As Main.kt as well as
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.denizd.substitutionplan.data
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum class for declaring Firebase Cloud Messaging topics without using the raw strings
|
|
||||||
* throughout the project to avoid human error
|
|
||||||
*/
|
|
||||||
internal enum class Topic(val tag: String) {
|
|
||||||
ANDROID("substitutions-android"),
|
|
||||||
BROADCAST("substitutions-broadcast"),
|
|
||||||
IOS("substitutions-ios"),
|
|
||||||
DEVELOPMENT("substitutions-debug")
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.denizd.substitutionplan.database
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.os.AsyncTask
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import com.denizd.substitutionplan.models.Food
|
|
||||||
|
|
||||||
internal class FoodRepository(application: Application) {
|
|
||||||
|
|
||||||
private val substDao: SubstDao?
|
|
||||||
val allFoods: LiveData<List<Food>>?
|
|
||||||
|
|
||||||
init {
|
|
||||||
val database =
|
|
||||||
SubstDatabase.getFoodInstance(application)
|
|
||||||
substDao = database?.substDao()
|
|
||||||
allFoods = substDao?.allFoods
|
|
||||||
}
|
|
||||||
|
|
||||||
fun insert(food: Food) {
|
|
||||||
substDao?.insertFood(food)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteAll() {
|
|
||||||
substDao?.deleteAllFoods()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package com.denizd.substitutionplan.database
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.view.View
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
||||||
import com.denizd.substitutionplan.data.DataFetcher
|
|
||||||
import com.denizd.substitutionplan.models.Food
|
|
||||||
|
|
||||||
internal class FoodViewModel(application: Application) : AndroidViewModel(application) {
|
|
||||||
|
|
||||||
private val repository: FoodRepository = FoodRepository(application)
|
|
||||||
val allFoods: LiveData<List<Food>>?
|
|
||||||
private val app = application
|
|
||||||
|
|
||||||
init {
|
|
||||||
allFoods = repository.allFoods
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refresh(swipeRefreshLayout: SwipeRefreshLayout, rootView: View) {
|
|
||||||
swipeRefreshLayout.isRefreshing = true
|
|
||||||
DataFetcher(
|
|
||||||
isPlan = false,
|
|
||||||
isMenu = true,
|
|
||||||
isJobService = false,
|
|
||||||
context = app,
|
|
||||||
application = app,
|
|
||||||
parentView = rootView
|
|
||||||
).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.denizd.substitutionplan.database
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import com.denizd.substitutionplan.models.Ringtone
|
||||||
|
|
||||||
|
internal class SettingsRepository(private val application: Application) {
|
||||||
|
|
||||||
|
val ringtones: List<Ringtone> by lazy {
|
||||||
|
lateinit var ringtoneCursor: Cursor
|
||||||
|
val ringtoneManager = RingtoneManager(application).apply {
|
||||||
|
setType(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
ringtoneCursor = cursor
|
||||||
|
}
|
||||||
|
val alarms = ArrayList<Ringtone>()
|
||||||
|
|
||||||
|
while (!ringtoneCursor.isAfterLast && ringtoneCursor.moveToNext()) {
|
||||||
|
val position = ringtoneCursor.position
|
||||||
|
alarms.add(
|
||||||
|
Ringtone(
|
||||||
|
ringtoneManager.getRingtone(position).getTitle(application),
|
||||||
|
ringtoneManager.getRingtoneUri(position).toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
alarms
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -37,12 +37,12 @@ internal abstract class SubstDatabase : RoomDatabase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var instance: SubstDatabase? = null
|
private var substInstance: SubstDatabase? = null
|
||||||
|
|
||||||
fun getInstance(context: Context): SubstDatabase? {
|
fun getSubstInstance(context: Context): SubstDatabase? {
|
||||||
if (instance == null) {
|
if (substInstance == null) {
|
||||||
synchronized (SubstDatabase::class) {
|
synchronized (SubstDatabase::class) {
|
||||||
instance = Room.databaseBuilder(context.applicationContext,
|
substInstance = Room.databaseBuilder(context.applicationContext,
|
||||||
SubstDatabase::class.java,
|
SubstDatabase::class.java,
|
||||||
"subst_database")
|
"subst_database")
|
||||||
.addMigrations(addTeacherColumn, addTypeColumn, addDatePriorityColumn, addWebsitePriorityColumn)
|
.addMigrations(addTeacherColumn, addTypeColumn, addDatePriorityColumn, addWebsitePriorityColumn)
|
||||||
@@ -50,7 +50,7 @@ internal abstract class SubstDatabase : RoomDatabase() {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return instance
|
return substInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
private var foodInstance: SubstDatabase? = null
|
private var foodInstance: SubstDatabase? = null
|
||||||
|
|||||||
@@ -1,30 +1,379 @@
|
|||||||
package com.denizd.substitutionplan.database
|
package com.denizd.substitutionplan.database
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.AsyncTask
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.denizd.substitutionplan.R
|
||||||
|
import com.denizd.substitutionplan.activities.Main
|
||||||
|
import com.denizd.substitutionplan.data.Caller
|
||||||
|
import com.denizd.substitutionplan.data.SubstUtil
|
||||||
|
import com.denizd.substitutionplan.models.Food
|
||||||
import com.denizd.substitutionplan.models.Substitution
|
import com.denizd.substitutionplan.models.Substitution
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
|
||||||
internal class SubstRepository(application: Application) {
|
internal class SubstRepository(private val application: Application) {
|
||||||
|
|
||||||
private val substDao: SubstDao?
|
private val substitutionDao: SubstDao?
|
||||||
|
private val foodMenuDao: SubstDao?
|
||||||
val allSubstitutionsSorted: LiveData<List<Substitution>>?
|
val allSubstitutionsSorted: LiveData<List<Substitution>>?
|
||||||
val allSubstitutionsOriginal: LiveData<List<Substitution>>?
|
val allSubstitutionsOriginal: LiveData<List<Substitution>>?
|
||||||
|
val allFoodItems: LiveData<List<Food>>?
|
||||||
|
|
||||||
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
|
|
||||||
|
val substitutionPlanColours: Map<String, String>
|
||||||
|
get() = mapOf(
|
||||||
|
"German" to prefs.getNonNullString("cardGerman"),
|
||||||
|
"Maths" to prefs.getNonNullString("cardMaths"),
|
||||||
|
"English" to prefs.getNonNullString("cardEnglish"),
|
||||||
|
"PhysEd" to prefs.getNonNullString("cardPhysEd"),
|
||||||
|
"Politics" to prefs.getNonNullString("cardPolitics"),
|
||||||
|
"Theatre" to prefs.getNonNullString("cardTheatre"),
|
||||||
|
"Physics" to prefs.getNonNullString("cardPhysics"),
|
||||||
|
"Biology" to prefs.getNonNullString("cardBiology"),
|
||||||
|
"Chemistry" to prefs.getNonNullString("cardChemistry"),
|
||||||
|
"Philosophy" to prefs.getNonNullString("cardPhilosophy"),
|
||||||
|
"Latin" to prefs.getNonNullString("cardLatin"),
|
||||||
|
"Spanish" to prefs.getNonNullString("cardSpanish"),
|
||||||
|
"French" to prefs.getNonNullString("cardFrench"),
|
||||||
|
"CompSci" to prefs.getNonNullString("cardCompSci"),
|
||||||
|
"History" to prefs.getNonNullString("cardHistory"),
|
||||||
|
"Religion" to prefs.getNonNullString("cardReligion"),
|
||||||
|
"Geography" to prefs.getNonNullString("cardGeography"),
|
||||||
|
"Arts" to prefs.getNonNullString("cardArts"),
|
||||||
|
"Music" to prefs.getNonNullString("cardMusic"),
|
||||||
|
"Turkish" to prefs.getNonNullString("cardTurkish"),
|
||||||
|
"Chinese" to prefs.getNonNullString("cardChinese"),
|
||||||
|
"GLL" to prefs.getNonNullString("cardGLL"),
|
||||||
|
"WAT" to prefs.getNonNullString("cardWAT"),
|
||||||
|
"Forder" to prefs.getNonNullString("cardForder"),
|
||||||
|
"WP" to prefs.getNonNullString("cardWP")
|
||||||
|
)
|
||||||
|
val shouldAutoRefresh: Boolean
|
||||||
|
get() = prefs.getBoolean("autoRefresh", false)
|
||||||
|
|
||||||
|
val shouldUseAppSorting: Boolean
|
||||||
|
get() = prefs.getBoolean("app_specific_sorting", true)
|
||||||
|
|
||||||
|
val emptyGeneralPlan: List<Substitution> by lazy {
|
||||||
|
listOf(Substitution("", "", "", application.getString(R.string.plan_empty), "", "", "", "", 0, 0, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
val emptyPersonalSubstitution: Substitution by lazy {
|
||||||
|
Substitution("", "", "", application.getString(R.string.personal_plan_empty), "", "", "", "", 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val emptyFoodMenu: List<Food> by lazy {
|
||||||
|
listOf(Food("\n${application.getString(R.string.food_menu_empty)}\n", 0))
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val database =
|
val substInstance = SubstDatabase.getSubstInstance(application)
|
||||||
SubstDatabase.getInstance(application)
|
val foodInstance = SubstDatabase.getFoodInstance(application)
|
||||||
substDao = database?.substDao()
|
substitutionDao = substInstance?.substDao()
|
||||||
allSubstitutionsSorted = substDao?.allSubstitutionsSorted
|
foodMenuDao = foodInstance?.substDao()
|
||||||
allSubstitutionsOriginal = substDao?.allSubstitutionsOriginal
|
allSubstitutionsSorted = substitutionDao?.allSubstitutionsSorted
|
||||||
|
allSubstitutionsOriginal = substitutionDao?.allSubstitutionsOriginal
|
||||||
|
allFoodItems = foodMenuDao?.allFoods
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insert(substitution: Substitution) {
|
private fun insert(substitution: Substitution) {
|
||||||
substDao?.insertSubst(substitution)
|
substitutionDao?.insertSubst(substitution)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAllSubst() {
|
private fun insert(food: Food) {
|
||||||
substDao?.deleteAllSubst()
|
foodMenuDao?.insertFood(food)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteAllSubst() {
|
||||||
|
substitutionDao?.deleteAllSubst()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteAllFoodItems() {
|
||||||
|
foodMenuDao?.deleteAllFoods()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGridColumnCount(config: Configuration): Int {
|
||||||
|
return if (application.resources.getBoolean(R.bool.isTablet)) {
|
||||||
|
when (config.orientation) {
|
||||||
|
Configuration.ORIENTATION_PORTRAIT -> 2
|
||||||
|
else -> 3
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when (config.orientation) {
|
||||||
|
Configuration.ORIENTATION_PORTRAIT -> 1
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SharedPreferences.getNonNullString(key: String): String {
|
||||||
|
return getString(key, "") ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun externalNonNullString(key: String): String {
|
||||||
|
return prefs.getNonNullString(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SharedPreferences.putAndApplyString(key: String, value: String) {
|
||||||
|
edit().putString(key, value).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun putAndApplyString(key: String, value: String) {
|
||||||
|
prefs.putAndApplyString(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Substitution.checkIfPersonal(classPreference: String, coursePreference: String, includesPsa: Boolean = false): Boolean {
|
||||||
|
if (includesPsa && date.isNotEmpty() && date.substring(0, 3) == "psa") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (coursePreference.isEmpty() && classPreference.isNotEmpty()) {
|
||||||
|
if (group.isNotEmpty()) {
|
||||||
|
if (classPreference.contains(group) || group.contains(classPreference)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (classPreference.isNotEmpty() && coursePreference.isNotEmpty()) {
|
||||||
|
if (group != "" && course != "") {
|
||||||
|
if ((classPreference.contains(group) || group.contains(classPreference))) {
|
||||||
|
val questionMark = course.indexOf("?")
|
||||||
|
if (questionMark == -1) {
|
||||||
|
if (coursePreference.contains(course)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (coursePreference.contains(course.substring(0, questionMark - 1))
|
||||||
|
|| coursePreference.contains(course.substring(questionMark + 1))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkIfSubstitutionPersonal(substitution: Substitution, classPreference: String, coursePreference: String, includesPsa: Boolean): Boolean {
|
||||||
|
return substitution.checkIfPersonal(classPreference, coursePreference, includesPsa)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getData(caller: Caller): Pair<String, Boolean> {
|
||||||
|
|
||||||
|
fun requestSubstPlan(url: String): Pair<String, Boolean> {
|
||||||
|
var notificationText = ""
|
||||||
|
var hasUpdated = false
|
||||||
|
|
||||||
|
val doc = Jsoup.connect(url).get()
|
||||||
|
val currentTime = doc.select("h1")[0].text()
|
||||||
|
|
||||||
|
if (currentTime != prefs.getNonNullString("timeNew")) {
|
||||||
|
val rows = doc.select("tr")
|
||||||
|
val paragraphs = doc.select("h6")
|
||||||
|
val substArray = ArrayList<Substitution>()
|
||||||
|
|
||||||
|
val coursePreference = prefs.getNonNullString("courses")
|
||||||
|
val classPreference = prefs.getNonNullString("classes")
|
||||||
|
|
||||||
|
var informational = ""
|
||||||
|
for (i in 0 until paragraphs.size) {
|
||||||
|
if (i == 0) {
|
||||||
|
informational = paragraphs[i].html().replace("<br>", "\n")
|
||||||
|
} else {
|
||||||
|
informational += "\n\n" + paragraphs[i].html().replace("<br>", "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefs.putAndApplyString("informational", informational)
|
||||||
|
|
||||||
|
deleteAllSubst()
|
||||||
|
|
||||||
|
for (i in 0 until rows.size) {
|
||||||
|
val row = rows[i]
|
||||||
|
val cols = row.select("th")
|
||||||
|
|
||||||
|
val group = cols[0].text()
|
||||||
|
val date = cols[1].text()
|
||||||
|
val subst = Substitution(
|
||||||
|
group = group,
|
||||||
|
date = date,
|
||||||
|
time = cols[2].text(),
|
||||||
|
course = cols[3].text(),
|
||||||
|
room = cols[4].text(),
|
||||||
|
additional = cols[5].text(),
|
||||||
|
teacher = cols[6].text(),
|
||||||
|
type = cols[7].text(),
|
||||||
|
priority = SubstUtil.assignRanking(group, (date.length > 2 && date.substring(0, 3) == "psa")), // TODO make fancier pls
|
||||||
|
date_priority = SubstUtil.assignDatePriority(date),
|
||||||
|
website_priority = i
|
||||||
|
)
|
||||||
|
if (caller == Caller.JOBSERVICE && prefs.getBoolean("notif", true)) {
|
||||||
|
if (subst.checkIfPersonal(classPreference, coursePreference)) {
|
||||||
|
substArray.add(subst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insert(subst)
|
||||||
|
}
|
||||||
|
var countOfNotificationItems = 0
|
||||||
|
var countOfMoreNotificationItems = 0
|
||||||
|
if (caller == Caller.JOBSERVICE && prefs.getBoolean("notif", true)) {
|
||||||
|
substArray.forEach { substItem ->
|
||||||
|
if (countOfNotificationItems < 4) {
|
||||||
|
notificationText += ("${
|
||||||
|
if (notificationText.isNotEmpty()) ",\n" else ""
|
||||||
|
}${substItem.course}: ${
|
||||||
|
if (substItem.additional.isNotEmpty()) substItem.additional else if (substItem.type.isNotEmpty()) substItem.type else "---"
|
||||||
|
}")
|
||||||
|
countOfNotificationItems += 1
|
||||||
|
} else {
|
||||||
|
countOfMoreNotificationItems += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (countOfMoreNotificationItems > 0) {
|
||||||
|
notificationText += application.resources.getQuantityString(R.plurals.notification_more_messages, countOfMoreNotificationItems, countOfMoreNotificationItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefs.putAndApplyString("timeNew", currentTime)
|
||||||
|
hasUpdated = true
|
||||||
|
}
|
||||||
|
return Pair(notificationText, hasUpdated)
|
||||||
|
}
|
||||||
|
fun requestFoodMenuData(url: String): Boolean {
|
||||||
|
val docFood = Jsoup.connect(url).get()
|
||||||
|
val currentFoodTime = docFood.select("h1")[0].text()
|
||||||
|
return if (currentFoodTime != prefs.getString("newFoodTime", "")) {
|
||||||
|
val foodElements = docFood.select("th")
|
||||||
|
deleteAllFoodItems()
|
||||||
|
|
||||||
|
val indices = ArrayList<Int>()
|
||||||
|
val daysAndVon = arrayOf("montag", "dienstag", "mittwoch", "donnerstag", "freitag", "von", "wünschen")
|
||||||
|
for (i in 0 until foodElements.size) {
|
||||||
|
if (SubstUtil.checkStringForArray(foodElements[i].text(), daysAndVon, true)) indices.add(i)
|
||||||
|
}
|
||||||
|
indices.add(foodElements.size)
|
||||||
|
|
||||||
|
for (l in 0 until indices.size - 1) {
|
||||||
|
var s = ""
|
||||||
|
for (i2 in indices[l] until indices[l + 1]) {
|
||||||
|
if (s.isEmpty()) {
|
||||||
|
s = foodElements[i2].text()
|
||||||
|
} else {
|
||||||
|
s += "\n${foodElements[i2].text()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insert(Food(s, l))
|
||||||
|
}
|
||||||
|
prefs.putAndApplyString("newFoodTime", currentFoodTime)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun sendNotification(notificationText: String) {
|
||||||
|
val openApp = Intent(application, Main::class.java)
|
||||||
|
openApp.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
openApp.flags += Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
val openAppPending = PendingIntent.getActivity(application, 0, openApp, 0)
|
||||||
|
|
||||||
|
val notificationLayout = RemoteViews(application.packageName, R.layout.notification)
|
||||||
|
notificationLayout.setTextViewText(R.id.notification_title, application.getString(
|
||||||
|
R.string.substitution_plan
|
||||||
|
))
|
||||||
|
notificationLayout.setTextViewText(R.id.notification_textview, notificationText)
|
||||||
|
|
||||||
|
val manager = application.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
SubstUtil.getNotificationChannel(application, prefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
val notification = NotificationCompat.Builder(application, SubstUtil.notificationChannelId)
|
||||||
|
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
|
||||||
|
.setCustomContentView(notificationLayout)
|
||||||
|
.setContentIntent(openAppPending)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setColor(ContextCompat.getColor(application, R.color.colorAccent))
|
||||||
|
.setSmallIcon(
|
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) R.drawable.ic_avh else R.drawable.ic_avhlogo
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
val sound = if (prefs.getNonNullString("ringtoneUri").isNotEmpty()) {
|
||||||
|
Uri.parse(prefs.getNonNullString("ringtoneUri"))
|
||||||
|
} else {
|
||||||
|
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
}
|
||||||
|
notification.setSound(sound)
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.notify(1, notification.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
val urlPair = when {
|
||||||
|
prefs.getBoolean("testUrls", false) -> Pair(
|
||||||
|
"https://djd4rkn355.github.io/subst_test.html",
|
||||||
|
"https://djd4rkn355.github.io/food_test.html"
|
||||||
|
)
|
||||||
|
prefs.getNonNullString("custom_test_url").isNotEmpty() -> Pair(
|
||||||
|
"https://djd4rkn355.github.io/${prefs.getString("custom_test_url", "")}",
|
||||||
|
"https://djd4rkn355.github.io/food.html"
|
||||||
|
)
|
||||||
|
else -> Pair(
|
||||||
|
"https://djd4rkn355.github.io/avh_substitutions.html",
|
||||||
|
"https://djd4rkn355.github.io/food.html"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = false
|
||||||
|
val returnText = try {
|
||||||
|
val foodMenuHasUpdated = requestFoodMenuData(urlPair.second)
|
||||||
|
val planResult = requestSubstPlan(urlPair.first)
|
||||||
|
|
||||||
|
val notificationText = planResult.first
|
||||||
|
val planHasUpdated = planResult.second
|
||||||
|
|
||||||
|
if (caller == Caller.JOBSERVICE && notificationText.isNotEmpty()) {
|
||||||
|
sendNotification(notificationText)
|
||||||
|
}
|
||||||
|
when (caller) {
|
||||||
|
Caller.SUBSTITUTION -> {
|
||||||
|
if (planHasUpdated) {
|
||||||
|
application.getString(R.string.plan_updated)
|
||||||
|
} else {
|
||||||
|
"${application.getString(R.string.last_updated)} ${prefs.getNonNullString("timeNew")}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Caller.FOODMENU -> {
|
||||||
|
if (foodMenuHasUpdated) {
|
||||||
|
application.getString(R.string.food_menu_updated)
|
||||||
|
} else {
|
||||||
|
"${application.getString(R.string.last_updated)} ${prefs.getNonNullString("newFoodTime")}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Caller.SETTINGS -> application.getString(R.string.force_refresh_successful)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
error = true
|
||||||
|
prefs.putAndApplyString("debug_recent_exception", Log.getStackTraceString(e))
|
||||||
|
application.getString(R.string.an_error_occurred)
|
||||||
|
}
|
||||||
|
return Pair(returnText, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchDataOnline(caller: Caller): Pair<String, Boolean> {
|
||||||
|
return getData(caller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package com.denizd.substitutionplan.database
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.view.View
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
||||||
import com.denizd.substitutionplan.data.DataFetcher
|
|
||||||
import com.denizd.substitutionplan.models.Substitution
|
|
||||||
|
|
||||||
internal class SubstViewModel(application: Application) : AndroidViewModel(application) {
|
|
||||||
|
|
||||||
private val repository: SubstRepository = SubstRepository(application)
|
|
||||||
val allSubstitutionsSorted: LiveData<List<Substitution>>?
|
|
||||||
val allSubstitutionsOriginal: LiveData<List<Substitution>>?
|
|
||||||
private val app = application
|
|
||||||
|
|
||||||
init {
|
|
||||||
allSubstitutionsSorted = repository.allSubstitutionsSorted
|
|
||||||
allSubstitutionsOriginal = repository.allSubstitutionsOriginal
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refresh(swipeRefreshLayout: SwipeRefreshLayout, rootView: View, refreshMenu: Boolean) {
|
|
||||||
swipeRefreshLayout.isRefreshing = true
|
|
||||||
DataFetcher(
|
|
||||||
isPlan = true,
|
|
||||||
isMenu = refreshMenu,
|
|
||||||
isJobService = false,
|
|
||||||
context = app,
|
|
||||||
application = app,
|
|
||||||
parentView = rootView
|
|
||||||
).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +1,29 @@
|
|||||||
package com.denizd.substitutionplan.fragments
|
package com.denizd.substitutionplan.fragments
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import com.denizd.substitutionplan.R
|
||||||
import com.denizd.substitutionplan.adapters.FoodAdapter
|
import com.denizd.substitutionplan.adapters.FoodAdapter
|
||||||
import com.denizd.substitutionplan.data.SubstUtil
|
import com.denizd.substitutionplan.viewmodels.FoodViewModel
|
||||||
import com.denizd.substitutionplan.database.FoodViewModel
|
|
||||||
import com.denizd.substitutionplan.databinding.FoodLayoutBinding
|
import com.denizd.substitutionplan.databinding.FoodLayoutBinding
|
||||||
import com.denizd.substitutionplan.models.Food
|
import com.denizd.substitutionplan.models.Food
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
internal class FoodFragment : Fragment() {
|
internal class FoodFragment : Fragment() {
|
||||||
|
|
||||||
private val mAdapter = FoodAdapter(ArrayList())
|
private val mAdapter = FoodAdapter(ArrayList())
|
||||||
private lateinit var mContext: Context
|
private lateinit var viewModel: FoodViewModel
|
||||||
private lateinit var prefs: SharedPreferences
|
|
||||||
private lateinit var foodViewModel: FoodViewModel
|
|
||||||
|
|
||||||
private lateinit var binding: FoodLayoutBinding
|
private lateinit var binding: FoodLayoutBinding
|
||||||
|
private lateinit var snackBarContainer: CoordinatorLayout
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
super.onAttach(context)
|
|
||||||
mContext = context
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(mContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
binding = FoodLayoutBinding.inflate(inflater, container, false)
|
binding = FoodLayoutBinding.inflate(inflater, container, false)
|
||||||
@@ -40,28 +33,41 @@ internal class FoodFragment : Fragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
snackBarContainer = view.rootView.findViewById(R.id.coordination) // TODO replace findViewById with ViewBinding
|
||||||
|
|
||||||
binding.recyclerView.apply {
|
binding.recyclerView.apply {
|
||||||
hasFixedSize()
|
hasFixedSize()
|
||||||
layoutManager = GridLayoutManager(mContext, 1)
|
layoutManager = GridLayoutManager(requireContext(), 1)
|
||||||
adapter = mAdapter
|
adapter = mAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
foodViewModel = ViewModelProviders.of(this).get(FoodViewModel::class.java)
|
viewModel = ViewModelProviders.of(this).get(FoodViewModel::class.java)
|
||||||
foodViewModel.allFoods?.observe(this, Observer<List<Food>> { foodList ->
|
viewModel.allFoodItems?.observe(this, Observer<List<Food>> { foodList ->
|
||||||
binding.recyclerView.scheduleLayoutAnimation()
|
binding.recyclerView.scheduleLayoutAnimation()
|
||||||
mAdapter.setFood(if (foodList.isEmpty()) {
|
mAdapter.setFood(if (foodList.isEmpty()) {
|
||||||
SubstUtil.getEmptyFoodMenu(mContext)
|
viewModel.emptyFoodMenu
|
||||||
} else {
|
} else {
|
||||||
foodList
|
foodList
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if (prefs.getBoolean("autoRefresh", false)) {
|
if (viewModel.shouldAutoRefresh) {
|
||||||
foodViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView)
|
refreshAndDisplaySnackBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
foodViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView)
|
refreshAndDisplaySnackBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshAndDisplaySnackBar() {
|
||||||
|
viewModel.refresh { result, error ->
|
||||||
|
val snackBar = Snackbar.make(snackBarContainer, result, Snackbar.LENGTH_LONG)
|
||||||
|
if (error) {
|
||||||
|
snackBar.setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.colorError))
|
||||||
|
}
|
||||||
|
snackBar.show()
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ package com.denizd.substitutionplan.fragments
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.denizd.substitutionplan.data.SubstUtil
|
|
||||||
import com.denizd.substitutionplan.models.Substitution
|
import com.denizd.substitutionplan.models.Substitution
|
||||||
|
|
||||||
internal class GeneralPlanFragment : PlanFragment() {
|
internal class GeneralPlanFragment : PlanFragment() {
|
||||||
@@ -13,7 +12,7 @@ internal class GeneralPlanFragment : PlanFragment() {
|
|||||||
|
|
||||||
substitutionPlan?.observe(this, Observer<List<Substitution>> { substitutions ->
|
substitutionPlan?.observe(this, Observer<List<Substitution>> { substitutions ->
|
||||||
mAdapter.setSubst(if (substitutions.isEmpty()) {
|
mAdapter.setSubst(if (substitutions.isEmpty()) {
|
||||||
SubstUtil.getEmptyGeneralSubstitution(mContext)
|
viewModel.emptyGeneralPlan
|
||||||
} else {
|
} else {
|
||||||
substitutions
|
substitutions
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.denizd.substitutionplan.fragments
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.denizd.substitutionplan.data.SubstUtil
|
|
||||||
import com.denizd.substitutionplan.models.Substitution
|
import com.denizd.substitutionplan.models.Substitution
|
||||||
|
|
||||||
internal class PersonalPlanFragment : PlanFragment() {
|
internal class PersonalPlanFragment : PlanFragment() {
|
||||||
@@ -11,30 +10,28 @@ internal class PersonalPlanFragment : PlanFragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val coursePreference = prefs.getString("courses", "") ?: ""
|
val coursePreference = viewModel.getNonNullString("courses")
|
||||||
val classPreference = prefs.getString("classes", "") ?: ""
|
val classPreference = viewModel.getNonNullString("classes")
|
||||||
|
|
||||||
substitutionPlan?.observe(this, Observer<List<Substitution>> { substitutions ->
|
substitutionPlan?.observe(this, Observer<List<Substitution>> { substitutions ->
|
||||||
planCardList.clear()
|
val list = ArrayList<Substitution>()
|
||||||
isPersonalPlanEmpty = true
|
|
||||||
binding.recyclerView.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
substitutions.filter { substItem ->
|
substitutions.filter { substItem ->
|
||||||
SubstUtil.checkPersonalSubstitutions(
|
viewModel.checkIfSubstitutionPersonal(
|
||||||
substItem,
|
substItem,
|
||||||
coursePreference,
|
|
||||||
classPreference,
|
classPreference,
|
||||||
|
coursePreference,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
}.forEach { substItem ->
|
}.forEach { substItem ->
|
||||||
planCardList.add(substItem)
|
list.add(substItem)
|
||||||
}
|
}
|
||||||
isPersonalPlanEmpty = (planCardList.size == 1 && planCardList[0].date.substring(0, 3) == "psa") || planCardList.isEmpty()
|
val isPersonalPlanEmpty = (list.size == 1 && list[0].date.substring(0, 3) == "psa") || list.isEmpty()
|
||||||
if (isPersonalPlanEmpty) {
|
if (isPersonalPlanEmpty) {
|
||||||
planCardList.add(SubstUtil.getEmptyPersonalSubstitution(mContext))
|
list.add(viewModel.emptyPersonalSubstitution)
|
||||||
}
|
}
|
||||||
binding.recyclerView.scheduleLayoutAnimation()
|
binding.recyclerView.scheduleLayoutAnimation()
|
||||||
mAdapter.setSubst(planCardList)
|
mAdapter.setSubst(list)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,33 @@
|
|||||||
package com.denizd.substitutionplan.fragments
|
package com.denizd.substitutionplan.fragments
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.denizd.substitutionplan.R
|
import com.denizd.substitutionplan.R
|
||||||
import com.denizd.substitutionplan.adapters.SubstitutionAdapter
|
import com.denizd.substitutionplan.adapters.SubstitutionAdapter
|
||||||
import com.denizd.substitutionplan.database.SubstViewModel
|
import com.denizd.substitutionplan.viewmodels.SubstViewModel
|
||||||
import com.denizd.substitutionplan.databinding.PlanBinding
|
import com.denizd.substitutionplan.databinding.PlanBinding
|
||||||
import com.denizd.substitutionplan.models.Substitution
|
import com.denizd.substitutionplan.models.Substitution
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
internal open class PlanFragment : Fragment() {
|
internal open class PlanFragment : Fragment() {
|
||||||
internal lateinit var mAdapter: SubstitutionAdapter
|
|
||||||
internal var planCardList = ArrayList<Substitution>()
|
|
||||||
private lateinit var substViewModel: SubstViewModel
|
|
||||||
internal lateinit var mContext: Context
|
|
||||||
internal lateinit var prefs: SharedPreferences
|
|
||||||
|
|
||||||
internal var isPersonalPlanEmpty: Boolean = true
|
internal val mAdapter: SubstitutionAdapter by lazy {
|
||||||
internal val handler = Handler()
|
SubstitutionAdapter(ArrayList(), viewModel.substitutionPlanColours)
|
||||||
internal var substitutionPlan: LiveData<List<Substitution>>? = null
|
|
||||||
|
|
||||||
internal lateinit var binding: PlanBinding
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
super.onAttach(context)
|
|
||||||
mContext = context
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(mContext)
|
|
||||||
}
|
}
|
||||||
|
lateinit var viewModel: SubstViewModel
|
||||||
|
internal var substitutionPlan: LiveData<List<Substitution>>? = null
|
||||||
|
internal lateinit var binding: PlanBinding
|
||||||
|
private lateinit var snackBarContainer: CoordinatorLayout
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
binding = PlanBinding.inflate(inflater, container, false)
|
binding = PlanBinding.inflate(inflater, container, false)
|
||||||
@@ -46,51 +36,50 @@ internal open class PlanFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
binding.recyclerView.layoutManager = GridLayoutManager(mContext, getGridColumnCount(newConfig))
|
binding.recyclerView.layoutManager = GridLayoutManager(requireContext(), viewModel.getGridColumnCount(newConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
substViewModel = ViewModelProviders.of(this).get(SubstViewModel::class.java)
|
|
||||||
substitutionPlan = if (prefs.getBoolean("app_specific_sorting", true)) {
|
snackBarContainer = view.rootView.findViewById(R.id.coordination) // TODO replace findViewById with ViewBinding
|
||||||
substViewModel.allSubstitutionsSorted
|
|
||||||
|
viewModel = ViewModelProviders.of(this).get(SubstViewModel::class.java)
|
||||||
|
substitutionPlan = if (viewModel.shouldUseAppSorting) {
|
||||||
|
viewModel.allSubstitutionsSorted
|
||||||
} else {
|
} else {
|
||||||
substViewModel.allSubstitutionsOriginal
|
viewModel.allSubstitutionsOriginal
|
||||||
}
|
}
|
||||||
|
|
||||||
mAdapter = SubstitutionAdapter(planCardList, prefs)
|
|
||||||
binding.recyclerView.apply {
|
binding.recyclerView.apply {
|
||||||
hasFixedSize()
|
hasFixedSize()
|
||||||
layoutManager = GridLayoutManager(mContext, getGridColumnCount(resources.configuration))
|
layoutManager = GridLayoutManager(requireContext(), viewModel.getGridColumnCount(resources.configuration))
|
||||||
adapter = mAdapter
|
adapter = mAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefs.getInt("firstTimeOpening", 0) == 1) {
|
// TODO figure something out
|
||||||
substViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView, refreshMenu = true)
|
// if (prefs.getInt("firstTimeOpening", 0) == 1) {
|
||||||
prefs.edit().putInt("firstTimeOpening", 2).apply()
|
// refreshAndDisplaySnackBar()
|
||||||
}
|
// prefs.edit().putInt("firstTimeOpening", 2).apply()
|
||||||
|
// }
|
||||||
|
|
||||||
if (prefs.getBoolean("autoRefresh", false)) {
|
if (viewModel.shouldAutoRefresh) {
|
||||||
substViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView, refreshMenu = false)
|
refreshAndDisplaySnackBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
substViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView, refreshMenu = false)
|
refreshAndDisplaySnackBar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGridColumnCount(config: Configuration): Int {
|
private fun refreshAndDisplaySnackBar() {
|
||||||
val tabletSize = resources.getBoolean(R.bool.isTablet)
|
viewModel.refresh { result, error ->
|
||||||
return if (tabletSize) {
|
val snackBar = Snackbar.make(snackBarContainer, result, Snackbar.LENGTH_LONG)
|
||||||
when (config.orientation) {
|
if (error) {
|
||||||
Configuration.ORIENTATION_PORTRAIT -> 2
|
snackBar.setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.colorError)) // TODO check if requireContext() is dangerous ;(
|
||||||
else -> 3
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
when (config.orientation) {
|
|
||||||
Configuration.ORIENTATION_PORTRAIT -> 1
|
|
||||||
else -> 2
|
|
||||||
}
|
}
|
||||||
|
snackBar.show()
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,6 @@ import android.content.ActivityNotFoundException
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.database.Cursor
|
|
||||||
import android.media.RingtoneManager
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -17,25 +15,23 @@ import android.text.TextWatcher
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.denizd.substitutionplan.*
|
import com.denizd.substitutionplan.*
|
||||||
import com.denizd.substitutionplan.adapters.ColourPickerAdapter
|
import com.denizd.substitutionplan.adapters.ColourPickerAdapter
|
||||||
import com.denizd.substitutionplan.adapters.CourseColourAdapter
|
import com.denizd.substitutionplan.adapters.CourseColourAdapter
|
||||||
import com.denizd.substitutionplan.adapters.RingtoneAdapter
|
import com.denizd.substitutionplan.adapters.RingtoneAdapter
|
||||||
import com.denizd.substitutionplan.data.DataFetcher
|
|
||||||
import com.denizd.substitutionplan.data.SubstUtil
|
import com.denizd.substitutionplan.data.SubstUtil
|
||||||
import com.denizd.substitutionplan.data.Topic
|
|
||||||
import com.denizd.substitutionplan.databinding.ContentSettingsBinding
|
import com.denizd.substitutionplan.databinding.ContentSettingsBinding
|
||||||
import com.denizd.substitutionplan.models.Colour
|
import com.denizd.substitutionplan.models.Colour
|
||||||
import com.denizd.substitutionplan.models.Ringtone
|
import com.denizd.substitutionplan.viewmodels.SettingsViewModel
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.firebase.messaging.FirebaseMessaging
|
import com.google.firebase.messaging.FirebaseMessaging
|
||||||
@@ -48,28 +44,21 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
private lateinit var mContext: Context
|
private lateinit var mContext: Context
|
||||||
private lateinit var prefs: SharedPreferences
|
private lateinit var prefs: SharedPreferences
|
||||||
private val customTabsIntent = CustomTabsIntent.Builder().build() as CustomTabsIntent
|
private val customTabsIntent = CustomTabsIntent.Builder().build() as CustomTabsIntent
|
||||||
private var window: Window? = null
|
private var customiseColoursButtonLongPressed = false
|
||||||
private var longPressed = false
|
|
||||||
private var versionCount = 0
|
private var versionCount = 0
|
||||||
private var currentCourseSelectedInColourPicker = ""
|
private var currentCourseSelectedInColourPicker = ""
|
||||||
|
|
||||||
private val ringtones: List<Ringtone> by lazy {
|
|
||||||
getRingtones()
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var colourRecycler: RecyclerView
|
private lateinit var colourRecycler: RecyclerView
|
||||||
private lateinit var ringtoneDialog: AlertDialog
|
private lateinit var ringtoneDialog: AlertDialog
|
||||||
private lateinit var colourPickerDialog: AlertDialog
|
private lateinit var colourPickerDialog: AlertDialog
|
||||||
|
|
||||||
private lateinit var binding: ContentSettingsBinding
|
private lateinit var binding: ContentSettingsBinding
|
||||||
private lateinit var textCustomiseColoursTitle: TextView
|
private lateinit var viewModel: SettingsViewModel
|
||||||
private lateinit var textCustomiseColoursDesc: TextView
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
mContext = context
|
mContext = context
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(mContext)
|
prefs = PreferenceManager.getDefaultSharedPreferences(mContext)
|
||||||
window = activity?.window
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
@@ -80,12 +69,11 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
viewModel = ViewModelProviders.of(this)[SettingsViewModel::class.java]
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
val thisFragment = this@SettingsFragment
|
val thisFragment = this@SettingsFragment
|
||||||
|
|
||||||
thisFragment.textCustomiseColoursTitle = textCustomiseColoursTitle
|
|
||||||
thisFragment.textCustomiseColoursDesc = textCustomiseColoursDesc
|
|
||||||
|
|
||||||
// Set text
|
// Set text
|
||||||
setRingtoneText()
|
setRingtoneText()
|
||||||
|
|
||||||
@@ -174,15 +162,15 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||||
if (Build.VERSION.SDK_INT in 23..28) {
|
if (Build.VERSION.SDK_INT in 23..28) {
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
activity?.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||||
window?.navigationBarColor = ContextCompat.getColor(mContext, R.color.colorBackground)
|
activity?.window?.navigationBarColor = ContextCompat.getColor(mContext, R.color.colorBackground)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) // only accessible on API 28+
|
2 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) // only accessible on API 28+
|
||||||
else -> {
|
else -> {
|
||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||||
window?.navigationBarColor = ContextCompat.getColor(mContext, R.color.colorBackground)
|
activity?.window?.navigationBarColor = ContextCompat.getColor(mContext, R.color.colorBackground)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,14 +207,14 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
R.id.button_courses_help -> createDialog(getString(R.string.courses_help_dialog_title), getString(R.string.courses_help_dialog_text))
|
R.id.button_courses_help -> createDialog(getString(R.string.courses_help_dialog_title), getString(R.string.courses_help_dialog_text))
|
||||||
R.id.button_order_help -> createDialog(getString(R.string.ordering_systems_dialog_title), getString(R.string.ordering_systems_dialog_text))
|
R.id.button_order_help -> createDialog(getString(R.string.ordering_systems_dialog_title), getString(R.string.ordering_systems_dialog_text))
|
||||||
R.id.button_forced_refresh -> {
|
R.id.button_forced_refresh -> {
|
||||||
DataFetcher(
|
// val repo = SubstRepository(activity?.application!!)
|
||||||
isPlan = true,
|
// repo.putAndApplyString("timeNew", "")
|
||||||
isMenu = true,
|
// repo.putAndApplyString("newFoodTime", "")
|
||||||
forced = true,
|
// repo.fetchDataOnline(Caller.SETTINGS)
|
||||||
context = mContext,
|
// TODO needs async
|
||||||
application = activity!!.application,
|
// TODO put in viewModel
|
||||||
parentView = view!!.rootView
|
// TODO create viewModel
|
||||||
).execute()
|
// TODO remove TODOs
|
||||||
}
|
}
|
||||||
R.id.button_visit_website -> {
|
R.id.button_visit_website -> {
|
||||||
try {
|
try {
|
||||||
@@ -250,14 +238,14 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
override fun onLongClick(v: View?): Boolean {
|
override fun onLongClick(v: View?): Boolean {
|
||||||
return when (v?.id) {
|
return when (v?.id) {
|
||||||
R.id.button_customise_colours -> {
|
R.id.button_customise_colours -> {
|
||||||
if (!longPressed) {
|
if (!customiseColoursButtonLongPressed) {
|
||||||
textCustomiseColoursTitle.text = getString(R.string.made_by_deniz)
|
binding.textCustomiseColoursTitle.text = getString(R.string.made_by_deniz)
|
||||||
textCustomiseColoursDesc.text = getString(R.string.thanks_for_using)
|
binding.textCustomiseColoursDesc.text = getString(R.string.thanks_for_using)
|
||||||
} else {
|
} else {
|
||||||
textCustomiseColoursTitle.text = getString(R.string.customise_colours_title)
|
binding.textCustomiseColoursTitle.text = getString(R.string.customise_colours_title)
|
||||||
textCustomiseColoursDesc.text = getString(R.string.customise_colours_desc)
|
binding.textCustomiseColoursDesc.text = getString(R.string.customise_colours_desc)
|
||||||
}
|
}
|
||||||
longPressed = !longPressed
|
customiseColoursButtonLongPressed = !customiseColoursButtonLongPressed
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.button_courses_help -> {
|
R.id.button_courses_help -> {
|
||||||
@@ -298,9 +286,9 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
R.id.switch_notifications -> {
|
R.id.switch_notifications -> {
|
||||||
prefs.edit().putBoolean("notif", isChecked).apply()
|
prefs.edit().putBoolean("notif", isChecked).apply()
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
subscribeToTopic(Topic.ANDROID)
|
subscribeToTopic(SubstUtil.FB_TOPIC_ANDROID)
|
||||||
} else {
|
} else {
|
||||||
unsubscribeFromTopic(Topic.ANDROID)
|
unsubscribeFromTopic(SubstUtil.FB_TOPIC_ANDROID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.switch_personalised_plan -> {
|
R.id.switch_personalised_plan -> {
|
||||||
@@ -389,11 +377,14 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
val titleText = dialogView.findViewById<TextView>(R.id.empty_textviewtitle)
|
val titleText = dialogView.findViewById<TextView>(R.id.empty_textviewtitle)
|
||||||
titleText.text = getString(R.string.pick_ringtone_dialog_title)
|
titleText.text = getString(R.string.pick_ringtone_dialog_title)
|
||||||
|
|
||||||
val recycler = dialogView.findViewById<RecyclerView>(R.id.recyclerView)
|
dialogView.findViewById<RecyclerView>(R.id.recyclerView).apply {
|
||||||
recycler.apply {
|
|
||||||
hasFixedSize()
|
hasFixedSize()
|
||||||
layoutManager = GridLayoutManager(mContext, 1)
|
layoutManager = GridLayoutManager(mContext, 1)
|
||||||
adapter = RingtoneAdapter(ringtones, this@SettingsFragment)
|
if (!viewModel.ringtonesInitialised) {
|
||||||
|
viewModel.getRingtones {
|
||||||
|
adapter = RingtoneAdapter(viewModel.ringtones, this@SettingsFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ringtoneDialog = ringtoneCustomiserBuilder.setView(dialogView).create()
|
ringtoneDialog = ringtoneCustomiserBuilder.setView(dialogView).create()
|
||||||
@@ -464,10 +455,10 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
}
|
}
|
||||||
this == "_devchannel" -> {
|
this == "_devchannel" -> {
|
||||||
val subbed = if (prefs.getBoolean("subscribedToFBDebugChannel", false)) {
|
val subbed = if (prefs.getBoolean("subscribedToFBDebugChannel", false)) {
|
||||||
unsubscribeFromTopic(Topic.DEVELOPMENT)
|
unsubscribeFromTopic(SubstUtil.FB_TOPIC_DEVELOPMENT)
|
||||||
"Unsubscribed from"
|
"Unsubscribed from"
|
||||||
} else {
|
} else {
|
||||||
subscribeToTopic(Topic.DEVELOPMENT)
|
subscribeToTopic(SubstUtil.FB_TOPIC_DEVELOPMENT)
|
||||||
"Subscribed to"
|
"Subscribed to"
|
||||||
}
|
}
|
||||||
prefs.edit().putBoolean("subscribedToFBDebugChannel", !prefs.getBoolean("subscribedToFBDebugChannel", false)).apply()
|
prefs.edit().putBoolean("subscribedToFBDebugChannel", !prefs.getBoolean("subscribedToFBDebugChannel", false)).apply()
|
||||||
@@ -475,10 +466,10 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
}
|
}
|
||||||
this == "_ioschannel" -> {
|
this == "_ioschannel" -> {
|
||||||
val subbed = if (prefs.getBoolean("subscribedToiOSChannel", false)) {
|
val subbed = if (prefs.getBoolean("subscribedToiOSChannel", false)) {
|
||||||
unsubscribeFromTopic(Topic.IOS)
|
unsubscribeFromTopic(SubstUtil.FB_TOPIC_IOS)
|
||||||
"Unsubscribed from"
|
"Unsubscribed from"
|
||||||
} else {
|
} else {
|
||||||
subscribeToTopic(Topic.IOS)
|
subscribeToTopic(SubstUtil.FB_TOPIC_IOS)
|
||||||
"Subscribed to"
|
"Subscribed to"
|
||||||
}
|
}
|
||||||
prefs.edit().putBoolean("subscribedToiOSChannel", !prefs.getBoolean("subscribedToiOSChannel", false)).apply()
|
prefs.edit().putBoolean("subscribedToiOSChannel", !prefs.getBoolean("subscribedToiOSChannel", false)).apply()
|
||||||
@@ -570,26 +561,6 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
return colours
|
return colours
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRingtones(): ArrayList<Ringtone> {
|
|
||||||
lateinit var ringtoneCursor: Cursor
|
|
||||||
val ringtoneManager = RingtoneManager(activity).apply {
|
|
||||||
setType(RingtoneManager.TYPE_NOTIFICATION)
|
|
||||||
ringtoneCursor = cursor
|
|
||||||
}
|
|
||||||
val alarms = ArrayList<Ringtone>()
|
|
||||||
|
|
||||||
while (!ringtoneCursor.isAfterLast && ringtoneCursor.moveToNext()) {
|
|
||||||
val position = ringtoneCursor.position
|
|
||||||
alarms.add(
|
|
||||||
Ringtone(
|
|
||||||
ringtoneManager.getRingtone(position).getTitle(mContext),
|
|
||||||
ringtoneManager.getRingtoneUri(position).toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return alarms
|
|
||||||
}
|
|
||||||
|
|
||||||
// Functions for handling other things
|
// Functions for handling other things
|
||||||
private fun setRingtoneText() {
|
private fun setRingtoneText() {
|
||||||
binding.textCustomiseRingtoneDesc.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
binding.textCustomiseRingtoneDesc.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
@@ -620,16 +591,20 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
"\n\nSubscribed to dev channel: ${prefs.getBoolean("subscribedToFBDebugChannel", false)}"
|
"\n\nSubscribed to dev channel: ${prefs.getBoolean("subscribedToFBDebugChannel", false)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun subscribeToTopic(topic: Topic) = FirebaseMessaging.getInstance().subscribeToTopic(topic.tag)
|
private fun subscribeToTopic(topic: String) = FirebaseMessaging.getInstance().subscribeToTopic(topic)
|
||||||
private fun unsubscribeFromTopic(topic: Topic) = FirebaseMessaging.getInstance().unsubscribeFromTopic(topic.tag)
|
private fun unsubscribeFromTopic(topic: String) = FirebaseMessaging.getInstance().unsubscribeFromTopic(topic)
|
||||||
|
|
||||||
// Below this point follow string literals that I didn't bother putting in /res/values/strings
|
// Below this point follow string literals that I didn't bother putting in /res/values/strings
|
||||||
|
|
||||||
private val licences = "Libraries:" +
|
// TODO check if this is displayed properly
|
||||||
"\n • jsoup HTML parser © 2009-2018 Jonathan Hedley, licensed under the open source MIT Licence" +
|
private val licences = """Libraries:
|
||||||
"\n\nFont:" +
|
• jsoup HTML parser © 2009-2018 Jonathan Hedley, licensed under the open source MIT Licence
|
||||||
"\n • Manrope © 2018-2019 Michael Sharanda, licensed under the SIL Open Font Licence 1.1" +
|
Font:
|
||||||
"\n\nIcons:" +
|
• Manrope © 2018-2019 Michael Sharanda, licensed under the SIL Open Font Licence 1.1
|
||||||
"\n • bqlqn\n • fjstudio\n • Freepik\n • Smashicons" +
|
Icons:
|
||||||
"\n • © 2013-2019 Freepik Company S.L., licensed under Creative Commons BY 3.0"
|
• bqlqn
|
||||||
|
• fjstudio
|
||||||
|
• Freepik
|
||||||
|
• Smashicons
|
||||||
|
• © 2013-2019 Freepik Company S.L., licensed under Creative Commons BY 3.0""".trimIndent()
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package com.denizd.substitutionplan.services
|
package com.denizd.substitutionplan.services
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import com.denizd.substitutionplan.data.DataFetcher
|
import com.denizd.substitutionplan.data.Caller
|
||||||
|
import com.denizd.substitutionplan.database.SubstRepository
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService
|
import com.google.firebase.messaging.FirebaseMessagingService
|
||||||
import com.google.firebase.messaging.RemoteMessage
|
import com.google.firebase.messaging.RemoteMessage
|
||||||
|
|
||||||
@@ -13,13 +14,6 @@ import com.google.firebase.messaging.RemoteMessage
|
|||||||
internal class FBNotificationService : FirebaseMessagingService() {
|
internal class FBNotificationService : FirebaseMessagingService() {
|
||||||
|
|
||||||
override fun onMessageReceived(p0: RemoteMessage) {
|
override fun onMessageReceived(p0: RemoteMessage) {
|
||||||
DataFetcher(
|
SubstRepository(application).fetchDataOnline(Caller.JOBSERVICE)
|
||||||
isPlan = true,
|
|
||||||
isMenu = true,
|
|
||||||
isJobService = true,
|
|
||||||
context = applicationContext,
|
|
||||||
application = application,
|
|
||||||
parentView = null
|
|
||||||
).execute()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package com.denizd.substitutionplan.services
|
|||||||
import android.app.job.JobParameters
|
import android.app.job.JobParameters
|
||||||
import android.app.job.JobService
|
import android.app.job.JobService
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.denizd.substitutionplan.data.Topic
|
import com.denizd.substitutionplan.data.SubstUtil
|
||||||
import com.google.firebase.FirebaseApp
|
import com.google.firebase.FirebaseApp
|
||||||
import com.google.firebase.messaging.FirebaseMessaging
|
import com.google.firebase.messaging.FirebaseMessaging
|
||||||
|
|
||||||
@@ -35,24 +35,24 @@ internal class FBPingService : JobService() {
|
|||||||
if (!prefs.getBoolean("firstTime", true)) {
|
if (!prefs.getBoolean("firstTime", true)) {
|
||||||
FirebaseMessaging.getInstance().apply {
|
FirebaseMessaging.getInstance().apply {
|
||||||
if (prefs.getBoolean("notif", true)) {
|
if (prefs.getBoolean("notif", true)) {
|
||||||
subscribeToTopic(Topic.ANDROID.tag)
|
subscribeToTopic(SubstUtil.FB_TOPIC_ANDROID)
|
||||||
} else {
|
} else {
|
||||||
unsubscribeFromTopic(Topic.ANDROID.tag)
|
unsubscribeFromTopic(SubstUtil.FB_TOPIC_ANDROID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefs.getBoolean("subscribedToFBDebugChannel", false)) {
|
if (prefs.getBoolean("subscribedToFBDebugChannel", false)) {
|
||||||
subscribeToTopic(Topic.DEVELOPMENT.tag)
|
subscribeToTopic(SubstUtil.FB_TOPIC_DEVELOPMENT)
|
||||||
} else {
|
} else {
|
||||||
unsubscribeFromTopic(Topic.DEVELOPMENT.tag)
|
unsubscribeFromTopic(SubstUtil.FB_TOPIC_DEVELOPMENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefs.getBoolean("subscribedToiOSChannel", false)) {
|
if (prefs.getBoolean("subscribedToiOSChannel", false)) {
|
||||||
subscribeToTopic(Topic.IOS.tag)
|
subscribeToTopic(SubstUtil.FB_TOPIC_IOS)
|
||||||
} else {
|
} else {
|
||||||
unsubscribeFromTopic(Topic.IOS.tag)
|
unsubscribeFromTopic(SubstUtil.FB_TOPIC_IOS)
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeToTopic(Topic.BROADCAST.tag)
|
subscribeToTopic(SubstUtil.FB_TOPIC_BROADCAST)
|
||||||
|
|
||||||
prefs.edit().putInt("pingFB", prefs.getInt("pingFB", 0) + 1).apply()
|
prefs.edit().putInt("pingFB", prefs.getInt("pingFB", 0) + 1).apply()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.denizd.substitutionplan.viewmodels
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import com.denizd.substitutionplan.data.Caller
|
||||||
|
import com.denizd.substitutionplan.database.SubstRepository
|
||||||
|
import com.denizd.substitutionplan.models.Food
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
internal class FoodViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
|
private val repo = SubstRepository(application)
|
||||||
|
val allFoodItems: LiveData<List<Food>>?
|
||||||
|
val emptyFoodMenu = repo.emptyFoodMenu
|
||||||
|
val shouldAutoRefresh = repo.shouldAutoRefresh
|
||||||
|
|
||||||
|
init {
|
||||||
|
allFoodItems = repo.allFoodItems
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh(updateUi: (result: String, error: Boolean) -> Unit) {
|
||||||
|
GlobalScope.launch {
|
||||||
|
val task = GlobalScope.async { repo.fetchDataOnline(Caller.FOODMENU) }
|
||||||
|
val result = task.await()
|
||||||
|
updateUi(result.first, result.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.denizd.substitutionplan.viewmodels
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import com.denizd.substitutionplan.database.SettingsRepository
|
||||||
|
import com.denizd.substitutionplan.models.Ringtone
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
internal class SettingsViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
|
private val repo = SettingsRepository(application)
|
||||||
|
|
||||||
|
lateinit var ringtones: List<Ringtone>
|
||||||
|
val ringtonesInitialised = ::ringtones.isInitialized
|
||||||
|
|
||||||
|
fun getRingtones(updateUi: () -> Unit) {
|
||||||
|
GlobalScope.launch {
|
||||||
|
val task = GlobalScope.async { repo.ringtones }
|
||||||
|
ringtones = task.await()
|
||||||
|
updateUi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.denizd.substitutionplan.viewmodels
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import com.denizd.substitutionplan.data.Caller
|
||||||
|
import com.denizd.substitutionplan.database.SubstRepository
|
||||||
|
import com.denizd.substitutionplan.models.Substitution
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
|
internal class SubstViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
|
private val repo = SubstRepository(application)
|
||||||
|
val allSubstitutionsSorted: LiveData<List<Substitution>>?
|
||||||
|
val allSubstitutionsOriginal: LiveData<List<Substitution>>?
|
||||||
|
val substitutionPlanColours = repo.substitutionPlanColours
|
||||||
|
val shouldAutoRefresh = repo.shouldAutoRefresh
|
||||||
|
val shouldUseAppSorting = repo.shouldUseAppSorting
|
||||||
|
val emptyGeneralPlan = repo.emptyGeneralPlan
|
||||||
|
val emptyPersonalSubstitution = repo.emptyPersonalSubstitution
|
||||||
|
|
||||||
|
init {
|
||||||
|
allSubstitutionsSorted = repo.allSubstitutionsSorted
|
||||||
|
allSubstitutionsOriginal = repo.allSubstitutionsOriginal
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNonNullString(key: String): String {
|
||||||
|
return repo.externalNonNullString(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkIfSubstitutionPersonal(substitution: Substitution, classPreference: String, coursePreference: String, includesPsa: Boolean): Boolean {
|
||||||
|
return repo.checkIfSubstitutionPersonal(substitution, classPreference, coursePreference, includesPsa)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGridColumnCount(config: Configuration): Int {
|
||||||
|
return repo.getGridColumnCount(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh(updateUi: (result: String, error: Boolean) -> Unit) {
|
||||||
|
GlobalScope.launch {
|
||||||
|
val task = GlobalScope.async { repo.fetchDataOnline(Caller.SUBSTITUTION) }
|
||||||
|
val result = task.await()
|
||||||
|
updateUi(result.first, result.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">AvH-Plan</string>
|
<string name="app_name">AvH-Plan</string>
|
||||||
<string name="filters">Filter</string>
|
<string name="filters">Filter</string>
|
||||||
<string name="no_internet_connection">Keine Internetverbindung</string>
|
<string name="an_error_occurred">Ein Fehler ist aufgetreten</string>
|
||||||
<string name="general">Allgemein</string>
|
<string name="general">Allgemein</string>
|
||||||
<string name="grade">Klasse</string>
|
<string name="grade">Klasse</string>
|
||||||
<string name="courses">Kurse</string>
|
<string name="courses">Kurse</string>
|
||||||
@@ -192,4 +192,6 @@
|
|||||||
<item>Weiß</item>
|
<item>Weiß</item>
|
||||||
<item>Schwarz</item>
|
<item>Schwarz</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string name="plan_updated">Der Plan wurde aktualisiert</string>
|
||||||
|
<string name="food_menu_updated">Der Speiseplan wurde aktualisiert</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<string name="app_name">AvH Plan</string>
|
<string name="app_name">AvH Plan</string>
|
||||||
<string name="substitution_plan">Substitution Plan</string>
|
<string name="substitution_plan">Substitution Plan</string>
|
||||||
<string name="filters">Filters</string>
|
<string name="filters">Filters</string>
|
||||||
<string name="no_internet_connection">No internet connection</string>
|
<string name="an_error_occurred">An error has occurred</string>
|
||||||
<string name="general">General</string>
|
<string name="general">General</string>
|
||||||
<string name="grade">Grade</string>
|
<string name="grade">Grade</string>
|
||||||
<string name="courses">Courses</string>
|
<string name="courses">Courses</string>
|
||||||
@@ -205,4 +205,6 @@
|
|||||||
<item>White</item>
|
<item>White</item>
|
||||||
<item>Black</item>
|
<item>Black</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string name="plan_updated">The plan has been updated</string>
|
||||||
|
<string name="food_menu_updated">The food menu has been updated</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user