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:
@@ -2,7 +2,6 @@ package com.denizd.substitutionplan.adapters
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.text.SpannableString
|
||||
import android.text.style.StrikethroughSpan
|
||||
@@ -26,9 +25,9 @@ import java.util.*
|
||||
* PlanFragment.kt and its subclasses
|
||||
*
|
||||
* @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
|
||||
@@ -114,11 +113,7 @@ internal class SubstitutionAdapter(private var substitutions: List<Substitution>
|
||||
View.GONE
|
||||
} else {
|
||||
val colourString = SubstUtil.getColourString(holder.course.text.toString())
|
||||
val colourPrefsInt = if (colourString.isNotEmpty()) {
|
||||
prefs.getString("card$colourString", "") ?: ""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val colourPrefsInt = colours[colourString] ?: ""
|
||||
colour = SubstUtil.getColourForString(colourPrefsInt, holder.context)
|
||||
cardBackgroundColour = if (colour != 0) {
|
||||
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"
|
||||
|
||||
fun getEmptyGeneralSubstitution(context: Context) = arrayOf(
|
||||
Substitution("", "", "", context.getString(R.string.plan_empty), "", "", "", "", 0, 0, 0)
|
||||
).toList()
|
||||
|
||||
fun getEmptyPersonalSubstitution(context: Context) =
|
||||
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()
|
||||
// Constants for Firebase Cloud Messaging Topics
|
||||
const val FB_TOPIC_ANDROID = "substitutions-android"
|
||||
const val FB_TOPIC_BROADCAST = "substitutions-broadcast"
|
||||
const val FB_TOPIC_IOS = "substitutions-ios"
|
||||
const val FB_TOPIC_DEVELOPMENT = "substitutions-debug"
|
||||
|
||||
/**
|
||||
* This function returns an array that contains all data relevant to tinting individual entries
|
||||
@@ -248,39 +243,6 @@ internal object SubstUtil {
|
||||
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
|
||||
* 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? {
|
||||
if (instance == null) {
|
||||
fun getSubstInstance(context: Context): SubstDatabase? {
|
||||
if (substInstance == null) {
|
||||
synchronized (SubstDatabase::class) {
|
||||
instance = Room.databaseBuilder(context.applicationContext,
|
||||
substInstance = Room.databaseBuilder(context.applicationContext,
|
||||
SubstDatabase::class.java,
|
||||
"subst_database")
|
||||
.addMigrations(addTeacherColumn, addTypeColumn, addDatePriorityColumn, addWebsitePriorityColumn)
|
||||
@@ -50,7 +50,7 @@ internal abstract class SubstDatabase : RoomDatabase() {
|
||||
.build()
|
||||
}
|
||||
}
|
||||
return instance
|
||||
return substInstance
|
||||
}
|
||||
|
||||
private var foodInstance: SubstDatabase? = null
|
||||
|
||||
@@ -1,30 +1,379 @@
|
||||
package com.denizd.substitutionplan.database
|
||||
|
||||
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.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 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 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 {
|
||||
val database =
|
||||
SubstDatabase.getInstance(application)
|
||||
substDao = database?.substDao()
|
||||
allSubstitutionsSorted = substDao?.allSubstitutionsSorted
|
||||
allSubstitutionsOriginal = substDao?.allSubstitutionsOriginal
|
||||
val substInstance = SubstDatabase.getSubstInstance(application)
|
||||
val foodInstance = SubstDatabase.getFoodInstance(application)
|
||||
substitutionDao = substInstance?.substDao()
|
||||
foodMenuDao = foodInstance?.substDao()
|
||||
allSubstitutionsSorted = substitutionDao?.allSubstitutionsSorted
|
||||
allSubstitutionsOriginal = substitutionDao?.allSubstitutionsOriginal
|
||||
allFoodItems = foodMenuDao?.allFoods
|
||||
}
|
||||
|
||||
fun insert(substitution: Substitution) {
|
||||
substDao?.insertSubst(substitution)
|
||||
private fun insert(substitution: Substitution) {
|
||||
substitutionDao?.insertSubst(substitution)
|
||||
}
|
||||
|
||||
fun deleteAllSubst() {
|
||||
substDao?.deleteAllSubst()
|
||||
private fun insert(food: Food) {
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.denizd.substitutionplan.R
|
||||
import com.denizd.substitutionplan.adapters.FoodAdapter
|
||||
import com.denizd.substitutionplan.data.SubstUtil
|
||||
import com.denizd.substitutionplan.database.FoodViewModel
|
||||
import com.denizd.substitutionplan.viewmodels.FoodViewModel
|
||||
import com.denizd.substitutionplan.databinding.FoodLayoutBinding
|
||||
import com.denizd.substitutionplan.models.Food
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
internal class FoodFragment : Fragment() {
|
||||
|
||||
private val mAdapter = FoodAdapter(ArrayList())
|
||||
private lateinit var mContext: Context
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private lateinit var foodViewModel: FoodViewModel
|
||||
private lateinit var viewModel: FoodViewModel
|
||||
|
||||
private lateinit var binding: FoodLayoutBinding
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
mContext = context
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(mContext)
|
||||
}
|
||||
private lateinit var snackBarContainer: CoordinatorLayout
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
binding = FoodLayoutBinding.inflate(inflater, container, false)
|
||||
@@ -40,28 +33,41 @@ internal class FoodFragment : Fragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
snackBarContainer = view.rootView.findViewById(R.id.coordination) // TODO replace findViewById with ViewBinding
|
||||
|
||||
binding.recyclerView.apply {
|
||||
hasFixedSize()
|
||||
layoutManager = GridLayoutManager(mContext, 1)
|
||||
layoutManager = GridLayoutManager(requireContext(), 1)
|
||||
adapter = mAdapter
|
||||
}
|
||||
|
||||
foodViewModel = ViewModelProviders.of(this).get(FoodViewModel::class.java)
|
||||
foodViewModel.allFoods?.observe(this, Observer<List<Food>> { foodList ->
|
||||
viewModel = ViewModelProviders.of(this).get(FoodViewModel::class.java)
|
||||
viewModel.allFoodItems?.observe(this, Observer<List<Food>> { foodList ->
|
||||
binding.recyclerView.scheduleLayoutAnimation()
|
||||
mAdapter.setFood(if (foodList.isEmpty()) {
|
||||
SubstUtil.getEmptyFoodMenu(mContext)
|
||||
viewModel.emptyFoodMenu
|
||||
} else {
|
||||
foodList
|
||||
})
|
||||
})
|
||||
|
||||
if (prefs.getBoolean("autoRefresh", false)) {
|
||||
foodViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView)
|
||||
if (viewModel.shouldAutoRefresh) {
|
||||
refreshAndDisplaySnackBar()
|
||||
}
|
||||
|
||||
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.view.View
|
||||
import androidx.lifecycle.Observer
|
||||
import com.denizd.substitutionplan.data.SubstUtil
|
||||
import com.denizd.substitutionplan.models.Substitution
|
||||
|
||||
internal class GeneralPlanFragment : PlanFragment() {
|
||||
@@ -13,7 +12,7 @@ internal class GeneralPlanFragment : PlanFragment() {
|
||||
|
||||
substitutionPlan?.observe(this, Observer<List<Substitution>> { substitutions ->
|
||||
mAdapter.setSubst(if (substitutions.isEmpty()) {
|
||||
SubstUtil.getEmptyGeneralSubstitution(mContext)
|
||||
viewModel.emptyGeneralPlan
|
||||
} else {
|
||||
substitutions
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.denizd.substitutionplan.fragments
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Observer
|
||||
import com.denizd.substitutionplan.data.SubstUtil
|
||||
import com.denizd.substitutionplan.models.Substitution
|
||||
|
||||
internal class PersonalPlanFragment : PlanFragment() {
|
||||
@@ -11,30 +10,28 @@ internal class PersonalPlanFragment : PlanFragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val coursePreference = prefs.getString("courses", "") ?: ""
|
||||
val classPreference = prefs.getString("classes", "") ?: ""
|
||||
val coursePreference = viewModel.getNonNullString("courses")
|
||||
val classPreference = viewModel.getNonNullString("classes")
|
||||
|
||||
substitutionPlan?.observe(this, Observer<List<Substitution>> { substitutions ->
|
||||
planCardList.clear()
|
||||
isPersonalPlanEmpty = true
|
||||
binding.recyclerView.visibility = View.VISIBLE
|
||||
val list = ArrayList<Substitution>()
|
||||
|
||||
substitutions.filter { substItem ->
|
||||
SubstUtil.checkPersonalSubstitutions(
|
||||
viewModel.checkIfSubstitutionPersonal(
|
||||
substItem,
|
||||
coursePreference,
|
||||
classPreference,
|
||||
coursePreference,
|
||||
true
|
||||
)
|
||||
}.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) {
|
||||
planCardList.add(SubstUtil.getEmptyPersonalSubstitution(mContext))
|
||||
list.add(viewModel.emptyPersonalSubstitution)
|
||||
}
|
||||
binding.recyclerView.scheduleLayoutAnimation()
|
||||
mAdapter.setSubst(planCardList)
|
||||
mAdapter.setSubst(list)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,33 @@
|
||||
package com.denizd.substitutionplan.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.denizd.substitutionplan.R
|
||||
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.models.Substitution
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
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 handler = Handler()
|
||||
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)
|
||||
internal val mAdapter: SubstitutionAdapter by lazy {
|
||||
SubstitutionAdapter(ArrayList(), viewModel.substitutionPlanColours)
|
||||
}
|
||||
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? {
|
||||
binding = PlanBinding.inflate(inflater, container, false)
|
||||
@@ -46,51 +36,50 @@ internal open class PlanFragment : Fragment() {
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
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?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
substViewModel = ViewModelProviders.of(this).get(SubstViewModel::class.java)
|
||||
substitutionPlan = if (prefs.getBoolean("app_specific_sorting", true)) {
|
||||
substViewModel.allSubstitutionsSorted
|
||||
|
||||
snackBarContainer = view.rootView.findViewById(R.id.coordination) // TODO replace findViewById with ViewBinding
|
||||
|
||||
viewModel = ViewModelProviders.of(this).get(SubstViewModel::class.java)
|
||||
substitutionPlan = if (viewModel.shouldUseAppSorting) {
|
||||
viewModel.allSubstitutionsSorted
|
||||
} else {
|
||||
substViewModel.allSubstitutionsOriginal
|
||||
viewModel.allSubstitutionsOriginal
|
||||
}
|
||||
|
||||
mAdapter = SubstitutionAdapter(planCardList, prefs)
|
||||
binding.recyclerView.apply {
|
||||
hasFixedSize()
|
||||
layoutManager = GridLayoutManager(mContext, getGridColumnCount(resources.configuration))
|
||||
layoutManager = GridLayoutManager(requireContext(), viewModel.getGridColumnCount(resources.configuration))
|
||||
adapter = mAdapter
|
||||
}
|
||||
|
||||
if (prefs.getInt("firstTimeOpening", 0) == 1) {
|
||||
substViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView, refreshMenu = true)
|
||||
prefs.edit().putInt("firstTimeOpening", 2).apply()
|
||||
}
|
||||
// TODO figure something out
|
||||
// if (prefs.getInt("firstTimeOpening", 0) == 1) {
|
||||
// refreshAndDisplaySnackBar()
|
||||
// prefs.edit().putInt("firstTimeOpening", 2).apply()
|
||||
// }
|
||||
|
||||
if (prefs.getBoolean("autoRefresh", false)) {
|
||||
substViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView, refreshMenu = false)
|
||||
if (viewModel.shouldAutoRefresh) {
|
||||
refreshAndDisplaySnackBar()
|
||||
}
|
||||
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
substViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView, refreshMenu = false)
|
||||
refreshAndDisplaySnackBar()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGridColumnCount(config: Configuration): Int {
|
||||
val tabletSize = resources.getBoolean(R.bool.isTablet)
|
||||
return if (tabletSize) {
|
||||
when (config.orientation) {
|
||||
Configuration.ORIENTATION_PORTRAIT -> 2
|
||||
else -> 3
|
||||
}
|
||||
} else {
|
||||
when (config.orientation) {
|
||||
Configuration.ORIENTATION_PORTRAIT -> 1
|
||||
else -> 2
|
||||
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)) // TODO check if requireContext() is dangerous ;(
|
||||
}
|
||||
snackBar.show()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.database.Cursor
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -17,25 +15,23 @@ import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.denizd.substitutionplan.*
|
||||
import com.denizd.substitutionplan.adapters.ColourPickerAdapter
|
||||
import com.denizd.substitutionplan.adapters.CourseColourAdapter
|
||||
import com.denizd.substitutionplan.adapters.RingtoneAdapter
|
||||
import com.denizd.substitutionplan.data.DataFetcher
|
||||
import com.denizd.substitutionplan.data.SubstUtil
|
||||
import com.denizd.substitutionplan.data.Topic
|
||||
import com.denizd.substitutionplan.databinding.ContentSettingsBinding
|
||||
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.button.MaterialButton
|
||||
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 prefs: SharedPreferences
|
||||
private val customTabsIntent = CustomTabsIntent.Builder().build() as CustomTabsIntent
|
||||
private var window: Window? = null
|
||||
private var longPressed = false
|
||||
private var customiseColoursButtonLongPressed = false
|
||||
private var versionCount = 0
|
||||
private var currentCourseSelectedInColourPicker = ""
|
||||
|
||||
private val ringtones: List<Ringtone> by lazy {
|
||||
getRingtones()
|
||||
}
|
||||
|
||||
private lateinit var colourRecycler: RecyclerView
|
||||
private lateinit var ringtoneDialog: AlertDialog
|
||||
private lateinit var colourPickerDialog: AlertDialog
|
||||
|
||||
private lateinit var binding: ContentSettingsBinding
|
||||
private lateinit var textCustomiseColoursTitle: TextView
|
||||
private lateinit var textCustomiseColoursDesc: TextView
|
||||
private lateinit var viewModel: SettingsViewModel
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
mContext = context
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(mContext)
|
||||
window = activity?.window
|
||||
}
|
||||
|
||||
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?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this)[SettingsViewModel::class.java]
|
||||
|
||||
binding.apply {
|
||||
val thisFragment = this@SettingsFragment
|
||||
|
||||
thisFragment.textCustomiseColoursTitle = textCustomiseColoursTitle
|
||||
thisFragment.textCustomiseColoursDesc = textCustomiseColoursDesc
|
||||
|
||||
// Set text
|
||||
setRingtoneText()
|
||||
|
||||
@@ -174,15 +162,15 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||
if (Build.VERSION.SDK_INT in 23..28) {
|
||||
@SuppressLint("InlinedApi")
|
||||
window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
window?.navigationBarColor = ContextCompat.getColor(mContext, R.color.colorBackground)
|
||||
activity?.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
activity?.window?.navigationBarColor = ContextCompat.getColor(mContext, R.color.colorBackground)
|
||||
}
|
||||
}
|
||||
2 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) // only accessible on API 28+
|
||||
else -> {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
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_order_help -> createDialog(getString(R.string.ordering_systems_dialog_title), getString(R.string.ordering_systems_dialog_text))
|
||||
R.id.button_forced_refresh -> {
|
||||
DataFetcher(
|
||||
isPlan = true,
|
||||
isMenu = true,
|
||||
forced = true,
|
||||
context = mContext,
|
||||
application = activity!!.application,
|
||||
parentView = view!!.rootView
|
||||
).execute()
|
||||
// val repo = SubstRepository(activity?.application!!)
|
||||
// repo.putAndApplyString("timeNew", "")
|
||||
// repo.putAndApplyString("newFoodTime", "")
|
||||
// repo.fetchDataOnline(Caller.SETTINGS)
|
||||
// TODO needs async
|
||||
// TODO put in viewModel
|
||||
// TODO create viewModel
|
||||
// TODO remove TODOs
|
||||
}
|
||||
R.id.button_visit_website -> {
|
||||
try {
|
||||
@@ -250,14 +238,14 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
||||
override fun onLongClick(v: View?): Boolean {
|
||||
return when (v?.id) {
|
||||
R.id.button_customise_colours -> {
|
||||
if (!longPressed) {
|
||||
textCustomiseColoursTitle.text = getString(R.string.made_by_deniz)
|
||||
textCustomiseColoursDesc.text = getString(R.string.thanks_for_using)
|
||||
if (!customiseColoursButtonLongPressed) {
|
||||
binding.textCustomiseColoursTitle.text = getString(R.string.made_by_deniz)
|
||||
binding.textCustomiseColoursDesc.text = getString(R.string.thanks_for_using)
|
||||
} else {
|
||||
textCustomiseColoursTitle.text = getString(R.string.customise_colours_title)
|
||||
textCustomiseColoursDesc.text = getString(R.string.customise_colours_desc)
|
||||
binding.textCustomiseColoursTitle.text = getString(R.string.customise_colours_title)
|
||||
binding.textCustomiseColoursDesc.text = getString(R.string.customise_colours_desc)
|
||||
}
|
||||
longPressed = !longPressed
|
||||
customiseColoursButtonLongPressed = !customiseColoursButtonLongPressed
|
||||
true
|
||||
}
|
||||
R.id.button_courses_help -> {
|
||||
@@ -298,9 +286,9 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
||||
R.id.switch_notifications -> {
|
||||
prefs.edit().putBoolean("notif", isChecked).apply()
|
||||
if (isChecked) {
|
||||
subscribeToTopic(Topic.ANDROID)
|
||||
subscribeToTopic(SubstUtil.FB_TOPIC_ANDROID)
|
||||
} else {
|
||||
unsubscribeFromTopic(Topic.ANDROID)
|
||||
unsubscribeFromTopic(SubstUtil.FB_TOPIC_ANDROID)
|
||||
}
|
||||
}
|
||||
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)
|
||||
titleText.text = getString(R.string.pick_ringtone_dialog_title)
|
||||
|
||||
val recycler = dialogView.findViewById<RecyclerView>(R.id.recyclerView)
|
||||
recycler.apply {
|
||||
dialogView.findViewById<RecyclerView>(R.id.recyclerView).apply {
|
||||
hasFixedSize()
|
||||
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()
|
||||
@@ -464,10 +455,10 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
||||
}
|
||||
this == "_devchannel" -> {
|
||||
val subbed = if (prefs.getBoolean("subscribedToFBDebugChannel", false)) {
|
||||
unsubscribeFromTopic(Topic.DEVELOPMENT)
|
||||
unsubscribeFromTopic(SubstUtil.FB_TOPIC_DEVELOPMENT)
|
||||
"Unsubscribed from"
|
||||
} else {
|
||||
subscribeToTopic(Topic.DEVELOPMENT)
|
||||
subscribeToTopic(SubstUtil.FB_TOPIC_DEVELOPMENT)
|
||||
"Subscribed to"
|
||||
}
|
||||
prefs.edit().putBoolean("subscribedToFBDebugChannel", !prefs.getBoolean("subscribedToFBDebugChannel", false)).apply()
|
||||
@@ -475,10 +466,10 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
||||
}
|
||||
this == "_ioschannel" -> {
|
||||
val subbed = if (prefs.getBoolean("subscribedToiOSChannel", false)) {
|
||||
unsubscribeFromTopic(Topic.IOS)
|
||||
unsubscribeFromTopic(SubstUtil.FB_TOPIC_IOS)
|
||||
"Unsubscribed from"
|
||||
} else {
|
||||
subscribeToTopic(Topic.IOS)
|
||||
subscribeToTopic(SubstUtil.FB_TOPIC_IOS)
|
||||
"Subscribed to"
|
||||
}
|
||||
prefs.edit().putBoolean("subscribedToiOSChannel", !prefs.getBoolean("subscribedToiOSChannel", false)).apply()
|
||||
@@ -570,26 +561,6 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
||||
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
|
||||
private fun setRingtoneText() {
|
||||
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)}"
|
||||
}
|
||||
|
||||
private fun subscribeToTopic(topic: Topic) = FirebaseMessaging.getInstance().subscribeToTopic(topic.tag)
|
||||
private fun unsubscribeFromTopic(topic: Topic) = FirebaseMessaging.getInstance().unsubscribeFromTopic(topic.tag)
|
||||
private fun subscribeToTopic(topic: String) = FirebaseMessaging.getInstance().subscribeToTopic(topic)
|
||||
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
|
||||
|
||||
private val licences = "Libraries:" +
|
||||
"\n • jsoup HTML parser © 2009-2018 Jonathan Hedley, licensed under the open source MIT Licence" +
|
||||
"\n\nFont:" +
|
||||
"\n • Manrope © 2018-2019 Michael Sharanda, licensed under the SIL Open Font Licence 1.1" +
|
||||
"\n\nIcons:" +
|
||||
"\n • bqlqn\n • fjstudio\n • Freepik\n • Smashicons" +
|
||||
"\n • © 2013-2019 Freepik Company S.L., licensed under Creative Commons BY 3.0"
|
||||
// TODO check if this is displayed properly
|
||||
private val licences = """Libraries:
|
||||
• jsoup HTML parser © 2009-2018 Jonathan Hedley, licensed under the open source MIT Licence
|
||||
Font:
|
||||
• Manrope © 2018-2019 Michael Sharanda, licensed under the SIL Open Font Licence 1.1
|
||||
Icons:
|
||||
• 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
|
||||
|
||||
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.RemoteMessage
|
||||
|
||||
@@ -13,13 +14,6 @@ import com.google.firebase.messaging.RemoteMessage
|
||||
internal class FBNotificationService : FirebaseMessagingService() {
|
||||
|
||||
override fun onMessageReceived(p0: RemoteMessage) {
|
||||
DataFetcher(
|
||||
isPlan = true,
|
||||
isMenu = true,
|
||||
isJobService = true,
|
||||
context = applicationContext,
|
||||
application = application,
|
||||
parentView = null
|
||||
).execute()
|
||||
SubstRepository(application).fetchDataOnline(Caller.JOBSERVICE)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package com.denizd.substitutionplan.services
|
||||
import android.app.job.JobParameters
|
||||
import android.app.job.JobService
|
||||
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.messaging.FirebaseMessaging
|
||||
|
||||
@@ -35,24 +35,24 @@ internal class FBPingService : JobService() {
|
||||
if (!prefs.getBoolean("firstTime", true)) {
|
||||
FirebaseMessaging.getInstance().apply {
|
||||
if (prefs.getBoolean("notif", true)) {
|
||||
subscribeToTopic(Topic.ANDROID.tag)
|
||||
subscribeToTopic(SubstUtil.FB_TOPIC_ANDROID)
|
||||
} else {
|
||||
unsubscribeFromTopic(Topic.ANDROID.tag)
|
||||
unsubscribeFromTopic(SubstUtil.FB_TOPIC_ANDROID)
|
||||
}
|
||||
|
||||
if (prefs.getBoolean("subscribedToFBDebugChannel", false)) {
|
||||
subscribeToTopic(Topic.DEVELOPMENT.tag)
|
||||
subscribeToTopic(SubstUtil.FB_TOPIC_DEVELOPMENT)
|
||||
} else {
|
||||
unsubscribeFromTopic(Topic.DEVELOPMENT.tag)
|
||||
unsubscribeFromTopic(SubstUtil.FB_TOPIC_DEVELOPMENT)
|
||||
}
|
||||
|
||||
if (prefs.getBoolean("subscribedToiOSChannel", false)) {
|
||||
subscribeToTopic(Topic.IOS.tag)
|
||||
subscribeToTopic(SubstUtil.FB_TOPIC_IOS)
|
||||
} 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()
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
<string name="app_name">AvH-Plan</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="grade">Klasse</string>
|
||||
<string name="courses">Kurse</string>
|
||||
@@ -192,4 +192,6 @@
|
||||
<item>Weiß</item>
|
||||
<item>Schwarz</item>
|
||||
</string-array>
|
||||
<string name="plan_updated">Der Plan wurde aktualisiert</string>
|
||||
<string name="food_menu_updated">Der Speiseplan wurde aktualisiert</string>
|
||||
</resources>
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="app_name">AvH Plan</string>
|
||||
<string name="substitution_plan">Substitution Plan</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="grade">Grade</string>
|
||||
<string name="courses">Courses</string>
|
||||
@@ -205,4 +205,6 @@
|
||||
<item>White</item>
|
||||
<item>Black</item>
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user