Archived
UI updates launched from view models are now launched in a Dispatchers.Main context as some devices will throw an exception for UI thread operations on a non-UI thread
This commit is contained in:
@@ -1,16 +1,20 @@
|
|||||||
package com.denizd.substitutionplan.database
|
package com.denizd.substitutionplan.database
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.denizd.substitutionplan.data.Caller
|
||||||
import com.denizd.substitutionplan.data.SubstUtil
|
import com.denizd.substitutionplan.data.SubstUtil
|
||||||
import com.denizd.substitutionplan.models.Ringtone
|
import com.denizd.substitutionplan.models.Ringtone
|
||||||
|
|
||||||
internal class SettingsRepository(private val application: Application) {
|
internal class SettingsRepository(private val application: Application) {
|
||||||
|
|
||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(application)
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
val notificationChannel = SubstUtil.getNotificationChannel(application, prefs)
|
|
||||||
|
@TargetApi(26)
|
||||||
|
fun getNotificationChannel() = SubstUtil.getNotificationChannel(application, prefs)
|
||||||
|
|
||||||
val ringtones: List<Ringtone> by lazy {
|
val ringtones: List<Ringtone> by lazy {
|
||||||
lateinit var ringtoneCursor: Cursor
|
lateinit var ringtoneCursor: Cursor
|
||||||
@@ -56,5 +60,12 @@ internal class SettingsRepository(private val application: Application) {
|
|||||||
prefs.edit().putString(key, value).apply()
|
prefs.edit().putString(key, value).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearTimes() {
|
||||||
|
prefs.edit().putString("timeNew", "").putString("newFoodTime", "").apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forceRefresh(): Pair<String, Boolean> {
|
||||||
|
clearTimes()
|
||||||
|
return SubstRepository(application).fetchDataOnline(Caller.SETTINGS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@ 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.coordinatorlayout.widget.CoordinatorLayout
|
||||||
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.lifecycle.ViewModelProviders
|
||||||
@@ -32,6 +33,7 @@ import com.denizd.substitutionplan.models.Colour
|
|||||||
import com.denizd.substitutionplan.viewmodels.SettingsViewModel
|
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.android.material.snackbar.Snackbar
|
||||||
import com.google.firebase.messaging.FirebaseMessaging
|
import com.google.firebase.messaging.FirebaseMessaging
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
@@ -51,6 +53,7 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
|
|
||||||
private lateinit var binding: ContentSettingsBinding
|
private lateinit var binding: ContentSettingsBinding
|
||||||
private lateinit var viewModel: SettingsViewModel
|
private lateinit var viewModel: SettingsViewModel
|
||||||
|
private lateinit var snackBarContainer: CoordinatorLayout
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
@@ -66,6 +69,7 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this)[SettingsViewModel::class.java]
|
viewModel = ViewModelProviders.of(this)[SettingsViewModel::class.java]
|
||||||
|
snackBarContainer = view.rootView.findViewById(R.id.coordination) // TODO replace findViewById with ViewBinding
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
val thisFragment = this@SettingsFragment
|
val thisFragment = this@SettingsFragment
|
||||||
@@ -92,7 +96,6 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
// Set on long click listeners
|
// Set on long click listeners
|
||||||
buttonCustomiseColours.setOnLongClickListener(thisFragment)
|
buttonCustomiseColours.setOnLongClickListener(thisFragment)
|
||||||
|
|
||||||
buttonCoursesHelp.setOnLongClickListener(thisFragment)
|
|
||||||
buttonOrderHelp.setOnLongClickListener(thisFragment)
|
buttonOrderHelp.setOnLongClickListener(thisFragment)
|
||||||
|
|
||||||
buttonForcedRefresh.setOnLongClickListener(thisFragment)
|
buttonForcedRefresh.setOnLongClickListener(thisFragment)
|
||||||
@@ -203,14 +206,13 @@ 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 -> {
|
||||||
// val repo = SubstRepository(activity?.application!!)
|
viewModel.forceRefresh { result, error ->
|
||||||
// repo.putAndApplyString("timeNew", "")
|
val snackBar = Snackbar.make(snackBarContainer, result, Snackbar.LENGTH_LONG)
|
||||||
// repo.putAndApplyString("newFoodTime", "")
|
if (error) {
|
||||||
// repo.fetchDataOnline(Caller.SETTINGS)
|
snackBar.setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.colorError))
|
||||||
// TODO needs async
|
}
|
||||||
// TODO put in viewModel
|
snackBar.show()
|
||||||
// TODO create viewModel
|
}
|
||||||
// TODO remove TODOs
|
|
||||||
}
|
}
|
||||||
R.id.button_visit_website -> {
|
R.id.button_visit_website -> {
|
||||||
try {
|
try {
|
||||||
@@ -244,29 +246,12 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
customiseColoursButtonLongPressed = !customiseColoursButtonLongPressed
|
customiseColoursButtonLongPressed = !customiseColoursButtonLongPressed
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.button_courses_help -> {
|
|
||||||
val link = Uri.parse("https://www.youtube.com/watch?v=Jc2xfYuLWgE") // 'Freak'
|
|
||||||
makeToast(String(Character.toChars(0x2764)))
|
|
||||||
try {
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW, link)
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setPackage("com.google.android.youtube")
|
|
||||||
startActivity(intent)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
try {
|
|
||||||
customTabsIntent.launchUrl(mContext, link)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
makeToast(getString(R.string.youtube_not_found))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.button_order_help -> {
|
R.id.button_order_help -> {
|
||||||
debugMenu()
|
debugMenu()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.button_forced_refresh -> {
|
R.id.button_forced_refresh -> {
|
||||||
viewModel.setAndApplyString("timeNew", "")
|
viewModel.clearTimes()
|
||||||
viewModel.setAndApplyString("newFoodTime", "")
|
|
||||||
makeToast(mContext.getString(R.string.force_refresh_cleared_times))
|
makeToast(mContext.getString(R.string.force_refresh_cleared_times))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -332,6 +317,8 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Functions for handling dialog creation
|
// Functions for handling dialog creation
|
||||||
|
// TODO put this in view model layer
|
||||||
|
// TODO possibly replace with DialogFragment
|
||||||
private fun createDialog(title: String, text: String) {
|
private fun createDialog(title: String, text: String) {
|
||||||
val alertDialog = AlertDialog.Builder(mContext)
|
val alertDialog = AlertDialog.Builder(mContext)
|
||||||
val dialogView = View.inflate(mContext, R.layout.simple_dialog, null)
|
val dialogView = View.inflate(mContext, R.layout.simple_dialog, null)
|
||||||
@@ -359,7 +346,7 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
|
|
||||||
private fun createRingtoneDialog() {
|
private fun createRingtoneDialog() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
viewModel.notificationChannel
|
viewModel.getNotificationChannel()
|
||||||
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
|
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
|
||||||
putExtra(Settings.EXTRA_APP_PACKAGE, mContext.applicationContext.packageName)
|
putExtra(Settings.EXTRA_APP_PACKAGE, mContext.applicationContext.packageName)
|
||||||
putExtra(Settings.EXTRA_CHANNEL_ID, "general")
|
putExtra(Settings.EXTRA_CHANNEL_ID, "general")
|
||||||
@@ -602,14 +589,15 @@ internal class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongC
|
|||||||
// 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
|
||||||
|
|
||||||
// TODO check if this is displayed properly
|
// TODO check if this is displayed properly
|
||||||
private val licences = """Libraries:
|
private val licences = """
|
||||||
• jsoup HTML parser © 2009-2018 Jonathan Hedley, licensed under the open source MIT Licence
|
Libraries:
|
||||||
Font:
|
• jsoup HTML parser © 2009-2018 Jonathan Hedley, licensed under the open source MIT Licence
|
||||||
• Manrope © 2018-2019 Michael Sharanda, licensed under the SIL Open Font Licence 1.1
|
|
||||||
Icons:
|
Font:
|
||||||
• bqlqn
|
• Manrope © 2018-2019 Michael Sharanda, licensed under the SIL Open Font Licence 1.1
|
||||||
• fjstudio
|
|
||||||
• Freepik
|
Icons:
|
||||||
• Smashicons
|
• Authors: bqlqn, fjstudio, Freepik, Smashicons
|
||||||
• © 2013-2019 Freepik Company S.L., licensed under Creative Commons BY 3.0""".trimIndent()
|
• © 2013-2019 Freepik Company S.L., licensed under Creative Commons BY 3.0
|
||||||
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import androidx.lifecycle.LiveData
|
|||||||
import com.denizd.substitutionplan.data.Caller
|
import com.denizd.substitutionplan.data.Caller
|
||||||
import com.denizd.substitutionplan.database.SubstRepository
|
import com.denizd.substitutionplan.database.SubstRepository
|
||||||
import com.denizd.substitutionplan.models.Food
|
import com.denizd.substitutionplan.models.Food
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -23,9 +24,9 @@ internal class FoodViewModel(application: Application) : AndroidViewModel(applic
|
|||||||
|
|
||||||
fun refresh(updateUi: (result: String, error: Boolean) -> Unit) {
|
fun refresh(updateUi: (result: String, error: Boolean) -> Unit) {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
val task = GlobalScope.async { repo.fetchDataOnline(Caller.FOODMENU) }
|
val task = async { repo.fetchDataOnline(Caller.FOODMENU) }
|
||||||
val result = task.await()
|
val result = task.await()
|
||||||
updateUi(result.first, result.second)
|
launch(Dispatchers.Main) { updateUi(result.first, result.second) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package com.denizd.substitutionplan.viewmodels
|
package com.denizd.substitutionplan.viewmodels
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import com.denizd.substitutionplan.data.SubstUtil
|
|
||||||
import com.denizd.substitutionplan.database.SettingsRepository
|
import com.denizd.substitutionplan.database.SettingsRepository
|
||||||
import com.denizd.substitutionplan.models.Ringtone
|
import com.denizd.substitutionplan.models.Ringtone
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -25,7 +26,7 @@ internal class SettingsViewModel(application: Application) : AndroidViewModel(ap
|
|||||||
get() = repo.getBool("autoRefresh")
|
get() = repo.getBool("autoRefresh")
|
||||||
set(it) = repo.setAndApplyBool("autoRefresh", it)
|
set(it) = repo.setAndApplyBool("autoRefresh", it)
|
||||||
var shouldUseAppSorting: Boolean
|
var shouldUseAppSorting: Boolean
|
||||||
get() = repo.getBool("app_specific_sorting")
|
get() = repo.getBool("app_specific_sorting", true)
|
||||||
set(it) = repo.setAndApplyBool("app_specific_sorting", it)
|
set(it) = repo.setAndApplyBool("app_specific_sorting", it)
|
||||||
|
|
||||||
|
|
||||||
@@ -43,16 +44,17 @@ internal class SettingsViewModel(application: Application) : AndroidViewModel(ap
|
|||||||
get() = repo.getInt("themeInt")
|
get() = repo.getInt("themeInt")
|
||||||
set(it) = repo.setAndApplyInt("themeInt", it)
|
set(it) = repo.setAndApplyInt("themeInt", it)
|
||||||
|
|
||||||
val notificationChannel = repo.notificationChannel
|
@TargetApi(26)
|
||||||
|
fun getNotificationChannel() = repo.getNotificationChannel()
|
||||||
|
|
||||||
lateinit var ringtones: List<Ringtone>
|
lateinit var ringtones: List<Ringtone>
|
||||||
val ringtonesInitialised = ::ringtones.isInitialized
|
val ringtonesInitialised = ::ringtones.isInitialized
|
||||||
|
|
||||||
fun getRingtones(updateUi: () -> Unit) {
|
fun getRingtones(updateUi: () -> Unit) {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
val task = GlobalScope.async { repo.ringtones }
|
val task = async { repo.ringtones }
|
||||||
ringtones = task.await()
|
ringtones = task.await()
|
||||||
updateUi()
|
launch(Dispatchers.Main) { updateUi() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,4 +64,11 @@ internal class SettingsViewModel(application: Application) : AndroidViewModel(ap
|
|||||||
fun setAndApplyBool(key: String, value: Boolean) = repo.setAndApplyBool(key, value)
|
fun setAndApplyBool(key: String, value: Boolean) = repo.setAndApplyBool(key, value)
|
||||||
fun getString(key: String, default: String = "", ifNull: String = "") = repo.getString(key, default) ?: ifNull
|
fun getString(key: String, default: String = "", ifNull: String = "") = repo.getString(key, default) ?: ifNull
|
||||||
fun setAndApplyString(key: String, value: String) = repo.setAndApplyString(key, value)
|
fun setAndApplyString(key: String, value: String) = repo.setAndApplyString(key, value)
|
||||||
|
|
||||||
|
fun clearTimes() = repo.clearTimes()
|
||||||
|
fun forceRefresh(updateUi: (result: String, error: Boolean) -> Unit) = GlobalScope.launch {
|
||||||
|
val task = async { repo.forceRefresh() }
|
||||||
|
val result = task.await()
|
||||||
|
launch(Dispatchers.Main) { updateUi(result.first, result.second) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.app.Application
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.denizd.substitutionplan.data.Caller
|
import com.denizd.substitutionplan.data.Caller
|
||||||
import com.denizd.substitutionplan.database.SubstRepository
|
import com.denizd.substitutionplan.database.SubstRepository
|
||||||
import com.denizd.substitutionplan.models.Substitution
|
import com.denizd.substitutionplan.models.Substitution
|
||||||
@@ -37,11 +38,9 @@ internal class SubstViewModel(application: Application) : AndroidViewModel(appli
|
|||||||
return repo.getGridColumnCount(config)
|
return repo.getGridColumnCount(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refresh(updateUi: (result: String, error: Boolean) -> Unit) {
|
fun refresh(updateUi: (result: String, error: Boolean) -> Unit) = GlobalScope.launch {
|
||||||
GlobalScope.launch {
|
val task = async { repo.fetchDataOnline(Caller.SUBSTITUTION) }
|
||||||
val task = GlobalScope.async { repo.fetchDataOnline(Caller.SUBSTITUTION) }
|
val result = task.await()
|
||||||
val result = task.await()
|
launch(Dispatchers.Main) { updateUi(result.first, result.second) }
|
||||||
updateUi(result.first, result.second)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user