diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 5960165..b59d794 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -7,11 +7,11 @@
-
+
-
+
\ No newline at end of file
diff --git a/app/schemas/com.denizk0461.weserplaner.db.AppDatabase/21.json b/app/schemas/com.denizk0461.weserplaner.db.AppDatabase/21.json
index f12a434..f4890c2 100644
--- a/app/schemas/com.denizk0461.weserplaner.db.AppDatabase/21.json
+++ b/app/schemas/com.denizk0461.weserplaner.db.AppDatabase/21.json
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 21,
- "identityHash": "4ac175b58eab4b387bd1ceb98f39a913",
+ "identityHash": "202a385ce4f79f957d41c8e4a2b8ab22",
"entities": [
{
"tableName": "studip_events",
@@ -268,7 +268,7 @@
},
{
"tableName": "event_tasks",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`taskId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `eventId` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `title` TEXT NOT NULL, `notes` TEXT NOT NULL, `room` TEXT NOT NULL, FOREIGN KEY(`eventId`) REFERENCES `studip_events`(`eventId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`taskId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `eventId` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `notifyDate` INTEGER NOT NULL, `title` TEXT NOT NULL, `notes` TEXT NOT NULL, `room` TEXT NOT NULL, FOREIGN KEY(`eventId`) REFERENCES `studip_events`(`eventId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "taskId",
@@ -288,6 +288,12 @@
"affinity": "INTEGER",
"notNull": true
},
+ {
+ "fieldPath": "notifyDate",
+ "columnName": "notifyDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
{
"fieldPath": "title",
"columnName": "title",
@@ -332,7 +338,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4ac175b58eab4b387bd1ceb98f39a913')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '202a385ce4f79f957d41c8e4a2b8ab22')"
]
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/denizk0461/weserplaner/adapter/TaskOverviewAdapter.kt b/app/src/main/java/com/denizk0461/weserplaner/adapter/TaskOverviewAdapter.kt
index 6bdae47..c02a75f 100644
--- a/app/src/main/java/com/denizk0461/weserplaner/adapter/TaskOverviewAdapter.kt
+++ b/app/src/main/java/com/denizk0461/weserplaner/adapter/TaskOverviewAdapter.kt
@@ -1,16 +1,18 @@
package com.denizk0461.weserplaner.adapter
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
+import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
-import com.denizk0461.weserplaner.BuildConfig
+import androidx.transition.TransitionManager
+import com.denizk0461.weserplaner.R
import com.denizk0461.weserplaner.data.AppDiffUtilCallback
+import com.denizk0461.weserplaner.data.isInThePast
import com.denizk0461.weserplaner.databinding.ItemTaskBinding
import com.denizk0461.weserplaner.model.EventTask
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
+import com.denizk0461.weserplaner.model.FormattedDate
class TaskOverviewAdapter(
private val onClickListener: OnClickListener,
@@ -46,16 +48,65 @@ class TaskOverviewAdapter(
// Retrieve item for current position
val currentItem = tasks[position]
+ // Determine whether this item's view is expanded
+ var isExpanded = false
+
+ holder.binding.layoutExpandedContainer.visibility = View.GONE
+
// 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 title text
+ holder.binding.textTitle.text = currentItem.title
+
+ // Set due date text to formatted date, TODO let user pick the date format
+ val dueDate = FormattedDate(currentItem.dueDate)
+
+ holder.binding.textDueDate.text = dueDate.localisedString(context)
+
+ if (currentItem.notifyDate == -1L) {
+
+ holder.binding.layoutDueDate.visibility = View.GONE
+
+ holder.binding.iconNotifyHeader.setImageResource(0)
+ } else {
+ val notificationDrawable = AppCompatResources.getDrawable(
+ context,
+ R.drawable.notifications,
+ )
+
+ holder.binding.layoutDueDate.visibility = View.VISIBLE
+
+ holder.binding.iconNotifyHeader.setImageDrawable(notificationDrawable)
+
+ holder.binding.iconNotifyDate.setImageDrawable(notificationDrawable)
+
+ // TODO let user pick date format
+ val notifyDate = FormattedDate(currentItem.notifyDate)
+
+ holder.binding.textNotifyDate.text = context.getString(if (notifyDate.date.isInThePast()) {
+ R.string.task_item_notification_active_past
+ } else {
+ R.string.task_item_notification_active_future
+ }, notifyDate.dateString, notifyDate.timeString)
+ }
// Set up single click listener
holder.binding.linearLayout.setOnClickListener {
- onClickListener.onClick(currentItem)
+ // Prepare layout animation
+ TransitionManager.beginDelayedTransition(holder.binding.root.parent as ViewGroup)
+
+ // Set visibility of further items
+ holder.binding.layoutExpandedContainer.visibility = if (isExpanded) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
+
+ // Set value of isExpanded depending on whether the view is expanded
+ isExpanded = !isExpanded
}
// Set up long press listener
@@ -89,14 +140,6 @@ class TaskOverviewAdapter(
*/
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.
*
diff --git a/app/src/main/java/com/denizk0461/weserplaner/data/Extensions.kt b/app/src/main/java/com/denizk0461/weserplaner/data/Extensions.kt
index 8f44165..8f14be1 100644
--- a/app/src/main/java/com/denizk0461/weserplaner/data/Extensions.kt
+++ b/app/src/main/java/com/denizk0461/weserplaner/data/Extensions.kt
@@ -17,9 +17,8 @@ import com.denizk0461.weserplaner.exception.ParcelNotFoundException
import com.denizk0461.weserplaner.values.TextSheetContentId
import com.denizk0461.weserplaner.sheet.TextSheet
import com.google.android.material.snackbar.Snackbar
-import java.text.SimpleDateFormat
+import java.util.Calendar
import java.util.Date
-import java.util.Locale
import kotlin.jvm.Throws
/*
@@ -39,6 +38,25 @@ fun String.parseToMinutes(): Int = try {
0
}
+/**
+ * Retrieves the current date using a Calendar instance.
+ *
+ * @return date object containing the current time
+ */
+fun getCurrentTime(): Date = Calendar.getInstance().run {
+ Date((get(Calendar.MINUTE) + get(Calendar.HOUR_OF_DAY) * 60L))
+}
+
+/**
+ * Determines whether a given date lies in the past.
+ *
+ * @receiver date to check
+ * @param against date to check against, defaults to current date
+ * @return true if it is in the past, false if it is in the future or at this moment
+ */
+fun Date.isInThePast(against: Date = getCurrentTime()): Boolean =
+ this.time < against.time
+
/**
* Retrieves a specified colour customised to the currently applied theme.
*
@@ -151,19 +169,6 @@ 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.
*/
diff --git a/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt b/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt
index 4a3f7e8..2636831 100644
--- a/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt
+++ b/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt
@@ -76,6 +76,7 @@ interface AppDAO {
"SELECT event_tasks.taskId, " +
"event_tasks.eventId, " +
"event_tasks.dueDate, " +
+ "event_tasks.notifyDate, " +
"event_tasks.title AS taskTitle, " +
"studip_events.title AS eventTitle, " +
"studip_events.lecturer, " +
@@ -96,6 +97,7 @@ interface AppDAO {
"SELECT event_tasks.taskId, " +
"event_tasks.eventId, " +
"event_tasks.dueDate, " +
+ "event_tasks.notifyDate, " +
"event_tasks.title AS taskTitle, " +
"studip_events.title AS eventTitle, " +
"studip_events.lecturer, " +
@@ -115,6 +117,7 @@ interface AppDAO {
"SELECT event_tasks.taskId, " +
"event_tasks.eventId, " +
"event_tasks.dueDate, " +
+ "event_tasks.notifyDate, " +
"event_tasks.title AS taskTitle, " +
"studip_events.title AS eventTitle, " +
"studip_events.lecturer, " +
diff --git a/app/src/main/java/com/denizk0461/weserplaner/fragment/SettingsFragment.kt b/app/src/main/java/com/denizk0461/weserplaner/fragment/SettingsFragment.kt
index 78d20f6..cfa034d 100644
--- a/app/src/main/java/com/denizk0461/weserplaner/fragment/SettingsFragment.kt
+++ b/app/src/main/java/com/denizk0461/weserplaner/fragment/SettingsFragment.kt
@@ -16,11 +16,11 @@ 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
import com.denizk0461.weserplaner.databinding.FragmentSettingsBinding
+import com.denizk0461.weserplaner.model.FormattedDate
import com.denizk0461.weserplaner.sheet.AllergenConfigSheet
import com.denizk0461.weserplaner.viewmodel.SettingsViewModel
import java.nio.charset.StandardCharsets
@@ -318,7 +318,7 @@ class SettingsFragment : AppFragment() {
// Set build version code and time text
@SuppressLint("SetTextI18n")
binding.buildTimeText.text = "v${BuildConfig.VERSION_CODE} | ${
- Date(BuildConfig.BUILD_TIME_MILLIS).formatToIso8601String(useHighPrecision = true)
+ FormattedDate(BuildConfig.BUILD_TIME_MILLIS).commaSeparatedString(context)
}"
// Set up switch for showing beta screens
diff --git a/app/src/main/java/com/denizk0461/weserplaner/fragment/TaskOverviewFragment.kt b/app/src/main/java/com/denizk0461/weserplaner/fragment/TaskOverviewFragment.kt
index 2837f85..40a9844 100644
--- a/app/src/main/java/com/denizk0461/weserplaner/fragment/TaskOverviewFragment.kt
+++ b/app/src/main/java/com/denizk0461/weserplaner/fragment/TaskOverviewFragment.kt
@@ -91,10 +91,6 @@ class TaskOverviewFragment : AppFragment(),
}
}
- override fun onClick(task: EventTask) {
- // TODO
- }
-
override fun onLongClick(task: EventTask): Boolean {
// TODO
return false
diff --git a/app/src/main/java/com/denizk0461/weserplaner/model/EventTask.kt b/app/src/main/java/com/denizk0461/weserplaner/model/EventTask.kt
index 200bad2..e913532 100644
--- a/app/src/main/java/com/denizk0461/weserplaner/model/EventTask.kt
+++ b/app/src/main/java/com/denizk0461/weserplaner/model/EventTask.kt
@@ -7,12 +7,14 @@ 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
+ * @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 notifyDate time and date at which the user wants to be notified about this task; -1 if
+ * the user doesn't want to be notified
+ * @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",
@@ -28,7 +30,8 @@ import androidx.room.PrimaryKey
data class EventTask(
@PrimaryKey(autoGenerate = true) val taskId: Int = 0,
val eventId: Int,
- val dueDate: Int,
+ val dueDate: Long,
+ val notifyDate: Long,
val title: String,
val notes: String,
val room: String,
diff --git a/app/src/main/java/com/denizk0461/weserplaner/model/EventTaskExtended.kt b/app/src/main/java/com/denizk0461/weserplaner/model/EventTaskExtended.kt
index 72beca9..d167ec2 100644
--- a/app/src/main/java/com/denizk0461/weserplaner/model/EventTaskExtended.kt
+++ b/app/src/main/java/com/denizk0461/weserplaner/model/EventTaskExtended.kt
@@ -7,6 +7,8 @@ package com.denizk0461.weserplaner.model
* @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 notifyDate time and date at which the user wants to be notified about this task; -1 if
+ * the user doesn't want to be notified
* @param eventTitle title of the event
* @param taskTitle title of the task
* @param lecturer lecturer(s) organising the event
@@ -17,6 +19,7 @@ data class EventTaskExtended(
val taskId: Int,
val eventId: Int,
val dueDate: Int,
+ val notifyDate: Int,
val eventTitle: String,
val taskTitle: String,
val lecturer: String,
diff --git a/app/src/main/java/com/denizk0461/weserplaner/model/FormattedDate.kt b/app/src/main/java/com/denizk0461/weserplaner/model/FormattedDate.kt
new file mode 100644
index 0000000..fb63e82
--- /dev/null
+++ b/app/src/main/java/com/denizk0461/weserplaner/model/FormattedDate.kt
@@ -0,0 +1,49 @@
+package com.denizk0461.weserplaner.model
+
+import android.content.Context
+import com.denizk0461.weserplaner.R
+import com.denizk0461.weserplaner.values.DateFormat
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/**
+ * Wrapper for Java's date object that exposes pre-formatted date and time strings.
+ *
+ * @param date date object to wrap
+ * @param format format the date should be presented in; see [com.denizk0461.weserplaner.values.DateFormat]
+ */
+class FormattedDate(val date: Date, format: DateFormat = DateFormat.ISO8601) {
+
+ constructor(time: Long, format: DateFormat = DateFormat.ISO8601) : this(Date(time), format)
+
+ var dateString: String = SimpleDateFormat(format.dateFormat, Locale.GERMANY).format(date)
+ var timeString: String = SimpleDateFormat(format.timeFormat, Locale.GERMANY).format(date)
+
+ /**
+ * Produces a string containing date and time of the object, separated by a comma. Example:
+ * 24.05.2023, 8:45
+ *
+ * @param context used to retrieve the string resource
+ * @return formatted string
+ */
+ fun commaSeparatedString(context: Context): String = context.getString(
+ R.string.formatted_date_string_comma,
+ dateString,
+ timeString,
+ )
+
+ /**
+ * Produces a string containing date and time of the object, localised to the user's locale. Example:
+ * 24.05.2023 at 8:45 (English)
+ * 24.05.2023 um 8:45 (German)
+ *
+ * @param context used to retrieve the string resource
+ * @return formatted string
+ */
+ fun localisedString(context: Context): String = context.getString(
+ R.string.formatted_date_string_localised,
+ dateString,
+ timeString,
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/denizk0461/weserplaner/values/DateFormat.kt b/app/src/main/java/com/denizk0461/weserplaner/values/DateFormat.kt
new file mode 100644
index 0000000..3d891a1
--- /dev/null
+++ b/app/src/main/java/com/denizk0461/weserplaner/values/DateFormat.kt
@@ -0,0 +1,12 @@
+package com.denizk0461.weserplaner.values
+
+/**
+ * Determines the format a date should be put into.
+ */
+enum class DateFormat(val dateFormat: String, val timeFormat: String) {
+ EUROPEAN("dd.MM.yyyy", "H:mm"), // 24.05.2023, 8:45
+ ISO8601("yyyy-MM-dd", "HH:mm"), // 2023-05-24, 08:45
+ ISO8601_PRECISE("yyyy-MM-dd", "HH:mm:ss.SSS"), // 2023-05-24, 08:45:11.518
+ FUCKED_UP("MM/dd/yy", "h:mm a"), // 05/24/23, 8:45 AM
+ ;
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/denizk0461/weserplaner/viewmodel/TaskOverviewViewModel.kt b/app/src/main/java/com/denizk0461/weserplaner/viewmodel/TaskOverviewViewModel.kt
index 873be29..8ca5e8e 100644
--- a/app/src/main/java/com/denizk0461/weserplaner/viewmodel/TaskOverviewViewModel.kt
+++ b/app/src/main/java/com/denizk0461/weserplaner/viewmodel/TaskOverviewViewModel.kt
@@ -18,8 +18,8 @@ class TaskOverviewViewModel(application: Application) : AppViewModel(application
// fun getTasks(order: TaskOrder): LiveData> = 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"),
+ EventTask(0, 0, 1684758756L, -1L, "title1", "user notes", "GW2 B-idk"),
+ EventTask(1, 0, 1684798756L, -1L, "title2", "user notes", "GW3 C-idk"),
+ EventTask(2, 0, 1689758756L, 1689958756L, "title3", "user notes", "GW4 D-idk"),
)
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/notifications.xml b/app/src/main/res/drawable/notifications.xml
new file mode 100644
index 0000000..1d038a4
--- /dev/null
+++ b/app/src/main/res/drawable/notifications.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/notifications_off.xml b/app/src/main/res/drawable/notifications_off.xml
new file mode 100644
index 0000000..a7f4911
--- /dev/null
+++ b/app/src/main/res/drawable/notifications_off.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/item_task.xml b/app/src/main/res/layout/item_task.xml
index 3ef72a0..9ed977b 100644
--- a/app/src/main/res/layout/item_task.xml
+++ b/app/src/main/res/layout/item_task.xml
@@ -1,6 +1,7 @@
+ android:padding="16dp"
+ android:background="@drawable/selectable_item_background"
+ android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index eb28037..1739320 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -90,6 +90,12 @@
Erstellungszeit
Abgabezeit
+ %s um %s
+
+ Du wirst nicht benachrichtigt.
+ Du wirst am %s um %s benachrichtigt.
+ Du wurdest am %s um %s benachrichtigt.
+
Aufgaben sind noch nicht implementiert!
Aufgabe hinzufügen
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3b2e8af..62dd7a2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -91,6 +91,13 @@
Date created
Date due
+ %s, %s
+ %s at %s
+
+ You won\'t be notified
+ You will be notified on %s at %s.
+ You were notified on %s at %s.
+
Tasks are not yet implemented!
Add task