Android 12 support, update AndroidX libraries (#2367)
* Android 12 support, update AndroidX libraries * fix ktlint * add Android 12 splash screen support * fix comments in MainActivity * remove deprecated Intent.ACTION_CLOSE_SYSTEM_DIALOGS * delete TimelineViewModelTest * fix notifications on Android 12 * improve splash screen * handle pending intent flags in a dedicated function
This commit is contained in:
parent
221cdb3611
commit
55513e8e2b
25 changed files with 260 additions and 488 deletions
|
@ -15,11 +15,11 @@ def getGitSha = {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 31
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId APP_ID
|
applicationId APP_ID
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 31
|
||||||
versionCode 87
|
versionCode 87
|
||||||
versionName "16.0"
|
versionName "16.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
@ -89,8 +89,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.coroutinesVersion = "1.6.0"
|
ext.coroutinesVersion = "1.6.0"
|
||||||
ext.lifecycleVersion = "2.3.1"
|
ext.lifecycleVersion = "2.4.1"
|
||||||
ext.roomVersion = '2.3.0'
|
ext.roomVersion = '2.4.2'
|
||||||
ext.retrofitVersion = '2.9.0'
|
ext.retrofitVersion = '2.9.0'
|
||||||
ext.okhttpVersion = '4.9.3'
|
ext.okhttpVersion = '4.9.3'
|
||||||
ext.glideVersion = '4.12.0'
|
ext.glideVersion = '4.12.0'
|
||||||
|
@ -104,31 +104,33 @@ dependencies {
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$coroutinesVersion"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$coroutinesVersion"
|
||||||
|
|
||||||
implementation "androidx.core:core-ktx:1.5.0"
|
implementation "androidx.core:core-ktx:1.7.0"
|
||||||
implementation "androidx.appcompat:appcompat:1.3.0"
|
implementation "androidx.appcompat:appcompat:1.4.1"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.3.4"
|
implementation "androidx.fragment:fragment-ktx:1.4.1"
|
||||||
implementation "androidx.browser:browser:1.3.0"
|
implementation "androidx.browser:browser:1.4.0"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
implementation "androidx.exifinterface:exifinterface:1.3.3"
|
implementation "androidx.exifinterface:exifinterface:1.3.3"
|
||||||
implementation "androidx.cardview:cardview:1.0.0"
|
implementation "androidx.cardview:cardview:1.0.0"
|
||||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||||
implementation "androidx.sharetarget:sharetarget:1.1.0"
|
implementation "androidx.sharetarget:sharetarget:1.2.0-rc01"
|
||||||
implementation "androidx.emoji:emoji:1.1.0"
|
implementation "androidx.emoji:emoji:1.1.0"
|
||||||
implementation "androidx.emoji:emoji-appcompat:1.1.0"
|
implementation "androidx.emoji:emoji-appcompat:1.1.0"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.2"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
|
||||||
implementation "androidx.paging:paging-runtime-ktx:3.0.0"
|
implementation "androidx.paging:paging-runtime-ktx:3.1.0"
|
||||||
implementation "androidx.viewpager2:viewpager2:1.0.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-ktx:$roomVersion"
|
||||||
|
implementation "androidx.room:room-paging:$roomVersion"
|
||||||
implementation "androidx.room:room-rxjava3:$roomVersion"
|
implementation "androidx.room:room-rxjava3:$roomVersion"
|
||||||
kapt "androidx.room:room-compiler:$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"
|
implementation "com.google.code.gson:gson:2.8.9"
|
||||||
|
|
||||||
|
|
6
app/src/green/res/values/flavor-colors.xml
Normal file
6
app/src/green/res/values/flavor-colors.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<color name="notification_color">#19A341</color>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -20,20 +20,7 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/TuskyTheme"
|
android:theme="@style/TuskyTheme"
|
||||||
android:usesCleartextTraffic="false">
|
android:usesCleartextTraffic="false">
|
||||||
<activity
|
|
||||||
android:name=".SplashActivity"
|
|
||||||
android:theme="@style/SplashTheme">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.app.shortcuts"
|
|
||||||
android:resource="@xml/share_shortcuts" />
|
|
||||||
|
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".components.login.LoginActivity"
|
android:name=".components.login.LoginActivity"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
@ -41,7 +28,15 @@
|
||||||
<activity android:name=".components.login.LoginWebViewActivity" />
|
<activity android:name=".components.login.LoginWebViewActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize">
|
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize"
|
||||||
|
android:theme="@style/SplashTheme"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
|
@ -88,6 +83,9 @@
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.service.chooser.chooser_target_service"
|
android:name="android.service.chooser.chooser_target_service"
|
||||||
android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
|
android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.shortcuts"
|
||||||
|
android:resource="@xml/share_shortcuts" />
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
|
@ -97,7 +95,6 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ViewThreadActivity"
|
android:name=".ViewThreadActivity"
|
||||||
android:configChanges="orientation|screenSize" />
|
android:configChanges="orientation|screenSize" />
|
||||||
<activity android:name=".ViewTagActivity" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ViewMediaActivity"
|
android:name=".ViewMediaActivity"
|
||||||
android:theme="@style/TuskyBaseTheme" />
|
android:theme="@style/TuskyBaseTheme" />
|
||||||
|
@ -115,7 +112,8 @@
|
||||||
android:theme="@style/Base.Theme.AppCompat" />
|
android:theme="@style/Base.Theme.AppCompat" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".components.search.SearchActivity"
|
android:name=".components.search.SearchActivity"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop"
|
||||||
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
@ -125,7 +123,6 @@
|
||||||
android:resource="@xml/searchable" />
|
android:resource="@xml/searchable" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".ListsActivity" />
|
<activity android:name=".ListsActivity" />
|
||||||
<activity android:name=".ModalTimelineActivity" />
|
|
||||||
<activity android:name=".LicenseActivity" />
|
<activity android:name=".LicenseActivity" />
|
||||||
<activity android:name=".FiltersActivity" />
|
<activity android:name=".FiltersActivity" />
|
||||||
<activity
|
<activity
|
||||||
|
@ -136,7 +133,8 @@
|
||||||
<activity android:name=".components.announcements.AnnouncementsActivity" />
|
<activity android:name=".components.announcements.AnnouncementsActivity" />
|
||||||
<activity android:name=".components.drafts.DraftsActivity" />
|
<activity android:name=".components.drafts.DraftsActivity" />
|
||||||
|
|
||||||
<receiver android:name=".receiver.NotificationClearBroadcastReceiver" />
|
<receiver android:name=".receiver.NotificationClearBroadcastReceiver"
|
||||||
|
android:exported="false" />
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".receiver.SendStatusBroadcastReceiver"
|
android:name=".receiver.SendStatusBroadcastReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
@ -147,13 +145,15 @@
|
||||||
android:icon="@drawable/ic_tusky"
|
android:icon="@drawable/ic_tusky"
|
||||||
android:label="Compose Toot"
|
android:label="Compose Toot"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
|
android:exported="true"
|
||||||
tools:targetApi="24">
|
tools:targetApi="24">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service android:name=".service.SendTootService" />
|
<service android:name=".service.SendTootService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
@ -167,10 +167,16 @@
|
||||||
|
|
||||||
<!-- disable automatic WorkManager initialization -->
|
<!-- disable automatic WorkManager initialization -->
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
android:authorities="${applicationId}.workmanager-init"
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
tools:node="merge">
|
||||||
|
<meta-data
|
||||||
|
android:name="androidx.work.WorkManagerInitializer"
|
||||||
|
android:value="androidx.startup"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -35,6 +35,7 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.emoji.text.EmojiCompat
|
import androidx.emoji.text.EmojiCompat
|
||||||
import androidx.emoji.text.EmojiCompat.InitCallback
|
import androidx.emoji.text.EmojiCompat.InitCallback
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
@ -159,8 +160,12 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// delete old notification channels
|
||||||
|
NotificationHelper.deleteLegacyNotificationChannels(this, accountManager)
|
||||||
|
|
||||||
val activeAccount = accountManager.activeAccount
|
val activeAccount = accountManager.activeAccount
|
||||||
?: return // will be redirected to LoginActivity by BaseActivity
|
?: return // will be redirected to LoginActivity by BaseActivity
|
||||||
|
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,7 +16,9 @@
|
||||||
package com.keylesspalace.tusky.components.compose
|
package com.keylesspalace.tusky.components.compose
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.app.ProgressDialog
|
import android.app.ProgressDialog
|
||||||
|
import android.content.ClipData
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
@ -45,8 +47,8 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
import androidx.core.view.ContentInfoCompat
|
||||||
import androidx.core.view.inputmethod.InputContentInfoCompat
|
import androidx.core.view.OnReceiveContentListener
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
@ -105,7 +107,7 @@ class ComposeActivity :
|
||||||
ComposeAutoCompleteAdapter.AutocompletionProvider,
|
ComposeAutoCompleteAdapter.AutocompletionProvider,
|
||||||
OnEmojiSelectedListener,
|
OnEmojiSelectedListener,
|
||||||
Injectable,
|
Injectable,
|
||||||
InputConnectionCompat.OnCommitContentListener,
|
OnReceiveContentListener,
|
||||||
ComposeScheduleView.OnTimeSetListener {
|
ComposeScheduleView.OnTimeSetListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -149,6 +151,18 @@ class ComposeActivity :
|
||||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
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 preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
|
val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
|
||||||
if (theme == "black") {
|
if (theme == "black") {
|
||||||
|
@ -282,7 +296,7 @@ class ComposeActivity :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupComposeField(preferences: SharedPreferences, startingText: String?) {
|
private fun setupComposeField(preferences: SharedPreferences, startingText: String?) {
|
||||||
binding.composeEditField.setOnCommitContentListener(this)
|
binding.composeEditField.setOnReceiveContentListener(this)
|
||||||
|
|
||||||
binding.composeEditField.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) }
|
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. */
|
/** This is for the fancy keyboards which can insert images and stuff, and drag&drop etc */
|
||||||
override fun onCommitContent(inputContentInfo: InputContentInfoCompat, flags: Int, opts: Bundle?): Boolean {
|
override fun onReceiveContent(view: View, contentInfo: ContentInfoCompat): ContentInfoCompat? {
|
||||||
// Verify the returned content's type is of the correct MIME type
|
if (contentInfo.clip.description.hasMimeType("image/*")) {
|
||||||
val supported = inputContentInfo.description.hasMimeType("image/*")
|
val split = contentInfo.partition { item: ClipData.Item -> item.uri != null }
|
||||||
|
split.first?.let { content ->
|
||||||
if (supported) {
|
for (i in 0 until content.clip.itemCount) {
|
||||||
val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
|
pickMedia(content.clip.getItemAt(i).uri)
|
||||||
if (lacksPermission) {
|
|
||||||
try {
|
|
||||||
inputContentInfo.requestPermission()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "InputContentInfoCompat#requestPermission() failed." + e.message)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pickMedia(inputContentInfo.contentUri, inputContentInfo)
|
return split.second
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
return contentInfo
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendStatus() {
|
private fun sendStatus() {
|
||||||
|
@ -784,12 +790,11 @@ class ComposeActivity :
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.sendStatus(contentText, spoilerText).observe(
|
viewModel.sendStatus(contentText, spoilerText).observe(
|
||||||
this,
|
this
|
||||||
{
|
) {
|
||||||
finishingUploadDialog?.dismiss()
|
finishingUploadDialog?.dismiss()
|
||||||
deleteDraftAndFinish()
|
deleteDraftAndFinish()
|
||||||
}
|
}
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
binding.composeEditField.error = getString(R.string.error_compose_character_limit)
|
binding.composeEditField.error = getString(R.string.error_compose_character_limit)
|
||||||
enableButtons(true)
|
enableButtons(true)
|
||||||
|
@ -859,12 +864,9 @@ class ComposeActivity :
|
||||||
viewModel.removeMediaFromQueue(item)
|
viewModel.removeMediaFromQueue(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pickMedia(uri: Uri, contentInfoCompat: InputContentInfoCompat? = null) {
|
private fun pickMedia(uri: Uri) {
|
||||||
withLifecycleContext {
|
withLifecycleContext {
|
||||||
viewModel.pickMedia(uri).observe { exceptionOrItem ->
|
viewModel.pickMedia(uri).observe { exceptionOrItem ->
|
||||||
|
|
||||||
contentInfoCompat?.releasePermission()
|
|
||||||
|
|
||||||
exceptionOrItem.asLeftOrNull()?.let {
|
exceptionOrItem.asLeftOrNull()?.let {
|
||||||
val errorId = when (it) {
|
val errorId = when (it) {
|
||||||
is VideoSizeException -> {
|
is VideoSizeException -> {
|
||||||
|
@ -1043,12 +1045,32 @@ class ComposeActivity :
|
||||||
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
||||||
|
|
||||||
internal const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS"
|
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"
|
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
|
@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 {
|
return Intent(context, ComposeActivity::class.java).apply {
|
||||||
putExtra(COMPOSE_OPTIONS_EXTRA, options)
|
putExtra(COMPOSE_OPTIONS_EXTRA, options)
|
||||||
|
if (notificationId != null) {
|
||||||
|
putExtra(NOTIFICATION_ID_EXTRA, notificationId)
|
||||||
|
}
|
||||||
|
if (accountId != null) {
|
||||||
|
putExtra(ACCOUNT_ID_EXTRA, accountId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ import android.util.AttributeSet
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputConnection
|
import android.view.inputmethod.InputConnection
|
||||||
import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView
|
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.EditorInfoCompat
|
||||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
import androidx.core.view.inputmethod.InputConnectionCompat
|
||||||
import androidx.emoji.widget.EmojiEditTextHelper
|
import androidx.emoji.widget.EmojiEditTextHelper
|
||||||
|
@ -32,41 +34,33 @@ class EditTextTyped @JvmOverloads constructor(
|
||||||
) :
|
) :
|
||||||
AppCompatMultiAutoCompleteTextView(context, attributeSet) {
|
AppCompatMultiAutoCompleteTextView(context, attributeSet) {
|
||||||
|
|
||||||
private var onCommitContentListener: InputConnectionCompat.OnCommitContentListener? = null
|
|
||||||
private val emojiEditTextHelper: EmojiEditTextHelper = EmojiEditTextHelper(this)
|
private val emojiEditTextHelper: EmojiEditTextHelper = EmojiEditTextHelper(this)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// fix a bug with autocomplete and some keyboards
|
// fix a bug with autocomplete and some keyboards
|
||||||
val newInputType = inputType and (inputType xor InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE)
|
val newInputType = inputType and (inputType xor InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE)
|
||||||
inputType = newInputType
|
inputType = newInputType
|
||||||
super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener))
|
super.setKeyListener(emojiEditTextHelper.getKeyListener(keyListener))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setKeyListener(input: KeyListener) {
|
override fun setKeyListener(input: KeyListener?) {
|
||||||
super.setKeyListener(getEmojiEditTextHelper().getKeyListener(input))
|
if (input != null) {
|
||||||
|
super.setKeyListener(emojiEditTextHelper.getKeyListener(input))
|
||||||
|
} else {
|
||||||
|
super.setKeyListener(input)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setOnCommitContentListener(listener: InputConnectionCompat.OnCommitContentListener) {
|
fun setOnReceiveContentListener(listener: OnReceiveContentListener) {
|
||||||
onCommitContentListener = listener
|
ViewCompat.setOnReceiveContentListener(this, arrayOf("image/*"), listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection {
|
override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection {
|
||||||
val connection = super.onCreateInputConnection(editorInfo)
|
val connection = super.onCreateInputConnection(editorInfo)
|
||||||
return if (onCommitContentListener != null) {
|
|
||||||
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*"))
|
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*"))
|
||||||
getEmojiEditTextHelper().onCreateInputConnection(
|
return emojiEditTextHelper.onCreateInputConnection(
|
||||||
InputConnectionCompat.createWrapper(
|
InputConnectionCompat.createWrapper(this, connection, editorInfo),
|
||||||
connection, editorInfo,
|
|
||||||
onCommitContentListener!!
|
|
||||||
),
|
|
||||||
editorInfo
|
editorInfo
|
||||||
)!!
|
)!!
|
||||||
} else {
|
|
||||||
connection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getEmojiEditTextHelper(): EmojiEditTextHelper {
|
|
||||||
return emojiEditTextHelper
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
@ -46,9 +45,9 @@ import androidx.work.WorkRequest;
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
import com.bumptech.glide.request.FutureTarget;
|
import com.bumptech.glide.request.FutureTarget;
|
||||||
import com.keylesspalace.tusky.BuildConfig;
|
|
||||||
import com.keylesspalace.tusky.MainActivity;
|
import com.keylesspalace.tusky.MainActivity;
|
||||||
import com.keylesspalace.tusky.R;
|
import com.keylesspalace.tusky.R;
|
||||||
|
import com.keylesspalace.tusky.components.compose.ComposeActivity;
|
||||||
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;
|
||||||
|
@ -67,6 +66,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -88,8 +88,6 @@ public class NotificationHelper {
|
||||||
|
|
||||||
public static final String REPLY_ACTION = "REPLY_ACTION";
|
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_REPLY = "KEY_REPLY";
|
||||||
|
|
||||||
public static final String KEY_SENDER_ACCOUNT_ID = "KEY_SENDER_ACCOUNT_ID";
|
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_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+
|
||||||
**/
|
**/
|
||||||
|
@ -206,21 +200,24 @@ public class NotificationHelper {
|
||||||
.setLabel(context.getString(R.string.label_quick_reply))
|
.setLabel(context.getString(R.string.label_quick_reply))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
PendingIntent quickReplyPendingIntent = getStatusReplyIntent(REPLY_ACTION, context, body, account);
|
PendingIntent quickReplyPendingIntent = getStatusReplyIntent(context, body, account);
|
||||||
|
|
||||||
NotificationCompat.Action quickReplyAction =
|
NotificationCompat.Action quickReplyAction =
|
||||||
new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp,
|
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)
|
.addRemoteInput(replyRemoteInput)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
builder.addAction(quickReplyAction);
|
builder.addAction(quickReplyAction);
|
||||||
|
|
||||||
PendingIntent composePendingIntent = getStatusReplyIntent(COMPOSE_ACTION, context, body, account);
|
PendingIntent composeIntent = getStatusComposeIntent(context, body, account);
|
||||||
|
|
||||||
NotificationCompat.Action composeAction =
|
NotificationCompat.Action composeAction =
|
||||||
new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp,
|
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();
|
.build();
|
||||||
|
|
||||||
builder.addAction(composeAction);
|
builder.addAction(composeAction);
|
||||||
|
@ -237,7 +234,6 @@ public class NotificationHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
// =======
|
|
||||||
final NotificationCompat.Builder summaryBuilder = newNotification(context, body, account, true);
|
final NotificationCompat.Builder summaryBuilder = newNotification(context, body, account, true);
|
||||||
|
|
||||||
if (currentNotifications.length() != 1) {
|
if (currentNotifications.length() != 1) {
|
||||||
|
@ -275,7 +271,7 @@ public class NotificationHelper {
|
||||||
summaryStackBuilder.addNextIntent(summaryResultIntent);
|
summaryStackBuilder.addNextIntent(summaryResultIntent);
|
||||||
|
|
||||||
PendingIntent summaryResultPendingIntent = summaryStackBuilder.getPendingIntent((int) (notificationId + account.getId() * 10000),
|
PendingIntent summaryResultPendingIntent = summaryStackBuilder.getPendingIntent((int) (notificationId + account.getId() * 10000),
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
pendingIntentFlags(false));
|
||||||
|
|
||||||
// we have to switch account here
|
// we have to switch account here
|
||||||
Intent eventResultIntent = new Intent(context, MainActivity.class);
|
Intent eventResultIntent = new Intent(context, MainActivity.class);
|
||||||
|
@ -285,18 +281,18 @@ public class NotificationHelper {
|
||||||
eventStackBuilder.addNextIntent(eventResultIntent);
|
eventStackBuilder.addNextIntent(eventResultIntent);
|
||||||
|
|
||||||
PendingIntent eventResultPendingIntent = eventStackBuilder.getPendingIntent((int) account.getId(),
|
PendingIntent eventResultPendingIntent = eventStackBuilder.getPendingIntent((int) account.getId(),
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
pendingIntentFlags(false));
|
||||||
|
|
||||||
Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class);
|
Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class);
|
||||||
deleteIntent.putExtra(ACCOUNT_ID, account.getId());
|
deleteIntent.putExtra(ACCOUNT_ID, account.getId());
|
||||||
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, summary ? (int) account.getId() : notificationId, deleteIntent,
|
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))
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getChannelId(account, body))
|
||||||
.setSmallIcon(R.drawable.ic_notify)
|
.setSmallIcon(R.drawable.ic_notify)
|
||||||
.setContentIntent(summary ? summaryResultPendingIntent : eventResultPendingIntent)
|
.setContentIntent(summary ? summaryResultPendingIntent : eventResultPendingIntent)
|
||||||
.setDeleteIntent(deletePendingIntent)
|
.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())
|
.setGroup(account.getAccountId())
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setShortcutId(Long.toString(account.getId()))
|
.setShortcutId(Long.toString(account.getId()))
|
||||||
|
@ -307,11 +303,9 @@ public class NotificationHelper {
|
||||||
return builder;
|
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();
|
Status status = body.getStatus();
|
||||||
|
|
||||||
String citedLocalAuthor = status.getAccount().getLocalUsername();
|
|
||||||
String citedText = status.getContent().toString();
|
|
||||||
String inReplyToId = status.getId();
|
String inReplyToId = status.getId();
|
||||||
Status actionableStatus = status.getActionableStatus();
|
Status actionableStatus = status.getActionableStatus();
|
||||||
Status.Visibility replyVisibility = actionableStatus.getVisibility();
|
Status.Visibility replyVisibility = actionableStatus.getVisibility();
|
||||||
|
@ -326,9 +320,7 @@ public class NotificationHelper {
|
||||||
mentionedUsernames = new ArrayList<>(new LinkedHashSet<>(mentionedUsernames));
|
mentionedUsernames = new ArrayList<>(new LinkedHashSet<>(mentionedUsernames));
|
||||||
|
|
||||||
Intent replyIntent = new Intent(context, SendStatusBroadcastReceiver.class)
|
Intent replyIntent = new Intent(context, SendStatusBroadcastReceiver.class)
|
||||||
.setAction(action)
|
.setAction(REPLY_ACTION)
|
||||||
.putExtra(KEY_CITED_AUTHOR_LOCAL, citedLocalAuthor)
|
|
||||||
.putExtra(KEY_CITED_TEXT, citedText)
|
|
||||||
.putExtra(KEY_SENDER_ACCOUNT_ID, account.getId())
|
.putExtra(KEY_SENDER_ACCOUNT_ID, account.getId())
|
||||||
.putExtra(KEY_SENDER_ACCOUNT_IDENTIFIER, account.getIdentifier())
|
.putExtra(KEY_SENDER_ACCOUNT_IDENTIFIER, account.getIdentifier())
|
||||||
.putExtra(KEY_SENDER_ACCOUNT_FULL_NAME, account.getFullName())
|
.putExtra(KEY_SENDER_ACCOUNT_FULL_NAME, account.getFullName())
|
||||||
|
@ -341,7 +333,50 @@ public class NotificationHelper {
|
||||||
return PendingIntent.getBroadcast(context.getApplicationContext(),
|
return PendingIntent.getBroadcast(context.getApplicationContext(),
|
||||||
notificationId,
|
notificationId,
|
||||||
replyIntent,
|
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<Status.Mention> mentions = actionableStatus.getMentions();
|
||||||
|
Set<String> 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) {
|
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);
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
notificationManager.deleteNotificationChannelGroup(account.getIdentifier());
|
notificationManager.deleteNotificationChannelGroup(account.getIdentifier());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,7 +454,6 @@ public class NotificationHelper {
|
||||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
// used until Tusky 1.4
|
// used until Tusky 1.4
|
||||||
//noinspection ConstantConditions
|
|
||||||
notificationManager.deleteNotificationChannel(CHANNEL_MENTION);
|
notificationManager.deleteNotificationChannel(CHANNEL_MENTION);
|
||||||
notificationManager.deleteNotificationChannel(CHANNEL_FAVOURITE);
|
notificationManager.deleteNotificationChannel(CHANNEL_FAVOURITE);
|
||||||
notificationManager.deleteNotificationChannel(CHANNEL_BOOST);
|
notificationManager.deleteNotificationChannel(CHANNEL_BOOST);
|
||||||
|
@ -440,7 +472,6 @@ public class NotificationHelper {
|
||||||
// on Android >= O, notifications are enabled, if at least one channel is enabled
|
// on Android >= O, notifications are enabled, if at least one channel is enabled
|
||||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
if (notificationManager.areNotificationsEnabled()) {
|
if (notificationManager.areNotificationsEnabled()) {
|
||||||
for (NotificationChannel channel : notificationManager.getNotificationChannels()) {
|
for (NotificationChannel channel : notificationManager.getNotificationChannels()) {
|
||||||
if (channel.getImportance() > NotificationManager.IMPORTANCE_NONE) {
|
if (channel.getImportance() > NotificationManager.IMPORTANCE_NONE) {
|
||||||
|
@ -491,7 +522,6 @@ public class NotificationHelper {
|
||||||
accountManager.saveAccount(account);
|
accountManager.saveAccount(account);
|
||||||
|
|
||||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
//noinspection ConstantConditions
|
|
||||||
notificationManager.cancel((int) account.getId());
|
notificationManager.cancel((int) account.getId());
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
|
@ -511,7 +541,6 @@ public class NotificationHelper {
|
||||||
// unknown notificationtype
|
// unknown notificationtype
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//noinspection ConstantConditions
|
|
||||||
NotificationChannel channel = notificationManager.getNotificationChannel(channelId);
|
NotificationChannel channel = notificationManager.getNotificationChannel(channelId);
|
||||||
return channel.getImportance() > NotificationManager.IMPORTANCE_NONE;
|
return channel.getImportance() > NotificationManager.IMPORTANCE_NONE;
|
||||||
}
|
}
|
||||||
|
@ -674,4 +703,11 @@ public class NotificationHelper {
|
||||||
return null;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.keylesspalace.tusky.MainActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.SplashActivity
|
|
||||||
import com.keylesspalace.tusky.databinding.DialogEmojicompatBinding
|
import com.keylesspalace.tusky.databinding.DialogEmojicompatBinding
|
||||||
import com.keylesspalace.tusky.databinding.ItemEmojiPrefBinding
|
import com.keylesspalace.tusky.databinding.ItemEmojiPrefBinding
|
||||||
import com.keylesspalace.tusky.util.EmojiCompatFont
|
import com.keylesspalace.tusky.util.EmojiCompatFont
|
||||||
|
@ -215,7 +215,7 @@ class EmojiPreference(
|
||||||
.setPositiveButton(R.string.restart) { _, _ ->
|
.setPositiveButton(R.string.restart) { _, _ ->
|
||||||
// Restart the app
|
// Restart the app
|
||||||
// From https://stackoverflow.com/a/17166729/5070653
|
// 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(
|
val mPendingIntent = PendingIntent.getActivity(
|
||||||
context,
|
context,
|
||||||
0x1f973, // This is the codepoint of the party face emoji :D
|
0x1f973, // This is the codepoint of the party face emoji :D
|
||||||
|
|
|
@ -280,7 +280,7 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateHttpProxySummary() {
|
private fun updateHttpProxySummary() {
|
||||||
val sharedPreferences = preferenceManager.sharedPreferences
|
preferenceManager.sharedPreferences?.let { sharedPreferences ->
|
||||||
val httpProxyEnabled = sharedPreferences.getBoolean(PrefKeys.HTTP_PROXY_ENABLED, false)
|
val httpProxyEnabled = sharedPreferences.getBoolean(PrefKeys.HTTP_PROXY_ENABLED, false)
|
||||||
val httpServer = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_SERVER, "")
|
val httpServer = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_SERVER, "")
|
||||||
|
|
||||||
|
@ -298,6 +298,7 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
|
|
||||||
httpProxyPref?.summary = ""
|
httpProxyPref?.summary = ""
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(): PreferencesFragment {
|
fun newInstance(): PreferencesFragment {
|
||||||
|
|
|
@ -90,9 +90,9 @@ class TimelineFragment :
|
||||||
|
|
||||||
private val viewModel: TimelineViewModel by lazy {
|
private val viewModel: TimelineViewModel by lazy {
|
||||||
if (kind == TimelineViewModel.Kind.HOME) {
|
if (kind == TimelineViewModel.Kind.HOME) {
|
||||||
ViewModelProvider(this, viewModelFactory).get(CachedTimelineViewModel::class.java)
|
ViewModelProvider(this, viewModelFactory)[CachedTimelineViewModel::class.java]
|
||||||
} else {
|
} 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)
|
isSwipeToRefreshEnabled = arguments.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true)
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
val statusDisplayOptions = StatusDisplayOptions(
|
val statusDisplayOptions = StatusDisplayOptions(
|
||||||
animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
|
animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
|
||||||
mediaPreviewEnabled = accountManager.activeAccount!!.mediaPreviewEnabled,
|
mediaPreviewEnabled = accountManager.activeAccount!!.mediaPreviewEnabled,
|
||||||
|
@ -224,7 +224,7 @@ class TimelineFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actionButtonPresent()) {
|
if (actionButtonPresent()) {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
hideFab = preferences.getBoolean("fabHide", false)
|
hideFab = preferences.getBoolean("fabHide", false)
|
||||||
scrollListener = object : RecyclerView.OnScrollListener() {
|
scrollListener = object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
@ -401,7 +401,7 @@ class TimelineFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPreferenceChanged(key: String) {
|
private fun onPreferenceChanged(key: String) {
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
when (key) {
|
when (key) {
|
||||||
PrefKeys.FAB_HIDE -> {
|
PrefKeys.FAB_HIDE -> {
|
||||||
hideFab = sharedPreferences.getBoolean(PrefKeys.FAB_HIDE, false)
|
hideFab = sharedPreferences.getBoolean(PrefKeys.FAB_HIDE, false)
|
||||||
|
@ -468,7 +468,7 @@ class TimelineFragment :
|
||||||
* Auto dispose observable on pause
|
* Auto dispose observable on pause
|
||||||
*/
|
*/
|
||||||
private fun startUpdateTimestamp() {
|
private fun startUpdateTimestamp() {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
val useAbsoluteTime = preferences.getBoolean(PrefKeys.ABSOLUTE_TIME_VIEW, false)
|
val useAbsoluteTime = preferences.getBoolean(PrefKeys.ABSOLUTE_TIME_VIEW, false)
|
||||||
if (!useAbsoluteTime) {
|
if (!useAbsoluteTime) {
|
||||||
Observable.interval(1, TimeUnit.MINUTES)
|
Observable.interval(1, TimeUnit.MINUTES)
|
||||||
|
|
|
@ -23,7 +23,6 @@ import com.keylesspalace.tusky.FiltersActivity
|
||||||
import com.keylesspalace.tusky.LicenseActivity
|
import com.keylesspalace.tusky.LicenseActivity
|
||||||
import com.keylesspalace.tusky.ListsActivity
|
import com.keylesspalace.tusky.ListsActivity
|
||||||
import com.keylesspalace.tusky.MainActivity
|
import com.keylesspalace.tusky.MainActivity
|
||||||
import com.keylesspalace.tusky.SplashActivity
|
|
||||||
import com.keylesspalace.tusky.StatusListActivity
|
import com.keylesspalace.tusky.StatusListActivity
|
||||||
import com.keylesspalace.tusky.TabPreferenceActivity
|
import com.keylesspalace.tusky.TabPreferenceActivity
|
||||||
import com.keylesspalace.tusky.ViewMediaActivity
|
import com.keylesspalace.tusky.ViewMediaActivity
|
||||||
|
@ -88,9 +87,6 @@ abstract class ActivitiesModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun contributesLoginWebViewActivity(): LoginWebViewActivity
|
abstract fun contributesLoginWebViewActivity(): LoginWebViewActivity
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
|
||||||
abstract fun contributesSplashActivity(): SplashActivity
|
|
||||||
|
|
||||||
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
|
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
|
||||||
abstract fun contributesPreferencesActivity(): PreferencesActivity
|
abstract fun contributesPreferencesActivity(): PreferencesActivity
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,14 @@ package com.keylesspalace.tusky.receiver
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.RemoteInput
|
import androidx.core.app.RemoteInput
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.keylesspalace.tusky.BuildConfig
|
||||||
import com.keylesspalace.tusky.R
|
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.components.notifications.NotificationHelper
|
||||||
import com.keylesspalace.tusky.db.AccountManager
|
import com.keylesspalace.tusky.db.AccountManager
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
|
@ -45,6 +45,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
AndroidInjection.inject(this, context)
|
AndroidInjection.inject(this, context)
|
||||||
|
|
||||||
|
if (intent.action == NotificationHelper.REPLY_ACTION) {
|
||||||
val notificationId = intent.getIntExtra(NotificationHelper.KEY_NOTIFICATION_ID, -1)
|
val notificationId = intent.getIntExtra(NotificationHelper.KEY_NOTIFICATION_ID, -1)
|
||||||
val senderId = intent.getLongExtra(NotificationHelper.KEY_SENDER_ACCOUNT_ID, -1)
|
val senderId = intent.getLongExtra(NotificationHelper.KEY_SENDER_ACCOUNT_ID, -1)
|
||||||
val senderIdentifier = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_IDENTIFIER)
|
val senderIdentifier = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_IDENTIFIER)
|
||||||
|
@ -53,15 +54,11 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
|
||||||
val visibility = intent.getSerializableExtra(NotificationHelper.KEY_VISIBILITY) as Status.Visibility
|
val visibility = intent.getSerializableExtra(NotificationHelper.KEY_VISIBILITY) as Status.Visibility
|
||||||
val spoiler = intent.getStringExtra(NotificationHelper.KEY_SPOILER) ?: ""
|
val spoiler = intent.getStringExtra(NotificationHelper.KEY_SPOILER) ?: ""
|
||||||
val mentions = intent.getStringArrayExtra(NotificationHelper.KEY_MENTIONS) ?: emptyArray()
|
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 account = accountManager.getAccountById(senderId)
|
||||||
|
|
||||||
val notificationManager = NotificationManagerCompat.from(context)
|
val notificationManager = NotificationManagerCompat.from(context)
|
||||||
|
|
||||||
if (intent.action == NotificationHelper.REPLY_ACTION) {
|
|
||||||
|
|
||||||
val message = getReplyMessage(intent)
|
val message = getReplyMessage(intent)
|
||||||
|
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
|
@ -109,9 +106,15 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
context.startService(sendIntent)
|
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)
|
val builder = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MENTION + senderIdentifier)
|
||||||
.setSmallIcon(R.drawable.ic_notify)
|
.setSmallIcon(R.drawable.ic_notify)
|
||||||
.setColor(ContextCompat.getColor(context, (R.color.tusky_blue)))
|
.setColor(color)
|
||||||
.setGroup(senderFullName)
|
.setGroup(senderFullName)
|
||||||
.setDefaults(0) // So it doesn't ring twice, notify only in Target callback
|
.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())
|
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ import com.keylesspalace.tusky.appstore.EventHub
|
||||||
import com.keylesspalace.tusky.appstore.StatusComposedEvent
|
import com.keylesspalace.tusky.appstore.StatusComposedEvent
|
||||||
import com.keylesspalace.tusky.appstore.StatusScheduledEvent
|
import com.keylesspalace.tusky.appstore.StatusScheduledEvent
|
||||||
import com.keylesspalace.tusky.components.drafts.DraftHelper
|
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.AccountManager
|
||||||
import com.keylesspalace.tusky.db.AppDatabase
|
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.entity.NewPoll
|
import com.keylesspalace.tusky.entity.NewPoll
|
||||||
import com.keylesspalace.tusky.entity.NewStatus
|
import com.keylesspalace.tusky.entity.NewStatus
|
||||||
|
@ -50,8 +50,6 @@ class SendTootService : Service(), Injectable {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var eventHub: EventHub
|
lateinit var eventHub: EventHub
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var database: AppDatabase
|
|
||||||
@Inject
|
|
||||||
lateinit var draftHelper: DraftHelper
|
lateinit var draftHelper: DraftHelper
|
||||||
|
|
||||||
private val supervisorJob = SupervisorJob()
|
private val supervisorJob = SupervisorJob()
|
||||||
|
@ -95,7 +93,7 @@ class SendTootService : Service(), Injectable {
|
||||||
.setContentText(notificationText)
|
.setContentText(notificationText)
|
||||||
.setProgress(1, 0, true)
|
.setProgress(1, 0, true)
|
||||||
.setOngoing(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))
|
.addAction(0, getString(android.R.string.cancel), cancelSendingIntent(sendingNotificationId))
|
||||||
|
|
||||||
if (tootsToSend.size == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
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)
|
.setSmallIcon(R.drawable.ic_notify)
|
||||||
.setContentTitle(getString(R.string.send_toot_notification_error_title))
|
.setContentTitle(getString(R.string.send_toot_notification_error_title))
|
||||||
.setContentText(getString(R.string.send_toot_notification_saved_content))
|
.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.cancel(tootId)
|
||||||
notificationManager.notify(errorNotificationId--, builder.build())
|
notificationManager.notify(errorNotificationId--, builder.build())
|
||||||
|
@ -232,7 +230,7 @@ class SendTootService : Service(), Injectable {
|
||||||
.setSmallIcon(R.drawable.ic_notify)
|
.setSmallIcon(R.drawable.ic_notify)
|
||||||
.setContentTitle(getString(R.string.send_toot_notification_cancel_title))
|
.setContentTitle(getString(R.string.send_toot_notification_cancel_title))
|
||||||
.setContentText(getString(R.string.send_toot_notification_saved_content))
|
.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())
|
notificationManager.notify(tootId, builder.build())
|
||||||
|
|
||||||
|
@ -267,12 +265,9 @@ class SendTootService : Service(), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelSendingIntent(tootId: Int): PendingIntent {
|
private fun cancelSendingIntent(tootId: Int): PendingIntent {
|
||||||
|
|
||||||
val intent = Intent(this, SendTootService::class.java)
|
val intent = Intent(this, SendTootService::class.java)
|
||||||
|
|
||||||
intent.putExtra(KEY_CANCEL, tootId)
|
intent.putExtra(KEY_CANCEL, tootId)
|
||||||
|
return PendingIntent.getService(this, tootId, intent, NotificationHelper.pendingIntentFlags(false))
|
||||||
return PendingIntent.getService(this, tootId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
Binary file not shown.
Before Width: | Height: | Size: 29 KiB |
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item>
|
|
||||||
<color android:color="@color/tusky_grey_20"/>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<bitmap
|
|
||||||
android:src="@drawable/splash"
|
|
||||||
android:gravity="center" />
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
|
15
app/src/main/res/drawable/ic_splash.xml
Normal file
15
app/src/main/res/drawable/ic_splash.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_height="240dp"
|
||||||
|
android:layout_width="240dp">
|
||||||
|
|
||||||
|
<item android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:top="40dp"
|
||||||
|
android:bottom="40dp"
|
||||||
|
android:left="40dp"
|
||||||
|
android:right="40dp"
|
||||||
|
android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
|
||||||
|
</layer-list>
|
|
@ -1,15 +1,5 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
|
|
||||||
<item name="android:windowBackground">@drawable/background_splash</item>
|
|
||||||
<item name="colorPrimary">@color/tusky_grey_20</item>
|
|
||||||
<item name="colorPrimaryDark">@color/tusky_grey_20</item>
|
|
||||||
<item name="android:windowNoTitle">true</item>
|
|
||||||
<item name="android:windowLightNavigationBar">false</item>
|
|
||||||
<item name="android:navigationBarColor">@color/tusky_grey_20</item>
|
|
||||||
<item name="android:navigationBarDividerColor">@color/tusky_grey_25</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="TuskyTheme" parent="TuskyBaseTheme">
|
<style name="TuskyTheme" parent="TuskyBaseTheme">
|
||||||
<item name="android:windowLightNavigationBar">@bool/lightNavigationBar</item>
|
<item name="android:windowLightNavigationBar">@bool/lightNavigationBar</item>
|
||||||
<item name="android:navigationBarColor">@color/colorBackground</item>
|
<item name="android:navigationBarColor">@color/colorBackground</item>
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
<color name="white">#fff</color>
|
<color name="white">#fff</color>
|
||||||
<color name="black">#000</color>
|
<color name="black">#000</color>
|
||||||
|
|
||||||
|
<color name="notification_color">@color/tusky_blue</color>
|
||||||
|
|
||||||
<!-- the number roughly corresponds to the % lightness of the grey -->
|
<!-- the number roughly corresponds to the % lightness of the grey -->
|
||||||
<color name="tusky_grey_05">#070b14</color>
|
<color name="tusky_grey_05">#070b14</color>
|
||||||
<color name="tusky_grey_10">#16191f</color>
|
<color name="tusky_grey_10">#16191f</color>
|
||||||
|
@ -24,7 +26,6 @@
|
||||||
<color name="tusky_grey_90">#d9e1e8</color>
|
<color name="tusky_grey_90">#d9e1e8</color>
|
||||||
<color name="tusky_grey_95">#ebeff4</color>
|
<color name="tusky_grey_95">#ebeff4</color>
|
||||||
|
|
||||||
|
|
||||||
<color name="transparent_tusky_blue">#8c2b90d9</color>
|
<color name="transparent_tusky_blue">#8c2b90d9</color>
|
||||||
<color name="transparent_black">#8f000000</color>
|
<color name="transparent_black">#8f000000</color>
|
||||||
<color name="header_background_filter_dark">#44000000</color>
|
<color name="header_background_filter_dark">#44000000</color>
|
||||||
|
|
|
@ -30,11 +30,10 @@
|
||||||
<item name="status_text_large">22sp</item>
|
<item name="status_text_large">22sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SplashTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
<style name="SplashTheme" parent="Theme.SplashScreen">
|
||||||
<item name="android:windowBackground">@drawable/background_splash</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_splash</item>
|
||||||
<item name="colorPrimary">@color/tusky_grey_10</item>
|
<item name="windowSplashScreenBackground">@color/tusky_grey_20</item>
|
||||||
<item name="colorPrimaryDark">@color/tusky_grey_10</item>
|
<item name="postSplashScreenTheme">@style/TuskyTheme</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TuskyTheme" parent="TuskyBaseTheme" />
|
<style name="TuskyTheme" parent="TuskyBaseTheme" />
|
||||||
|
|
|
@ -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", "<https://mastodon.examples/api/v1/favourites?limit=30&max_id=1>; rel=\"next\", <https://mastodon.example/api/v1/favourites?limit=30&min_id=5>; 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) {}
|
|
||||||
}
|
|
Loading…
Reference in a new issue