Login page now disallows visiting a domain other than the school's login page to prevent a successful login from another domain allowing access to the app's content

This commit is contained in:
Deniz Düzgören
2019-09-23 17:23:59 +02:00
parent 088aa90aa4
commit fd40115549
21 changed files with 328 additions and 295 deletions
@@ -11,29 +11,33 @@ import com.denizd.substitutionplan.models.Colour
import com.denizd.substitutionplan.R
import com.google.android.material.card.MaterialCardView
internal class ColourAdapter(private var mColours: List<Colour>, onClickListener: OnClickListener) : RecyclerView.Adapter<ColourAdapter.ColourViewHolder>() {
/**
* Adapter class used for displaying the courses as well as their associated colours in the
* customisation dialog. Used in SettingsFragment.kt
*
* @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>() {
private val mOnClickListener = onClickListener
class ColourViewHolder(view: View, clickListener: OnClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
internal class ColourViewHolder(view: View, private val clickListener: OnClickListener) : 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)
val cardView: MaterialCardView = view.findViewById(R.id.cardView)
private val mClickListener = clickListener
init { view.setOnClickListener(this) }
override fun onClick(v: View?) { mClickListener.onClick(adapterPosition, title.text.toString(), titleNoLang.text.toString()) }
override fun onClick(v: View?) { clickListener.onClick(adapterPosition, title.text.toString(), titleNoLang.text.toString()) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColourViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return ColourViewHolder(v, mOnClickListener)
return ColourViewHolder(v, onClickListener)
}
override fun onBindViewHolder(holder: ColourViewHolder, position: Int) {
val currentItem = mColours[position]
val currentItem = colours[position]
holder.title.text = currentItem.title
holder.titleNoLang.text = currentItem.titleNoLang
@@ -54,7 +58,7 @@ internal class ColourAdapter(private var mColours: List<Colour>, onClickListener
holder.title.setTextColor(ContextCompat.getColor(holder.title.context, textColor))
}
override fun getItemCount(): Int = mColours.size
override fun getItemCount(): Int = colours.size
internal interface OnClickListener {
fun onClick(position: Int, title: String, titleNoLang: String)
@@ -5,39 +5,38 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import java.util.ArrayList
import androidx.recyclerview.widget.RecyclerView
import com.denizd.substitutionplan.models.Food
import com.denizd.substitutionplan.R
internal class FoodAdapter(food: ArrayList<Food>) : RecyclerView.Adapter<FoodAdapter.CardViewHolder>() {
private var mFood: List<Food>? = null
/**
* Adapter class used in FoodFragment.kt to display the food menu to the user
*
* @param foods a list of all food entries on the menu
*/
internal class FoodAdapter(private var foods: List<Food>) : RecyclerView.Adapter<FoodAdapter.FoodViewHolder>() {
class CardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var mFood: TextView = itemView.findViewById(R.id.cardInfoText)
internal class FoodViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var text: TextView = itemView.findViewById(R.id.cardInfoText)
}
init {
mFood = food
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FoodViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.just_a_card, parent, false)
return CardViewHolder(v)
return FoodViewHolder(v)
}
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
val currentItem = mFood!![position]
override fun onBindViewHolder(holder: FoodViewHolder, position: Int) {
val currentItem = foods[position]
holder.mFood.text = currentItem.food
holder.text.text = currentItem.food
}
override fun getItemCount(): Int {
return mFood!!.size
return foods.size
}
fun setFood(food: List<Food>) {
mFood = food
fun setFood(foods: List<Food>) {
this.foods = foods
notifyDataSetChanged()
}
}
@@ -10,40 +10,39 @@ import com.denizd.substitutionplan.R
import com.denizd.substitutionplan.models.Ringtone
import com.google.android.material.card.MaterialCardView
internal class RingtoneAdapter(private var _ringtones: List<Ringtone>, onClickListener: OnClickListener) : RecyclerView.Adapter<RingtoneAdapter.RingtoneViewHolder>() {
/**
* Adapter class used for displaying all available notification ringtones to the user up to
* Android version 7 (Nougat). Used in SettingsFragment.kt
*
* @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>() {
private val _onClickListener = onClickListener
class RingtoneViewHolder(view: View, clickListener: OnClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
internal class RingtoneViewHolder(view: View, private val clickListener: OnClickListener) : 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 cardView: MaterialCardView = view.findViewById(R.id.cardView)
private val _clickListener = clickListener
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.text.toString()) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RingtoneViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return RingtoneViewHolder(
v,
_onClickListener
)
return RingtoneViewHolder(v, onClickListener)
}
override fun onBindViewHolder(holder: RingtoneViewHolder, position: Int) {
val currentItem = _ringtones[position]
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.cardView.setCardBackgroundColor(ContextCompat.getColor(holder.cardView.context, R.color.colorBackground))
}
override fun getItemCount(): Int = _ringtones.size
override fun getItemCount(): Int = ringtones.size
internal interface OnClickListener {
fun onRingtoneClick(position: Int, name: String, uri: String)
@@ -1,6 +1,7 @@
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
@@ -16,18 +17,20 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.denizd.substitutionplan.data.HelperFunctions
import com.denizd.substitutionplan.R
import com.denizd.substitutionplan.models.Subst
import com.denizd.substitutionplan.models.Substitution
import com.google.android.material.card.MaterialCardView
import java.util.*
internal class CardAdapter(private var mSubst: List<Subst>, private val prefs: SharedPreferences) : RecyclerView.Adapter<CardAdapter.CardViewHolder>() {
/**
* Adapter class for the Recycler View used to display the substitution plan in
* 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
*/
internal class SubstitutionAdapter(private var substitutions: List<Substitution>, private val prefs: SharedPreferences) : RecyclerView.Adapter<SubstitutionAdapter.CardViewHolder>() {
private var colour = 0
private var colourString = ""
private var colorCheck = ""
private val cancellations = arrayOf("eigenverantwortliches arbeiten", "entfall", "entfällt", "fällt aus", "freisetzung", "vtr. ohne lehrer")
class CardViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
internal class CardViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
var iconView: ImageView = view.findViewById(R.id.iconView)
var group: TextView = view.findViewById(R.id.group)
@@ -38,16 +41,16 @@ internal class CardAdapter(private var mSubst: List<Subst>, private val prefs: S
var additional: TextView = view.findViewById(R.id.additional)
var teacher: TextView = view.findViewById(R.id.teacher)
var spacer: TextView = view.findViewById(R.id.spacer)
var card: MaterialCardView = view.findViewById(R.id.planCard)
val context: Context = card.context
init { view.setOnClickListener(this) }
override fun onClick(v: View?) {
if (date.text.toString().length > 7 && date.text.toString().substring(3, 7) == "http") {
try {
CustomTabsIntent.Builder().build().launchUrl(card.context,
Uri.parse(date.text.toString().substring(3)))
CustomTabsIntent.Builder().build().launchUrl(context,
Uri.parse(date.text.substring(3)))
} catch (e: ActivityNotFoundException) {
Toast.makeText(card.context, card.context.getString(R.string.chrome_not_found), Toast.LENGTH_LONG).show()
Toast.makeText(context, context.getString(R.string.chrome_not_found), Toast.LENGTH_LONG).show()
}
}
}
@@ -59,12 +62,13 @@ internal class CardAdapter(private var mSubst: List<Subst>, private val prefs: S
}
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
val currentItem = mSubst[position]
val currentItem = substitutions[position]
var psa = false
val strings = arrayOf(SpannableString(currentItem.group), SpannableString(currentItem.time),
SpannableString(currentItem.course), SpannableString(currentItem.room),
SpannableString(currentItem.teacher))
var cardBackgroundColour = 0
val cardBackgroundColour: Int
var colour = 0
for (string in strings) {
val questionMarkIndex = string.indexOf("?")
@@ -76,11 +80,11 @@ internal class CardAdapter(private var mSubst: List<Subst>, private val prefs: S
val add = currentItem.additional.toLowerCase(Locale.ROOT)
val type = currentItem.type.toLowerCase(Locale.ROOT)
if (add.isNotEmpty()) {
if (HelperFunctions.checkStringForArray(add, cancellations)) {
if (HelperFunctions.checkStringForArray(add, HelperFunctions.cancellations)) {
strikeThrough(strings)
}
} else {
if (HelperFunctions.checkStringForArray(type, cancellations)) {
if (HelperFunctions.checkStringForArray(type, HelperFunctions.cancellations)) {
strikeThrough(strings)
}
}
@@ -100,7 +104,7 @@ internal class CardAdapter(private var mSubst: List<Subst>, private val prefs: S
cardBackgroundColour = R.color.colorAccent
View.GONE
} else {
colourString = getColourString(holder.course.text.toString())
val colourString = HelperFunctions.getColourString(holder.course.text.toString())
val colourPrefsInt = if (colourString.isNotEmpty()) {
prefs.getString("card$colourString", "") ?: ""
} else {
@@ -150,58 +154,13 @@ internal class CardAdapter(private var mSubst: List<Subst>, private val prefs: S
holder.teacher.setTextColor(textColor)
}
override fun getItemCount(): Int = mSubst.size
override fun getItemCount(): Int = substitutions.size
fun setSubst(subst: List<Subst>) {
mSubst = subst
fun setSubst(substitutions: List<Substitution>) {
this.substitutions = substitutions
notifyDataSetChanged()
}
private fun getColourString(course: String): String {
return try {
colorCheck = course.toLowerCase(Locale.ROOT).substring(0, 3)
when (colorCheck) {
"deu", "dep", "daz", "fda" -> "German"
"mat", "map" -> "Maths"
"eng", "enp", "ena" -> "English"
"spo", "spp", "spth" -> "PhysEd"
"pol", "pop" -> "Politics"
"dar", "dap" -> "Theatre"
"phy", "php" -> "Physics"
"bio", "bip", "nw1", "nw2", "nw3", "nw4" -> "Biology"
"che", "chp" -> "Chemistry"
"phi", "psp" -> "Philosophy"
"laa", "laf", "lat" -> "Latin"
"spa", "spf" -> "Spanish"
"fra", "frf", "frz" -> "French"
"inf" -> "CompSci"
"ges" -> "History"
"rel" -> "Religion"
"geg" -> "Geography"
"kun" -> "Arts"
"mus" -> "Music"
"tue" -> "Turkish"
"chi" -> "Chinese"
"gll" -> "GLL"
"wat" -> "WAT"
"för" -> "Forder"
"met", "wpb" -> "WP"
else -> ""
}
} catch (e: StringIndexOutOfBoundsException) {
try {
colorCheck = course.toLowerCase(Locale.ROOT).substring(0, 2)
when (colorCheck) {
"nw" -> "Biology"
"wp" -> "WP"
else -> ""
}
} catch (e2: StringIndexOutOfBoundsException) {
""
}
}
}
private fun strikeThrough(strings: Array<SpannableString>) {
for (i in 2..4) { strings[i].setSpan(StrikethroughSpan(), 0, strings[i].length, 0) }
}
@@ -18,7 +18,7 @@ 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.Subst
import com.denizd.substitutionplan.models.Substitution
import com.google.android.material.snackbar.Snackbar
import org.jsoup.Jsoup
import java.lang.ref.WeakReference
@@ -57,7 +57,7 @@ internal class DataFetcher(isPlan: Boolean, isMenu: Boolean, isJobService: Boole
private val edit = prefs.edit()
private var currentTime = ""
private var currentFoodTime = ""
private var substUrl = "https://djd4rkn355.github.io/subst.html"
private var substUrl = "https://djd4rkn355.github.io/avh_substitutions.html"
private var foodUrl = "https://djd4rkn355.github.io/food.html"
override fun doInBackground(vararg params: Void?): Void? {
@@ -149,7 +149,7 @@ internal class DataFetcher(isPlan: Boolean, isMenu: Boolean, isJobService: Boole
if (currentTime != prefs.getString("timeNew", "")) {
val rows = doc.select("tr")
val paragraphs = doc.select("p")
val substArray = ArrayList<Subst>()
val substArray = ArrayList<Substitution>()
val coursePreference = prefs.getString("courses", "") ?: ""
val classPreference = prefs.getString("classes", "") ?: ""
@@ -169,7 +169,7 @@ internal class DataFetcher(isPlan: Boolean, isMenu: Boolean, isJobService: Boole
val row = rows[i]
val cols = row.select("th")
val subst = Subst(
val subst = Substitution(
group = cols[0].text(),
date = cols[1].text(),
time = cols[2].text(),
@@ -237,11 +237,12 @@ internal class DataFetcher(isPlan: Boolean, isMenu: Boolean, isJobService: Boole
val notification = NotificationCompat.Builder(context, HelperFunctions.notificationChannelId)
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(notificationLayout)
.setSmallIcon(R.drawable.ic_avhlogo)
.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", "") ?: "")
@@ -20,7 +20,7 @@ import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.fragment.app.FragmentActivity
import com.denizd.substitutionplan.R
import com.denizd.substitutionplan.models.Subst
import com.denizd.substitutionplan.models.Substitution
import java.util.*
import java.io.File
import java.io.FileOutputStream
@@ -45,6 +45,12 @@ internal object HelperFunctions {
"brown", "grey", "pureWhite", "salmon", "tangerine", "banana", "flora", "spindrift", "sky", "orchid",
"lavender", "carnation", "brown2", "pureBlack")
/**
* A list of all phrases used to describe that a course has been cancelled. Expand this
* if necessary. No further code changes required if this is expanded
*/
val cancellations = arrayOf("eigenverantwortliches arbeiten", "entfall", "entfällt", "fällt aus", "freisetzung", "vtr. ohne lehrer")
private val colourIntegers = intArrayOf(
0,
R.color.bgRed,
@@ -135,6 +141,60 @@ internal object HelperFunctions {
}
}
/**
* This function returns a string that is used to get the colour set for a specific course
* from Shared Preferences
*
* @param course the course abbreviation as a string
*
* @return the string used to store the course's colour in Shared Preferences
*/
fun getColourString(course: String): String {
var colorCheck: String
return try {
colorCheck = course.toLowerCase(Locale.ROOT).substring(0, 3)
when (colorCheck) {
"deu", "dep", "daz", "fda" -> "German"
"mat", "map" -> "Maths"
"eng", "enp", "ena" -> "English"
"spo", "spp", "spth" -> "PhysEd"
"pol", "pop" -> "Politics"
"dar", "dap" -> "Theatre"
"phy", "php" -> "Physics"
"bio", "bip", "nw1", "nw2", "nw3", "nw4" -> "Biology"
"che", "chp" -> "Chemistry"
"phi", "psp" -> "Philosophy"
"laa", "laf", "lat" -> "Latin"
"spa", "spf" -> "Spanish"
"fra", "frf", "frz" -> "French"
"inf" -> "CompSci"
"ges" -> "History"
"rel" -> "Religion"
"geg" -> "Geography"
"kun" -> "Arts"
"mus" -> "Music"
"tue" -> "Turkish"
"chi" -> "Chinese"
"gll" -> "GLL"
"wat" -> "WAT"
"för" -> "Forder"
"met", "wpb" -> "WP"
else -> ""
}
} catch (e: StringIndexOutOfBoundsException) {
try {
colorCheck = course.toLowerCase(Locale.ROOT).substring(0, 2)
when (colorCheck) {
"nw" -> "Biology"
"wp" -> "WP"
else -> ""
}
} catch (e2: StringIndexOutOfBoundsException) {
""
}
}
}
/**
* This function served as a way to transfer a deprecated method of storing user-defined
* course colours to a new method. It is not required otherwise, but still remains in
@@ -163,17 +223,17 @@ internal object HelperFunctions {
* 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 subst the substitution item
* @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(subst: Subst, coursePreference: String, classPreference: String, psa: Boolean): Boolean {
val group = subst.group
val course = subst.course
if (psa && subst.date.isNotEmpty() && subst.date.substring(0, 3) == "psa") {
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()) {
@@ -1,8 +1,11 @@
package com.denizd.substitutionplan.data
import android.graphics.Bitmap
import android.util.Log
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import java.lang.IllegalStateException
/**
* Class that extends WebViewClient to provide a function that checks the webpage's cookies to
@@ -10,27 +13,40 @@ import android.webkit.WebViewClient
*
* @param successListener a reference to the OnLoginSuccessListener implemented in an activity
*/
internal class LoginWebViewClient(successListener: OnLoginSuccessListener) : WebViewClient() {
internal class LoginWebViewClient(private val successListener: OnLoginSuccessListener) : WebViewClient() {
private val mSuccessListener = successListener
/**
* onPageStarted has been overridden to prevent users from reaching a domain different from
* the school's login page
*/
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
if (url != "https://307.joomla.schule.bremen.de/index.php/component/users/profile?Itemid=171"
&& url != "https://307.joomla.schule.bremen.de/index.php/component/users/#top") {
reloadLoginPage(webView = view)
}
}
/**
* OnPageFinished has been overridden to check if the cookie "joomla_user_state=logged_in"
* exists, and returns true to the OnLoginSuccessListener
*
* @param view a reference to the WebView
* @param url a reference to the url of the WebView
*/
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
view?.scrollY = -4000
view?.scrollY = -10_000
val cookies = CookieManager.getInstance().getCookie(url)
if (cookies.contains("joomla_user_state=logged_in")) {
mSuccessListener.onLoginSucceeded(true)
successListener.onLoginSucceeded(true)
}
}
private fun reloadLoginPage(webView: WebView?) {
webView?.loadUrl("https://307.joomla.schule.bremen.de/index.php/component/users/#top")
}
/**
* The interface that provides onLoginSucceeded to the activity or fragment that implements it
*/
@@ -3,23 +3,23 @@ package com.denizd.substitutionplan.database
import androidx.lifecycle.LiveData
import androidx.room.*
import com.denizd.substitutionplan.models.Food
import com.denizd.substitutionplan.models.Subst
import com.denizd.substitutionplan.models.Substitution
@Dao
internal interface SubstDao {
// @get:Query("SELECT * FROM subst_table ORDER BY date ASC, `group` ASC, time ASC, priority DESC")
@get:Query("SELECT * FROM subst_table ORDER BY priority DESC")
val allSubst: LiveData<List<Subst>>
val allSubstitutions: LiveData<List<Substitution>>
@Insert
fun insertSubst(subst: Subst)
fun insertSubst(substitution: Substitution)
@Update
fun updateSubst(subst: Subst)
fun updateSubst(substitution: Substitution)
@Delete
fun deleteSubst(subst: Subst)
fun deleteSubst(substitution: Substitution)
@Query("DELETE FROM subst_table")
fun deleteAllSubst()
@@ -7,9 +7,9 @@ import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.denizd.substitutionplan.models.Food
import com.denizd.substitutionplan.models.Subst
import com.denizd.substitutionplan.models.Substitution
@Database(entities = [Subst::class, Food::class], version = 7, exportSchema = false)
@Database(entities = [Substitution::class, Food::class], version = 7, exportSchema = false)
internal abstract class SubstDatabase : RoomDatabase() {
abstract fun substDao(): SubstDao
@@ -4,32 +4,32 @@ import android.app.Application
import android.os.AsyncTask
import androidx.lifecycle.LiveData
import com.denizd.substitutionplan.models.Subst
import com.denizd.substitutionplan.models.Substitution
internal class SubstRepository(application: Application) {
private val substDao: SubstDao?
val allSubst: LiveData<List<Subst>>?
val allSubstitutions: LiveData<List<Substitution>>?
init {
val database =
SubstDatabase.getInstance(application)
substDao = database?.substDao()
allSubst = substDao?.allSubst
allSubstitutions = substDao?.allSubstitutions
}
fun insert(subst: Subst) {
InsertSubstAsync(substDao).execute(subst)
fun insert(substitution: Substitution) {
InsertSubstAsync(substDao).execute(substitution)
}
fun deleteAllSubst() {
DeleteAllSubstAsync(substDao).execute()
}
class InsertSubstAsync(substDao: SubstDao?) : AsyncTask<Subst, Void, Void>() {
class InsertSubstAsync(substDao: SubstDao?) : AsyncTask<Substitution, Void, Void>() {
private val mSubstDao = substDao
override fun doInBackground(vararg substs: Subst): Void? {
mSubstDao?.insertSubst(substs[0])
override fun doInBackground(vararg substitutions: Substitution): Void? {
mSubstDao?.insertSubst(substitutions[0])
return null
}
}
@@ -6,16 +6,16 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.denizd.substitutionplan.data.DataFetcher
import com.denizd.substitutionplan.models.Subst
import com.denizd.substitutionplan.models.Substitution
internal class SubstViewModel(application: Application) : AndroidViewModel(application) {
private val repository: SubstRepository = SubstRepository(application)
val allSubst: LiveData<List<Subst>>?
val allSubstitutions: LiveData<List<Substitution>>?
private val app = application
init {
allSubst = repository.allSubst
allSubstitutions = repository.allSubstitutions
}
fun refresh(swipeRefreshLayout: SwipeRefreshLayout, rootView: View, refreshMenu: Boolean) {
@@ -35,18 +35,11 @@ internal class FoodFragment : Fragment(R.layout.food_layout) {
super.onViewCreated(view, savedInstanceState)
val pullToRefresh = view.findViewById<SwipeRefreshLayout>(R.id.pullToRefresh)
try {
recyclerView = view.findViewById(R.id.linear_food)
recyclerView.hasFixedSize()
recyclerView.layoutManager = GridLayoutManager(mContext, 1)
recyclerView.adapter = mAdapter
try {
recyclerView.removeAllViews()
} catch (ignored: NullPointerException) {}
} catch (ignored: NullPointerException) {}
foodViewModel = ViewModelProviders.of(this).get(FoodViewModel::class.java)
foodViewModel.allFoods?.observe(this, Observer<List<Food>> { foodList ->
foodArrayList.clear()
@@ -3,14 +3,14 @@ package com.denizd.substitutionplan.fragments
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import com.denizd.substitutionplan.models.Subst
import com.denizd.substitutionplan.models.Substitution
internal class GeneralPlanFragment : PlanFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
substViewModel.allSubst?.observe(this, Observer<List<Subst>> { substitutions ->
substViewModel.allSubstitutions?.observe(this, Observer<List<Substitution>> { substitutions ->
mAdapter.setSubst(substitutions)
recyclerView.scheduleLayoutAnimation()
})
@@ -5,7 +5,7 @@ import android.view.View
import androidx.lifecycle.Observer
import com.denizd.substitutionplan.data.HelperFunctions
import com.denizd.substitutionplan.R
import com.denizd.substitutionplan.models.Subst
import com.denizd.substitutionplan.models.Substitution
internal class PersonalPlanFragment : PlanFragment() {
@@ -19,7 +19,7 @@ internal class PersonalPlanFragment : PlanFragment() {
val coursePreference = prefs.getString("courses", "") ?: ""
val classPreference = prefs.getString("classes", "") ?: ""
substViewModel.allSubst?.observe(this, Observer<List<Subst>> { substitutions ->
substViewModel.allSubstitutions?.observe(this, Observer<List<Substitution>> { substitutions ->
planCardList.clear()
personalPlanEmptyEmoticon.visibility = View.GONE
personalPlanEmptyText.visibility = View.GONE
@@ -15,15 +15,15 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.denizd.substitutionplan.*
import com.denizd.substitutionplan.adapters.CardAdapter
import com.denizd.substitutionplan.adapters.SubstitutionAdapter
import com.denizd.substitutionplan.database.SubstViewModel
import com.denizd.substitutionplan.models.Subst
import com.denizd.substitutionplan.models.Substitution
import kotlin.collections.ArrayList
internal open class PlanFragment : Fragment(R.layout.plan) {
lateinit var recyclerView: RecyclerView
lateinit var mAdapter: CardAdapter
var planCardList = ArrayList<Subst>()
lateinit var mAdapter: SubstitutionAdapter
var planCardList = ArrayList<Substitution>()
lateinit var substViewModel: SubstViewModel
private lateinit var mContext: Context
lateinit var prefs: SharedPreferences
@@ -53,7 +53,7 @@ internal open class PlanFragment : Fragment(R.layout.plan) {
recyclerView = view.findViewById(R.id.linearRecycler)
recyclerView.hasFixedSize()
recyclerView.layoutManager = GridLayoutManager(mContext, getGridColumnCount(resources.configuration))
mAdapter = CardAdapter(planCardList, prefs)
mAdapter = SubstitutionAdapter(planCardList, prefs)
recyclerView.adapter = mAdapter
if (prefs.getInt("firstTimeOpening", 0) == 1) {
@@ -188,69 +188,6 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
}
}
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.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.btnTerms -> {
createDialog("Terms & Conditions", termsAndConditions)
}
R.id.btnPrivacyP -> {
createDialog("Privacy Policy", privacyPolicy)
}
R.id.btnForceRefresh -> {
DataFetcher(
isPlan = true,
isMenu = true,
isJobService = false,
context = mContext,
application = activity!!.application,
parentView = view!!.rootView,
forced = true
).execute()
}
}
}
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()
}
}
}
}
private fun createDialog(title: String, text: String) {
val alertDialog = AlertDialog.Builder(mContext,
R.style.AlertDialog
@@ -262,7 +199,7 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
}
private fun createColourDialog() {
val colourCustomiserBuilder = AlertDialog.Builder(mContext, R.style.AlertDialog)
val colourCustomisationDialogBuilder = AlertDialog.Builder(mContext, R.style.AlertDialog)
val dialogView = View.inflate(mContext, R.layout.recycler_dialog, null)
val titleText = dialogView.findViewById<TextView>(R.id.empty_textviewtitle)
@@ -273,9 +210,9 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
layoutManager = GridLayoutManager(mContext, 1)
adapter = ColourAdapter(getColourList(), this@SettingsFragment)
}
colourCustomiserBuilder.setView(dialogView)
val colourCustomiserDialog = colourCustomiserBuilder.create()
colourCustomiserDialog.show()
colourCustomisationDialogBuilder.setView(dialogView)
val colourCustomisationDialog = colourCustomisationDialogBuilder.create()
colourCustomisationDialog.show()
}
private fun getColourList(): ArrayList<Colour> {
@@ -371,15 +308,6 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
return alarms
}
override fun onRingtoneClick(position: Int, name: String, uri: String) {
prefs.edit().apply {
putString("ringtoneName", name)
putString("ringtoneUri", uri)
}.apply()
setRingtoneText()
ringtoneCustomiserDialog.dismiss()
}
private fun setRingtoneText() {
currentRingtone.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mContext.getString(R.string.pick_ringtone_desc_o)
@@ -393,36 +321,6 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
}
}
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()
}
private fun debugMenu(): Boolean {
val alertDialog = AlertDialog.Builder(mContext, R.style.AlertDialog)
val dialogView = View.inflate(mContext, R.layout.edittext_dialog, null)
@@ -437,16 +335,14 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
)
dialogButton.setOnClickListener {
when (dialogEditText.text.toString()) {
"_DIAGNOSTICS" -> {
"_STATISTICS" -> {
val alertDialogDev = AlertDialog.Builder(mContext, R.style.AlertDialog)
val devDialogView = View.inflate(mContext, R.layout.diagnostics_dialog, null)
val devDialogText = devDialogView.findViewById<TextView>(R.id.dialogtext)
val resetLaunchBtn = devDialogView.findViewById<Button>(R.id.btnResetLaunch)
val resetNotificationBtn = devDialogView.findViewById<Button>(R.id.btnResetNotif)
devDialogView.findViewById<TextView>(R.id.textviewtitle).text = getString(
R.string.diagnostics_dialog_title
)
devDialogView.findViewById<TextView>(R.id.textviewtitle).text = getString(R.string.statistics_dialog_title)
devDialogText.text = getDiagnosticsText()
resetLaunchBtn.setOnClickListener {
@@ -462,15 +358,19 @@ internal class SettingsFragment : Fragment(R.layout.content_settings), View.OnCl
alertDialogDev.setView(devDialogView)
alertDialogDev.show()
}
"2018-04-20" -> {
"2019-06-06" -> {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.youtube.com/watch?v=Jc2xfYuLWgE")) // Freak
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")
}
"_FIRSTTIME" -> {
prefs.edit().putBoolean("firstTime", true).apply()
makeToast("First time flag cleared")
@@ -531,6 +431,115 @@ 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.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.btnTerms -> {
createDialog("Terms & Conditions", termsAndConditions)
}
R.id.btnPrivacyP -> {
createDialog("Privacy Policy", privacyPolicy)
}
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()
}
}
}
}
private val licences = "Libraries:" +
"\n • jsoup HTML parser © 2009-2018 Jonathan Hedley, licensed under the open source MIT Licence" +
"\n\nFont:" +
@@ -4,7 +4,7 @@ import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "subst_table")
internal data class Subst(
internal data class Substitution(
val group: String,
val date: String,
val time: String,
+1 -1
View File
@@ -106,7 +106,7 @@
android:id="@+id/temporary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="subst plan"
android:text="substitution plan"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
-6
View File
@@ -14,10 +14,6 @@
android:layout_marginBottom="3dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/cardInfoText"
android:layout_width="match_parent"
@@ -28,6 +24,4 @@
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
+2 -2
View File
@@ -33,7 +33,7 @@
<string name="setup_successful">Viel Spaß!</string>
<string name="greeting_title">Aktiviere Begrüßungen</string>
<string name="greeting_desc">Falls du möchtest, dass ich nett bin :)</string>
<string name="licences_title">Lizenzen und Mitwirkende</string>
<string name="licences_title">Lizenzen &amp; Mitwirkende</string>
<string name="licences_desc">Was die App ausmacht (Englisch)</string>
<string name="version">Versionsnummer</string>
<string name="privacy_policy_title">Datenschutzerklärung</string>
@@ -52,7 +52,7 @@
<string name="apply">Anwenden</string>
<string name="invalid_code">Ungültiger Code</string>
<string name="experimental_menu_dialog_title">Experimentalmenü</string>
<string name="diagnostics_dialog_title">Diagnosemenü</string>
<string name="statistics_dialog_title">Statistikmenü</string>
<string name="enable_course_notifications">Kursbenachrichtigungen aktivieren</string>
<string name="no_personal_substitutions">Kein Entfall für dich</string>
<string name="customise_colours_title">Benutzerdefinierte Farben</string>
+2 -3
View File
@@ -10,7 +10,6 @@
<string name="personal">Personal</string>
<string name="settings">Settings</string>
<!-- for Settings fragment -->
<string name="enable_course_notifications_title">Enable course notifications</string>
<string name="enable_course_notifications_desc">This does not affect broadcasts</string>
@@ -39,7 +38,7 @@
<string name="greeting_title">Enable greeting</string>
<string name="greeting_desc">If you want me to be nice :)</string>
<string name="licences_title">Licences and contributors</string>
<string name="licences_title">Licences &amp; contributors</string>
<string name="licences_desc">What went into making this app</string>
<string name="version">Version Number</string>
@@ -59,7 +58,7 @@
<string name="courses_help_dialog_title">How to enter your courses</string>
<string name="courses_help_dialog_text">You need to enter the course abbreviation for every course you want in your personal plan and want to be notified about. You can find them on your time table.\n\nHere\'s a few examples:\n\nMathematics in 5th grade (juniors) would be MAT.\n\nAdvanced English in E-Phase (seniors) for the English profile may be ENP1.\n\nBasic Biology in Q1 could be bio1.\n\nIn most cases, entering courses while in junior phase is redundant.\nSeparate multiple courses by spaces, commas are optional.\nCapitalisation is required.</string>
<string name="experimental_menu_dialog_title">Experimental Menu</string>
<string name="diagnostics_dialog_title">Diagnostics Menu</string>
<string name="statistics_dialog_title">Statistics Menu</string>
<string name="experimental_menu_dialog_text">This menu is currently intended for testing only. Additional features may be added in the future. Feel free to try arbitrary codes.</string>
<string name="apply">Apply</string>
<string name="invalid_code">Invalid code</string>