Archived
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:
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user