diff --git a/app/build.gradle b/app/build.gradle index 330d85c..fdb72bf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,9 @@ android { kotlinOptions { jvmTarget = "1.8" } + androidExtensions { + experimental = true + } } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e40243f..e23c585 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/net/underdesk/circolapp/AlarmBroadcastReceiver.kt b/app/src/main/java/net/underdesk/circolapp/AlarmBroadcastReceiver.kt new file mode 100644 index 0000000..2c4c5e6 --- /dev/null +++ b/app/src/main/java/net/underdesk/circolapp/AlarmBroadcastReceiver.kt @@ -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 . + */ + +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) + } + } +} diff --git a/app/src/main/java/net/underdesk/circolapp/adapters/CircularLetterAdapter.kt b/app/src/main/java/net/underdesk/circolapp/adapters/CircularLetterAdapter.kt index 04eeecc..f06cbb0 100644 --- a/app/src/main/java/net/underdesk/circolapp/adapters/CircularLetterAdapter.kt +++ b/app/src/main/java/net/underdesk/circolapp/adapters/CircularLetterAdapter.kt @@ -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) : @@ -123,6 +127,29 @@ class CircularLetterAdapter(private val circulars: List) : }.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 diff --git a/app/src/main/java/net/underdesk/circolapp/data/Circular.kt b/app/src/main/java/net/underdesk/circolapp/data/Circular.kt index afe1696..23c626b 100644 --- a/app/src/main/java/net/underdesk/circolapp/data/Circular.kt +++ b/app/src/main/java/net/underdesk/circolapp/data/Circular.kt @@ -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 = mutableListOf(), val attachmentsUrls: MutableList = mutableListOf() -) { +) : Parcelable { companion object { fun generateFromString(string: String, url: String): Circular { val id = string.split(" ")[1] diff --git a/app/src/main/java/net/underdesk/circolapp/data/CircularDao.kt b/app/src/main/java/net/underdesk/circolapp/data/CircularDao.kt index 6be0377..5e1eaad 100644 --- a/app/src/main/java/net/underdesk/circolapp/data/CircularDao.kt +++ b/app/src/main/java/net/underdesk/circolapp/data/CircularDao.kt @@ -26,9 +26,15 @@ interface CircularDao { @Query("SELECT * FROM circulars ORDER BY id DESC") fun getCirculars(): List + @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> + @Query("SELECT * FROM circulars WHERE reminder ORDER BY id DESC") + fun getReminders(): LiveData> + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(circulars: List) diff --git a/app/src/main/java/net/underdesk/circolapp/fragments/NewReminderFragment.kt b/app/src/main/java/net/underdesk/circolapp/fragments/NewReminderFragment.kt new file mode 100644 index 0000000..f3ea476 --- /dev/null +++ b/app/src/main/java/net/underdesk/circolapp/fragments/NewReminderFragment.kt @@ -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 . + */ + +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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/underdesk/circolapp/fragments/RemindersFragment.kt b/app/src/main/java/net/underdesk/circolapp/fragments/RemindersFragment.kt index cbec325..5e618ac 100644 --- a/app/src/main/java/net/underdesk/circolapp/fragments/RemindersFragment.kt +++ b/app/src/main/java/net/underdesk/circolapp/fragments/RemindersFragment.kt @@ -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 } diff --git a/app/src/main/java/net/underdesk/circolapp/viewmodels/RemindersViewModel.kt b/app/src/main/java/net/underdesk/circolapp/viewmodels/RemindersViewModel.kt new file mode 100644 index 0000000..827b743 --- /dev/null +++ b/app/src/main/java/net/underdesk/circolapp/viewmodels/RemindersViewModel.kt @@ -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 . + */ + +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> = + AppDatabase.getInstance(getApplication()).circularDao().getReminders() +} \ No newline at end of file diff --git a/app/src/main/java/net/underdesk/circolapp/works/PollWork.kt b/app/src/main/java/net/underdesk/circolapp/works/PollWork.kt index a7d94be..d7e769f 100644 --- a/app/src/main/java/net/underdesk/circolapp/works/PollWork.kt +++ b/app/src/main/java/net/underdesk/circolapp/works/PollWork.kt @@ -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 diff --git a/app/src/main/res/layout/dialog_reminder.xml b/app/src/main/res/layout/dialog_reminder.xml new file mode 100644 index 0000000..8bf6262 --- /dev/null +++ b/app/src/main/res/layout/dialog_reminder.xml @@ -0,0 +1,52 @@ + + + + + + + +