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