Task overview now has a RecyclerView adapter and displays dummy data in empty items; added database queries to retrieve tasks

This commit is contained in:
denizk0461
2023-05-22 15:32:19 +02:00
parent 61155eaec4
commit a1f18b3548
12 changed files with 306 additions and 8 deletions
@@ -0,0 +1,108 @@
package com.denizk0461.weserplaner.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.denizk0461.weserplaner.BuildConfig
import com.denizk0461.weserplaner.data.AppDiffUtilCallback
import com.denizk0461.weserplaner.databinding.ItemTaskBinding
import com.denizk0461.weserplaner.model.EventTask
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class TaskOverviewAdapter(
private val onClickListener: OnClickListener,
) : RecyclerView.Adapter<TaskOverviewAdapter.TaskViewHolder>() {
/**
* List of all events.
*/
private val tasks: MutableList<EventTask> = mutableListOf()
/**
* View holder class for parent class
*
* @param binding view binding object
*/
class TaskViewHolder(val binding: ItemTaskBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
return TaskViewHolder(
ItemTaskBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
// Set the amount of items to the size of the filtered list, so only events of the current day
override fun getItemCount(): Int = tasks.size
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
// Retrieve item for current position
val currentItem = tasks[position]
// Context for easier reference
val context = holder.binding.root.context
// view set-up
SimpleDateFormat("yyyy-MM-dd, HH:mm:ss.SSS", Locale.GERMANY)
.format(Date(BuildConfig.BUILD_TIME_MILLIS))
// Set up single click listener
holder.binding.linearLayout.setOnClickListener {
onClickListener.onClick(currentItem)
}
// Set up long press listener
holder.binding.linearLayout.setOnLongClickListener {
onClickListener.onLongClick(currentItem)
}
}
/**
* Updates the data and calculates the difference between the old dataset and the newly provided
* dataset.
*
* @param newData new dataset to be displayed
*/
fun setNewData(newData: List<EventTask>) {
// Calculate the difference between the old list and the new list
val diffResult = DiffUtil.calculateDiff(AppDiffUtilCallback(tasks, newData))
// Remove all items from the list
tasks.clear()
// Add all items from the new list
tasks.addAll(newData)
// Tell the DiffUtil which items have changed between the two lists
diffResult.dispatchUpdatesTo(this)
}
/**
* Interface used to evaluate clicks and long presses on a given item.
*/
interface OnClickListener {
/**
* Executed when an item has been clicked.
*
* @param task item that has been clicked
*/
fun onClick(task: EventTask)
/**
* Executed when an item has been long-pressed.
*
* @param task item that has been long-pressed
* @return whether the long press was successful
*/
fun onLongClick(task: EventTask): Boolean
}
}
@@ -17,6 +17,9 @@ import com.denizk0461.weserplaner.exception.ParcelNotFoundException
import com.denizk0461.weserplaner.model.TextSheetContentId
import com.denizk0461.weserplaner.sheet.TextSheet
import com.google.android.material.snackbar.Snackbar
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.jvm.Throws
/*
@@ -148,6 +151,19 @@ fun SwipeRefreshLayout.setRainbowProgressCircle() {
)
}
/**
* Formats a date object to a string in the ISO 8601 format. Example:
* 2023-05-21, 14:32:59.067
*
* @receiver date object to convert to string
* @param useHighPrecision determines whether seconds and milliseconds are shown
* @return date formatted as ISO 8601 string
*/
fun Date.formatToIso8601String(useHighPrecision: Boolean = false): String = SimpleDateFormat(
"yyyy-MM-dd, HH:mm${if (useHighPrecision) ":ss.SSS" else ""}",
Locale.GERMANY,
).format(this)
/**
* Timeslots for which a conversion to an academic quarter is applicable.
*/
@@ -65,6 +65,66 @@ interface AppDAO {
@Query("DELETE FROM studip_events")
fun nukeEvents()
/* --- event tasks --- */
/**
* Retrieve all event tasks ordered by their ID (order: date created).
*
* @return tasks ordered by date created
*/
@Query(
"SELECT event_tasks.taskId, " +
"event_tasks.eventId, " +
"event_tasks.dueDate, " +
"event_tasks.title AS taskTitle, " +
"studip_events.title AS eventTitle, " +
"studip_events.lecturer, " +
"event_tasks.notes, " +
"studip_events.room " +
"FROM event_tasks " +
"JOIN studip_events ON event_tasks.eventId = studip_events.eventId " +
"ORDER BY taskId"
)
fun getAllTasksOrderDateCreated(): LiveData<List<EventTaskExtended>>
/**
* Retrieve all event tasks ordered alphabetically by their title.
*
* @return tasks ordered by title
*/
@Query(
"SELECT event_tasks.taskId, " +
"event_tasks.eventId, " +
"event_tasks.dueDate, " +
"event_tasks.title AS taskTitle, " +
"studip_events.title AS eventTitle, " +
"studip_events.lecturer, " +
"event_tasks.notes, " +
"studip_events.room " +
"FROM event_tasks " +
"JOIN studip_events ON event_tasks.eventId = studip_events.eventId " +
"ORDER BY taskTitle")
fun getAllTasksOrderAlphabetically(): LiveData<List<EventTaskExtended>>
/**
* Retrieve all event tasks ordered by their due date.
*
* @return tasks ordered by date due
*/
@Query(
"SELECT event_tasks.taskId, " +
"event_tasks.eventId, " +
"event_tasks.dueDate, " +
"event_tasks.title AS taskTitle, " +
"studip_events.title AS eventTitle, " +
"studip_events.lecturer, " +
"event_tasks.notes, " +
"studip_events.room " +
"FROM event_tasks " +
"JOIN studip_events ON event_tasks.eventId = studip_events.eventId " +
"ORDER BY dueDate")
fun getAllTasksOrderDateDue(): LiveData<List<EventTaskExtended>>
/* --- canteen offers --- */
/**
@@ -80,6 +80,18 @@ class AppRepository(app: Application) {
*/
fun nukeEvents() { dao.nukeEvents() }
/**
* Retrieves tasks for all events ordered as specified.
*
* @param order order in which the tasks are returned
* @return tasks wrapped in a LiveData object
*/
fun getTasks(order: TaskOrder): LiveData<List<EventTaskExtended>> = when (order) {
TaskOrder.DATE_CREATED -> dao.getAllTasksOrderDateCreated()
TaskOrder.ALPHABETICALLY -> dao.getAllTasksOrderAlphabetically()
else -> dao.getAllTasksOrderDateDue() // TaskOrder.DATE_DUE
}
/**
* Retrieves the canteen stored in the database. Should always be 0 or 1.
*
@@ -16,6 +16,7 @@ import com.denizk0461.weserplaner.BuildConfig
import com.denizk0461.weserplaner.R
import com.denizk0461.weserplaner.activity.FetcherActivity
import com.denizk0461.weserplaner.activity.ImageActivity
import com.denizk0461.weserplaner.data.formatToIso8601String
import com.denizk0461.weserplaner.data.getTextSheet
import com.denizk0461.weserplaner.data.showSnackBar
import com.denizk0461.weserplaner.data.showToast
@@ -23,7 +24,6 @@ import com.denizk0461.weserplaner.databinding.FragmentSettingsBinding
import com.denizk0461.weserplaner.sheet.AllergenConfigSheet
import com.denizk0461.weserplaner.viewmodel.SettingsViewModel
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.util.*
/**
@@ -318,8 +318,7 @@ class SettingsFragment : AppFragment<FragmentSettingsBinding>() {
// Set build version code and time text
@SuppressLint("SetTextI18n")
binding.buildTimeText.text = "v${BuildConfig.VERSION_CODE} | ${
SimpleDateFormat("yyyy-MM-dd, HH:mm:ss.SSS", Locale.GERMANY)
.format(Date(BuildConfig.BUILD_TIME_MILLIS))
Date(BuildConfig.BUILD_TIME_MILLIS).formatToIso8601String(useHighPrecision = true)
}"
// Set up switch for showing beta screens
@@ -6,19 +6,27 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.denizk0461.weserplaner.R
import com.denizk0461.weserplaner.adapter.TaskOverviewAdapter
import com.denizk0461.weserplaner.data.showSnackBar
import com.denizk0461.weserplaner.databinding.FragmentTaskOverviewBinding
import com.denizk0461.weserplaner.model.EventTask
import com.denizk0461.weserplaner.model.TaskOrder
import com.denizk0461.weserplaner.viewmodel.TaskOverviewViewModel
/**
* Fragment that shows the user an overview of their tasks and exams.
*/
class TaskOverviewFragment : AppFragment<FragmentTaskOverviewBinding>() {
class TaskOverviewFragment : AppFragment<FragmentTaskOverviewBinding>(),
TaskOverviewAdapter.OnClickListener {
// View model reference for providing access to the database
private val viewModel: TaskOverviewViewModel by viewModels()
// Task adapter for the recycler view
private val taskAdapter: TaskOverviewAdapter = TaskOverviewAdapter(this)
// Instantiate the view binding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentTaskOverviewBinding.inflate(inflater, container, false)
@@ -38,8 +46,6 @@ class TaskOverviewFragment : AppFragment<FragmentTaskOverviewBinding>() {
)
binding.buttonOrder.setOnClickListener {
// Create menu for selecting a new canteen
PopupMenu(binding.root.context, binding.buttonOrder).apply {
setOnMenuItemClickListener { item ->
@@ -60,6 +66,17 @@ class TaskOverviewFragment : AppFragment<FragmentTaskOverviewBinding>() {
}
}
binding.recyclerViewLayout.recyclerView.apply {
// Set page to scroll vertically
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
// Assign adapter to this recycler view
adapter = taskAdapter
}
// insert sample data TODO change to db
taskAdapter.setNewData(viewModel.getTasks(TaskOrder.DATE_DUE))
// Set up scroll change listener to shrink and extend FAB accordingly
binding.recyclerViewLayout.recyclerView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
// Calculate the vertical scroll difference
@@ -73,4 +90,13 @@ class TaskOverviewFragment : AppFragment<FragmentTaskOverviewBinding>() {
}
}
}
override fun onClick(task: EventTask) {
// TODO
}
override fun onLongClick(task: EventTask): Boolean {
// TODO
return false
}
}
@@ -4,6 +4,16 @@ import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
/**
* Database entity used to store tasks assigned to specific [StudIPEvent] elements.
*
* @param taskId primary key for the entity
* @param eventId foreign key referencing the event this task is assigned to
* @param dueDate time and date at which the task is due
* @param title title of the task
* @param notes user-added notes for the task
* @param room room the task may take place in
*/
@Entity(
tableName = "event_tasks",
foreignKeys = [
@@ -0,0 +1,25 @@
package com.denizk0461.weserplaner.model
/**
* Extended representation of [com.denizk0461.weserplaner.model.EventTask] that includes important
* attributes from a given task's associated [com.denizk0461.weserplaner.model.StudIPEvent].
*
* @param taskId primary key for the entity
* @param eventId foreign key referencing the event this task is assigned to
* @param dueDate time and date at which the task is due
* @param eventTitle title of the event
* @param taskTitle title of the task
* @param lecturer lecturer(s) organising the event
* @param notes user-added notes for the task
* @param room room the task may take place in
*/
data class EventTaskExtended(
val taskId: Int,
val eventId: Int,
val dueDate: Int,
val eventTitle: String,
val taskTitle: String,
val lecturer: String,
val notes: String,
val room: String,
)
@@ -0,0 +1,25 @@
package com.denizk0461.weserplaner.model
/**
* Used to determine the order in which tasks should be shown.
*
* @param key index by which to tell which order method to use
*/
enum class TaskOrder(key: Int) {
/**
* Order tasks by date due.
*/
DATE_DUE(0),
/**
* Order tasks by date created.
*/
DATE_CREATED(1),
/**
* Order tasks alphabetically.
*/
ALPHABETICALLY(2),
;
}
@@ -1,11 +1,26 @@
package com.denizk0461.weserplaner.viewmodel
import android.app.Application
import androidx.lifecycle.LiveData
import com.denizk0461.weserplaner.model.EventTask
import com.denizk0461.weserplaner.model.TaskOrder
/**
* View model class for [com.denizk0461.weserplaner.fragment.TaskOverviewFragment].
*/
class TaskOverviewViewModel(application: Application) : AppViewModel(application) {
/**
* Retrieves tasks for all events ordered as specified.
*
* @param order order in which the tasks are returned
* @return tasks wrapped in a LiveData object
*/
// fun getTasks(order: TaskOrder): LiveData<List<EventTask>> = repo.getTasks(order)
fun getTasks(order: TaskOrder) = listOf( // dummy data
EventTask(0, 0, 1684758756, "title1", "user notes", "GW2 B-idk"),
EventTask(1, 0, 1684798756, "title2", "user notes", "GW3 C-idk"),
EventTask(2, 0, 1689758756, "title3", "user notes", "GW4 D-idk"),
)
}
@@ -64,7 +64,8 @@
<include layout="@layout/recycler_view"
android:id="@+id/recycler_view_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab_add_task"
+2 -1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"