diff --git a/app/build.gradle b/app/build.gradle index 43a078aae..be574f269 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,11 +15,11 @@ def getGitSha = { } android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { applicationId APP_ID minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 87 versionName "16.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -89,8 +89,8 @@ android { } ext.coroutinesVersion = "1.6.0" -ext.lifecycleVersion = "2.3.1" -ext.roomVersion = '2.3.0' +ext.lifecycleVersion = "2.4.1" +ext.roomVersion = '2.4.2' ext.retrofitVersion = '2.9.0' ext.okhttpVersion = '4.9.3' ext.glideVersion = '4.12.0' @@ -104,31 +104,33 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$coroutinesVersion" - implementation "androidx.core:core-ktx:1.5.0" - implementation "androidx.appcompat:appcompat:1.3.0" - implementation "androidx.fragment:fragment-ktx:1.3.4" - implementation "androidx.browser:browser:1.3.0" + implementation "androidx.core:core-ktx:1.7.0" + implementation "androidx.appcompat:appcompat:1.4.1" + implementation "androidx.fragment:fragment-ktx:1.4.1" + implementation "androidx.browser:browser:1.4.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.exifinterface:exifinterface:1.3.3" implementation "androidx.cardview:cardview:1.0.0" - implementation "androidx.preference:preference-ktx:1.1.1" - implementation "androidx.sharetarget:sharetarget:1.1.0" + implementation "androidx.preference:preference-ktx:1.2.0" + implementation "androidx.sharetarget:sharetarget:1.2.0-rc01" implementation "androidx.emoji:emoji:1.1.0" implementation "androidx.emoji:emoji-appcompat:1.1.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion" - implementation "androidx.constraintlayout:constraintlayout:2.1.2" - implementation "androidx.paging:paging-runtime-ktx:3.0.0" + implementation "androidx.constraintlayout:constraintlayout:2.1.3" + implementation "androidx.paging:paging-runtime-ktx:3.1.0" implementation "androidx.viewpager2:viewpager2:1.0.0" - implementation "androidx.work:work-runtime:2.5.0" + implementation "androidx.work:work-runtime:2.7.1" implementation "androidx.room:room-ktx:$roomVersion" + implementation "androidx.room:room-paging:$roomVersion" implementation "androidx.room:room-rxjava3:$roomVersion" kapt "androidx.room:room-compiler:$roomVersion" + implementation 'androidx.core:core-splashscreen:1.0.0-beta01' - implementation "com.google.android.material:material:1.4.0" + implementation "com.google.android.material:material:1.5.0" implementation "com.google.code.gson:gson:2.8.9" diff --git a/app/src/green/res/values/flavor-colors.xml b/app/src/green/res/values/flavor-colors.xml new file mode 100644 index 000000000..e1f58f2ea --- /dev/null +++ b/app/src/green/res/values/flavor-colors.xml @@ -0,0 +1,6 @@ + + + + #19A341 + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a32259b77..a5e49b741 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,20 +20,7 @@ android:supportsRtl="true" android:theme="@style/TuskyTheme" android:usesCleartextTraffic="false"> - - - - - - - - - @@ -41,7 +28,15 @@ + android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize" + android:theme="@style/SplashTheme" + android:exported="true"> + + + + + + @@ -88,6 +83,9 @@ + - @@ -115,7 +112,8 @@ android:theme="@style/Base.Theme.AppCompat" /> + android:launchMode="singleTop" + android:exported="false"> @@ -125,7 +123,6 @@ android:resource="@xml/searchable" /> - - + - + + tools:node="merge"> + + + diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 73aedbd95..2e9f6f3de 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -35,6 +35,7 @@ import androidx.appcompat.app.AlertDialog import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.emoji.text.EmojiCompat import androidx.emoji.text.EmojiCompat.InitCallback import androidx.lifecycle.Lifecycle @@ -159,8 +160,12 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen() super.onCreate(savedInstanceState) + // delete old notification channels + NotificationHelper.deleteLegacyNotificationChannels(this, accountManager) + val activeAccount = accountManager.activeAccount ?: return // will be redirected to LoginActivity by BaseActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt deleted file mode 100644 index 0225147f6..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright 2018 Conny Duck - * - * 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 . */ - -package com.keylesspalace.tusky - -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import com.keylesspalace.tusky.components.login.LoginActivity -import com.keylesspalace.tusky.components.notifications.NotificationHelper -import com.keylesspalace.tusky.db.AccountManager -import com.keylesspalace.tusky.di.Injectable -import javax.inject.Inject - -class SplashActivity : AppCompatActivity(), Injectable { - - @Inject - lateinit var accountManager: AccountManager - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - /** delete old notification channels */ - NotificationHelper.deleteLegacyNotificationChannels(this, accountManager) - - /** Determine whether the user is currently logged in, and if so go ahead and load the - * timeline. Otherwise, start the activity_login screen. */ - - val intent = if (accountManager.activeAccount != null) { - Intent(this, MainActivity::class.java) - } else { - LoginActivity.getIntent(this, false) - } - startActivity(intent) - finish() - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index caee042fd..a9a9c2d4a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -16,7 +16,9 @@ package com.keylesspalace.tusky.components.compose import android.Manifest +import android.app.NotificationManager import android.app.ProgressDialog +import android.content.ClipData import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -45,8 +47,8 @@ import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider -import androidx.core.view.inputmethod.InputConnectionCompat -import androidx.core.view.inputmethod.InputContentInfoCompat +import androidx.core.view.ContentInfoCompat +import androidx.core.view.OnReceiveContentListener import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.preference.PreferenceManager @@ -105,7 +107,7 @@ class ComposeActivity : ComposeAutoCompleteAdapter.AutocompletionProvider, OnEmojiSelectedListener, Injectable, - InputConnectionCompat.OnCommitContentListener, + OnReceiveContentListener, ComposeScheduleView.OnTimeSetListener { @Inject @@ -149,6 +151,18 @@ class ComposeActivity : public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val notificationId = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1) + if (notificationId != -1) { + // ComposeActivity was opened from a notification, delete the notification + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(notificationId) + } + + val accountId = intent.getLongExtra(ACCOUNT_ID_EXTRA, -1) + if (accountId != -1L) { + accountManager.setActiveAccount(accountId) + } + val preferences = PreferenceManager.getDefaultSharedPreferences(this) val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT) if (theme == "black") { @@ -282,7 +296,7 @@ class ComposeActivity : } private fun setupComposeField(preferences: SharedPreferences, startingText: String?) { - binding.composeEditField.setOnCommitContentListener(this) + binding.composeEditField.setOnReceiveContentListener(this) binding.composeEditField.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) } @@ -742,26 +756,18 @@ class ComposeActivity : } } - /** This is for the fancy keyboards which can insert images and stuff. */ - override fun onCommitContent(inputContentInfo: InputContentInfoCompat, flags: Int, opts: Bundle?): Boolean { - // Verify the returned content's type is of the correct MIME type - val supported = inputContentInfo.description.hasMimeType("image/*") - - if (supported) { - val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0 - if (lacksPermission) { - try { - inputContentInfo.requestPermission() - } catch (e: Exception) { - Log.e(TAG, "InputContentInfoCompat#requestPermission() failed." + e.message) - return false + /** This is for the fancy keyboards which can insert images and stuff, and drag&drop etc */ + override fun onReceiveContent(view: View, contentInfo: ContentInfoCompat): ContentInfoCompat? { + if (contentInfo.clip.description.hasMimeType("image/*")) { + val split = contentInfo.partition { item: ClipData.Item -> item.uri != null } + split.first?.let { content -> + for (i in 0 until content.clip.itemCount) { + pickMedia(content.clip.getItemAt(i).uri) } } - pickMedia(inputContentInfo.contentUri, inputContentInfo) - return true + return split.second } - - return false + return contentInfo } private fun sendStatus() { @@ -784,12 +790,11 @@ class ComposeActivity : } viewModel.sendStatus(contentText, spoilerText).observe( - this, - { - finishingUploadDialog?.dismiss() - deleteDraftAndFinish() - } - ) + this + ) { + finishingUploadDialog?.dismiss() + deleteDraftAndFinish() + } } else { binding.composeEditField.error = getString(R.string.error_compose_character_limit) enableButtons(true) @@ -859,12 +864,9 @@ class ComposeActivity : viewModel.removeMediaFromQueue(item) } - private fun pickMedia(uri: Uri, contentInfoCompat: InputContentInfoCompat? = null) { + private fun pickMedia(uri: Uri) { withLifecycleContext { viewModel.pickMedia(uri).observe { exceptionOrItem -> - - contentInfoCompat?.releasePermission() - exceptionOrItem.asLeftOrNull()?.let { val errorId = when (it) { is VideoSizeException -> { @@ -1043,12 +1045,32 @@ class ComposeActivity : private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1 internal const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS" + private const val NOTIFICATION_ID_EXTRA = "NOTIFICATION_ID" + private const val ACCOUNT_ID_EXTRA = "ACCOUNT_ID" private const val PHOTO_UPLOAD_URI_KEY = "PHOTO_UPLOAD_URI" + /** + * @param options ComposeOptions to configure the ComposeActivity + * @param notificationId the id of the notification that starts the Activity + * @param accountId the id of the account to compose with, null for the current account + * @return an Intent to start the ComposeActivity + */ @JvmStatic - fun startIntent(context: Context, options: ComposeOptions): Intent { + @JvmOverloads + fun startIntent( + context: Context, + options: ComposeOptions, + notificationId: Int? = null, + accountId: Long? = null + ): Intent { return Intent(context, ComposeActivity::class.java).apply { putExtra(COMPOSE_OPTIONS_EXTRA, options) + if (notificationId != null) { + putExtra(NOTIFICATION_ID_EXTRA, notificationId) + } + if (accountId != null) { + putExtra(ACCOUNT_ID_EXTRA, accountId) + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt index a8403c954..dca696d84 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt @@ -22,6 +22,8 @@ import android.util.AttributeSet import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView +import androidx.core.view.OnReceiveContentListener +import androidx.core.view.ViewCompat import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.InputConnectionCompat import androidx.emoji.widget.EmojiEditTextHelper @@ -32,41 +34,33 @@ class EditTextTyped @JvmOverloads constructor( ) : AppCompatMultiAutoCompleteTextView(context, attributeSet) { - private var onCommitContentListener: InputConnectionCompat.OnCommitContentListener? = null private val emojiEditTextHelper: EmojiEditTextHelper = EmojiEditTextHelper(this) init { // fix a bug with autocomplete and some keyboards val newInputType = inputType and (inputType xor InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) inputType = newInputType - super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener)) + super.setKeyListener(emojiEditTextHelper.getKeyListener(keyListener)) } - override fun setKeyListener(input: KeyListener) { - super.setKeyListener(getEmojiEditTextHelper().getKeyListener(input)) + override fun setKeyListener(input: KeyListener?) { + if (input != null) { + super.setKeyListener(emojiEditTextHelper.getKeyListener(input)) + } else { + super.setKeyListener(input) + } } - fun setOnCommitContentListener(listener: InputConnectionCompat.OnCommitContentListener) { - onCommitContentListener = listener + fun setOnReceiveContentListener(listener: OnReceiveContentListener) { + ViewCompat.setOnReceiveContentListener(this, arrayOf("image/*"), listener) } override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection { val connection = super.onCreateInputConnection(editorInfo) - return if (onCommitContentListener != null) { - EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*")) - getEmojiEditTextHelper().onCreateInputConnection( - InputConnectionCompat.createWrapper( - connection, editorInfo, - onCommitContentListener!! - ), - editorInfo - )!! - } else { - connection - } - } - - private fun getEmojiEditTextHelper(): EmojiEditTextHelper { - return emojiEditTextHelper + EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*")) + return emojiEditTextHelper.onCreateInputConnection( + InputConnectionCompat.createWrapper(this, connection, editorInfo), + editorInfo + )!! } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java index c0bf149f4..6b9afce1d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java @@ -24,7 +24,6 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; import android.os.Build; import android.provider.Settings; import android.text.TextUtils; @@ -46,9 +45,9 @@ import androidx.work.WorkRequest; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.FutureTarget; -import com.keylesspalace.tusky.BuildConfig; import com.keylesspalace.tusky.MainActivity; import com.keylesspalace.tusky.R; +import com.keylesspalace.tusky.components.compose.ComposeActivity; import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.entity.Notification; @@ -67,6 +66,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -88,8 +88,6 @@ public class 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"; @@ -108,10 +106,6 @@ public class NotificationHelper { 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+ **/ @@ -206,21 +200,24 @@ public class NotificationHelper { .setLabel(context.getString(R.string.label_quick_reply)) .build(); - PendingIntent quickReplyPendingIntent = getStatusReplyIntent(REPLY_ACTION, context, body, account); + PendingIntent quickReplyPendingIntent = getStatusReplyIntent(context, body, account); NotificationCompat.Action quickReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp, - context.getString(R.string.action_quick_reply), quickReplyPendingIntent) + context.getString(R.string.action_quick_reply), + quickReplyPendingIntent) .addRemoteInput(replyRemoteInput) .build(); builder.addAction(quickReplyAction); - PendingIntent composePendingIntent = getStatusReplyIntent(COMPOSE_ACTION, context, body, account); + PendingIntent composeIntent = getStatusComposeIntent(context, body, account); NotificationCompat.Action composeAction = new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp, - context.getString(R.string.action_compose_shortcut), composePendingIntent) + context.getString(R.string.action_compose_shortcut), + composeIntent) + .setShowsUserInterface(true) .build(); builder.addAction(composeAction); @@ -237,7 +234,6 @@ public class NotificationHelper { } // Summary - // ======= final NotificationCompat.Builder summaryBuilder = newNotification(context, body, account, true); if (currentNotifications.length() != 1) { @@ -275,7 +271,7 @@ public class NotificationHelper { summaryStackBuilder.addNextIntent(summaryResultIntent); PendingIntent summaryResultPendingIntent = summaryStackBuilder.getPendingIntent((int) (notificationId + account.getId() * 10000), - PendingIntent.FLAG_UPDATE_CURRENT); + pendingIntentFlags(false)); // we have to switch account here Intent eventResultIntent = new Intent(context, MainActivity.class); @@ -285,18 +281,18 @@ public class NotificationHelper { eventStackBuilder.addNextIntent(eventResultIntent); PendingIntent eventResultPendingIntent = eventStackBuilder.getPendingIntent((int) account.getId(), - PendingIntent.FLAG_UPDATE_CURRENT); + pendingIntentFlags(false)); 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); + pendingIntentFlags(false)); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getChannelId(account, body)) .setSmallIcon(R.drawable.ic_notify) .setContentIntent(summary ? summaryResultPendingIntent : eventResultPendingIntent) .setDeleteIntent(deletePendingIntent) - .setColor(BuildConfig.FLAVOR == "green" ? Color.parseColor("#19A341") : ContextCompat.getColor(context, R.color.tusky_blue)) + .setColor(ContextCompat.getColor(context, R.color.notification_color)) .setGroup(account.getAccountId()) .setAutoCancel(true) .setShortcutId(Long.toString(account.getId())) @@ -307,11 +303,9 @@ public class NotificationHelper { return builder; } - private static PendingIntent getStatusReplyIntent(String action, Context context, Notification body, AccountEntity account) { + private static PendingIntent getStatusReplyIntent(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(); @@ -326,9 +320,7 @@ public class NotificationHelper { 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) + .setAction(REPLY_ACTION) .putExtra(KEY_SENDER_ACCOUNT_ID, account.getId()) .putExtra(KEY_SENDER_ACCOUNT_IDENTIFIER, account.getIdentifier()) .putExtra(KEY_SENDER_ACCOUNT_FULL_NAME, account.getFullName()) @@ -341,7 +333,50 @@ public class NotificationHelper { return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, replyIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + pendingIntentFlags(true)); + } + + private static PendingIntent getStatusComposeIntent(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(); + List mentions = actionableStatus.getMentions(); + Set mentionedUsernames = new LinkedHashSet<>(); + mentionedUsernames.add(actionableStatus.getAccount().getUsername()); + for (Status.Mention mention : mentions) { + String mentionedUsername = mention.getUsername(); + if (!mentionedUsername.equals(account.getUsername())) { + mentionedUsernames.add(mention.getUsername()); + } + } + + ComposeActivity.ComposeOptions composeOptions = new ComposeActivity.ComposeOptions(); + composeOptions.setInReplyToId(inReplyToId); + composeOptions.setReplyVisibility(replyVisibility); + composeOptions.setContentWarning(contentWarning); + composeOptions.setReplyingStatusAuthor(citedLocalAuthor); + composeOptions.setReplyingStatusContent(citedText); + composeOptions.setMentionedUsernames(mentionedUsernames); + composeOptions.setModifiedInitialState(true); + + Intent composeIntent = ComposeActivity.startIntent( + context, + composeOptions, + notificationId, + account.getId() + ); + + composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + return PendingIntent.getActivity(context.getApplicationContext(), + notificationId, + composeIntent, + pendingIntentFlags(false)); } public static void createNotificationChannelsForAccount(@NonNull AccountEntity account, @NonNull Context context) { @@ -409,9 +444,7 @@ public class NotificationHelper { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - //noinspection ConstantConditions notificationManager.deleteNotificationChannelGroup(account.getIdentifier()); - } } @@ -421,7 +454,6 @@ public class NotificationHelper { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); // used until Tusky 1.4 - //noinspection ConstantConditions notificationManager.deleteNotificationChannel(CHANNEL_MENTION); notificationManager.deleteNotificationChannel(CHANNEL_FAVOURITE); notificationManager.deleteNotificationChannel(CHANNEL_BOOST); @@ -440,7 +472,6 @@ public class NotificationHelper { // on Android >= O, notifications are enabled, if at least one channel is enabled NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - //noinspection ConstantConditions if (notificationManager.areNotificationsEnabled()) { for (NotificationChannel channel : notificationManager.getNotificationChannels()) { if (channel.getImportance() > NotificationManager.IMPORTANCE_NONE) { @@ -491,7 +522,6 @@ public class NotificationHelper { accountManager.saveAccount(account); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - //noinspection ConstantConditions notificationManager.cancel((int) account.getId()); return true; }) @@ -511,7 +541,6 @@ public class NotificationHelper { // unknown notificationtype return false; } - //noinspection ConstantConditions NotificationChannel channel = notificationManager.getNotificationChannel(channelId); return channel.getImportance() > NotificationManager.IMPORTANCE_NONE; } @@ -674,4 +703,11 @@ public class NotificationHelper { return null; } + public static int pendingIntentFlags(boolean mutable) { + if (mutable) { + return PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_MUTABLE : 0); + } else { + return PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0); + } + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt index e793f17f1..dc0cae4d2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt @@ -13,8 +13,8 @@ import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import androidx.preference.PreferenceManager +import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.SplashActivity import com.keylesspalace.tusky.databinding.DialogEmojicompatBinding import com.keylesspalace.tusky.databinding.ItemEmojiPrefBinding import com.keylesspalace.tusky.util.EmojiCompatFont @@ -215,7 +215,7 @@ class EmojiPreference( .setPositiveButton(R.string.restart) { _, _ -> // Restart the app // From https://stackoverflow.com/a/17166729/5070653 - val launchIntent = Intent(context, SplashActivity::class.java) + val launchIntent = Intent(context, MainActivity::class.java) val mPendingIntent = PendingIntent.getActivity( context, 0x1f973, // This is the codepoint of the party face emoji :D diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index 9e41f5017..9bd88bb56 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -280,23 +280,24 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { } private fun updateHttpProxySummary() { - val sharedPreferences = preferenceManager.sharedPreferences - val httpProxyEnabled = sharedPreferences.getBoolean(PrefKeys.HTTP_PROXY_ENABLED, false) - val httpServer = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_SERVER, "") + preferenceManager.sharedPreferences?.let { sharedPreferences -> + val httpProxyEnabled = sharedPreferences.getBoolean(PrefKeys.HTTP_PROXY_ENABLED, false) + val httpServer = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_SERVER, "") - try { - val httpPort = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_PORT, "-1") - .toInt() + try { + val httpPort = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_PORT, "-1") + .toInt() - if (httpProxyEnabled && httpServer.isNotBlank() && httpPort > 0 && httpPort < 65535) { - httpProxyPref?.summary = "$httpServer:$httpPort" - return + if (httpProxyEnabled && httpServer.isNotBlank() && httpPort > 0 && httpPort < 65535) { + httpProxyPref?.summary = "$httpServer:$httpPort" + return + } + } catch (e: NumberFormatException) { + // user has entered wrong port, fall back to empty summary } - } catch (e: NumberFormatException) { - // user has entered wrong port, fall back to empty summary - } - httpProxyPref?.summary = "" + httpProxyPref?.summary = "" + } } companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt index da7535849..95bf4a9d1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt @@ -90,9 +90,9 @@ class TimelineFragment : private val viewModel: TimelineViewModel by lazy { if (kind == TimelineViewModel.Kind.HOME) { - ViewModelProvider(this, viewModelFactory).get(CachedTimelineViewModel::class.java) + ViewModelProvider(this, viewModelFactory)[CachedTimelineViewModel::class.java] } else { - ViewModelProvider(this, viewModelFactory).get(NetworkTimelineViewModel::class.java) + ViewModelProvider(this, viewModelFactory)[NetworkTimelineViewModel::class.java] } } @@ -136,7 +136,7 @@ class TimelineFragment : isSwipeToRefreshEnabled = arguments.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true) - val preferences = PreferenceManager.getDefaultSharedPreferences(activity) + val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val statusDisplayOptions = StatusDisplayOptions( animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), mediaPreviewEnabled = accountManager.activeAccount!!.mediaPreviewEnabled, @@ -224,7 +224,7 @@ class TimelineFragment : } if (actionButtonPresent()) { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) hideFab = preferences.getBoolean("fabHide", false) scrollListener = object : RecyclerView.OnScrollListener() { override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) { @@ -401,7 +401,7 @@ class TimelineFragment : } private fun onPreferenceChanged(key: String) { - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) when (key) { PrefKeys.FAB_HIDE -> { hideFab = sharedPreferences.getBoolean(PrefKeys.FAB_HIDE, false) @@ -468,7 +468,7 @@ class TimelineFragment : * Auto dispose observable on pause */ private fun startUpdateTimestamp() { - val preferences = PreferenceManager.getDefaultSharedPreferences(activity) + val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val useAbsoluteTime = preferences.getBoolean(PrefKeys.ABSOLUTE_TIME_VIEW, false) if (!useAbsoluteTime) { Observable.interval(1, TimeUnit.MINUTES) diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index 285fe916c..d767a64c5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -23,7 +23,6 @@ import com.keylesspalace.tusky.FiltersActivity import com.keylesspalace.tusky.LicenseActivity import com.keylesspalace.tusky.ListsActivity import com.keylesspalace.tusky.MainActivity -import com.keylesspalace.tusky.SplashActivity import com.keylesspalace.tusky.StatusListActivity import com.keylesspalace.tusky.TabPreferenceActivity import com.keylesspalace.tusky.ViewMediaActivity @@ -88,9 +87,6 @@ abstract class ActivitiesModule { @ContributesAndroidInjector abstract fun contributesLoginWebViewActivity(): LoginWebViewActivity - @ContributesAndroidInjector - abstract fun contributesSplashActivity(): SplashActivity - @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) abstract fun contributesPreferencesActivity(): PreferencesActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt index fb1d78673..03c0d868b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt +++ b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt @@ -18,14 +18,14 @@ package com.keylesspalace.tusky.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.graphics.Color import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.RemoteInput import androidx.core.content.ContextCompat +import com.keylesspalace.tusky.BuildConfig import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.components.compose.ComposeActivity -import com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeOptions import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Status @@ -45,22 +45,19 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { 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) ?: emptyArray() - 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 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) ?: emptyArray() + + val account = accountManager.getAccountById(senderId) + + val notificationManager = NotificationManagerCompat.from(context) val message = getReplyMessage(intent) @@ -109,9 +106,15 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { context.startService(sendIntent) + val color = if (BuildConfig.FLAVOR == "green") { + Color.parseColor("#19A341") + } else { + ContextCompat.getColor(context, R.color.tusky_blue) + } + val builder = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MENTION + senderIdentifier) .setSmallIcon(R.drawable.ic_notify) - .setColor(ContextCompat.getColor(context, (R.color.tusky_blue))) + .setColor(color) .setGroup(senderFullName) .setDefaults(0) // So it doesn't ring twice, notify only in Target callback @@ -125,29 +128,6 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { 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.startIntent( - context, - ComposeOptions( - inReplyToId = citedStatusId, - replyVisibility = visibility, - contentWarning = spoiler, - mentionedUsernames = mentions.toSet(), - replyingStatusAuthor = localAuthorId, - replyingStatusContent = citedText - ) - ) - - composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - context.startActivity(composeIntent) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt index ed69a49d4..3a5dbb576 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt @@ -19,8 +19,8 @@ import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.StatusComposedEvent import com.keylesspalace.tusky.appstore.StatusScheduledEvent import com.keylesspalace.tusky.components.drafts.DraftHelper +import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.db.AccountManager -import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.NewPoll import com.keylesspalace.tusky.entity.NewStatus @@ -50,8 +50,6 @@ class SendTootService : Service(), Injectable { @Inject lateinit var eventHub: EventHub @Inject - lateinit var database: AppDatabase - @Inject lateinit var draftHelper: DraftHelper private val supervisorJob = SupervisorJob() @@ -95,7 +93,7 @@ class SendTootService : Service(), Injectable { .setContentText(notificationText) .setProgress(1, 0, true) .setOngoing(true) - .setColor(ContextCompat.getColor(this, R.color.tusky_blue)) + .setColor(ContextCompat.getColor(this, R.color.notification_color)) .addAction(0, getString(android.R.string.cancel), cancelSendingIntent(sendingNotificationId)) if (tootsToSend.size == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -183,7 +181,7 @@ class SendTootService : Service(), Injectable { .setSmallIcon(R.drawable.ic_notify) .setContentTitle(getString(R.string.send_toot_notification_error_title)) .setContentText(getString(R.string.send_toot_notification_saved_content)) - .setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) + .setColor(ContextCompat.getColor(this@SendTootService, R.color.notification_color)) notificationManager.cancel(tootId) notificationManager.notify(errorNotificationId--, builder.build()) @@ -232,7 +230,7 @@ class SendTootService : Service(), Injectable { .setSmallIcon(R.drawable.ic_notify) .setContentTitle(getString(R.string.send_toot_notification_cancel_title)) .setContentText(getString(R.string.send_toot_notification_saved_content)) - .setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) + .setColor(ContextCompat.getColor(this, R.color.notification_color)) notificationManager.notify(tootId, builder.build()) @@ -267,12 +265,9 @@ class SendTootService : Service(), Injectable { } private fun cancelSendingIntent(tootId: Int): PendingIntent { - val intent = Intent(this, SendTootService::class.java) - intent.putExtra(KEY_CANCEL, tootId) - - return PendingIntent.getService(this, tootId, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getService(this, tootId, intent, NotificationHelper.pendingIntentFlags(false)) } override fun onDestroy() { diff --git a/app/src/main/res/drawable-hdpi/splash.png b/app/src/main/res/drawable-hdpi/splash.png deleted file mode 100644 index 965c97d30..000000000 Binary files a/app/src/main/res/drawable-hdpi/splash.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/splash.png b/app/src/main/res/drawable-mdpi/splash.png deleted file mode 100644 index 019fc271f..000000000 Binary files a/app/src/main/res/drawable-mdpi/splash.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/splash.png b/app/src/main/res/drawable-xhdpi/splash.png deleted file mode 100644 index b7ed665bd..000000000 Binary files a/app/src/main/res/drawable-xhdpi/splash.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/splash.png b/app/src/main/res/drawable-xxhdpi/splash.png deleted file mode 100644 index 0ba150ca1..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/splash.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/splash.png b/app/src/main/res/drawable-xxxhdpi/splash.png deleted file mode 100644 index 0b656a305..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/splash.png and /dev/null differ diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml deleted file mode 100644 index d79dee514..000000000 --- a/app/src/main/res/drawable/background_splash.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_splash.xml b/app/src/main/res/drawable/ic_splash.xml new file mode 100644 index 000000000..9405fce2a --- /dev/null +++ b/app/src/main/res/drawable/ic_splash.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml index 78d327474..586c7f348 100644 --- a/app/src/main/res/values-v27/styles.xml +++ b/app/src/main/res/values-v27/styles.xml @@ -1,15 +1,5 @@ - - -