mirror of
https://github.com/Matte23/circolapp.git
synced 2025-12-06 07:29:10 +00:00
Add reminders
This commit is contained in:
@@ -30,6 +30,9 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".AlarmBroadcastReceiver" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Circolapp
|
||||
* Copyright (C) 2019 Matteo Schiff
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.underdesk.circolapp
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import net.underdesk.circolapp.data.AppDatabase
|
||||
import net.underdesk.circolapp.data.Circular
|
||||
|
||||
class AlarmBroadcastReceiver : BroadcastReceiver() {
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "net.underdesk.circolapp.REMINDER"
|
||||
const val CIRCULAR_ID = "circular_id"
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
object : Thread() {
|
||||
override fun run() {
|
||||
createNotificationChannel(context)
|
||||
val circular = AppDatabase.getInstance(context).circularDao().getCircular(
|
||||
intent.getLongExtra(
|
||||
CIRCULAR_ID, 0
|
||||
)
|
||||
)
|
||||
createNotification(
|
||||
context,
|
||||
circular
|
||||
)
|
||||
AppDatabase.getInstance(context).circularDao()
|
||||
.update(circular.apply { reminder = false })
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun createNotification(context: Context, circular: Circular) {
|
||||
val intent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
val pendingIntent: PendingIntent =
|
||||
PendingIntent.getActivity(context, 0, intent, 0)
|
||||
|
||||
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notifications_black_24dp)
|
||||
.setContentTitle(context.getString(R.string.notification_title_reminder))
|
||||
.setContentText(circular.name)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_REMINDER)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setGroup(CHANNEL_ID)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.bigText(circular.name)
|
||||
)
|
||||
|
||||
with(NotificationManagerCompat.from(context)) {
|
||||
notify(circular.id.toInt(), builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNotificationChannel(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val name = context.getString(R.string.channel_name_reminder)
|
||||
val descriptionText = context.getString(R.string.channel_description_reminder)
|
||||
val importance = NotificationManager.IMPORTANCE_HIGH
|
||||
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
|
||||
description = descriptionText
|
||||
}
|
||||
|
||||
val notificationManager: NotificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
package net.underdesk.circolapp.adapters
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
@@ -28,12 +29,15 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.item_circular.view.*
|
||||
import net.underdesk.circolapp.AlarmBroadcastReceiver
|
||||
import net.underdesk.circolapp.R
|
||||
import net.underdesk.circolapp.data.AppDatabase
|
||||
import net.underdesk.circolapp.data.Circular
|
||||
import net.underdesk.circolapp.fragments.NewReminderFragment
|
||||
|
||||
|
||||
class CircularLetterAdapter(private val circulars: List<Circular>) :
|
||||
@@ -123,6 +127,29 @@ class CircularLetterAdapter(private val circulars: List<Circular>) :
|
||||
}.start()
|
||||
}
|
||||
|
||||
holder.reminderButton.setOnClickListener {
|
||||
if (circulars[position].reminder) {
|
||||
object : Thread() {
|
||||
override fun run() {
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
circulars[position].id.toInt(),
|
||||
Intent(context, AlarmBroadcastReceiver::class.java),
|
||||
0
|
||||
)
|
||||
|
||||
pendingIntent.cancel()
|
||||
|
||||
AppDatabase.getInstance(context).circularDao()
|
||||
.update(circulars[position].apply { reminder = false })
|
||||
}
|
||||
}.start()
|
||||
} else {
|
||||
NewReminderFragment.create(circulars[position])
|
||||
.show((context as FragmentActivity).supportFragmentManager, "NewReminderDialog")
|
||||
}
|
||||
}
|
||||
|
||||
holder.collapseButton.setOnClickListener {
|
||||
collapsedItems = if (collapsedItems == position) {
|
||||
-1
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
|
||||
package net.underdesk.circolapp.data
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "circulars")
|
||||
data class Circular(
|
||||
@PrimaryKey val id: Long,
|
||||
@@ -29,9 +32,10 @@ data class Circular(
|
||||
val url: String,
|
||||
val date: String,
|
||||
var favourite: Boolean = false,
|
||||
var reminder: Boolean = false,
|
||||
val attachmentsNames: MutableList<String> = mutableListOf(),
|
||||
val attachmentsUrls: MutableList<String> = mutableListOf()
|
||||
) {
|
||||
) : Parcelable {
|
||||
companion object {
|
||||
fun generateFromString(string: String, url: String): Circular {
|
||||
val id = string.split(" ")[1]
|
||||
|
||||
@@ -26,9 +26,15 @@ interface CircularDao {
|
||||
@Query("SELECT * FROM circulars ORDER BY id DESC")
|
||||
fun getCirculars(): List<Circular>
|
||||
|
||||
@Query("SELECT * FROM circulars WHERE id = :id ORDER BY id DESC")
|
||||
fun getCircular(id: Long): Circular
|
||||
|
||||
@Query("SELECT * FROM circulars WHERE favourite ORDER BY id DESC")
|
||||
fun getFavourites(): LiveData<List<Circular>>
|
||||
|
||||
@Query("SELECT * FROM circulars WHERE reminder ORDER BY id DESC")
|
||||
fun getReminders(): LiveData<List<Circular>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAll(circulars: List<Circular>)
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Circolapp
|
||||
* Copyright (C) 2019 Matteo Schiff
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.underdesk.circolapp.fragments
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.app.AlarmManagerCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import kotlinx.android.synthetic.main.dialog_reminder.*
|
||||
import net.underdesk.circolapp.AlarmBroadcastReceiver
|
||||
import net.underdesk.circolapp.R
|
||||
import net.underdesk.circolapp.data.AppDatabase
|
||||
import net.underdesk.circolapp.data.Circular
|
||||
import java.util.*
|
||||
|
||||
|
||||
class NewReminderFragment : DialogFragment() {
|
||||
|
||||
companion object {
|
||||
private const val CIRCULAR = "circular"
|
||||
|
||||
fun create(circular: Circular): NewReminderFragment {
|
||||
val dialog = NewReminderFragment()
|
||||
dialog.arguments = Bundle().apply {
|
||||
putParcelable(CIRCULAR, circular)
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
|
||||
var dateNotChosen = true
|
||||
var circular: Circular? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
circular = arguments?.getParcelable(CIRCULAR)
|
||||
return inflater.inflate(R.layout.dialog_reminder, container)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
dialog_time_picker.setIs24HourView(true)
|
||||
|
||||
dialog_ok_button.setOnClickListener { next() }
|
||||
dialog_cancel_button.setOnClickListener { dismiss() }
|
||||
}
|
||||
|
||||
fun next() {
|
||||
if (dateNotChosen) {
|
||||
dialog_date_picker.visibility = View.GONE
|
||||
dialog_time_picker.visibility = View.VISIBLE
|
||||
dialog_ok_button.text = getString(R.string.dialog_ok)
|
||||
dateNotChosen = false
|
||||
} else {
|
||||
val calendar = Calendar.getInstance()
|
||||
val hour = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
dialog_time_picker.hour
|
||||
} else {
|
||||
dialog_time_picker.currentHour
|
||||
}
|
||||
|
||||
val minute = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
dialog_time_picker.minute
|
||||
} else {
|
||||
dialog_time_picker.currentMinute
|
||||
}
|
||||
|
||||
calendar.set(
|
||||
dialog_date_picker.year,
|
||||
dialog_date_picker.month,
|
||||
dialog_date_picker.dayOfMonth,
|
||||
hour,
|
||||
minute
|
||||
)
|
||||
|
||||
object : Thread() {
|
||||
override fun run() {
|
||||
context?.let { context ->
|
||||
circular?.let { circular ->
|
||||
AppDatabase.getInstance(context).circularDao()
|
||||
.update(circular.apply { reminder = true })
|
||||
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
circular.id.toInt(),
|
||||
Intent(context, AlarmBroadcastReceiver::class.java)
|
||||
.putExtra(AlarmBroadcastReceiver.CIRCULAR_ID, circular.id),
|
||||
0
|
||||
)
|
||||
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
context.getSystemService(Context.ALARM_SERVICE) as AlarmManager,
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
calendar.timeInMillis,
|
||||
pendingIntent
|
||||
)
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,23 +25,29 @@ import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.synthetic.main.fragment_circular_letters.view.*
|
||||
import net.underdesk.circolapp.R
|
||||
import net.underdesk.circolapp.viewmodels.CircularLetterViewModel
|
||||
import net.underdesk.circolapp.adapters.CircularLetterAdapter
|
||||
import net.underdesk.circolapp.viewmodels.RemindersViewModel
|
||||
|
||||
class RemindersFragment : Fragment() {
|
||||
|
||||
private lateinit var circularLetterViewModel: CircularLetterViewModel
|
||||
private lateinit var remindersViewModel: RemindersViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
circularLetterViewModel =
|
||||
ViewModelProviders.of(this).get(CircularLetterViewModel::class.java)
|
||||
val root = inflater.inflate(R.layout.fragment_circular_letters, container, false)
|
||||
circularLetterViewModel.circulars.observe(this, Observer {
|
||||
|
||||
root.circulars_list.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
remindersViewModel =
|
||||
ViewModelProviders.of(this).get(RemindersViewModel::class.java)
|
||||
remindersViewModel.circulars.observe(this, Observer {
|
||||
root.circulars_list.adapter = CircularLetterAdapter(it)
|
||||
})
|
||||
return root
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Circolapp
|
||||
* Copyright (C) 2019 Matteo Schiff
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.underdesk.circolapp.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import net.underdesk.circolapp.data.AppDatabase
|
||||
import net.underdesk.circolapp.data.Circular
|
||||
|
||||
class RemindersViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val circulars: LiveData<List<Circular>> =
|
||||
AppDatabase.getInstance(getApplication()).circularDao().getReminders()
|
||||
}
|
||||
@@ -144,8 +144,8 @@ class PollWork(appContext: Context, workerParams: WorkerParameters) :
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val name = applicationContext.getString(R.string.channel_name)
|
||||
val descriptionText = applicationContext.getString(R.string.channel_description)
|
||||
val name = applicationContext.getString(R.string.channel_name_new)
|
||||
val descriptionText = applicationContext.getString(R.string.channel_description_new)
|
||||
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
|
||||
description = descriptionText
|
||||
|
||||
52
app/src/main/res/layout/dialog_reminder.xml
Normal file
52
app/src/main/res/layout/dialog_reminder.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/dialog_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<DatePicker
|
||||
android:id="@+id/dialog_date_picker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:datePickerMode="calendar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TimePicker
|
||||
android:id="@+id/dialog_time_picker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/dialog_date_picker" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialog_cancel_button"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/dialog_cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/dialog_time_picker" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialog_ok_button"
|
||||
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/dialog_next"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/dialog_time_picker" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -9,8 +9,16 @@
|
||||
<string name="notification_summary">New circulars</string>
|
||||
<string name="notification_summary_text">%1$d new circulars</string>
|
||||
|
||||
<string name="channel_name">Circular letters</string>
|
||||
<string name="channel_description">Notify when your school issue a new circular letter</string>
|
||||
<string name="notification_title_reminder">Reminder</string>
|
||||
|
||||
<string name="channel_name_new">Circular letters</string>
|
||||
<string name="channel_description_new">Notify when your school issue a new circular letter</string>
|
||||
<string name="channel_name_reminder">Reminders</string>
|
||||
<string name="channel_description_reminder">Reminder linked with circular letter</string>
|
||||
|
||||
<string name="dialog_ok">OK</string>
|
||||
<string name="dialog_next">Next</string>
|
||||
<string name="dialog_cancel">Cancel</string>
|
||||
|
||||
<string name="snackbar_connection_not_available">Network not available. Results may be outdated</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user