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")