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 @@
-
-
-
diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineViewModelTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineViewModelTest.kt
deleted file mode 100644
index e99cb14a8..000000000
--- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineViewModelTest.kt
+++ /dev/null
@@ -1,216 +0,0 @@
-package com.keylesspalace.tusky.components.timeline
-
-import android.os.Looper
-import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import androidx.paging.AsyncPagingDataDiffer
-import androidx.paging.ExperimentalPagingApi
-import androidx.recyclerview.widget.ListUpdateCallback
-import androidx.room.Room
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.gson.Gson
-import com.keylesspalace.tusky.appstore.EventHub
-import com.keylesspalace.tusky.components.timeline.TimelinePagingAdapter.Companion.TimelineDifferCallback
-import com.keylesspalace.tusky.components.timeline.viewmodel.CachedTimelineViewModel
-import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel
-import com.keylesspalace.tusky.components.timeline.viewmodel.TimelineViewModel
-import com.keylesspalace.tusky.db.AccountEntity
-import com.keylesspalace.tusky.db.AccountManager
-import com.keylesspalace.tusky.db.AppDatabase
-import com.keylesspalace.tusky.db.Converters
-import com.keylesspalace.tusky.network.FilterModel
-import com.keylesspalace.tusky.network.MastodonApi
-import com.keylesspalace.tusky.network.TimelineCases
-import com.nhaarman.mockitokotlin2.doReturn
-import com.nhaarman.mockitokotlin2.mock
-import io.reactivex.rxjava3.core.Single
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.take
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.setMain
-import okhttp3.Headers
-import org.junit.After
-import org.junit.Assert.assertEquals
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.Shadows.shadowOf
-import org.robolectric.annotation.Config
-import retrofit2.Response
-import java.util.concurrent.Executors
-
-@ExperimentalCoroutinesApi
-@Config(sdk = [29])
-@RunWith(AndroidJUnit4::class)
-class TimelineViewModelTest {
-
- @get:Rule
- val instantRule = InstantTaskExecutorRule()
-
- private val testDispatcher = TestCoroutineDispatcher()
- private val testScope = TestCoroutineScope(testDispatcher)
-
- private val accountManager: AccountManager = mock {
- on { activeAccount } doReturn AccountEntity(
- id = 1,
- domain = "mastodon.example",
- accessToken = "token",
- isActive = true
- )
- }
-
- private val eventHub = EventHub()
-
- private lateinit var db: AppDatabase
-
- @Before
- fun setup() {
- Dispatchers.setMain(testDispatcher)
-
- shadowOf(Looper.getMainLooper()).idle()
-
- val context = InstrumentationRegistry.getInstrumentation().targetContext
- db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
- .addTypeConverter(Converters(Gson()))
- .setTransactionExecutor(Executors.newSingleThreadExecutor())
- .allowMainThreadQueries()
- .build()
- }
-
- @After
- fun tearDown() {
- Dispatchers.resetMain()
- testDispatcher.cleanupTestCoroutines()
- db.close()
- }
-
- @Test
- @ExperimentalPagingApi
- fun shouldLoadNetworkTimeline() = runBlocking {
-
- val api: MastodonApi = mock {
- on { publicTimeline(local = true, maxId = null, sinceId = null, limit = 30) } doReturn Single.just(
- Response.success(
- listOf(
- mockStatus("6"),
- mockStatus("5"),
- mockStatus("4")
- ),
- Headers.headersOf(
- "Link", "; rel=\"next\", ; rel=\"prev\""
- )
- )
- )
-
- on { publicTimeline(local = true, maxId = "1", sinceId = null, limit = 30) } doReturn Single.just(
- Response.success(emptyList())
- )
-
- on { getFilters() } doReturn Single.just(emptyList())
- }
-
- val viewModel = NetworkTimelineViewModel(
- TimelineCases(api, eventHub),
- api,
- eventHub,
- accountManager,
- mock(),
- FilterModel()
- )
-
- viewModel.init(TimelineViewModel.Kind.PUBLIC_LOCAL, null, emptyList())
-
- val differ = AsyncPagingDataDiffer(
- diffCallback = TimelineDifferCallback,
- updateCallback = NoopListCallback(),
- workerDispatcher = testDispatcher
- )
-
- viewModel.statuses.take(2).collectLatest {
- testScope.launch {
- differ.submitData(it)
- }
- }
-
- assertEquals(
- listOf(
- mockStatusViewData("6"),
- mockStatusViewData("5"),
- mockStatusViewData("4")
- ),
- differ.snapshot().items
- )
- }
-
- // ToDo: Find out why Room & coroutines are not playing nice here
- // @Test
- @ExperimentalPagingApi
- fun shouldLoadCachedTimeline() = runBlocking {
-
- val api: MastodonApi = mock {
- on { homeTimeline(limit = 30) } doReturn Single.just(
- Response.success(
- listOf(
- mockStatus("6"),
- mockStatus("5"),
- mockStatus("4")
- )
- )
- )
-
- on { homeTimeline(maxId = "1", sinceId = null, limit = 30) } doReturn Single.just(
- Response.success(emptyList())
- )
-
- on { getFilters() } doReturn Single.just(emptyList())
- }
-
- val viewModel = CachedTimelineViewModel(
- TimelineCases(api, eventHub),
- api,
- eventHub,
- accountManager,
- mock(),
- FilterModel(),
- db,
- Gson()
- )
-
- viewModel.init(TimelineViewModel.Kind.HOME, null, emptyList())
-
- val differ = AsyncPagingDataDiffer(
- diffCallback = TimelineDifferCallback,
- updateCallback = NoopListCallback(),
- workerDispatcher = testDispatcher
- )
-
- viewModel.statuses.take(1000).collectLatest {
- testScope.launch {
- differ.submitData(it)
- }
- }
-
- assertEquals(
- listOf(
- mockStatusViewData("6"),
- mockStatusViewData("5"),
- mockStatusViewData("4")
- ),
- differ.snapshot().items
- )
- }
-}
-
-class NoopListCallback : ListUpdateCallback {
- override fun onChanged(position: Int, count: Int, payload: Any?) {}
- override fun onMoved(fromPosition: Int, toPosition: Int) {}
- override fun onInserted(position: Int, count: Int) {}
- override fun onRemoved(position: Int, count: Int) {}
-}