diff options
-rw-r--r-- | app/src/main/java/org/pacien/tincapp/activities/start/ErrorNotificationFragment.kt | 60 | ||||
-rw-r--r-- | app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt | 81 | ||||
-rw-r--r-- | app/src/main/res/layout/start_activity.xml | 6 | ||||
-rw-r--r-- | app/src/main/res/layout/start_error_notification.xml | 69 | ||||
-rw-r--r-- | app/src/main/res/values/colors.xml | 3 | ||||
-rw-r--r-- | app/src/main/res/values/styles.xml | 6 | ||||
-rw-r--r-- | changelog.md | 2 |
7 files changed, 179 insertions, 48 deletions
diff --git a/app/src/main/java/org/pacien/tincapp/activities/start/ErrorNotificationFragment.kt b/app/src/main/java/org/pacien/tincapp/activities/start/ErrorNotificationFragment.kt new file mode 100644 index 0000000..ed60d63 --- /dev/null +++ b/app/src/main/java/org/pacien/tincapp/activities/start/ErrorNotificationFragment.kt | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon | ||
3 | * Copyright (C) 2017-2023 Pacien TRAN-GIRARD | ||
4 | * | ||
5 | * This program is free software: you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation, either version 3 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | */ | ||
18 | |||
19 | package org.pacien.tincapp.activities.start | ||
20 | |||
21 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener | ||
22 | import android.os.Bundle | ||
23 | import android.view.LayoutInflater | ||
24 | import android.view.View | ||
25 | import android.view.ViewGroup | ||
26 | import org.pacien.tincapp.activities.BaseFragment | ||
27 | import org.pacien.tincapp.context.App | ||
28 | import org.pacien.tincapp.context.AppNotificationManager | ||
29 | import org.pacien.tincapp.databinding.StartErrorNotificationBinding | ||
30 | |||
31 | /** | ||
32 | * @author pacien | ||
33 | */ | ||
34 | class ErrorNotificationFragment : BaseFragment() { | ||
35 | private val notificationManager by lazy { AppNotificationManager(context!!) } | ||
36 | private val notificationListener = OnSharedPreferenceChangeListener { _, _ -> updateView() } | ||
37 | private lateinit var viewBinding: StartErrorNotificationBinding | ||
38 | |||
39 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { | ||
40 | viewBinding = StartErrorNotificationBinding.inflate(inflater, container, false) | ||
41 | updateView() | ||
42 | return viewBinding.root | ||
43 | } | ||
44 | |||
45 | override fun onResume() { | ||
46 | super.onResume() | ||
47 | notificationManager.registerListener(notificationListener) | ||
48 | } | ||
49 | |||
50 | override fun onPause() { | ||
51 | super.onPause() | ||
52 | notificationManager.unregisterListener(notificationListener) | ||
53 | } | ||
54 | |||
55 | private fun updateView() { | ||
56 | val maybeError = notificationManager.getError() | ||
57 | viewBinding.errorNotification = maybeError | ||
58 | viewBinding.openManualAction = { App.openURL(maybeError?.manualLink!!) } | ||
59 | } | ||
60 | } | ||
diff --git a/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt b/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt index d6e21f5..29f72de 100644 --- a/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt +++ b/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt | |||
@@ -18,70 +18,59 @@ | |||
18 | 18 | ||
19 | package org.pacien.tincapp.context | 19 | package org.pacien.tincapp.context |
20 | 20 | ||
21 | import android.app.NotificationChannel | ||
22 | import android.app.NotificationManager | ||
23 | import android.content.Context | 21 | import android.content.Context |
24 | import android.content.Intent | 22 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener |
25 | import android.net.Uri | ||
26 | import android.os.Build | ||
27 | import androidx.annotation.RequiresApi | ||
28 | import androidx.core.app.NotificationCompat | ||
29 | import androidx.core.app.NotificationManagerCompat | ||
30 | import org.pacien.tincapp.R | ||
31 | import org.pacien.tincapp.utils.PendingIntentUtils | ||
32 | 23 | ||
33 | /** | 24 | /** |
34 | * @author pacien | 25 | * @author pacien |
35 | */ | 26 | */ |
36 | class AppNotificationManager(private val context: Context) { | 27 | class AppNotificationManager(private val context: Context) { |
28 | data class ErrorNotification( | ||
29 | val title: String, | ||
30 | val message: String, | ||
31 | val manualLink: String? | ||
32 | ) | ||
33 | |||
37 | companion object { | 34 | companion object { |
38 | private const val ERROR_CHANNEL_ID = "org.pacien.tincapp.notification.channels.error" | 35 | private val STORE_NAME = this::class.java.`package`!!.name |
39 | const val ERROR_NOTIFICATION_ID = 0 | 36 | private const val STORE_KEY_TITLE = "title" |
37 | private const val STORE_KEY_MESSAGE = "message" | ||
38 | private const val STORE_KEY_MANUAL_LINK = "manual_link" | ||
40 | } | 39 | } |
41 | 40 | ||
42 | init { | 41 | private val store by lazy { context.getSharedPreferences(STORE_NAME, Context.MODE_PRIVATE)!! } |
43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) registerChannels() | ||
44 | } | ||
45 | 42 | ||
46 | fun notifyError(title: String, message: String, manualLink: String? = null) { | 43 | fun getError(): ErrorNotification? { |
47 | val notification = NotificationCompat.Builder(context, ERROR_CHANNEL_ID) | 44 | if (!store.contains(STORE_KEY_TITLE)) return null; |
48 | .setSmallIcon(R.drawable.ic_warning_primary_24dp) | ||
49 | .setContentTitle(title) | ||
50 | .setContentText(message) | ||
51 | .setStyle(NotificationCompat.BigTextStyle().bigText(message)) | ||
52 | .setHighPriority() | ||
53 | .setAutoCancel(true) | ||
54 | .apply { if (manualLink != null) setManualLink(manualLink) } | ||
55 | .build() | ||
56 | 45 | ||
57 | NotificationManagerCompat.from(context) | 46 | return ErrorNotification( |
58 | .notify(ERROR_NOTIFICATION_ID, notification) | 47 | store.getString(STORE_KEY_TITLE, null)!!, |
48 | store.getString(STORE_KEY_MESSAGE, null)!!, | ||
49 | store.getString(STORE_KEY_MANUAL_LINK, null) | ||
50 | ) | ||
59 | } | 51 | } |
60 | 52 | ||
61 | fun dismissAll() { | 53 | fun notifyError(title: String, message: String, manualLink: String? = null) { |
62 | NotificationManagerCompat.from(context).cancelAll() | 54 | store |
55 | .edit() | ||
56 | .putString(STORE_KEY_TITLE, title) | ||
57 | .putString(STORE_KEY_MESSAGE, message) | ||
58 | .putString(STORE_KEY_MANUAL_LINK, manualLink) | ||
59 | .apply() | ||
63 | } | 60 | } |
64 | 61 | ||
65 | @RequiresApi(Build.VERSION_CODES.O) | 62 | fun dismissAll() { |
66 | private fun registerChannels() { | 63 | store |
67 | context.getSystemService(NotificationManager::class.java) | 64 | .edit() |
68 | .apply { | 65 | .clear() |
69 | createNotificationChannel(NotificationChannel( | 66 | .apply() |
70 | ERROR_CHANNEL_ID, | ||
71 | context.getString(R.string.notification_error_channel_name), | ||
72 | NotificationManager.IMPORTANCE_HIGH | ||
73 | )) | ||
74 | } | ||
75 | } | 67 | } |
76 | 68 | ||
77 | private fun NotificationCompat.Builder.setHighPriority() = apply { | 69 | fun registerListener(listener: OnSharedPreferenceChangeListener) { |
78 | priority = NotificationCompat.PRIORITY_MAX | 70 | store.registerOnSharedPreferenceChangeListener(listener) |
79 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) setDefaults(NotificationCompat.DEFAULT_SOUND) // force heads-up notification | ||
80 | } | 71 | } |
81 | 72 | ||
82 | private fun NotificationCompat.Builder.setManualLink(manualLink: String) = apply { | 73 | fun unregisterListener(listener: OnSharedPreferenceChangeListener) { |
83 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(manualLink)) | 74 | store.unregisterOnSharedPreferenceChangeListener(listener) |
84 | val pendingIntent = PendingIntentUtils.getActivity(context, 0, intent, 0) | ||
85 | addAction(R.drawable.ic_help_primary_24dp, context.getString(R.string.notification_error_action_open_manual), pendingIntent) | ||
86 | } | 75 | } |
87 | } | 76 | } |
diff --git a/app/src/main/res/layout/start_activity.xml b/app/src/main/res/layout/start_activity.xml index 2960711..f2069d5 100644 --- a/app/src/main/res/layout/start_activity.xml +++ b/app/src/main/res/layout/start_activity.xml | |||
@@ -31,6 +31,12 @@ | |||
31 | android:text="@string/start_network_list_warning_text"/> | 31 | android:text="@string/start_network_list_warning_text"/> |
32 | 32 | ||
33 | <fragment | 33 | <fragment |
34 | android:id="@+id/start_activity_error_notification_fragment" | ||
35 | android:name="org.pacien.tincapp.activities.start.ErrorNotificationFragment" | ||
36 | android:layout_width="match_parent" | ||
37 | android:layout_height="wrap_content"/> | ||
38 | |||
39 | <fragment | ||
34 | android:id="@+id/start_activity_network_list_fragment" | 40 | android:id="@+id/start_activity_network_list_fragment" |
35 | android:name="org.pacien.tincapp.activities.start.NetworkListFragment" | 41 | android:name="org.pacien.tincapp.activities.start.NetworkListFragment" |
36 | android:layout_width="match_parent" | 42 | android:layout_width="match_parent" |
diff --git a/app/src/main/res/layout/start_error_notification.xml b/app/src/main/res/layout/start_error_notification.xml new file mode 100644 index 0000000..3d |