Notification tweaks: Grouping and Quick Reply button (#587)
* Added notification grouping and Quick Reply button * Legal stuff * Coding style * Check whether account still exists when sending a quick reply * Add "compose" button * Polish translation * Improve strings * Code style * Cancel notification when user hits "compose" button * Notification counter * Make sure to open ComposeActivity for notification recipient account * Add ability to request account switch when starting an activity
This commit is contained in:
parent
aa48acdbec
commit
e8c79cce65
11 changed files with 369 additions and 61 deletions
|
@ -101,6 +101,11 @@
|
||||||
|
|
||||||
<receiver android:name=".receiver.NotificationClearBroadcastReceiver" />
|
<receiver android:name=".receiver.NotificationClearBroadcastReceiver" />
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".receiver.SendStatusBroadcastReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
tools:targetApi="24"
|
tools:targetApi="24"
|
||||||
android:name="com.keylesspalace.tusky.service.TuskyTileService"
|
android:name="com.keylesspalace.tusky.service.TuskyTileService"
|
||||||
|
|
|
@ -88,8 +88,6 @@ public final class AccountActivity extends BaseActivity implements ActionButtonA
|
||||||
@Inject
|
@Inject
|
||||||
public MastodonApi mastodonApi;
|
public MastodonApi mastodonApi;
|
||||||
@Inject
|
@Inject
|
||||||
public AccountManager accountManager;
|
|
||||||
@Inject
|
|
||||||
public DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
|
public DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
|
||||||
|
|
||||||
private String accountId;
|
private String accountId;
|
||||||
|
|
|
@ -34,8 +34,13 @@ import com.keylesspalace.tusky.db.AccountEntity;
|
||||||
import com.keylesspalace.tusky.db.AccountManager;
|
import com.keylesspalace.tusky.db.AccountManager;
|
||||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public abstract class BaseActivity extends AppCompatActivity {
|
public abstract class BaseActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AccountManager accountManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -48,6 +53,11 @@ public abstract class BaseActivity extends AppCompatActivity {
|
||||||
String theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT);
|
String theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT);
|
||||||
ThemeUtils.setAppNightMode(theme, this);
|
ThemeUtils.setAppNightMode(theme, this);
|
||||||
|
|
||||||
|
long accountId = getIntent().getLongExtra("account", -1);
|
||||||
|
if (accountId != -1) {
|
||||||
|
accountManager.setActiveAccount(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
int style;
|
int style;
|
||||||
switch (preferences.getString("statusTextSize", "medium")) {
|
switch (preferences.getString("statusTextSize", "medium")) {
|
||||||
case "large":
|
case "large":
|
||||||
|
|
|
@ -167,8 +167,6 @@ public final class ComposeActivity
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MastodonApi mastodonApi;
|
public MastodonApi mastodonApi;
|
||||||
@Inject
|
|
||||||
public AccountManager accountManager;
|
|
||||||
|
|
||||||
private TextView replyTextView;
|
private TextView replyTextView;
|
||||||
private TextView replyContentTextView;
|
private TextView replyContentTextView;
|
||||||
|
|
|
@ -92,8 +92,6 @@ public class MainActivity extends BaseActivity implements ActionButtonActivity,
|
||||||
public MastodonApi mastodonApi;
|
public MastodonApi mastodonApi;
|
||||||
@Inject
|
@Inject
|
||||||
public DispatchingAndroidInjector<Fragment> fragmentInjector;
|
public DispatchingAndroidInjector<Fragment> fragmentInjector;
|
||||||
@Inject
|
|
||||||
public AccountManager accountManager;
|
|
||||||
|
|
||||||
private static int COMPOSE_RESULT = 1;
|
private static int COMPOSE_RESULT = 1;
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,10 @@ import dagger.android.ContributesAndroidInjector
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
abstract class ActivitiesModule {
|
abstract class ActivitiesModule {
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract fun contributesBaseActivity(): BaseActivity
|
||||||
|
|
||||||
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
|
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
|
||||||
abstract fun contributesMainActivity(): MainActivity
|
abstract fun contributesMainActivity(): MainActivity
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* Copyright 2018 Conny Duck
|
/* Copyright 2018 Jeremiasz Nelz <remi6397(a)gmail.com>
|
||||||
|
* Copyright 2018 Conny Duck
|
||||||
*
|
*
|
||||||
* This file is a part of Tusky.
|
* This file is a part of Tusky.
|
||||||
*
|
*
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky.di
|
package com.keylesspalace.tusky.di
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver
|
||||||
import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver
|
import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.android.ContributesAndroidInjector
|
import dagger.android.ContributesAndroidInjector
|
||||||
|
@ -22,5 +24,8 @@ import dagger.android.ContributesAndroidInjector
|
||||||
@Module
|
@Module
|
||||||
abstract class BroadcastReceiverModule {
|
abstract class BroadcastReceiverModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
|
|
||||||
|
abstract fun contributeSendStatusBroadcastReceiver() : SendStatusBroadcastReceiver
|
||||||
|
|
||||||
abstract fun contributeNotificationClearBroadcastReceiver() : NotificationClearBroadcastReceiver
|
abstract fun contributeNotificationClearBroadcastReceiver() : NotificationClearBroadcastReceiver
|
||||||
}
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
/* Copyright 2018 Jeremiasz Nelz <remi6397(a)gmail.com>
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky.receiver
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.support.v4.app.NotificationCompat
|
||||||
|
import android.support.v4.app.NotificationManagerCompat
|
||||||
|
import android.support.v4.app.RemoteInput
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.util.Log
|
||||||
|
import com.keylesspalace.tusky.ComposeActivity
|
||||||
|
import com.keylesspalace.tusky.R
|
||||||
|
import com.keylesspalace.tusky.db.AccountManager
|
||||||
|
import com.keylesspalace.tusky.entity.Status
|
||||||
|
import com.keylesspalace.tusky.service.SendTootService
|
||||||
|
import com.keylesspalace.tusky.util.NotificationHelper
|
||||||
|
import dagger.android.AndroidInjection
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val TAG = "SendStatusBR"
|
||||||
|
|
||||||
|
class SendStatusBroadcastReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var accountManager: AccountManager
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
AndroidInjection.inject(this, context)
|
||||||
|
|
||||||
|
val notificationId = intent.getIntExtra(NotificationHelper.KEY_NOTIFICATION_ID, -1)
|
||||||
|
val senderId = intent.getLongExtra(NotificationHelper.KEY_SENDER_ACCOUNT_ID, -1)
|
||||||
|
val senderIdentifier = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_IDENTIFIER)
|
||||||
|
val senderFullName = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_FULL_NAME)
|
||||||
|
val citedStatusId = intent.getStringExtra(NotificationHelper.KEY_CITED_STATUS_ID)
|
||||||
|
val visibility = intent.getSerializableExtra(NotificationHelper.KEY_VISIBILITY) as Status.Visibility
|
||||||
|
val spoiler = intent.getStringExtra(NotificationHelper.KEY_SPOILER)
|
||||||
|
val mentions = intent.getStringArrayExtra(NotificationHelper.KEY_MENTIONS)
|
||||||
|
val citedText = intent.getStringExtra(NotificationHelper.KEY_CITED_TEXT)
|
||||||
|
val localAuthorId = intent.getStringExtra(NotificationHelper.KEY_CITED_AUTHOR_LOCAL)
|
||||||
|
|
||||||
|
val account = accountManager.getAccountById(senderId)
|
||||||
|
|
||||||
|
val notificationManager = NotificationManagerCompat.from(context)
|
||||||
|
|
||||||
|
|
||||||
|
if (intent.action == NotificationHelper.REPLY_ACTION) {
|
||||||
|
|
||||||
|
val message = getReplyMessage(intent)
|
||||||
|
|
||||||
|
if (account == null) {
|
||||||
|
Log.w(TAG, "Account \"$senderId\" not found in database. Aborting quick reply!")
|
||||||
|
|
||||||
|
val builder = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MENTION + senderIdentifier)
|
||||||
|
.setSmallIcon(R.drawable.ic_notify)
|
||||||
|
.setColor(ContextCompat.getColor(context, (R.color.primary)))
|
||||||
|
.setGroup(senderFullName)
|
||||||
|
.setDefaults(0) // So it doesn't ring twice, notify only in Target callback
|
||||||
|
|
||||||
|
builder.setContentTitle(context.getString(R.string.error_generic))
|
||||||
|
builder.setContentText(context.getString(R.string.error_sender_account_gone))
|
||||||
|
|
||||||
|
builder.setSubText(senderFullName)
|
||||||
|
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||||
|
builder.setOnlyAlertOnce(true)
|
||||||
|
|
||||||
|
notificationManager.notify(notificationId, builder.build())
|
||||||
|
} else {
|
||||||
|
val text = mentions.joinToString(" ", postfix = " ") { "@$it" } + message.toString()
|
||||||
|
|
||||||
|
val sendIntent = SendTootService.sendTootIntent(
|
||||||
|
context,
|
||||||
|
text,
|
||||||
|
spoiler,
|
||||||
|
visibility,
|
||||||
|
false,
|
||||||
|
emptyList(),
|
||||||
|
emptyList(),
|
||||||
|
citedStatusId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null, account, 0)
|
||||||
|
|
||||||
|
context.startService(sendIntent)
|
||||||
|
|
||||||
|
val builder = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MENTION + senderIdentifier)
|
||||||
|
.setSmallIcon(R.drawable.ic_notify)
|
||||||
|
.setColor(ContextCompat.getColor(context, (R.color.primary)))
|
||||||
|
.setGroup(senderFullName)
|
||||||
|
.setDefaults(0) // So it doesn't ring twice, notify only in Target callback
|
||||||
|
|
||||||
|
builder.setContentTitle(context.getString(R.string.status_sent))
|
||||||
|
builder.setContentText(context.getString(R.string.status_sent_long))
|
||||||
|
|
||||||
|
builder.setSubText(senderFullName)
|
||||||
|
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||||
|
builder.setOnlyAlertOnce(true)
|
||||||
|
|
||||||
|
notificationManager.notify(notificationId, builder.build())
|
||||||
|
}
|
||||||
|
} else if (intent.action == NotificationHelper.COMPOSE_ACTION) {
|
||||||
|
|
||||||
|
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
||||||
|
|
||||||
|
notificationManager.cancel(notificationId)
|
||||||
|
|
||||||
|
accountManager.setActiveAccount(senderId)
|
||||||
|
|
||||||
|
val composeIntent = ComposeActivity.IntentBuilder()
|
||||||
|
.inReplyToId(citedStatusId)
|
||||||
|
.replyVisibility(visibility)
|
||||||
|
.contentWarning(spoiler)
|
||||||
|
.mentionedUsernames(Arrays.asList(*mentions))
|
||||||
|
.repyingStatusAuthor(localAuthorId)
|
||||||
|
.replyingStatusContent(citedText)
|
||||||
|
.build(context)
|
||||||
|
|
||||||
|
context.startActivity(composeIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getReplyMessage(intent: Intent): CharSequence {
|
||||||
|
val remoteInput = RemoteInput.getResultsFromIntent(intent)
|
||||||
|
|
||||||
|
return remoteInput.getCharSequence(NotificationHelper.KEY_REPLY)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
/* Copyright 2017 Andrew Dawson
|
/* Copyright 2018 Jeremiasz Nelz <remi6397(a)gmail.com>
|
||||||
|
* Copyright 2017 Andrew Dawson
|
||||||
*
|
*
|
||||||
* This file is a part of Tusky.
|
* This file is a part of Tusky.
|
||||||
*
|
*
|
||||||
|
@ -27,6 +28,8 @@ import android.os.Build;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.NotificationManagerCompat;
|
||||||
|
import android.support.v4.app.RemoteInput;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -34,10 +37,13 @@ import android.util.Log;
|
||||||
import com.keylesspalace.tusky.MainActivity;
|
import com.keylesspalace.tusky.MainActivity;
|
||||||
import com.keylesspalace.tusky.R;
|
import com.keylesspalace.tusky.R;
|
||||||
import com.keylesspalace.tusky.TuskyApplication;
|
import com.keylesspalace.tusky.TuskyApplication;
|
||||||
|
import com.keylesspalace.tusky.ViewThreadActivity;
|
||||||
import com.keylesspalace.tusky.db.AccountEntity;
|
import com.keylesspalace.tusky.db.AccountEntity;
|
||||||
import com.keylesspalace.tusky.db.AccountManager;
|
import com.keylesspalace.tusky.db.AccountManager;
|
||||||
import com.keylesspalace.tusky.entity.Notification;
|
import com.keylesspalace.tusky.entity.Notification;
|
||||||
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver;
|
import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver;
|
||||||
|
import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver;
|
||||||
import com.keylesspalace.tusky.view.RoundedTransformation;
|
import com.keylesspalace.tusky.view.RoundedTransformation;
|
||||||
import com.squareup.picasso.Picasso;
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
@ -46,10 +52,14 @@ import org.json.JSONException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class NotificationHelper {
|
public class NotificationHelper {
|
||||||
|
|
||||||
|
private static int notificationId = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* constants used in Intents
|
* constants used in Intents
|
||||||
*/
|
*/
|
||||||
|
@ -57,13 +67,39 @@ public class NotificationHelper {
|
||||||
|
|
||||||
private static final String TAG = "NotificationHelper";
|
private static final String TAG = "NotificationHelper";
|
||||||
|
|
||||||
|
public static final String REPLY_ACTION = "REPLY_ACTION";
|
||||||
|
|
||||||
|
public static final String COMPOSE_ACTION = "COMPOSE_ACTION";
|
||||||
|
|
||||||
|
public static final String KEY_REPLY = "KEY_REPLY";
|
||||||
|
|
||||||
|
public static final String KEY_SENDER_ACCOUNT_ID = "KEY_SENDER_ACCOUNT_ID";
|
||||||
|
|
||||||
|
public static final String KEY_SENDER_ACCOUNT_IDENTIFIER = "KEY_SENDER_ACCOUNT_IDENTIFIER";
|
||||||
|
|
||||||
|
public static final String KEY_SENDER_ACCOUNT_FULL_NAME = "KEY_SENDER_ACCOUNT_FULL_NAME";
|
||||||
|
|
||||||
|
public static final String KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID";
|
||||||
|
|
||||||
|
public static final String KEY_CITED_STATUS_ID = "KEY_CITED_STATUS_ID";
|
||||||
|
|
||||||
|
public static final String KEY_VISIBILITY = "KEY_VISIBILITY";
|
||||||
|
|
||||||
|
public static final String KEY_SPOILER = "KEY_SPOILER";
|
||||||
|
|
||||||
|
public static final String KEY_MENTIONS = "KEY_MENTIONS";
|
||||||
|
|
||||||
|
public static final String KEY_CITED_TEXT = "KEY_CITED_TEXT";
|
||||||
|
|
||||||
|
public static final String KEY_CITED_AUTHOR_LOCAL = "KEY_CITED_AUTHOR_LOCAL";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* notification channels used on Android O+
|
* notification channels used on Android O+
|
||||||
**/
|
**/
|
||||||
private static final String CHANNEL_MENTION = "CHANNEL_MENTION";
|
public static final String CHANNEL_MENTION = "CHANNEL_MENTION";
|
||||||
private static final String CHANNEL_FOLLOW = "CHANNEL_FOLLOW";
|
public static final String CHANNEL_FOLLOW = "CHANNEL_FOLLOW";
|
||||||
private static final String CHANNEL_BOOST = "CHANNEL_BOOST";
|
public static final String CHANNEL_BOOST = "CHANNEL_BOOST";
|
||||||
private static final String CHANNEL_FAVOURITE = " CHANNEL_FAVOURITE";
|
public static final String CHANNEL_FAVOURITE = "CHANNEL_FAVOURITE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a given Mastodon notification and either creates a new Android notification or updates
|
* Takes a given Mastodon notification and either creates a new Android notification or updates
|
||||||
|
@ -104,75 +140,172 @@ public class NotificationHelper {
|
||||||
|
|
||||||
account.setActiveNotifications(currentNotifications.toString());
|
account.setActiveNotifications(currentNotifications.toString());
|
||||||
|
|
||||||
//no need to save account, this will be done in the calling function
|
// Notification group member
|
||||||
|
// =========================
|
||||||
|
final NotificationCompat.Builder builder = newNotification(context, body, account, false);
|
||||||
|
|
||||||
Intent resultIntent = new Intent(context, MainActivity.class);
|
notificationId++;
|
||||||
resultIntent.putExtra(ACCOUNT_ID, account.getId());
|
|
||||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
|
||||||
stackBuilder.addParentStack(MainActivity.class);
|
|
||||||
stackBuilder.addNextIntent(resultIntent);
|
|
||||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent((int) account.getId(),
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
|
|
||||||
Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class);
|
builder.setContentTitle(titleForType(context, body))
|
||||||
deleteIntent.putExtra(ACCOUNT_ID, account.getId());
|
.setContentText(bodyForType(body));
|
||||||
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, (int) account.getId(), deleteIntent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
|
|
||||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getChannelId(account, body))
|
if (body.getType() == Notification.Type.MENTION) {
|
||||||
.setSmallIcon(R.drawable.ic_notify)
|
builder.setStyle(new NotificationCompat.BigTextStyle()
|
||||||
.setContentIntent(resultPendingIntent)
|
.bigText(bodyForType(body)));
|
||||||
.setDeleteIntent(deletePendingIntent)
|
}
|
||||||
.setColor(ContextCompat.getColor(context, (R.color.primary)))
|
|
||||||
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback
|
|
||||||
|
|
||||||
setupPreferences(account, builder);
|
//load the avatar synchronously
|
||||||
|
Bitmap accountAvatar;
|
||||||
|
try {
|
||||||
|
accountAvatar = Picasso.with(context)
|
||||||
|
.load(body.getAccount().getAvatar())
|
||||||
|
.transform(new RoundedTransformation(20))
|
||||||
|
.get();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(TAG, "error loading account avatar", e);
|
||||||
|
accountAvatar = BitmapFactory.decodeResource(context.getResources(), R.drawable.avatar_default);
|
||||||
|
}
|
||||||
|
|
||||||
if (currentNotifications.length() == 1) {
|
builder.setLargeIcon(accountAvatar);
|
||||||
builder.setContentTitle(titleForType(context, body))
|
|
||||||
.setContentText(bodyForType(body));
|
|
||||||
|
|
||||||
if (body.getType() == Notification.Type.MENTION) {
|
// Reply to mention action; RemoteInput is available from KitKat Watch, but buttons are available from Nougat
|
||||||
builder.setStyle(new NotificationCompat.BigTextStyle()
|
if (body.getType() == Notification.Type.MENTION
|
||||||
.bigText(bodyForType(body)));
|
&& android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
}
|
RemoteInput replyRemoteInput = new RemoteInput.Builder(KEY_REPLY)
|
||||||
|
.setLabel(context.getString(R.string.label_quick_reply))
|
||||||
|
.build();
|
||||||
|
|
||||||
//load the avatar synchronously
|
PendingIntent quickReplyPendingIntent = getStatusReplyIntent(REPLY_ACTION, context, body, account);
|
||||||
Bitmap accountAvatar;
|
|
||||||
try {
|
|
||||||
accountAvatar = Picasso.with(context)
|
|
||||||
.load(body.getAccount().getAvatar())
|
|
||||||
.transform(new RoundedTransformation(20))
|
|
||||||
.get();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.d(TAG, "error loading account avatar", e);
|
|
||||||
accountAvatar = BitmapFactory.decodeResource(context.getResources(), R.drawable.avatar_default);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.setLargeIcon(accountAvatar);
|
NotificationCompat.Action quickReplyAction =
|
||||||
|
new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp,
|
||||||
|
context.getString(R.string.action_quick_reply), quickReplyPendingIntent)
|
||||||
|
.addRemoteInput(replyRemoteInput)
|
||||||
|
.build();
|
||||||
|
|
||||||
} else {
|
builder.addAction(quickReplyAction);
|
||||||
|
|
||||||
|
PendingIntent composePendingIntent = getStatusReplyIntent(COMPOSE_ACTION, context, body, account);
|
||||||
|
|
||||||
|
NotificationCompat.Action composeAction =
|
||||||
|
new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp,
|
||||||
|
context.getString(R.string.action_compose_shortcut), composePendingIntent)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
builder.addAction(composeAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setSubText(account.getFullName());
|
||||||
|
builder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
|
||||||
|
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
|
||||||
|
builder.setOnlyAlertOnce(true);
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
// =======
|
||||||
|
final NotificationCompat.Builder summaryBuilder = newNotification(context, body, account, true);
|
||||||
|
|
||||||
|
if (currentNotifications.length() != 1) {
|
||||||
try {
|
try {
|
||||||
String title = context.getString(R.string.notification_title_summary, currentNotifications.length());
|
String title = context.getString(R.string.notification_title_summary, currentNotifications.length());
|
||||||
String text = joinNames(context, currentNotifications);
|
String text = joinNames(context, currentNotifications);
|
||||||
builder.setContentTitle(title)
|
summaryBuilder.setContentTitle(title)
|
||||||
.setContentText(text);
|
.setContentText(text);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.d(TAG, Log.getStackTraceString(e));
|
Log.d(TAG, Log.getStackTraceString(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setSubText(account.getFullName());
|
summaryBuilder.setSubText(account.getFullName());
|
||||||
|
summaryBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
|
||||||
|
summaryBuilder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
|
||||||
|
summaryBuilder.setOnlyAlertOnce(true);
|
||||||
|
summaryBuilder.setGroupSummary(true);
|
||||||
|
|
||||||
builder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
|
|
||||||
|
|
||||||
builder.setOnlyAlertOnce(true);
|
|
||||||
|
|
||||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
notificationManager.notify((int) account.getId(), builder.build());
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
if (currentNotifications.length() == 1) {
|
||||||
|
notificationManager.notify((int) account.getId(), builder.setGroupSummary(true).build());
|
||||||
|
} else {
|
||||||
|
notificationManager.notify((int) account.getId(), summaryBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NotificationCompat.Builder newNotification(Context context, Notification body, AccountEntity account, boolean summary) {
|
||||||
|
Intent summaryResultIntent = new Intent(context, MainActivity.class);
|
||||||
|
summaryResultIntent.putExtra(ACCOUNT_ID, account.getId());
|
||||||
|
TaskStackBuilder summaryStackBuilder = TaskStackBuilder.create(context);
|
||||||
|
summaryStackBuilder.addParentStack(MainActivity.class);
|
||||||
|
summaryStackBuilder.addNextIntent(summaryResultIntent);
|
||||||
|
|
||||||
|
PendingIntent summaryResultPendingIntent = summaryStackBuilder.getPendingIntent(notificationId,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
// we have to switch account here
|
||||||
|
Intent eventResultIntent = new Intent(context, ViewThreadActivity.class);
|
||||||
|
eventResultIntent.putExtra("account", account.getId());
|
||||||
|
eventResultIntent.putExtra("id", body.getStatus().getId());
|
||||||
|
TaskStackBuilder eventStackBuilder = TaskStackBuilder.create(context);
|
||||||
|
eventStackBuilder.addParentStack(ViewThreadActivity.class);
|
||||||
|
eventStackBuilder.addNextIntent(eventResultIntent);
|
||||||
|
|
||||||
|
PendingIntent eventResultPendingIntent = eventStackBuilder.getPendingIntent((int) account.getId(),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class);
|
||||||
|
deleteIntent.putExtra(ACCOUNT_ID, account.getId());
|
||||||
|
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, summary ? (int) account.getId() : notificationId, deleteIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getChannelId(account, body))
|
||||||
|
.setSmallIcon(R.drawable.ic_notify)
|
||||||
|
.setContentIntent(summary ? summaryResultPendingIntent : eventResultPendingIntent)
|
||||||
|
.setDeleteIntent(deletePendingIntent)
|
||||||
|
.setColor(ContextCompat.getColor(context, (R.color.primary)))
|
||||||
|
.setGroup(account.getAccountId())
|
||||||
|
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback
|
||||||
|
|
||||||
|
setupPreferences(account, builder);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PendingIntent getStatusReplyIntent(String action, Context context, Notification body, AccountEntity account) {
|
||||||
|
Status status = body.getStatus();
|
||||||
|
|
||||||
|
String citedLocalAuthor = status.getAccount().getLocalUsername();
|
||||||
|
String citedText = status.getContent().toString();
|
||||||
|
String inReplyToId = status.getId();
|
||||||
|
Status actionableStatus = status.getActionableStatus();
|
||||||
|
Status.Visibility replyVisibility = actionableStatus.getVisibility();
|
||||||
|
String contentWarning = actionableStatus.getSpoilerText();
|
||||||
|
Status.Mention[] mentions = actionableStatus.getMentions();
|
||||||
|
List<String> mentionedUsernames = new ArrayList<>();
|
||||||
|
mentionedUsernames.add(actionableStatus.getAccount().getUsername());
|
||||||
|
for (Status.Mention mention : mentions) {
|
||||||
|
mentionedUsernames.add(mention.getUsername());
|
||||||
|
}
|
||||||
|
mentionedUsernames.removeAll(Collections.singleton(account.getUsername()));
|
||||||
|
mentionedUsernames = new ArrayList<>(new LinkedHashSet<>(mentionedUsernames));
|
||||||
|
|
||||||
|
Intent replyIntent = new Intent(context, SendStatusBroadcastReceiver.class)
|
||||||
|
.setAction(action)
|
||||||
|
.putExtra(KEY_CITED_AUTHOR_LOCAL, citedLocalAuthor)
|
||||||
|
.putExtra(KEY_CITED_TEXT, citedText)
|
||||||
|
.putExtra(KEY_SENDER_ACCOUNT_ID, account.getId())
|
||||||
|
.putExtra(KEY_SENDER_ACCOUNT_IDENTIFIER, account.getIdentifier())
|
||||||
|
.putExtra(KEY_SENDER_ACCOUNT_FULL_NAME, account.getFullName())
|
||||||
|
.putExtra(KEY_NOTIFICATION_ID, notificationId)
|
||||||
|
.putExtra(KEY_CITED_STATUS_ID, inReplyToId)
|
||||||
|
.putExtra(KEY_VISIBILITY, replyVisibility)
|
||||||
|
.putExtra(KEY_SPOILER, contentWarning)
|
||||||
|
.putExtra(KEY_MENTIONS, mentionedUsernames.toArray(new String[0]));
|
||||||
|
|
||||||
|
return PendingIntent.getBroadcast(context.getApplicationContext(),
|
||||||
|
notificationId,
|
||||||
|
replyIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void createNotificationChannelsForAccount(AccountEntity account, Context context) {
|
public static void createNotificationChannelsForAccount(AccountEntity account, Context context) {
|
||||||
|
|
|
@ -63,11 +63,14 @@
|
||||||
<string name="report_comment_hint">Dodatkowe komentarze?</string>
|
<string name="report_comment_hint">Dodatkowe komentarze?</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="label_quick_reply">Odpowiedz…</string>
|
||||||
|
<string name="action_quick_reply">Szybka odpowiedź</string>
|
||||||
<string name="action_reply">Odpowiedz</string>
|
<string name="action_reply">Odpowiedz</string>
|
||||||
<string name="action_reblog">Podbij</string>
|
<string name="action_reblog">Podbij</string>
|
||||||
<string name="action_favourite">Dodaj do ulubionych</string>
|
<string name="action_favourite">Dodaj do ulubionych</string>
|
||||||
<string name="action_more">Więcej</string>
|
<string name="action_more">Więcej</string>
|
||||||
<string name="action_compose">Napisz</string>
|
<string name="action_compose">Napisz</string>
|
||||||
|
<string name="action_compose_shortcut">Odpowiedz</string>
|
||||||
<string name="action_login">Zaloguj się Kontem Mastodon</string>
|
<string name="action_login">Zaloguj się Kontem Mastodon</string>
|
||||||
<string name="action_logout">Wyloguj się</string>
|
<string name="action_logout">Wyloguj się</string>
|
||||||
<string name="action_logout_confirm">Czy na pewno chcesz wylogować się z konta %1$s?</string>
|
<string name="action_logout_confirm">Czy na pewno chcesz wylogować się z konta %1$s?</string>
|
||||||
|
@ -318,5 +321,7 @@
|
||||||
<string name="download_image">Pobieranie %1$s…</string>
|
<string name="download_image">Pobieranie %1$s…</string>
|
||||||
|
|
||||||
<string name="action_copy_link">Skopiuj odnośnik</string>
|
<string name="action_copy_link">Skopiuj odnośnik</string>
|
||||||
|
<string name="status_sent">Wysłano!</string>
|
||||||
|
<string name="status_sent_long">Pomyślnie wysłano odpowiedź.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<string name="error_media_upload_sending">The upload failed.</string>
|
<string name="error_media_upload_sending">The upload failed.</string>
|
||||||
<string name="error_report_too_few_statuses">At least one status must be reported.</string>
|
<string name="error_report_too_few_statuses">At least one status must be reported.</string>
|
||||||
<string name="error_invalid_regex">Invalid regular expression</string>
|
<string name="error_invalid_regex">Invalid regular expression</string>
|
||||||
|
<string name="error_sender_account_gone">Error sending toot.</string>
|
||||||
|
|
||||||
<string name="title_home">Home</string>
|
<string name="title_home">Home</string>
|
||||||
<string name="title_advanced">Advanced</string>
|
<string name="title_advanced">Advanced</string>
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
<string name="report_username_format">Report @%s</string>
|
<string name="report_username_format">Report @%s</string>
|
||||||
<string name="report_comment_hint">Additional comments?</string>
|
<string name="report_comment_hint">Additional comments?</string>
|
||||||
|
|
||||||
|
<string name="action_quick_reply">Quick Reply</string>
|
||||||
<string name="action_reply">Reply</string>
|
<string name="action_reply">Reply</string>
|
||||||
<string name="action_reblog">Boost</string>
|
<string name="action_reblog">Boost</string>
|
||||||
<string name="action_favourite">Favourite</string>
|
<string name="action_favourite">Favourite</string>
|
||||||
|
@ -112,6 +114,9 @@
|
||||||
<string name="confirmation_unblocked">User unblocked</string>
|
<string name="confirmation_unblocked">User unblocked</string>
|
||||||
<string name="confirmation_unmuted">User unmuted</string>
|
<string name="confirmation_unmuted">User unmuted</string>
|
||||||
|
|
||||||
|
<string name="status_sent">Sent!</string>
|
||||||
|
<string name="status_sent_long">Reply sent successfully.</string>
|
||||||
|
|
||||||
<string name="hint_domain">Which instance?</string>
|
<string name="hint_domain">Which instance?</string>
|
||||||
<string name="hint_compose">What\'s happening?</string>
|
<string name="hint_compose">What\'s happening?</string>
|
||||||
<string name="hint_content_warning">Content warning</string>
|
<string name="hint_content_warning">Content warning</string>
|
||||||
|
@ -121,6 +126,7 @@
|
||||||
|
|
||||||
<string name="search_no_results">No results</string>
|
<string name="search_no_results">No results</string>
|
||||||
|
|
||||||
|
<string name="label_quick_reply">Reply…</string>
|
||||||
<string name="label_avatar">Avatar</string>
|
<string name="label_avatar">Avatar</string>
|
||||||
<string name="label_header">Header</string>
|
<string name="label_header">Header</string>
|
||||||
|
|
||||||
|
@ -295,11 +301,12 @@
|
||||||
<string name="lock_account_label">Lock account</string>
|
<string name="lock_account_label">Lock account</string>
|
||||||
<string name="lock_account_label_description">Requires you to manually approve followers</string>
|
<string name="lock_account_label_description">Requires you to manually approve followers</string>
|
||||||
<string name="compose_save_draft">Save draft?</string>
|
<string name="compose_save_draft">Save draft?</string>
|
||||||
<string name="send_toot_notification_title">Sending Toot...</string>
|
<string name="send_toot_notification_title">Sending Toot…</string>
|
||||||
<string name="send_toot_notification_error_title">Error sending toot</string>
|
<string name="send_toot_notification_error_title">Error sending toot</string>
|
||||||
<string name="send_toot_notification_channel_name">Sending Toots</string>
|
<string name="send_toot_notification_channel_name">Sending Toots</string>
|
||||||
<string name="send_toot_notification_cancel_title">Sending cancelled</string>
|
<string name="send_toot_notification_cancel_title">Sending cancelled</string>
|
||||||
<string name="send_toot_notification_saved_content">A copy of the toot has been saved to your drafts</string>
|
<string name="send_toot_notification_saved_content">A copy of the toot has been saved to your drafts</string>
|
||||||
|
<string name="action_compose_shortcut">Compose</string>
|
||||||
|
|
||||||
<string name="error_no_custom_emojis">Your instance %s does not have any custom emojis</string>
|
<string name="error_no_custom_emojis">Your instance %s does not have any custom emojis</string>
|
||||||
<string name="copy_to_clipboard_success">Copied to clipboard</string>
|
<string name="copy_to_clipboard_success">Copied to clipboard</string>
|
||||||
|
|
Loading…
Reference in a new issue