Merge branch 'notificationImprovements' into 'master'
Improvements to notifications Closes #307 and #306 See merge request pixeldroid/PixelDroid!418
This commit is contained in:
commit
b72b78619e
@ -34,9 +34,6 @@ android {
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
}
|
||||
lintOptions{
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/java'
|
||||
test.java.srcDirs += 'src/test/java'
|
||||
@ -96,6 +93,9 @@ android {
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-kapt'
|
||||
lint {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -108,15 +108,15 @@ dependencies {
|
||||
*/
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.4.0'
|
||||
implementation "androidx.browser:browser:1.4.0"
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.4.0'
|
||||
implementation 'androidx.paging:paging-runtime-ktx:3.1.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
|
||||
@ -126,18 +126,18 @@ dependencies {
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||
implementation 'androidx.fragment:fragment-ktx:1.4.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.4.1'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
||||
|
||||
// Use the most recent version of CameraX
|
||||
def cameraX_version = '1.0.2'
|
||||
implementation "androidx.camera:camera-core:${cameraX_version}"
|
||||
implementation "androidx.camera:camera-camera2:${cameraX_version}"
|
||||
def cameraX_version = '1.1.0-beta01'
|
||||
implementation "androidx.camera:camera-core:$cameraX_version"
|
||||
implementation "androidx.camera:camera-camera2:$cameraX_version"
|
||||
// CameraX Lifecycle library
|
||||
implementation "androidx.camera:camera-lifecycle:$cameraX_version"
|
||||
|
||||
// CameraX View class
|
||||
implementation 'androidx.camera:camera-view:1.0.0-alpha32'
|
||||
implementation "androidx.camera:camera-view:$cameraX_version"
|
||||
|
||||
def room_version = "2.4.1"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
|
@ -853,3 +853,69 @@
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/room#2.4.1
|
||||
- artifact: androidx.window:window:+
|
||||
name: window
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/window#1.0.0
|
||||
- artifact: com.android.support:animated-vector-drawable:+
|
||||
name: animated-vector-drawable
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:appcompat-v7:+
|
||||
name: appcompat-v7
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-annotations:+
|
||||
name: support-annotations
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-compat:+
|
||||
name: support-compat
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-core-ui:+
|
||||
name: support-core-ui
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-core-utils:+
|
||||
name: support-core-utils
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-fragment:+
|
||||
name: support-fragment
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-media-compat:+
|
||||
name: support-media-compat
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-v4:+
|
||||
name: support-v4
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-vector-drawable:+
|
||||
name: support-vector-drawable
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
File diff suppressed because one or more lines are too long
@ -9,6 +9,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
@ -31,6 +32,7 @@ import org.pixeldroid.app.utils.api.objects.Account
|
||||
import org.pixeldroid.app.utils.api.objects.Notification
|
||||
import org.pixeldroid.app.utils.api.objects.Status
|
||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||
import org.pixeldroid.app.utils.notificationsWorker.makeChannelGroupId
|
||||
|
||||
|
||||
/**
|
||||
@ -64,6 +66,16 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
with(NotificationManagerCompat.from(requireContext())) {
|
||||
// Cancel account notification group
|
||||
db.userDao().getActiveUser()?.let {
|
||||
cancel( makeChannelGroupId(it).hashCode())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* View Holder for a [Notification] RecyclerView list item.
|
||||
*/
|
||||
|
@ -4,6 +4,7 @@ import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.pixeldroid.app.MainActivity
|
||||
@ -83,7 +84,9 @@ class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceCha
|
||||
|
||||
//Hide Notification setting for Android versions where it doesn't work
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
preferenceScreen.removePreference(preferenceManager.findPreference("notification"))
|
||||
preferenceManager.findPreference<Preference?>("notification")?.let {
|
||||
preferenceScreen.removePreference(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import androidx.work.WorkerParameters
|
||||
import org.pixeldroid.app.MainActivity
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.posts.PostActivity
|
||||
import org.pixeldroid.app.posts.fromHtml
|
||||
import org.pixeldroid.app.utils.PixelDroidApplication
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI.Companion.apiForUser
|
||||
import org.pixeldroid.app.utils.api.objects.Notification
|
||||
@ -71,8 +72,7 @@ class NotificationsWorker(
|
||||
)
|
||||
|
||||
while (!newNotifications.isNullOrEmpty()
|
||||
&& newNotifications.map { it.created_at ?: Instant.MIN }
|
||||
.maxOrNull()!! > previouslyLatestNotification?.created_at ?: Instant.MIN
|
||||
&& newNotifications.maxOf { it.created_at ?: Instant.MIN } > previouslyLatestNotification?.created_at ?: Instant.MIN
|
||||
) {
|
||||
// Add to db
|
||||
val filteredNewNotifications: List<Notification> = newNotifications.filter {
|
||||
@ -83,6 +83,12 @@ class NotificationsWorker(
|
||||
|
||||
db.notificationDao().insertAll(filteredNewNotifications)
|
||||
|
||||
|
||||
//If multiple notifications, show summary of them
|
||||
if(filteredNewNotifications.size > 1){
|
||||
showNotificationSummary(filteredNewNotifications, uniqueUserId)
|
||||
}
|
||||
|
||||
// Launch new notifications
|
||||
filteredNewNotifications.forEach {
|
||||
showNotification(it, user, uniqueUserId)
|
||||
@ -106,6 +112,39 @@ class NotificationsWorker(
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun showNotificationSummary(notifications: List<Notification>, uniqueUserId: String) {
|
||||
val content = joinNames(
|
||||
applicationContext,
|
||||
notifications.mapNotNull { it.account?.getDisplayName() }
|
||||
)
|
||||
|
||||
val title: String = applicationContext.resources.getQuantityString(
|
||||
R.plurals.notification_title_summary,
|
||||
notifications.size,
|
||||
notifications.size
|
||||
)
|
||||
|
||||
val groupBuilder = NotificationCompat.Builder(applicationContext, makeChannelId(uniqueUserId, null))
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setGroupSummary(true)
|
||||
.setAutoCancel(true)
|
||||
.setGroup(uniqueUserId)
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(content))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(applicationContext, 0, Intent(applicationContext, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra(SHOW_NOTIFICATION_TAG, true)
|
||||
}, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
|
||||
with(NotificationManagerCompat.from(applicationContext)) {
|
||||
notify(uniqueUserId.hashCode(), groupBuilder.build())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun showNotification(
|
||||
notification: Notification,
|
||||
user: UserDatabaseEntity,
|
||||
@ -166,7 +205,7 @@ class NotificationsWorker(
|
||||
.setAutoCancel(true)
|
||||
|
||||
if (notification.type == mention || notification.type == comment || notification.type == poll){
|
||||
builder.setContentText(notification.status?.content)
|
||||
builder.setContentText(notification.status?.content?.let { fromHtml(it) })
|
||||
}
|
||||
|
||||
builder.setGroup(uniqueUserId)
|
||||
@ -237,7 +276,8 @@ fun makeNotificationChannels(context: Context, handle: String, channelGroupId: S
|
||||
|
||||
/**
|
||||
* [channelGroupId] is the id used to uniquely identify the group: for us it is a unique id
|
||||
* identifying a user consisting of the concatenation of the instance uri and user id.
|
||||
* identifying a user consisting of the concatenation of the instance uri and user id
|
||||
* (see [makeChannelGroupId]).
|
||||
*/
|
||||
private fun makeChannelId(channelGroupId: String, type: Notification.NotificationType?): String =
|
||||
(channelGroupId + (type ?: NotificationsWorker.otherNotificationType)).hashCode().toString()
|
||||
@ -265,3 +305,26 @@ fun removeNotificationChannelsFromAccount(context: Context, user: UserDatabaseEn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* BidiFormatter.unicodeWrap is insufficient in some cases (see Tusky#1921)
|
||||
* So we force isolation manually
|
||||
* https://unicode.org/reports/tr9/#Explicit_Directional_Isolates
|
||||
*/
|
||||
fun CharSequence.unicodeWrap(): String = "\u2068${this}\u2069"
|
||||
|
||||
private fun joinNames(context: Context, notifications: List<String>): String {
|
||||
return when {
|
||||
notifications.size > 3 -> {
|
||||
context.getString(R.string.notification_summary_large).format(
|
||||
*notifications.subList(0, 3).map { it.unicodeWrap() }.toTypedArray(),
|
||||
notifications.size - 3
|
||||
)
|
||||
}
|
||||
else -> context.getString( when(notifications.size) {
|
||||
2 -> R.string.notification_summary_small
|
||||
else /* ==3 */-> R.string.notification_summary_medium
|
||||
}).format(*notifications.map { it.unicodeWrap() }.toTypedArray())
|
||||
}
|
||||
}
|
18
app/src/main/res/drawable/notification_icon.xml
Normal file
18
app/src/main/res/drawable/notification_icon.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="4"
|
||||
android:scaleY="4"
|
||||
android:translateX="-53"
|
||||
android:translateY="-53">
|
||||
<path
|
||||
android:pathData="M23.0332,30.2725L27.5781,30.2725C31.8595,30.2725 35.3302,26.9088 35.3302,22.7596C35.3302,18.6103 31.8595,15.2467 27.5781,15.2467L21.0185,15.2467C18.5485,15.2467 16.5461,17.1872 16.5461,19.581L16.5461,36.451L23.0332,30.2725Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:strokeColor="#00000000"/>
|
||||
</group>
|
||||
</vector>
|
@ -64,6 +64,15 @@
|
||||
<string name="poll_notification_channel">"Polls"</string>
|
||||
<string name="other_notification_channel">"Other"</string>
|
||||
|
||||
<plurals name="notification_title_summary" >
|
||||
<item quantity="one">"%d new notification"</item>
|
||||
<item quantity="other">"%d new notifications"</item>
|
||||
</plurals>
|
||||
<string name="notification_summary_large">%1$s, %2$s, %3$s and %4$d others</string>
|
||||
<string name="notification_summary_medium">%1$s, %2$s, and %3$s</string>
|
||||
<string name="notification_summary_small">%1$s and %2$s</string>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Login page -->
|
||||
|
@ -7,7 +7,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||
classpath 'com.android.tools.build:gradle:7.1.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
Loading…
x
Reference in New Issue
Block a user