diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a40de46..5e6f1c0 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -166,5 +166,8 @@ dependencies {
// Square Logcat
implementation("com.squareup.logcat:logcat:${rootProject.extra["logcatVersion"]}")
+
+ // Worker + Coroutine
+ implementation("androidx.work:work-runtime-ktx:${rootProject.extra["workerVersion"]}")
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6cdb5c5..1873a75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,6 +19,7 @@
-->
@@ -43,6 +44,19 @@
+
+
+
+
+
+
diff --git a/app/src/main/java/dev/spikeysanju/einsen/MainActivity.kt b/app/src/main/java/dev/spikeysanju/einsen/MainActivity.kt
index 7bf4d28..84d5c1a 100644
--- a/app/src/main/java/dev/spikeysanju/einsen/MainActivity.kt
+++ b/app/src/main/java/dev/spikeysanju/einsen/MainActivity.kt
@@ -20,8 +20,8 @@
package dev.spikeysanju.einsen
import android.os.Bundle
-import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.Surface
@@ -42,7 +42,7 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
-class MainActivity : ComponentActivity() {
+class MainActivity : AppCompatActivity() {
@Inject
lateinit var themeManager: ThemeManager
diff --git a/app/src/main/java/dev/spikeysanju/einsen/app/EinsenApp.kt b/app/src/main/java/dev/spikeysanju/einsen/app/EinsenApp.kt
index 875b020..eb26660 100644
--- a/app/src/main/java/dev/spikeysanju/einsen/app/EinsenApp.kt
+++ b/app/src/main/java/dev/spikeysanju/einsen/app/EinsenApp.kt
@@ -20,6 +20,8 @@
package dev.spikeysanju.einsen.app
import android.app.Application
+import androidx.hilt.work.HiltWorkerFactory
+import androidx.work.Configuration
import com.google.firebase.analytics.FirebaseAnalytics
import dagger.hilt.android.HiltAndroidApp
import logcat.AndroidLogcatLogger
@@ -27,11 +29,19 @@ import logcat.LogPriority
import javax.inject.Inject
@HiltAndroidApp
-class EinsenApp : Application() {
+class EinsenApp : Application(), Configuration.Provider {
@Inject
lateinit var mFirebaseAnalytics: FirebaseAnalytics
+ @Inject
+ lateinit var workerFactory: HiltWorkerFactory
+
+ override fun getWorkManagerConfiguration() =
+ Configuration.Builder()
+ .setWorkerFactory(workerFactory)
+ .build()
+
override fun onCreate() {
super.onCreate()
// Log all priorities in debug builds, no-op in release builds.
diff --git a/app/src/main/java/dev/spikeysanju/einsen/components/DatePicker.kt b/app/src/main/java/dev/spikeysanju/einsen/components/DatePicker.kt
new file mode 100644
index 0000000..1ae0126
--- /dev/null
+++ b/app/src/main/java/dev/spikeysanju/einsen/components/DatePicker.kt
@@ -0,0 +1,86 @@
+/*
+ *
+ * * Copyright 2021 Spikey Sanju
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * https://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ *
+ */
+
+package dev.spikeysanju.einsen.components
+
+import android.content.Context
+import androidx.fragment.app.FragmentActivity
+import com.google.android.material.datepicker.CalendarConstraints
+import com.google.android.material.datepicker.MaterialDatePicker
+import com.google.android.material.timepicker.MaterialTimePicker
+import dev.spikeysanju.einsen.R
+import dev.spikeysanju.einsen.utils.DateValidator
+import java.util.*
+
+fun Context?.showDatePicker(
+ defaultCalendar: Calendar,
+ onDismiss: (() -> Unit)? = null,
+ minDate: Long = Calendar.getInstance().also {
+ it.set(Calendar.MINUTE, 0)
+ it.set(Calendar.HOUR_OF_DAY, 0)
+ it.set(Calendar.SECOND, 0)
+ it.set(Calendar.MILLISECOND, 0)
+ }.timeInMillis,
+ onDateSelect: (Calendar) -> Unit
+) {
+ (this as? FragmentActivity)?.supportFragmentManager?.let { manager ->
+
+ val calendarConstraints = CalendarConstraints.Builder().setStart(minDate)
+ .setValidator(DateValidator(minDate))
+ .build()
+
+ val builder = MaterialDatePicker.Builder.datePicker()
+ .setTheme(R.style.ThemeOverlay_App_DatePicker)
+ .setCalendarConstraints(calendarConstraints)
+ .setSelection(defaultCalendar.timeInMillis)
+ .build()
+
+ builder.addOnPositiveButtonClickListener { selectedDate ->
+ val newCal = Calendar.getInstance()
+ newCal.timeInMillis = selectedDate
+ onDateSelect.invoke(newCal)
+ }
+ builder.addOnDismissListener {
+ onDismiss?.invoke()
+ }
+ builder.show(manager, "DatePicker")
+ }
+}
+
+fun Context?.showTimePicker(
+ defaultCalendar: Calendar,
+ onDismiss: (() -> Unit)? = null,
+ onTimeSelected: (Calendar) -> Unit
+) {
+ (this as? FragmentActivity)?.supportFragmentManager?.let { manager ->
+ val builder = MaterialTimePicker.Builder()
+ .setHour(defaultCalendar.get(Calendar.HOUR_OF_DAY))
+ .setMinute(defaultCalendar.get(Calendar.MINUTE))
+ .build()
+ builder.addOnPositiveButtonClickListener {
+ defaultCalendar.set(Calendar.HOUR_OF_DAY, builder.hour)
+ defaultCalendar.set(Calendar.MINUTE, builder.minute)
+ onTimeSelected.invoke(defaultCalendar)
+ }
+ builder.addOnDismissListener {
+ onDismiss?.invoke()
+ }
+ builder.show(manager, "TimePicker")
+ }
+}
diff --git a/app/src/main/java/dev/spikeysanju/einsen/components/TextInput.kt b/app/src/main/java/dev/spikeysanju/einsen/components/TextInput.kt
index 080d853..99748bf 100644
--- a/app/src/main/java/dev/spikeysanju/einsen/components/TextInput.kt
+++ b/app/src/main/java/dev/spikeysanju/einsen/components/TextInput.kt
@@ -69,6 +69,8 @@ fun EinsenInputTextField(
modifier: Modifier = Modifier,
title: String,
value: String,
+ readOnly: Boolean = false,
+ enabled: Boolean = true,
onValueChanged: (String) -> Unit
) {
var textState by rememberSaveable { mutableStateOf("") }
@@ -83,6 +85,8 @@ fun EinsenInputTextField(
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp),
value = value,
+ readOnly = readOnly,
+ enabled = enabled,
onValueChange = {
textState = it
when (textState.isEmpty()) {
diff --git a/app/src/main/java/dev/spikeysanju/einsen/data/local/db/TaskDao.kt b/app/src/main/java/dev/spikeysanju/einsen/data/local/db/TaskDao.kt
index d180aac..c15cde8 100644
--- a/app/src/main/java/dev/spikeysanju/einsen/data/local/db/TaskDao.kt
+++ b/app/src/main/java/dev/spikeysanju/einsen/data/local/db/TaskDao.kt
@@ -45,6 +45,9 @@ interface TaskDao {
@Query("SELECT * FROM task where id=:id")
fun findByID(id: Int): Flow
+ @Query("SELECT * FROM task where id=:id")
+ fun findTaskByID(id: Int): Task
+
@Query("UPDATE task set isCompleted= :isCompleted where id=:id")
suspend fun updateTaskStatus(id: Int, isCompleted: Boolean)
diff --git a/app/src/main/java/dev/spikeysanju/einsen/di/AppModule.kt b/app/src/main/java/dev/spikeysanju/einsen/di/AppModule.kt
index 46e4d45..f9e09a1 100644
--- a/app/src/main/java/dev/spikeysanju/einsen/di/AppModule.kt
+++ b/app/src/main/java/dev/spikeysanju/einsen/di/AppModule.kt
@@ -19,6 +19,7 @@
package dev.spikeysanju.einsen.di
+import android.app.NotificationManager
import android.content.Context
import androidx.room.Room
import com.google.firebase.analytics.FirebaseAnalytics
@@ -60,4 +61,10 @@ object AppModule {
fun provideFirebaseAnalytics(@ApplicationContext context: Context): FirebaseAnalytics {
return FirebaseAnalytics.getInstance(context)
}
+
+ @Singleton
+ @Provides
+ fun provideNotificationManager(@ApplicationContext context: Context): NotificationManager {
+ return context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ }
}
diff --git a/app/src/main/java/dev/spikeysanju/einsen/model/task/Task.kt b/app/src/main/java/dev/spikeysanju/einsen/model/task/Task.kt
index 7dcc33f..ca97aa2 100644
--- a/app/src/main/java/dev/spikeysanju/einsen/model/task/Task.kt
+++ b/app/src/main/java/dev/spikeysanju/einsen/model/task/Task.kt
@@ -52,7 +52,9 @@ data class Task(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
val id: Int = 0
-)
+) {
+ fun getWorkerId() = "reminder_$id"
+}
enum class Priority(count: Int) {
URGENT(4),
diff --git a/app/src/main/java/dev/spikeysanju/einsen/utils/DateTimeHelper.kt b/app/src/main/java/dev/spikeysanju/einsen/utils/DateTimeHelper.kt
new file mode 100644
index 0000000..aeaaab7
--- /dev/null
+++ b/app/src/main/java/dev/spikeysanju/einsen/utils/DateTimeHelper.kt
@@ -0,0 +1,44 @@
+/*
+ *
+ * * Copyright 2021 Spikey Sanju
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * https://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ *
+ */
+
+package dev.spikeysanju.einsen.utils
+
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.*
+
+const val DUE_DATE_FORMAT = "dd MMM yyyy hh:mm aa"
+
+fun formatCalendar(calendar: Calendar, dateTimeFormat: String? = DUE_DATE_FORMAT): String {
+ val simpleDateFormat = SimpleDateFormat(dateTimeFormat, Locale.getDefault())
+ return simpleDateFormat.format(calendar.time)
+}
+
+fun getCalendar(dateTime: String): Calendar {
+ val simpleDateFormat = SimpleDateFormat(DUE_DATE_FORMAT, Locale.getDefault())
+ val cal = Calendar.getInstance()
+ try {
+ simpleDateFormat.parse(dateTime)?.let {
+ cal.time = it
+ }
+ } catch (e: ParseException) {
+ e.printStackTrace()
+ }
+ return cal
+}
diff --git a/app/src/main/java/dev/spikeysanju/einsen/utils/DateValidator.kt b/app/src/main/java/dev/spikeysanju/einsen/utils/DateValidator.kt
new file mode 100644
index 0000000..d6ec523
--- /dev/null
+++ b/app/src/main/java/dev/spikeysanju/einsen/utils/DateValidator.kt
@@ -0,0 +1,67 @@
+/*
+ *
+ * * Copyright 2021 Spikey Sanju
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * https://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ *
+ */
+
+package dev.spikeysanju.einsen.utils
+
+import android.os.Parcel
+import android.os.Parcelable
+import com.google.android.material.datepicker.CalendarConstraints
+
+class DateValidator() : CalendarConstraints.DateValidator {
+ var startDate: Long = -1
+ var endDate: Long = -1
+
+ constructor(startDate: Long = -1, endDate: Long = -1) : this() {
+ this.startDate = startDate
+ this.endDate = endDate
+ }
+
+ constructor(parcel: Parcel) : this() {
+ startDate = parcel.readLong()
+ endDate = parcel.readLong()
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeLong(startDate)
+ parcel.writeLong(endDate)
+ }
+
+ override fun isValid(date: Long): Boolean {
+ return if (startDate != -1L && endDate != -1L && date in startDate..endDate) {
+ // valid date between start and end date
+ true
+ } else if (startDate == -1L && date < endDate) {
+ true
+ } else endDate == -1L && date >= startDate
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): DateValidator {
+ return DateValidator(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+}
diff --git a/app/src/main/java/dev/spikeysanju/einsen/view/add/AddTaskScreen.kt b/app/src/main/java/dev/spikeysanju/einsen/view/add/AddTaskScreen.kt
index 6da5233..106697f 100644
--- a/app/src/main/java/dev/spikeysanju/einsen/view/add/AddTaskScreen.kt
+++ b/app/src/main/java/dev/spikeysanju/einsen/view/add/AddTaskScreen.kt
@@ -19,6 +19,7 @@
package dev.spikeysanju.einsen.view.add
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -60,6 +61,8 @@ import dev.spikeysanju.einsen.components.EinsenInputTextField
import dev.spikeysanju.einsen.components.EinsenStepSlider
import dev.spikeysanju.einsen.components.EmojiPlaceHolder
import dev.spikeysanju.einsen.components.PrimaryButton
+import dev.spikeysanju.einsen.components.showDatePicker
+import dev.spikeysanju.einsen.components.showTimePicker
import dev.spikeysanju.einsen.model.task.Priority
import dev.spikeysanju.einsen.model.task.task
import dev.spikeysanju.einsen.navigation.MainActions
@@ -67,9 +70,13 @@ import dev.spikeysanju.einsen.ui.theme.Sailec
import dev.spikeysanju.einsen.ui.theme.einsenColors
import dev.spikeysanju.einsen.ui.theme.typography
import dev.spikeysanju.einsen.utils.calculatePriority
+import dev.spikeysanju.einsen.utils.formatCalendar
+import dev.spikeysanju.einsen.utils.getCalendar
import dev.spikeysanju.einsen.utils.showToast
import dev.spikeysanju.einsen.view.viewmodel.MainViewModel
+import dev.spikeysanju.einsen.workers.scheduleReminders
import kotlinx.coroutines.launch
+import java.util.*
@Composable
fun AddTaskScreen(
@@ -100,7 +107,7 @@ fun AddTaskScreen(
urgency = defaultUrgency
importance = defaultImportance
priority = Priority.IMPORTANT
- due = "18/12/1998"
+ due = ""
isCompleted = false
}
)
@@ -213,6 +220,27 @@ fun AddTaskScreen(
}
}
+ // Due Date Time
+ item {
+ Spacer(modifier = modifier.height(24.dp))
+ EinsenInputTextField(
+ modifier = Modifier.clickable {
+ val calendar = getCalendar(taskState.due)
+ context.showDatePicker(calendar) {
+ calendar.set(Calendar.DAY_OF_MONTH, it.get(Calendar.DAY_OF_MONTH))
+ calendar.set(Calendar.MONTH, it.get(Calendar.MONTH))
+ calendar.set(Calendar.YEAR, it.get(Calendar.YEAR))
+ context.showTimePicker(calendar) { timeCalendar ->
+ taskState = taskState.copy(due = formatCalendar(timeCalendar))
+ }
+ }
+ },
+ title = stringResource(R.string.text_due_date_time),
+ value = taskState.due,
+ readOnly = true, enabled = false, {}
+ )
+ }
+
val titleStyle = TextStyle(
fontSize = 16.sp,
fontFamily = Sailec,
@@ -270,7 +298,7 @@ fun AddTaskScreen(
}
else -> {
viewModel.insertTask(taskState).run {
-
+ context.scheduleReminders(taskState)
showToast(context, "Task added successfully")
// log event to firebase
diff --git a/app/src/main/java/dev/spikeysanju/einsen/view/details/TaskDetailScreen.kt b/app/src/main/java/dev/spikeysanju/einsen/view/details/TaskDetailScreen.kt
index c98e741..fcf4c2f 100644
--- a/app/src/main/java/dev/spikeysanju/einsen/view/details/TaskDetailScreen.kt
+++ b/app/src/main/java/dev/spikeysanju/einsen/view/details/TaskDetailScreen.kt
@@ -75,6 +75,7 @@ import dev.spikeysanju.einsen.utils.viewstate.SingleViewState
import dev.spikeysanju.einsen.view.animationviewstate.AnimationViewState
import dev.spikeysanju.einsen.view.animationviewstate.ScreenState
import dev.spikeysanju.einsen.view.viewmodel.MainViewModel
+import dev.spikeysanju.einsen.workers.cancelReminder
import java.util.*
@Composable
@@ -84,6 +85,8 @@ fun TaskDetailsScreen(
action: MainActions
) {
+ val context = LocalContext.current
+
LaunchedEffect(key1 = Unit) {
// log event to firebase
val taskDetailsComposableBundle = bundleOf(
@@ -169,6 +172,7 @@ fun TaskDetailsScreen(
},
onDelete = {
viewModel.deleteTaskByID(taskState.id).run {
+ context.cancelReminder(taskState)
action.upPress.invoke().run {
// log event to firebase
val deleteTaskBundle = bundleOf(
diff --git a/app/src/main/java/dev/spikeysanju/einsen/view/edit/EditTaskScreen.kt b/app/src/main/java/dev/spikeysanju/einsen/view/edit/EditTaskScreen.kt
index 1f0936c..a5fd14a 100644
--- a/app/src/main/java/dev/spikeysanju/einsen/view/edit/EditTaskScreen.kt
+++ b/app/src/main/java/dev/spikeysanju/einsen/view/edit/EditTaskScreen.kt
@@ -19,6 +19,7 @@
package dev.spikeysanju.einsen.view.edit
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -61,6 +62,8 @@ import dev.spikeysanju.einsen.components.EinsenInputTextField
import dev.spikeysanju.einsen.components.EinsenStepSlider
import dev.spikeysanju.einsen.components.EmojiPlaceHolder
import dev.spikeysanju.einsen.components.PrimaryButton
+import dev.spikeysanju.einsen.components.showDatePicker
+import dev.spikeysanju.einsen.components.showTimePicker
import dev.spikeysanju.einsen.model.task.Priority
import dev.spikeysanju.einsen.model.task.task
import dev.spikeysanju.einsen.navigation.MainActions
@@ -68,12 +71,16 @@ import dev.spikeysanju.einsen.ui.theme.Sailec
import dev.spikeysanju.einsen.ui.theme.einsenColors
import dev.spikeysanju.einsen.ui.theme.typography
import dev.spikeysanju.einsen.utils.calculatePriority
+import dev.spikeysanju.einsen.utils.formatCalendar
+import dev.spikeysanju.einsen.utils.getCalendar
import dev.spikeysanju.einsen.utils.showToast
import dev.spikeysanju.einsen.utils.viewstate.SingleViewState
import dev.spikeysanju.einsen.view.animationviewstate.AnimationViewState
import dev.spikeysanju.einsen.view.animationviewstate.ScreenState
import dev.spikeysanju.einsen.view.viewmodel.MainViewModel
+import dev.spikeysanju.einsen.workers.scheduleReminders
import kotlinx.coroutines.launch
+import java.util.*
@Composable
fun EditTaskScreen(modifier: Modifier, viewModel: MainViewModel, actions: MainActions) {
@@ -97,7 +104,7 @@ fun EditTaskScreen(modifier: Modifier, viewModel: MainViewModel, actions: MainAc
var emojiState by remember { mutableStateOf("") }
var urgencyState by remember { mutableStateOf(0) }
var importanceState by remember { mutableStateOf(0) }
- var dueState by remember { mutableStateOf("18/12/1998") }
+ var dueState by remember { mutableStateOf("") }
var priorityState by remember { mutableStateOf(Priority.IMPORTANT) }
var isCompletedState by remember { mutableStateOf(false) }
var createdAtState by remember { mutableStateOf(0L) }
@@ -286,6 +293,27 @@ fun EditTaskScreen(modifier: Modifier, viewModel: MainViewModel, actions: MainAc
}
}
+ // Due Date Time
+ item {
+ Spacer(modifier = modifier.height(24.dp))
+ EinsenInputTextField(
+ modifier = Modifier.clickable {
+ val calendar = getCalendar(dueState)
+ context.showDatePicker(calendar) {
+ calendar.set(Calendar.DAY_OF_MONTH, it.get(Calendar.DAY_OF_MONTH))
+ calendar.set(Calendar.MONTH, it.get(Calendar.MONTH))
+ calendar.set(Calendar.YEAR, it.get(Calendar.YEAR))
+ context.showTimePicker(calendar) { timeCalendar ->
+ dueState = formatCalendar(timeCalendar)
+ }
+ }
+ },
+ title = stringResource(R.string.text_due_date_time),
+ value = dueState,
+ readOnly = true, enabled = false, {}
+ )
+ }
+
val titleStyle = TextStyle(
fontSize = 16.sp,
fontFamily = Sailec,
@@ -360,6 +388,7 @@ fun EditTaskScreen(modifier: Modifier, viewModel: MainViewModel, actions: MainAc
else -> {
viewModel.insertTask(task).run {
+ context.scheduleReminders(task)
showToast(context, "Task updated successfully!")
// log event to firebase
val updateTaskBundle = bundleOf(
diff --git a/app/src/main/java/dev/spikeysanju/einsen/workers/MyWorkManager.kt b/app/src/main/java/dev/spikeysanju/einsen/workers/MyWorkManager.kt
new file mode 100644
index 0000000..ee7d4ab
--- /dev/null
+++ b/app/src/main/java/dev/spikeysanju/einsen/workers/MyWorkManager.kt
@@ -0,0 +1,53 @@
+/*
+ *
+ * * Copyright 2021 Spikey Sanju
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * https://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ *
+ */
+
+package dev.spikeysanju.einsen.workers
+
+import android.content.Context
+import androidx.work.Data
+import androidx.work.ExistingWorkPolicy
+import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkManager
+import dev.spikeysanju.einsen.model.task.Task
+import dev.spikeysanju.einsen.utils.getCalendar
+import java.util.concurrent.TimeUnit
+
+fun Context.scheduleReminders(task: Task) {
+ val calendar = getCalendar(task.due)
+ val initialDelay =
+ (calendar.timeInMillis - TimeUnit.HOURS.toMillis(1)) - System.currentTimeMillis()
+ if (initialDelay > 0) {
+ val data = Data.Builder()
+ .putInt(ARG_ID, task.id)
+ .build()
+
+ val worker = OneTimeWorkRequest.Builder(ReminderWorker::class.java)
+ .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS)
+ .setInputData(data)
+ .build()
+ WorkManager.getInstance(this)
+ .enqueueUniqueWork(task.getWorkerId(), ExistingWorkPolicy.REPLACE, worker)
+ } else {
+ cancelReminder(task)
+ }
+}
+
+fun Context.cancelReminder(task: Task) {
+ WorkManager.getInstance(this).cancelUniqueWork(task.getWorkerId())
+}
diff --git a/app/src/main/java/dev/spikeysanju/einsen/workers/ReminderWorker.kt b/app/src/main/java/dev/spikeysanju/einsen/workers/ReminderWorker.kt
new file mode 100644
index 0000000..f47a406
--- /dev/null
+++ b/app/src/main/java/dev/spikeysanju/einsen/workers/ReminderWorker.kt
@@ -0,0 +1,83 @@
+/*
+ *
+ * * Copyright 2021 Spikey Sanju
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * https://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ *
+ */
+
+package dev.spikeysanju.einsen.workers
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.os.Build
+import androidx.core.app.NotificationCompat
+import androidx.hilt.work.HiltWorker
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dev.spikeysanju.einsen.R
+import dev.spikeysanju.einsen.data.local.db.TaskDao
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+const val ARG_ID = "arg_id"
+
+@HiltWorker
+class ReminderWorker @AssistedInject constructor(
+ @Assisted val context: Context,
+ @Assisted val workerParams: WorkerParameters,
+ private val taskDao: TaskDao,
+ private val notificationManager: NotificationManager
+) :
+ CoroutineWorker(context, workerParams) {
+ override suspend fun doWork(): Result {
+ val reminderId = workerParams.inputData.getInt(ARG_ID, -1)
+ if (reminderId == -1) {
+ return Result.failure()
+ }
+
+ withContext(Dispatchers.IO) {
+
+ val task = taskDao.findTaskByID(reminderId)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ context.getString(R.string.reminder_channel_id),
+ context.getString(R.string.reminder_channel_name),
+ NotificationManager.IMPORTANCE_HIGH,
+ )
+ channel.lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
+ channel.enableVibration(true)
+ channel.setBypassDnd(true)
+ notificationManager.createNotificationChannel(channel)
+ }
+
+ val builder =
+ NotificationCompat.Builder(context, context.getString(R.string.reminder_channel_id))
+ builder.setContentTitle(task.title)
+ builder.setSmallIcon(R.drawable.einsen_logo)
+ builder.setCategory(NotificationCompat.CATEGORY_ALARM)
+ builder.setContentText("Task due time in 1 hour")
+ builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ builder.priority = NotificationCompat.PRIORITY_MAX
+ builder.setAutoCancel(true)
+
+ notificationManager.notify(task.id, builder.build())
+ }
+ return Result.success()
+ }
+}
diff --git a/app/src/main/res/color/text_btn_text_color_selector.xml b/app/src/main/res/color/text_btn_text_color_selector.xml
new file mode 100644
index 0000000..42111d0
--- /dev/null
+++ b/app/src/main/res/color/text_btn_text_color_selector.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 46f2783..a569cbd 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -25,4 +25,9 @@
#FF03DAC5
#FF000000
#FFFFFFFF
+
+ #EEEEEE
+ #EEEEEE
+ #F4F4F4
+ #F4F7FD
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a450948..5967df3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -56,6 +56,7 @@
Title
Description
Category
+ Due Date Time
Importance
Save Task
Urgency
@@ -82,4 +83,9 @@
Go Back!
Retry Search
+
+
+ reminders
+ Reminders
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 0bde567..1f1d991 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -24,9 +24,12 @@
- false
- - @color/purple_500
- - @color/purple_700
- - @color/white
+ - @color/white_100
+ - @color/white_200
+ - @color/black
+ - @color/black
+
+ - @style/ThemeOverlay.App.TimePicker
+
+
+
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index fce874f..9f56beb 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -27,6 +27,7 @@ buildscript {
val composeNavigationVersion by extra("2.4.0-alpha03")
val hiltComposeNavVersion by extra("1.0.0-alpha03")
val hiltVersion by extra("2.37")
+ val workerVersion by extra("2.7.1")
val hiltAndroidXVersion by extra("1.0.0-alpha03")
val roomVersion by extra("2.3.0")
val dataStoreVersion by extra("1.0.0-beta02")