Refactoring, using view binding in all fragments, app now targets API 29, replaced deprecated support library PreferenceManager and Custom Tabs with AndroidX equivalents, version bump to 2.3.3

This commit is contained in:
Deniz Düzgören
2019-10-12 21:03:34 +02:00
parent f5666a20c6
commit 37cf130ba0
27 changed files with 827 additions and 801 deletions
@@ -8,7 +8,7 @@ import android.graphics.drawable.AnimatedVectorDrawable
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.preference.PreferenceManager
import androidx.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewAnimationUtils
@@ -50,11 +50,11 @@ internal class FirstTime : AppCompatActivity(R.layout.activity_first_time) {
val edit = prefs.edit()
val fab = findViewById<ExtendedFloatingActionButton>(R.id.efab)
val mainActivity = Intent(this, Main::class.java)
val nameEditText = findViewById<TextInputEditText>(R.id.txtName)
val gradeEditText = findViewById<TextInputEditText>(R.id.txtClasses)
val courseEditText = findViewById<TextInputEditText>(R.id.txtCourses)
val helpGradeButton = findViewById<ImageButton>(R.id.chipHelpClasses)
val helpCoursesButton = findViewById<ImageButton>(R.id.chipHelpCourses)
val nameEditText = findViewById<TextInputEditText>(R.id.edittext_name)
val gradeEditText = findViewById<TextInputEditText>(R.id.edittext_group)
val courseEditText = findViewById<TextInputEditText>(R.id.edittext_courses)
val helpGradeButton = findViewById<ImageButton>(R.id.button_group_help)
val helpCoursesButton = findViewById<ImageButton>(R.id.button_courses_help)
val greetingCheckBox = findViewById<CheckBox>(R.id.cbGreetings)
val notificationCheckBox = findViewById<CheckBox>(R.id.cbNotif)
val darkModeCheckBox = findViewById<CheckBox>(R.id.cbDark)
@@ -7,7 +7,7 @@ import android.graphics.drawable.AnimatedVectorDrawable
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.preference.PreferenceManager
import androidx.preference.PreferenceManager
import android.view.View
import android.view.ViewAnimationUtils
import android.view.animation.Animation
@@ -9,7 +9,7 @@ import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.preference.PreferenceManager
import android.view.*
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
@@ -20,7 +20,6 @@ import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.recyclerview.widget.RecyclerView
import com.crashlytics.android.Crashlytics
import com.denizd.substitutionplan.data.HelperFunctions
import com.denizd.substitutionplan.R
import com.denizd.substitutionplan.fragments.FoodFragment
@@ -33,6 +32,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.snackbar.Snackbar
import com.google.firebase.FirebaseApp
import java.lang.IllegalArgumentException
import java.lang.NullPointerException
import java.util.*
/**
@@ -49,173 +49,166 @@ internal class Main : AppCompatActivity(R.layout.app_bar_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
context = this
// prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs = PreferenceManager.getDefaultSharedPreferences(context)
val edit = prefs.edit()
val firstTime = Intent(context, FirstTime::class.java)
val login = Intent(context, Login::class.java)
Crashlytics.setUserIdentifier(prefs.getString("username", "") ?: "")
/**
* This cascade of if-else's allows the user to return to any point of the setup if they
* This when-cascade allows the user to return to any point of the setup if they
* decide to postpone it, e.g. logging in but not setting up their preferences. Furthermore,
* it prevents existing users from upgrading from a previous version of the app and skip the
* mandatory login
*/
if (!prefs.getBoolean("successful_login", false)) {
startActivity(login)
finish()
} else if (prefs.getBoolean("firstTime", true)) {
startActivity(firstTime)
finish()
} else {
if (!prefs.getBoolean("colourTransferred", false)) {
HelperFunctions.transferOldColourIntsToString(prefs)
edit.putBoolean("colourTransferred", true).apply()
when {
!prefs.getBoolean("successful_login", false) -> { // launch the login screen
startActivity(Intent(context, Login::class.java))
finish()
}
edit.putInt("launchDev", prefs.getInt("launchDev", 0) + 1)
edit.apply()
FirebaseApp.initializeApp(this)
pingFirebaseTopics()
val appBarLayout = findViewById<AppBarLayout>(R.id.appbarlayout)
val toolbarTxt = findViewById<TextView>(R.id.toolbarTxt)
val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav)
val contextView = findViewById<View>(R.id.coordination)
val window = this.window
/// Sets the theme explicitly to dismiss the splash screen
setTheme(R.style.AppTheme0)
/// Sets the system bar's colours according to the current Android version
val barColour = when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.M -> ContextCompat.getColor(context, R.color.legacyBlack)
Build.VERSION.SDK_INT <= Build.VERSION_CODES.P -> ContextCompat.getColor(context, R.color.colorBackground)
else -> 0
}
if (barColour != 0) {
window.navigationBarColor = barColour
window.statusBarColor = barColour
prefs.getBoolean("firstTime", true) -> { // launch the first time setup screen
startActivity(Intent(context, FirstTime::class.java))
finish()
}
else -> { // launch the app
/// Applies theming with additional workarounds for API levels 23-28 (M-P)
when (prefs.getInt("themeInt", 0)) {
0 -> {
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
if (!prefs.getBoolean("colourTransferred", false)) {
HelperFunctions.transferOldColourIntsToString(prefs)
edit.putBoolean("colourTransferred", true).apply()
}
edit.putInt("launchDev", prefs.getInt("launchDev", 0) + 1)
edit.apply()
FirebaseApp.initializeApp(this)
pingFirebaseTopics()
val appBarLayout = findViewById<AppBarLayout>(R.id.appbarlayout)
val toolbarTxt = findViewById<TextView>(R.id.toolbarTxt)
val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav)
val contextView = findViewById<View>(R.id.coordination)
val window = this.window
/// Sets the theme explicitly to dismiss the splash screen
setTheme(R.style.AppTheme0)
/// Sets the system bar's colours according to the current Android version
val barColour = when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.M -> ContextCompat.getColor(context, R.color.legacyBlack)
Build.VERSION.SDK_INT <= Build.VERSION_CODES.P -> ContextCompat.getColor(context, R.color.colorBackground)
else -> 0
}
if (barColour != 0) {
window.navigationBarColor = barColour
window.statusBarColor = barColour
}
/// Applies theming with additional workarounds for API levels 23-28 (M-P)
when (prefs.getInt("themeInt", 0)) {
0 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
if (Build.VERSION.SDK_INT in 23..28) {
@SuppressLint("InlinedApi")
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
}
2 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
else -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
/// Displays the date and time of the last refresh of the substitution plan in a snack bar
if (!prefs.getBoolean("autoRefresh", false) && prefs.getInt("firstTimeOpening", 0) != 0) {
try {
Snackbar.make(contextView, "${getString(R.string.last_updated)} ${prefs.getString("timeNew", "")}", Snackbar.LENGTH_LONG).show()
} catch (e: IllegalArgumentException) {
}
}
2 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
else -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
}
/// Displays the date and time of the last refresh of the substitution plan in a snack bar
if (!prefs.getBoolean("autoRefresh", false) && prefs.getInt("firstTimeOpening", 0) != 0) {
try {
Snackbar.make(contextView, "${getString(R.string.last_updated)} ${prefs.getString("timeNew", "")}", Snackbar.LENGTH_LONG).show()
} catch (e: IllegalArgumentException) {}
}
/**
* Retrieves the greeting string if enabled by the user, sets it empty otherwise.
* Don't set the text view of the greeting string to View.GONE, as that will mess
* with the constraints
*/
val textViewGreeting = findViewById<TextView>(R.id.text_greeting)
textViewGreeting.text = if (prefs.getBoolean("greeting", true) && (prefs.getString("username", "") ?: "").isNotEmpty()) {
getGreetingString()
} else {
""
}
/// Legacy function
if (prefs.getInt("firstTimeOpening", 0) == 0) {
edit.putInt("firstTimeOpening", prefs.getInt("firstTimeOpening", 0) + 1).apply()
}
/// Launch the user-specified fragment (general or personal plan)
val defaultFragment = if (prefs.getBoolean("defaultPersonalised", false)) {
bottomNav.selectedItemId = R.id.personal
toolbarTxt.text = personalPlanTitle()
PersonalPlanFragment()
} else {
bottomNav.selectedItemId = R.id.plan
toolbarTxt.text = getString(R.string.app_name)
GeneralPlanFragment()
}
loadFragment(defaultFragment)
/// Opens the corresponding fragment or the info dialog
bottomNav.setOnNavigationItemSelectedListener { item: MenuItem ->
var fragmentLoading = true
lateinit var fragment: Fragment
when (item.itemId) {
R.id.plan -> {
fragment = GeneralPlanFragment()
toolbarTxt.text = getString(R.string.app_name)
}
R.id.personal -> {
fragment = PersonalPlanFragment()
toolbarTxt.text = personalPlanTitle()
}
R.id.menu -> {
fragment = FoodFragment()
toolbarTxt.text = getString(R.string.food_menu)
}
R.id.openinfopanel -> {
openInfoDialog()
fragmentLoading = false
}
R.id.settings -> {
fragment = SettingsFragment()
toolbarTxt.text = getString(R.string.settings)
}
}
if (fragmentLoading) {
appBarLayout.setExpanded(true)
loadFragment(fragment)
/**
* Retrieves the greeting string if enabled by the user, sets it empty otherwise.
* Don't set the text view of the greeting string to View.GONE, as that will mess
* with the constraints
*/
val textViewGreeting = findViewById<TextView>(R.id.text_greeting)
textViewGreeting.text = if (
prefs.getBoolean("greeting", true)
&& (prefs.getString("username", "") ?: "").isNotEmpty()
) {
getGreetingString()
} else {
false
""
}
}
/// Scrolls to the top of any currently displayed fragment
bottomNav.setOnNavigationItemReselectedListener { item: MenuItem ->
when (item.itemId) {
R.id.plan, R.id.personal -> {
try {
val recyclerView = findViewById<RecyclerView>(R.id.linearRecycler)
recyclerView.post {
recyclerView.smoothScrollToPosition(0)
}
appBarLayout.setExpanded(true)
} catch (e: NullPointerException) {}
/// Legacy function
if (prefs.getInt("firstTimeOpening", 0) == 0) {
edit.putInt("firstTimeOpening", prefs.getInt("firstTimeOpening", 0) + 1).apply()
}
/// Launch the user-specified fragment (general or personal plan)
val defaultFragment = if (prefs.getBoolean("defaultPersonalised", false)) {
bottomNav.selectedItemId = R.id.personal
toolbarTxt.text = personalPlanTitle()
PersonalPlanFragment()
} else {
bottomNav.selectedItemId = R.id.plan
toolbarTxt.text = getString(R.string.app_name)
GeneralPlanFragment()
}
loadFragment(defaultFragment)
/// Opens the corresponding fragment or the info dialog
bottomNav.setOnNavigationItemSelectedListener { item: MenuItem ->
val fragment = when (item.itemId) {
R.id.plan -> {
toolbarTxt.text = getString(R.string.app_name)
GeneralPlanFragment()
}
R.id.personal -> {
toolbarTxt.text = personalPlanTitle()
PersonalPlanFragment()
}
R.id.menu -> {
toolbarTxt.text = getString(R.string.food_menu)
FoodFragment()
}
R.id.openinfopanel -> {
openInfoDialog()
null
}
else -> {
toolbarTxt.text = getString(R.string.settings)
SettingsFragment()
}
}
R.id.menu -> {
try {
val recyclerView = findViewById<RecyclerView>(R.id.linear_food)
recyclerView.post {
recyclerView.smoothScrollToPosition(0)
}
appBarLayout.setExpanded(true)
} catch (e: NullPointerException) {}
if (fragment == null) {
false
} else {
appBarLayout.setExpanded(true)
loadFragment(fragment)
}
R.id.settings -> {
try {
val nsv = findViewById<NestedScrollView>(R.id.nsvsettings)
nsv.post {
nsv.smoothScrollTo(0, 0)
}
/// Scrolls to the top of any currently displayed fragment
bottomNav.setOnNavigationItemReselectedListener { item: MenuItem ->
when (item.itemId) {
R.id.plan, R.id.personal, R.id.menu -> {
try {
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.post {
recyclerView.smoothScrollToPosition(0)
}
appBarLayout.setExpanded(true)
} catch (e: NullPointerException) {
}
appBarLayout.setExpanded(true)
} catch (e: NullPointerException) {}
}
R.id.settings -> {
try {
val nsv = findViewById<NestedScrollView>(R.id.settings_scroll_view)
nsv.post {
nsv.smoothScrollTo(0, 0)
}
appBarLayout.setExpanded(true)
} catch (e: NullPointerException) {
}
}
}
}
}
@@ -18,16 +18,16 @@ import com.google.android.material.card.MaterialCardView
* @param colours a list of all courses and their associated colour
* @param onClickListener a reference to an OnClickListener
*/
internal class ColourAdapter(private var colours: List<Colour>, private val onClickListener: OnClickListener) : RecyclerView.Adapter<ColourAdapter.ColourViewHolder>() {
internal class ColourAdapter(private var colours: List<Colour>, private val onClickListener: OnColourClickListener) : RecyclerView.Adapter<ColourAdapter.ColourViewHolder>() {
internal class ColourViewHolder(view: View, private val clickListener: OnClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
internal class ColourViewHolder(view: View, private val clickListener: OnColourClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
val title: TextView = view.findViewById(R.id.item_text)
var image: ImageView = view.findViewById(R.id.item_image)
val titleNoLang: TextView = view.findViewById(R.id.item_text_no_lang)
var titleNoLang: String = ""
val cardView: MaterialCardView = view.findViewById(R.id.cardView)
init { view.setOnClickListener(this) }
override fun onClick(v: View?) { clickListener.onClick(adapterPosition, title.text.toString(), titleNoLang.text.toString()) }
override fun onClick(v: View?) { clickListener.onColourClick(adapterPosition, title.text.toString(), titleNoLang) }
}
@@ -40,7 +40,7 @@ internal class ColourAdapter(private var colours: List<Colour>, private val onCl
val currentItem = colours[position]
holder.title.text = currentItem.title
holder.titleNoLang.text = currentItem.titleNoLang
holder.titleNoLang = currentItem.titleNoLang
holder.image.setImageDrawable(ContextCompat.getDrawable(holder.image.context, currentItem.icon))
val colour = if (currentItem.colour != 0) {
currentItem.colour
@@ -60,7 +60,7 @@ internal class ColourAdapter(private var colours: List<Colour>, private val onCl
override fun getItemCount(): Int = colours.size
internal interface OnClickListener {
fun onClick(position: Int, title: String, titleNoLang: String)
internal interface OnColourClickListener {
fun onColourClick(position: Int, title: String, titleNoLang: String)
}
}
@@ -3,6 +3,7 @@ package com.denizd.substitutionplan.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
@@ -17,15 +18,16 @@ import com.google.android.material.card.MaterialCardView
* @param ringtones a list of all ringtones including their URI
* @param onClickListener a reference to an OnClickListener to set the ringtone
*/
internal class RingtoneAdapter(private val ringtones: List<Ringtone>, private val onClickListener: OnClickListener) : RecyclerView.Adapter<RingtoneAdapter.RingtoneViewHolder>() {
internal class RingtoneAdapter(private val ringtones: List<Ringtone>, private val onClickListener: OnRingtoneClickListener) : RecyclerView.Adapter<RingtoneAdapter.RingtoneViewHolder>() {
internal class RingtoneViewHolder(view: View, private val clickListener: OnClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
internal class RingtoneViewHolder(view: View, private val clickListener: OnRingtoneClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
val name: TextView = view.findViewById(R.id.item_text)
val uri: TextView = view.findViewById(R.id.item_text_no_lang)
val imageView: ImageView = view.findViewById(R.id.item_image)
var uri: String = ""
val cardView: MaterialCardView = view.findViewById(R.id.cardView)
init { view.setOnClickListener(this) }
override fun onClick(v: View?) { clickListener.onRingtoneClick(adapterPosition, name.text.toString(), uri.text.toString()) }
override fun onClick(v: View?) { clickListener.onRingtoneClick(adapterPosition, name.text.toString(), uri) }
}
@@ -38,13 +40,14 @@ internal class RingtoneAdapter(private val ringtones: List<Ringtone>, private va
val currentItem = ringtones[position]
holder.name.text = currentItem.name
holder.uri.text = currentItem.uri
holder.cardView.setCardBackgroundColor(ContextCompat.getColor(holder.cardView.context, R.color.colorBackground))
holder.imageView.visibility = View.GONE
holder.uri = currentItem.uri
holder.cardView.setCardBackgroundColor(ContextCompat.getColor(holder.cardView.context, R.color.colorBackgroundLight))
}
override fun getItemCount(): Int = ringtones.size
internal interface OnClickListener {
internal interface OnRingtoneClickListener {
fun onRingtoneClick(position: Int, name: String, uri: String)
}
}
@@ -7,7 +7,7 @@ import android.media.RingtoneManager
import android.net.Uri
import android.os.AsyncTask
import android.os.Build
import android.preference.PreferenceManager
import androidx.preference.PreferenceManager
import android.view.View
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
@@ -33,14 +33,22 @@ import kotlin.collections.ArrayList
* @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
* @param forced if true, times for food menu and substitution table will be overwritten
* with empty values to enable a forced refresh
*/
internal class DataFetcher(isPlan: Boolean, isMenu: Boolean, isJobService: Boolean, context: Context, application: Application, parentView: View?, forced: Boolean) : AsyncTask<Void, Void, Void>() {
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
@@ -105,7 +113,7 @@ internal class DataFetcher(isPlan: Boolean, isMenu: Boolean, isJobService: Boole
override fun onPostExecute(result: Void?) {
mView.get()?.let {
try {
it.findViewById<SwipeRefreshLayout>(R.id.pullToRefresh).isRefreshing = false
it.findViewById<SwipeRefreshLayout>(R.id.swipe_refresh_layout).isRefreshing = false
} catch (ignored: Exception) {
}
}
@@ -47,7 +47,7 @@ internal object HelperFunctions {
"lavender", "carnation", "brown2", "pureBlack")
/**
* A lowercased list of all phrases used to describe that a course has been cancelled. Expand
* A lower-cased list of all phrases used to describe that a course has been cancelled. Expand
* this if necessary. No further code changes required if this is expanded
*/
val cancellations = arrayOf("eigenverantwortliches arbeiten", "entfall", "entfällt", "fällt aus", "freisetzung", "vtr. ohne lehrer")
@@ -80,6 +80,7 @@ internal object HelperFunctions {
R.color.bgBrown2,
R.color.bgPureBlack
)
const val notificationChannelId = "general"
fun getColourForString(name: String): Int {
@@ -314,8 +315,8 @@ internal object HelperFunctions {
}
/**
* Assigns an integer to a given group used to sort the substitution plan in SQL.
* The ranking is stored as 'priority' in the database
* Assigns an integer to a given group used to sort the substitution plan when retrieving from
* Room database. The ranking is stored as 'priority' in the database
*
* @param group the group string that should be assigned a ranking
* @param isPSA used to determine a PSA, assigns lowest value if true, therefore putting
@@ -377,7 +378,7 @@ internal object HelperFunctions {
private fun getPrefValue(prefs: SharedPreferences, type: String, key: String): Any {
return when (type) {
"string" -> prefs.getString(key, "")
"string" -> prefs.getString(key, "") ?: ""
"int" -> prefs.getInt(key, 0)
"bool" -> prefs.getBoolean(key, false)
else -> ""
@@ -404,6 +405,8 @@ internal object HelperFunctions {
localPrefKeys.add("card${languageIndependentCourses[i]}")
localPrefTypes.add("string")
}
// if bothered, replace this with an API 29-friendly solution
// if not, who cares really, this is not even accessible without special codes
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
val file = File(dir, "avh_plan_data.xml")
val out = OutputStreamWriter(FileOutputStream(file, false))
@@ -414,9 +417,11 @@ internal object HelperFunctions {
"\n\t<value>${getPrefValue(prefs, localPrefTypes[i], localPrefKeys[i])}</value>"
}
input += "\n</items>"
out.write(input)
out.flush()
out.close()
out.apply {
write(input)
flush()
close()
}
Toast.makeText(context, context.getString(R.string.success), Toast.LENGTH_LONG).show()
} else {
Toast.makeText(context, context.getString(R.string.permission_denied), Toast.LENGTH_LONG).show()
@@ -8,7 +8,7 @@ import android.webkit.WebViewClient
import java.lang.IllegalStateException
/**
* Class that extends WebViewClient to provide a function that checks the webpage's cookies to
* Class that extends WebViewClient to provide a function that checks the website's cookies to
* verify a login
*
* @param successListener a reference to the OnLoginSuccessListener implemented in an activity
@@ -66,4 +66,4 @@ internal class LoginWebViewClient(private val successListener: OnLoginSuccessLis
*/
fun onLoginSucceeded(success: Boolean)
}
}
}
@@ -26,8 +26,7 @@ internal class FoodViewModel(application: Application) : AndroidViewModel(applic
isJobService = false,
context = app,
application = app,
parentView = rootView,
forced = false
parentView = rootView
).execute()
}
}
@@ -28,8 +28,7 @@ internal class SubstViewModel(application: Application) : AndroidViewModel(appli
isJobService = false,
context = app,
application = app,
parentView = rootView,
forced = false
parentView = rootView
).execute()
}
}
@@ -3,42 +3,48 @@ package com.denizd.substitutionplan.fragments
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.denizd.substitutionplan.*
import com.denizd.substitutionplan.adapters.FoodAdapter
import com.denizd.substitutionplan.database.FoodViewModel
import com.denizd.substitutionplan.databinding.FoodLayoutBinding
import com.denizd.substitutionplan.models.Food
internal class FoodFragment : Fragment(R.layout.food_layout) {
internal class FoodFragment : Fragment() {
private val foodArrayList = ArrayList<Food>()
private val mAdapter = FoodAdapter(foodArrayList)
private lateinit var mContext: Context
private lateinit var prefs: SharedPreferences
private lateinit var recyclerView: RecyclerView
private lateinit var foodViewModel: FoodViewModel
private lateinit var binding: FoodLayoutBinding
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
prefs = PreferenceManager.getDefaultSharedPreferences(mContext)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FoodLayoutBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val pullToRefresh = view.findViewById<SwipeRefreshLayout>(R.id.pullToRefresh)
recyclerView = view.findViewById(R.id.linear_food)
recyclerView.hasFixedSize()
recyclerView.layoutManager = GridLayoutManager(mContext, 1)
recyclerView.adapter = mAdapter
binding.recyclerView.apply {
hasFixedSize()
layoutManager = GridLayoutManager(mContext, 1)
adapter = mAdapter
}
foodViewModel = ViewModelProviders.of(this).get(FoodViewModel::class.java)
foodViewModel.allFoods?.observe(this, Observer<List<Food>> { foodList ->
@@ -46,16 +52,16 @@ internal class FoodFragment : Fragment(R.layout.food_layout) {
for (item in foodList) {
foodArrayList.add(item)
}
recyclerView.scheduleLayoutAnimation()
binding.recyclerView.scheduleLayoutAnimation()
mAdapter.setFood(foodArrayList)
})
if (prefs.getBoolean("autoRefresh", false)) {
foodViewModel.refresh(swipeRefreshLayout = pullToRefresh, rootView = view.rootView)
foodViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView)
}
pullToRefresh.setOnRefreshListener {
foodViewModel.refresh(swipeRefreshLayout = pullToRefresh, rootView = view.rootView)
binding.swipeRefreshLayout.setOnRefreshListener {
foodViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView)
}
}
}
@@ -12,7 +12,7 @@ internal class GeneralPlanFragment : PlanFragment() {
substitutionPlan?.observe(this, Observer<List<Substitution>> { substitutions ->
mAdapter.setSubst(substitutions)
recyclerView.scheduleLayoutAnimation()
binding.recyclerView.scheduleLayoutAnimation()
})
}
}
@@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import com.denizd.substitutionplan.data.HelperFunctions
import com.denizd.substitutionplan.R
import com.denizd.substitutionplan.models.Substitution
internal class PersonalPlanFragment : PlanFragment() {
@@ -12,20 +11,16 @@ internal class PersonalPlanFragment : PlanFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
personalPlanEmptyEmoticon = view.findViewById(R.id.smileydown)
personalPlanEmptyText = view.findViewById(R.id.smileydowntext)
personalPlanEmptyLayout = view.findViewById(R.id.linearsmiley)
val coursePreference = prefs.getString("courses", "") ?: ""
val classPreference = prefs.getString("classes", "") ?: ""
substitutionPlan?.observe(this, Observer<List<Substitution>> { substitutions ->
planCardList.clear()
personalPlanEmptyEmoticon.visibility = View.GONE
personalPlanEmptyText.visibility = View.GONE
personalPlanEmptyLayout.visibility = View.GONE
binding.emptyPlanEmoticon.visibility = View.GONE
binding.emptyPlanText.visibility = View.GONE
binding.emptyPlanLayout.visibility = View.GONE
isPersonalPlanEmpty = true
recyclerView.visibility = View.VISIBLE
binding.recyclerView.visibility = View.VISIBLE
substitutions.filter { substItem ->
HelperFunctions.checkPersonalSubstitutions(
@@ -38,15 +33,15 @@ internal class PersonalPlanFragment : PlanFragment() {
planCardList.add(substItem)
}
isPersonalPlanEmpty = (planCardList.size == 1 && planCardList[0].date.substring(0, 3) == "psa") || planCardList.isEmpty()
recyclerView.scheduleLayoutAnimation()
binding.recyclerView.scheduleLayoutAnimation()
mAdapter.setSubst(planCardList)
handler.postDelayed({
if (isPersonalPlanEmpty) {
personalPlanEmptyEmoticon.visibility = View.VISIBLE
personalPlanEmptyText.visibility = View.VISIBLE
personalPlanEmptyLayout.visibility = View.VISIBLE
personalPlanEmptyLayout.scheduleLayoutAnimation()
binding.emptyPlanEmoticon.visibility = View.VISIBLE
binding.emptyPlanText.visibility = View.VISIBLE
binding.emptyPlanLayout.visibility = View.VISIBLE
binding.emptyPlanLayout.scheduleLayoutAnimation()
}
}, 64)
})
@@ -5,46 +5,48 @@ import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Bundle
import android.os.Handler
import android.preference.PreferenceManager
import androidx.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.denizd.substitutionplan.*
import com.denizd.substitutionplan.R
import com.denizd.substitutionplan.adapters.SubstitutionAdapter
import com.denizd.substitutionplan.database.SubstViewModel
import com.denizd.substitutionplan.databinding.PlanBinding
import com.denizd.substitutionplan.models.Substitution
import kotlin.collections.ArrayList
internal open class PlanFragment : Fragment(R.layout.plan) {
internal lateinit var recyclerView: RecyclerView
internal open class PlanFragment : Fragment() {
internal lateinit var mAdapter: SubstitutionAdapter
internal var planCardList = ArrayList<Substitution>()
private lateinit var substViewModel: SubstViewModel
private lateinit var mContext: Context
internal lateinit var prefs: SharedPreferences
internal lateinit var personalPlanEmptyEmoticon: TextView
internal lateinit var personalPlanEmptyText: TextView
internal lateinit var personalPlanEmptyLayout: LinearLayout
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)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = PlanBinding.inflate(inflater, container, false)
return binding.root
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
recyclerView.layoutManager = GridLayoutManager(mContext, getGridColumnCount(newConfig))
binding.recyclerView.layoutManager = GridLayoutManager(mContext, getGridColumnCount(newConfig))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -56,24 +58,24 @@ internal open class PlanFragment : Fragment(R.layout.plan) {
substViewModel.allSubstitutionsOriginal
}
val pullToRefresh = view.findViewById<SwipeRefreshLayout>(R.id.pullToRefresh)
recyclerView = view.findViewById(R.id.linearRecycler)
recyclerView.hasFixedSize()
recyclerView.layoutManager = GridLayoutManager(mContext, getGridColumnCount(resources.configuration))
mAdapter = SubstitutionAdapter(planCardList, prefs)
recyclerView.adapter = mAdapter
binding.recyclerView.apply {
hasFixedSize()
layoutManager = GridLayoutManager(mContext, getGridColumnCount(resources.configuration))
adapter = mAdapter
}
if (prefs.getInt("firstTimeOpening", 0) == 1) {
substViewModel.refresh(swipeRefreshLayout = pullToRefresh, rootView = view.rootView, refreshMenu = true)
substViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView, refreshMenu = true)
prefs.edit().putInt("firstTimeOpening", 2).apply()
}
if (prefs.getBoolean("autoRefresh", false)) {
substViewModel.refresh(swipeRefreshLayout = pullToRefresh, rootView = view.rootView, refreshMenu = false)
substViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView, refreshMenu = false)
}
pullToRefresh.setOnRefreshListener {
substViewModel.refresh(swipeRefreshLayout = pullToRefresh, rootView = view.rootView, refreshMenu = false)
binding.swipeRefreshLayout.setOnRefreshListener {
substViewModel.refresh(swipeRefreshLayout = binding.swipeRefreshLayout, rootView = view.rootView, refreshMenu = false)
}
}
@@ -10,11 +10,13 @@ import android.media.RingtoneManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.preference.PreferenceManager
import android.provider.Settings
import android.text.Editable
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
@@ -30,27 +32,33 @@ import com.denizd.substitutionplan.adapters.RingtoneAdapter
import com.denizd.substitutionplan.data.DataFetcher
import com.denizd.substitutionplan.data.HelperFunctions
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.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
import com.google.firebase.messaging.FirebaseMessaging
import kotlin.collections.ArrayList
internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnClickListener,
CompoundButton.OnCheckedChangeListener, ColourAdapter.OnClickListener,
RingtoneAdapter.OnClickListener {
internal class SettingsFragment : Fragment(), View.OnClickListener,
CompoundButton.OnCheckedChangeListener, ColourAdapter.OnColourClickListener,
RingtoneAdapter.OnRingtoneClickListener {
private lateinit var mContext: Context
private lateinit var prefs: SharedPreferences
private val builder = CustomTabsIntent.Builder()
private val customTabsIntent = builder.build() as CustomTabsIntent
private val customTabsIntent = CustomTabsIntent.Builder().build() as CustomTabsIntent
private var window: Window? = null
private lateinit var colourRecycler: RecyclerView
private lateinit var ringtoneCustomiserDialog: AlertDialog
private lateinit var currentRingtone: TextView
private var longPressed = false
private var courseHelpClicks = 0
private val ringtones: List<Ringtone> by lazy {
getRingtones()
}
private lateinit var colourRecycler: RecyclerView
private lateinit var ringtoneDialog: AlertDialog
private lateinit var binding: ContentSettingsBinding
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -59,164 +67,268 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
window = activity?.window
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = ContentSettingsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val nameEditText = view.findViewById<TextInputEditText>(R.id.txtName)
val gradeEditText = view.findViewById<TextInputEditText>(R.id.txtClasses)
val courseEditText = view.findViewById<TextInputEditText>(R.id.txtCourses)
binding.apply {
val thisFragment = this@SettingsFragment
val greetingSwitch = view.findViewById<Switch>(R.id.switchDisableGreeting)
val notificationSwitch = view.findViewById<Switch>(R.id.switchNotifications)
val defaultPlanSwitch = view.findViewById<Switch>(R.id.switchDefaultPlan)
val autoRefreshSwitch = view.findViewById<Switch>(R.id.switchAutoRefresh)
val versionNumberText = view.findViewById<TextView>(R.id.txtVersionTwo)
val helpGradeButton = view.findViewById<ImageButton>(R.id.chipHelpClasses)
val helpCoursesButton = view.findViewById<ImageButton>(R.id.chipHelpCourses)
val colourCustomisationButton = view.findViewById<LinearLayout>(R.id.btnCustomiseColours)
val versionButton = view.findViewById<LinearLayout>(R.id.btnVersion)
currentRingtone = view.findViewById(R.id.txtCustomiseRingtone2)
setRingtoneText()
val forceRefreshButton = view.findViewById<LinearLayout>(R.id.btnForceRefresh)
// Set text
setRingtoneText()
val colourTextTitle = view.findViewById<TextView>(R.id.txtCustomiseColours1)
val colourTextDesc = view.findViewById<TextView>(R.id.txtCustomiseColours2)
textAppVersionDesc.text = BuildConfig.VERSION_NAME
greetingSwitch.setOnCheckedChangeListener(this)
notificationSwitch.setOnCheckedChangeListener(this)
defaultPlanSwitch.setOnCheckedChangeListener(this)
autoRefreshSwitch.setOnCheckedChangeListener(this)
helpCoursesButton.setOnClickListener(this)
helpGradeButton.setOnClickListener(this)
view.findViewById<ImageButton>(R.id.chip_help_ordering).setOnClickListener(this)
colourCustomisationButton.setOnClickListener(this)
colourCustomisationButton.setOnLongClickListener {
if (!longPressed) {
colourTextTitle.text = getString(R.string.made_by_deniz)
colourTextDesc.text = getString(R.string.thanks_for_using)
} else {
colourTextTitle.text = getString(R.string.customise_colours_title)
colourTextDesc.text = getString(R.string.customise_colours_desc)
// Set on click listeners
buttonCustomiseColours.setOnClickListener(thisFragment)
buttonNotificationsHelp.setOnClickListener(thisFragment)
buttonCustomiseRingtone.setOnClickListener(thisFragment)
buttonGroupHelp.setOnClickListener(thisFragment)
buttonCoursesHelp.setOnClickListener(thisFragment)
buttonOrderHelp.setOnClickListener(thisFragment)
buttonForcedRefresh.setOnClickListener(thisFragment)
buttonVisitWebsite.setOnClickListener(thisFragment)
buttonLicences.setOnClickListener(thisFragment)
buttonVersion.setOnClickListener(thisFragment)
// Set on long click listeners
buttonCustomiseColours.setOnLongClickListener {
if (!longPressed) {
textCustomiseColoursTitle.text = getString(R.string.made_by_deniz)
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)
}
longPressed = !longPressed
true
}
longPressed = !longPressed
true
}
view.findViewById<LinearLayout>(R.id.btnNoNotif).setOnClickListener(this)
view.findViewById<LinearLayout>(R.id.btnCustomiseRingtone).setOnClickListener(this)
view.findViewById<LinearLayout>(R.id.btnWebsite).setOnClickListener(this)
view.findViewById<LinearLayout>(R.id.btnLicences).setOnClickListener(this)
versionButton.setOnClickListener(this)
forceRefreshButton.setOnClickListener(this)
forceRefreshButton.setOnLongClickListener {
prefs.edit().putString("timeNew", "").putString("newFoodTime", "").apply()
makeToast(mContext.getString(R.string.force_refresh_cleared_times))
true
}
val darkModeDropDown = view.findViewById<AutoCompleteTextView>(R.id.darkModeDropDownText)
val darkModeList = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
R.array.themes_pre_q
} else {
R.array.themes
}
buttonForcedRefresh.setOnLongClickListener {
prefs.edit().putString("timeNew", "").putString("newFoodTime", "").apply()
makeToast(mContext.getString(R.string.force_refresh_cleared_times))
true
}
val darkModeAdapter = ArrayAdapter.createFromResource(
mContext,
darkModeList,
R.layout.dropdown_item
)
darkModeAdapter.setDropDownViewResource(R.layout.dropdown_item)
darkModeDropDown.setAdapter(darkModeAdapter)
buttonVersion.setOnLongClickListener {
debugMenu()
}
darkModeDropDown.setText(darkModeDropDown.adapter.getItem(prefs.getInt("themeInt", 0)).toString(), false)
// Set on checked change listeners
switchGreeting.setOnCheckedChangeListener(thisFragment)
switchNotifications.setOnCheckedChangeListener(thisFragment)
switchPersonalisedPlan.setOnCheckedChangeListener(thisFragment)
switchAutoRefresh.setOnCheckedChangeListener(thisFragment)
darkModeDropDown.setOnItemClickListener { _, _, position, _ ->
prefs.edit().putInt("themeInt", position).apply()
// Set checked
switchGreeting.isChecked = prefs.getBoolean("greeting", true)
switchNotifications.isChecked = prefs.getBoolean("notif", false)
val bottomNav = view.rootView.findViewById<BottomNavigationView>(R.id.bottom_nav)
when (position) {
0 -> {
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)
switchPersonalisedPlan.isChecked = prefs.getBoolean("defaultPersonalised", false)
switchAutoRefresh.isChecked = prefs.getBoolean("autoRefresh", false)
// Set edit text (+ listeners)
edittextName.setText(prefs.getString("username", ""))
edittextName.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
prefs.edit().putString("username", edittextName.text.toString().trim()).apply()
}
})
edittextGroup.setText(prefs.getString("classes", ""))
edittextGroup.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
prefs.edit().putString("classes", edittextGroup.text.toString()).apply()
}
})
edittextCourses.setText(prefs.getString("courses", ""))
edittextCourses.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
prefs.edit().putString("courses", edittextCourses.text.toString()).apply()
}
})
// Set spinners (+ listeners)
val darkModeList = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
R.array.themes_pre_q
} else {
R.array.themes
}
val darkModeAdapter = ArrayAdapter.createFromResource(
mContext,
darkModeList,
R.layout.dropdown_item
)
autocompleteDarkMode.setAdapter(darkModeAdapter)
autocompleteDarkMode.setText(autocompleteDarkMode.adapter.getItem(prefs.getInt("themeInt", 0)).toString(), false)
autocompleteDarkMode.setOnItemClickListener { _, _, position, _ ->
prefs.edit().putInt("themeInt", position).apply()
when (position) {
0 -> {
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)
}
}
2 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) // only accessible on API 29+
else -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
window?.navigationBarColor = ContextCompat.getColor(mContext, R.color.colorBackground)
}
}
}
2 -> { // only accessible on Android 10 (and above)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
else -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
window?.navigationBarColor = ContextCompat.getColor(mContext, R.color.colorBackground)
}
view.rootView.findViewById<BottomNavigationView>(R.id.bottom_nav).selectedItemId = if (prefs.getBoolean("defaultPersonalised", false)) {
R.id.personal
} else {
R.id.plan
}
}
if (prefs.getBoolean("defaultPersonalised", false)) {
bottomNav.selectedItemId = R.id.personal
} else {
bottomNav.selectedItemId = R.id.plan
val selectedOrderingOption = if (prefs.getBoolean("app_specific_sorting", true)) 0 else 1
val orderingArrayAdapter = ArrayAdapter.createFromResource(
mContext,
R.array.ordering_systems,
R.layout.dropdown_item
)
autocompleteOrder.setAdapter(orderingArrayAdapter)
autocompleteOrder.setText(autocompleteOrder.adapter.getItem(selectedOrderingOption).toString(), false)
autocompleteOrder.setOnItemClickListener { _, _, position, _ ->
prefs.edit().putBoolean("app_specific_sorting", position == 0).apply()
}
}
val orderingDropDownText = view.findViewById<AutoCompleteTextView>(R.id.order_drop_down_text)
val orderingArrayAdapter = ArrayAdapter.createFromResource(
mContext,
R.array.ordering_systems,
R.layout.dropdown_item
)
orderingArrayAdapter.setDropDownViewResource(R.layout.dropdown_item)
orderingDropDownText.setAdapter(orderingArrayAdapter)
val selectedOrderingOption = if (prefs.getBoolean("app_specific_sorting", true)) 0 else 1
orderingDropDownText.setText(orderingDropDownText.adapter.getItem(selectedOrderingOption).toString(), false)
orderingDropDownText.setOnItemClickListener { _, _, position, _ ->
prefs.edit().putBoolean("app_specific_sorting", position == 0).apply()
}
greetingSwitch.isChecked = prefs.getBoolean("greeting", true)
notificationSwitch.isChecked = prefs.getBoolean("notif", false)
defaultPlanSwitch.isChecked = prefs.getBoolean("defaultPersonalised", false)
autoRefreshSwitch.isChecked = prefs.getBoolean("autoRefresh", false)
versionNumberText.text = BuildConfig.VERSION_NAME
nameEditText.setText(prefs.getString("username", ""))
nameEditText.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
prefs.edit().putString("username", nameEditText.text.toString().trim()).apply()
}
})
gradeEditText.setText(prefs.getString("classes", ""))
gradeEditText.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
prefs.edit().putString("classes", gradeEditText.text.toString()).apply()
}
})
courseEditText.setText(prefs.getString("courses", ""))
courseEditText.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
prefs.edit().putString("courses", courseEditText.text.toString()).apply()
}
})
versionButton.setOnLongClickListener {
debugMenu()
}
}
// Functions for handling touch events in this fragment
override fun onClick(v: View?) {
when (v?.id) {
R.id.button_customise_colours -> createColourDialog()
R.id.button_notifications_help -> createDialog(mContext.getString(R.string.no_notifications_title), mContext.getString(R.string.no_notifications_dialog_text))
R.id.button_customise_ringtone -> createRingtoneDialog()
R.id.button_group_help -> createDialog(getString(R.string.grade_help_dialog_title), getString(
R.string.grade_help_dialog_text
))
R.id.button_courses_help -> {
if (courseHelpClicks < 11) {
courseHelpClicks += 1
createDialog(getString(R.string.courses_help_dialog_title), getString(R.string.courses_help_dialog_text))
} else {
courseHelpClicks = 0
try {
makeToast(String(Character.toChars(0x2764)))
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.youtube.com/watch?v=Jc2xfYuLWgE")) // 'Freak'
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setPackage("com.google.android.youtube")
startActivity(intent)
} catch (e: ActivityNotFoundException) {
makeToast(getString(R.string.youtube_not_found))
}
}
}
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()
}
R.id.button_visit_website -> {
try {
customTabsIntent.launchUrl(mContext, Uri.parse("http://307.joomla.schule.bremen.de"))
} catch (e: ActivityNotFoundException) {
makeToast(getString(R.string.chrome_not_found))
}
}
R.id.button_licences -> {
createDialog(mContext.getString(R.string.licences_title), licences)
}
}
}
override fun onCheckedChanged(v: CompoundButton?, isChecked: Boolean) {
if (v?.isPressed == true) {
when (v.id) {
R.id.switch_greeting -> {
prefs.edit().putBoolean("greeting", isChecked).apply()
}
R.id.switch_notifications -> {
prefs.edit().putBoolean("notif", isChecked).apply()
if (isChecked) {
subscribeToTopic(Topic.ANDROID)
} else {
unsubscribeFromTopic(Topic.ANDROID)
}
}
R.id.switch_personalised_plan -> {
prefs.edit().putBoolean("defaultPersonalised", isChecked).apply()
}
R.id.switch_auto_refresh -> {
prefs.edit().putBoolean("autoRefresh", isChecked).apply()
}
}
}
}
// Functions for handling touch events for recycler view elements
override fun onColourClick(position: Int, title: String, titleNoLang: String) {
val colourPickerBuilder = AlertDialog.Builder(mContext, R.style.AlertDialog)
val pickerDialogView = View.inflate(mContext, R.layout.empty_dialog, null)
val pickerTitleText = pickerDialogView.findViewById<TextView>(R.id.empty_textviewtitle)
val pickerLayout = pickerDialogView.findViewById<LinearLayout>(R.id.empty_linearlayout)
pickerTitleText.text = title
val picker = View.inflate(mContext, R.layout.bg_colour_picker, null)
pickerLayout.addView(picker)
colourPickerBuilder.setView(pickerDialogView)
val colourPickerDialog: AlertDialog = colourPickerBuilder.create()
val buttons = intArrayOf(R.id.def, R.id.red, R.id.orange, R.id.yellow, R.id.green,
R.id.teal, R.id.cyan, R.id.blue, R.id.purple, R.id.pink, R.id.brown, R.id.grey,
R.id.pureWhite, R.id.salmon, R.id.tangerine, R.id.banana, R.id.flora, R.id.spindrift,
R.id.sky, R.id.orchid, R.id.lavender, R.id.carnation, R.id.brown2, R.id.pureBlack)
val colours = HelperFunctions.colourNames
for (i2 in buttons.indices) {
picker.findViewById<MaterialButton>(buttons[i2]).setOnClickListener {
prefs.edit().putString("card$titleNoLang", colours[i2]).apply()
val recyclerViewState = colourRecycler.layoutManager?.onSaveInstanceState()
colourRecycler.adapter = ColourAdapter(getColourList(), this)
colourRecycler.layoutManager?.onRestoreInstanceState(recyclerViewState)
colourPickerDialog.dismiss()
}
}
colourPickerDialog.show()
}
override fun onRingtoneClick(position: Int, name: String, uri: String) {
prefs.edit().apply {
putString("ringtoneName", name)
putString("ringtoneUri", uri)
}.apply()
setRingtoneText()
ringtoneDialog.dismiss()
}
// Functions for handling dialog creation
private fun createDialog(title: String, text: String) {
val alertDialog = AlertDialog.Builder(mContext,
R.style.AlertDialog
@@ -233,7 +345,7 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
val dialogView = View.inflate(mContext, R.layout.recycler_dialog, null)
val titleText = dialogView.findViewById<TextView>(R.id.empty_textviewtitle)
titleText.text = getString(R.string.customise_colours_title)
colourRecycler = dialogView.findViewById(R.id.recyclerView)
val colourRecycler = dialogView.findViewById<RecyclerView>(R.id.recyclerView)
colourRecycler.apply {
hasFixedSize()
layoutManager = GridLayoutManager(mContext, 1)
@@ -244,48 +356,6 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
colourCustomisationDialog.show()
}
private fun getColourList(): ArrayList<Colour> {
val colours = ArrayList<Colour>()
val coursesNoLang = HelperFunctions.languageIndependentCourses
val courses = arrayOf(getString(R.string.course_deu), getString(
R.string.course_eng
), getString(R.string.course_fra), getString(R.string.course_spa), getString(
R.string.course_lat
), getString(R.string.course_tue), getString(R.string.course_chi), getString(
R.string.course_kun
), getString(R.string.course_mus), getString(R.string.course_dar), getString(
R.string.course_geg
), getString(R.string.course_ges), getString(R.string.course_pol), getString(
R.string.course_phi
), getString(R.string.course_rel), getString(R.string.course_mat), getString(
R.string.course_bio
), getString(R.string.course_che), getString(R.string.course_phy), getString(
R.string.course_inf
), getString(R.string.course_spo), getString(R.string.course_gll), getString(
R.string.course_wat
), getString(R.string.course_foer), getString(R.string.course_wp))
val coursesIcons = intArrayOf(R.drawable.ic_german, R.drawable.ic_english, R.drawable.ic_french,
R.drawable.ic_spanish, R.drawable.ic_latin, R.drawable.ic_turkish, R.drawable.ic_chinese,
R.drawable.ic_arts, R.drawable.ic_music, R.drawable.ic_drama, R.drawable.ic_geography,
R.drawable.ic_history, R.drawable.ic_politics, R.drawable.ic_philosophy, R.drawable.ic_religion,
R.drawable.ic_maths, R.drawable.ic_biology, R.drawable.ic_chemistry, R.drawable.ic_physics,
R.drawable.ic_compsci, R.drawable.ic_pe, R.drawable.ic_gll, R.drawable.ic_wat,
R.drawable.ic_help, R.drawable.ic_pencil
)
for (i in coursesNoLang.indices) {
colours.add(
Colour(
courses[i],
coursesNoLang[i],
coursesIcons[i],
HelperFunctions.getColourForString(prefs.getString("card${coursesNoLang[i]}", "") ?: "")
)
)
}
return colours
}
private fun createRingtoneDialog() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
HelperFunctions.getNotificationChannel(mContext, prefs)
@@ -305,48 +375,15 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
recycler.apply {
hasFixedSize()
layoutManager = GridLayoutManager(mContext, 1)
adapter = RingtoneAdapter(ringtones, this@SettingsFragment)
}
ringtoneCustomiserDialog = ringtoneCustomiserBuilder.setView(dialogView).create()
ringtoneCustomiserDialog.show()
ringtoneDialog = ringtoneCustomiserBuilder.setView(dialogView).create()
ringtoneDialog.show()
recycler.postDelayed({
val ringtones = getRingtones().toList()
recycler.adapter = RingtoneAdapter(ringtones, this)
}, 100)
}
}
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
}
private fun setRingtoneText() {
currentRingtone.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mContext.getString(R.string.pick_ringtone_desc_o)
} else {
val tone = if ((prefs.getString("ringtoneName", "") ?: "").isNotEmpty()) {
prefs.getString("ringtoneName", "")
} else {
mContext.getString(R.string.default_ringtone)
}
mContext.getString(R.string.pick_ringtone_desc, tone)
// recycler.postDelayed({
// recycler.
// }, 100)
}
}
@@ -357,10 +394,10 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
val dialogButton = dialogView.findViewById<Button>(R.id.dialog_button)
dialogView.findViewById<TextView>(R.id.textviewtitle).text = getString(
R.string.experimental_menu_dialog_title
R.string.experimental_menu_dialog_title
)
dialogView.findViewById<TextView>(R.id.dialogtext).text = getString(
R.string.experimental_menu_dialog_text
R.string.experimental_menu_dialog_text
)
dialogButton.setOnClickListener {
when (dialogEditText.text.toString()) {
@@ -387,15 +424,6 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
alertDialogDev.setView(devDialogView)
alertDialogDev.show()
}
"2019-06-06" -> {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.youtube.com/watch?v=Jc2xfYuLWgE")) // 'Freak'
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setPackage("com.google.android.youtube")
startActivity(intent)
} catch (e: ActivityNotFoundException) {
makeToast(getString(R.string.youtube_not_found))
}
}
"_LOGIN" -> {
prefs.edit().putBoolean("successful_login", false).apply()
makeToast("Login flag cleared")
@@ -441,6 +469,112 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
return true
}
// Functions for retrieving data
private fun getColourList(): ArrayList<Colour> {
val colours = ArrayList<Colour>()
val coursesNoLang = HelperFunctions.languageIndependentCourses
val courses = arrayOf(
getString(R.string.course_deu),
getString(R.string.course_eng),
getString(R.string.course_fra),
getString(R.string.course_spa),
getString(R.string.course_lat),
getString(R.string.course_tue),
getString(R.string.course_chi),
getString(R.string.course_kun),
getString(R.string.course_mus),
getString(R.string.course_dar),
getString(R.string.course_geg),
getString(R.string.course_ges),
getString(R.string.course_pol),
getString(R.string.course_phi),
getString(R.string.course_rel),
getString(R.string.course_mat),
getString(R.string.course_bio),
getString(R.string.course_che),
getString(R.string.course_phy),
getString(R.string.course_inf),
getString(R.string.course_spo),
getString(R.string.course_gll),
getString(R.string.course_wat),
getString(R.string.course_foer),
getString(R.string.course_wp)
)
val coursesIcons = intArrayOf(
R.drawable.ic_german,
R.drawable.ic_english,
R.drawable.ic_french,
R.drawable.ic_spanish,
R.drawable.ic_latin,
R.drawable.ic_turkish,
R.drawable.ic_chinese,
R.drawable.ic_arts,
R.drawable.ic_music,
R.drawable.ic_drama,
R.drawable.ic_geography,
R.drawable.ic_history,
R.drawable.ic_politics,
R.drawable.ic_philosophy,
R.drawable.ic_religion,
R.drawable.ic_maths,
R.drawable.ic_biology,
R.drawable.ic_chemistry,
R.drawable.ic_physics,
R.drawable.ic_compsci,
R.drawable.ic_pe,
R.drawable.ic_gll,
R.drawable.ic_wat,
R.drawable.ic_help,
R.drawable.ic_pencil
)
for (i in coursesNoLang.indices) {
colours.add(
Colour(
courses[i],
coursesNoLang[i],
coursesIcons[i],
HelperFunctions.getColourForString(prefs.getString("card${coursesNoLang[i]}", "") ?: "")
)
)
}
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) {
mContext.getString(R.string.pick_ringtone_desc_o)
} else {
val tone = if ((prefs.getString("ringtoneName", "") ?: "").isNotEmpty()) {
prefs.getString("ringtoneName", "")
} else {
mContext.getString(R.string.default_ringtone)
}
mContext.getString(R.string.pick_ringtone_desc, tone)
}
}
private fun makeToast(text: String) {
Toast.makeText(mContext, text, Toast.LENGTH_LONG).show()
}
@@ -460,111 +594,7 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
private fun subscribeToTopic(topic: Topic) = FirebaseMessaging.getInstance().subscribeToTopic(topic.tag)
private fun unsubscribeFromTopic(topic: Topic) = FirebaseMessaging.getInstance().unsubscribeFromTopic(topic.tag)
override fun onClick(v: View?) {
when (v?.id) {
R.id.chipHelpCourses -> createDialog(getString(R.string.courses_help_dialog_title), getString(R.string.courses_help_dialog_text))
R.id.chipHelpClasses -> createDialog(getString(R.string.grade_help_dialog_title), getString(
R.string.grade_help_dialog_text
))
R.id.chip_help_ordering -> createDialog(getString(R.string.ordering_systems_dialog_title), getString(R.string.ordering_systems_dialog_text))
R.id.btnCustomiseColours -> createColourDialog()
R.id.btnNoNotif -> createDialog(mContext.getString(R.string.no_notifications_title), mContext.getString(R.string.no_notifications_dialog_text))
R.id.btnCustomiseRingtone -> createRingtoneDialog()
R.id.btnWebsite -> {
try {
customTabsIntent.launchUrl(mContext, Uri.parse("http://307.joomla.schule.bremen.de"))
} catch (e: ActivityNotFoundException) {
makeToast(getString(R.string.chrome_not_found))
}
}
R.id.btnLicences -> {
createDialog(mContext.getString(R.string.licences_title), licences)
}
R.id.btnForceRefresh -> {
DataFetcher(
isPlan = true,
isMenu = true,
isJobService = false,
context = mContext,
application = activity!!.application,
parentView = view!!.rootView,
forced = true
).execute()
}
}
}
override fun onRingtoneClick(position: Int, name: String, uri: String) {
prefs.edit().apply {
putString("ringtoneName", name)
putString("ringtoneUri", uri)
}.apply()
setRingtoneText()
ringtoneCustomiserDialog.dismiss()
}
/**
* OnClick method for colour customisation dialog
* @param position the position of the clicked item
* @param title the title of the clicked course
* @param titleNoLang the language-independent string used for storing the course's colour in
* Shared Preferences
*/
override fun onClick(position: Int, title: String, titleNoLang: String) {
val colourPickerBuilder = AlertDialog.Builder(mContext, R.style.AlertDialog)
val pickerDialogView = View.inflate(mContext, R.layout.empty_dialog, null)
val pickerTitleText = pickerDialogView.findViewById<TextView>(R.id.empty_textviewtitle)
val pickerLayout = pickerDialogView.findViewById<LinearLayout>(R.id.empty_linearlayout)
pickerTitleText.text = title
val picker = View.inflate(mContext, R.layout.bg_colour_picker, null)
pickerLayout.addView(picker)
colourPickerBuilder.setView(pickerDialogView)
val colourPickerDialog: AlertDialog = colourPickerBuilder.create()
val buttons = intArrayOf(R.id.def, R.id.red, R.id.orange, R.id.yellow, R.id.green,
R.id.teal, R.id.cyan, R.id.blue, R.id.purple, R.id.pink, R.id.brown, R.id.grey,
R.id.pureWhite, R.id.salmon, R.id.tangerine, R.id.banana, R.id.flora, R.id.spindrift,
R.id.sky, R.id.orchid, R.id.lavender, R.id.carnation, R.id.brown2, R.id.pureBlack)
val colours = HelperFunctions.colourNames
for (i2 in buttons.indices) {
picker.findViewById<MaterialButton>(buttons[i2]).setOnClickListener {
prefs.edit().putString("card$titleNoLang", colours[i2]).apply()
val recyclerViewState = colourRecycler.layoutManager?.onSaveInstanceState()
colourRecycler.adapter = ColourAdapter(getColourList(), this)
colourRecycler.layoutManager?.onRestoreInstanceState(recyclerViewState)
colourPickerDialog.dismiss()
}
}
colourPickerDialog.show()
}
override fun onCheckedChanged(v: CompoundButton?, isChecked: Boolean) {
if (v?.isPressed == true) {
when (v.id) {
R.id.switchDisableGreeting -> {
prefs.edit().putBoolean("greeting", isChecked).apply()
}
R.id.switchNotifications -> {
prefs.edit().putBoolean("notif", isChecked).apply()
if (isChecked) {
subscribeToTopic(Topic.ANDROID)
} else {
unsubscribeFromTopic(Topic.ANDROID)
}
}
R.id.switchDefaultPlan -> {
prefs.edit().putBoolean("defaultPersonalised", isChecked).apply()
}
R.id.switchAutoRefresh -> {
prefs.edit().putBoolean("autoRefresh", isChecked).apply()
}
}
}
}
/// Below this point follow string literals that I didn't bother putting in /res/values
// 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" +
@@ -1,5 +1,6 @@
package com.denizd.substitutionplan.services
import android.annotation.SuppressLint
import com.denizd.substitutionplan.data.DataFetcher
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
@@ -8,6 +9,7 @@ import com.google.firebase.messaging.RemoteMessage
* This class handles Firebase data notifications and triggers a refresh of the substitution plan
* as well as the food menu upon arrival
*/
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
internal class FBNotificationService : FirebaseMessagingService() {
override fun onMessageReceived(p0: RemoteMessage) {
@@ -17,8 +19,7 @@ internal class FBNotificationService : FirebaseMessagingService() {
isJobService = true,
context = applicationContext,
application = application,
parentView = null,
forced = false
parentView = null
).execute()
}
}
@@ -2,7 +2,7 @@ package com.denizd.substitutionplan.services
import android.app.job.JobParameters
import android.app.job.JobService
import android.preference.PreferenceManager
import androidx.preference.PreferenceManager
import com.denizd.substitutionplan.data.Topic
import com.google.firebase.FirebaseApp
import com.google.firebase.messaging.FirebaseMessaging