Api 33 support (#2719)

* update to Api 33, fix some deprecations

* fix deprecated serializable/parcelable methods

* ask for notification permission

* fix code formatting

* add back comment in PreferencesActivity
This commit is contained in:
Konrad Pozniak 2022-11-04 19:22:38 +01:00 committed by GitHub
parent 58e8f75287
commit d17a0c43ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 174 additions and 90 deletions

View File

@ -19,11 +19,11 @@ def getGitSha = {
} }
android { android {
compileSdkVersion 31 compileSdkVersion 33
defaultConfig { defaultConfig {
applicationId APP_ID applicationId APP_ID
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 31 targetSdkVersion 33
versionCode 94 versionCode 94
versionName "19.0" versionName "19.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -108,16 +108,17 @@ 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.8.0" implementation "androidx.core:core-ktx:1.9.0"
implementation "androidx.appcompat:appcompat:1.4.2" implementation "androidx.appcompat:appcompat:1.5.1"
implementation "androidx.fragment:fragment-ktx:1.5.1" implementation "androidx.activity:activity-ktx:1.6.0"
implementation "androidx.fragment:fragment-ktx:1.5.3"
implementation "androidx.browser:browser:1.4.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.4"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.sharetarget:sharetarget:1.2.0-rc01" implementation "androidx.sharetarget:sharetarget:1.2.0"
implementation "androidx.emoji2:emoji2:$emoji2_version" implementation "androidx.emoji2:emoji2:$emoji2_version"
implementation "androidx.emoji2:emoji2-views:$emoji2_version" implementation "androidx.emoji2:emoji2-views:$emoji2_version"
implementation "androidx.emoji2:emoji2-views-helper:$emoji2_version" implementation "androidx.emoji2:emoji2-views-helper:$emoji2_version"

View File

@ -4,6 +4,7 @@
package="com.keylesspalace.tusky"> package="com.keylesspalace.tusky">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" /> <!-- For notifications --> <uses-permission android:name="android.permission.VIBRATE" /> <!-- For notifications -->

View File

@ -20,6 +20,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import com.keylesspalace.tusky.databinding.ActivityAccountListBinding import com.keylesspalace.tusky.databinding.ActivityAccountListBinding
import com.keylesspalace.tusky.fragment.AccountListFragment import com.keylesspalace.tusky.fragment.AccountListFragment
import com.keylesspalace.tusky.util.requireSerializableExtra
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import javax.inject.Inject import javax.inject.Inject
@ -44,7 +45,7 @@ class AccountListActivity : BaseActivity(), HasAndroidInjector {
val binding = ActivityAccountListBinding.inflate(layoutInflater) val binding = ActivityAccountListBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
val type = intent.getSerializableExtra(EXTRA_TYPE) as Type val type: Type = intent.requireSerializableExtra(EXTRA_TYPE)
val id: String? = intent.getStringExtra(EXTRA_ID) val id: String? = intent.getStringExtra(EXTRA_ID)
val accountLocked: Boolean = intent.getBooleanExtra(EXTRA_ACCOUNT_LOCKED, false) val accountLocked: Boolean = intent.getBooleanExtra(EXTRA_ACCOUNT_LOCKED, false)

View File

@ -132,7 +132,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (item.getItemId() == android.R.id.home) {
onBackPressed(); getOnBackPressedDispatcher().onBackPressed();
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);

View File

@ -15,9 +15,11 @@
package com.keylesspalace.tusky package com.keylesspalace.tusky
import android.Manifest
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
@ -31,8 +33,11 @@ import android.view.KeyEvent
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -267,6 +272,33 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
} }
selectedEmojiPack = preferences.getString(EMOJI_PREFERENCE, "") selectedEmojiPack = preferences.getString(EMOJI_PREFERENCE, "")
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
when {
binding.mainDrawerLayout.isOpen -> {
binding.mainDrawerLayout.close()
}
binding.viewPager.currentItem != 0 -> {
binding.viewPager.currentItem = 0
}
else -> {
finish()
}
}
}
}
)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
1
)
}
} }
override fun onResume() { override fun onResume() {
@ -292,20 +324,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
} }
} }
override fun onBackPressed() {
when {
binding.mainDrawerLayout.isOpen -> {
binding.mainDrawerLayout.close()
}
binding.viewPager.currentItem != 0 -> {
binding.viewPager.currentItem = 0
}
else -> {
super.onBackPressed()
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_MENU -> { KeyEvent.KEYCODE_MENU -> {

View File

@ -20,9 +20,9 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -74,6 +74,12 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
private val hashtagRegex by lazy { Pattern.compile("([\\w_]*[\\p{Alpha}_][\\w_]*)", Pattern.CASE_INSENSITIVE) } private val hashtagRegex by lazy { Pattern.compile("([\\w_]*[\\p{Alpha}_][\\w_]*)", Pattern.CASE_INSENSITIVE) }
private val onFabDismissedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
toggleFab(false)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -149,6 +155,8 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
binding.maxTabsInfo.text = resources.getQuantityString(R.plurals.max_tab_number_reached, MAX_TAB_COUNT, MAX_TAB_COUNT) binding.maxTabsInfo.text = resources.getQuantityString(R.plurals.max_tab_number_reached, MAX_TAB_COUNT, MAX_TAB_COUNT)
updateAvailableTabs() updateAvailableTabs()
onBackPressedDispatcher.addCallback(onFabDismissedCallback)
} }
override fun onTabAdded(tab: TabData) { override fun onTabAdded(tab: TabData) {
@ -209,6 +217,8 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
binding.actionButton.visible(!expand) binding.actionButton.visible(!expand)
binding.sheet.visible(expand) binding.sheet.visible(expand)
binding.scrim.visible(expand) binding.scrim.visible(expand)
onFabDismissedCallback.isEnabled = expand
} }
private fun showAddHashtagDialog(tab: TabData? = null, tabPosition: Int = 0) { private fun showAddHashtagDialog(tab: TabData? = null, tabPosition: Int = 0) {
@ -338,14 +348,6 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
tabsChanged = true tabsChanged = true
} }
override fun onBackPressed() {
if (binding.actionButton.isVisible) {
super.onBackPressed()
} else {
toggleFab(false)
}
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
if (tabsChanged) { if (tabsChanged) {

View File

@ -54,6 +54,7 @@ import com.keylesspalace.tusky.fragment.ViewImageFragment
import com.keylesspalace.tusky.pager.ImagePagerAdapter import com.keylesspalace.tusky.pager.ImagePagerAdapter
import com.keylesspalace.tusky.pager.SingleImagePagerAdapter import com.keylesspalace.tusky.pager.SingleImagePagerAdapter
import com.keylesspalace.tusky.util.getTemporaryMediaFilename import com.keylesspalace.tusky.util.getTemporaryMediaFilename
import com.keylesspalace.tusky.util.parcelableArrayListExtra
import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.viewdata.AttachmentViewData import com.keylesspalace.tusky.viewdata.AttachmentViewData
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
@ -94,7 +95,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
supportPostponeEnterTransition() supportPostponeEnterTransition()
// Gather the parameters. // Gather the parameters.
attachments = intent.getParcelableArrayListExtra(EXTRA_ATTACHMENTS) attachments = intent.parcelableArrayListExtra(EXTRA_ATTACHMENTS)
val initialPosition = intent.getIntExtra(EXTRA_ATTACHMENT_INDEX, 0) val initialPosition = intent.getIntExtra(EXTRA_ATTACHMENT_INDEX, 0)
// Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener // Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener

View File

@ -40,6 +40,7 @@ import android.widget.ImageButton
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
@ -92,6 +93,8 @@ import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.highlightSpans import com.keylesspalace.tusky.util.highlightSpans
import com.keylesspalace.tusky.util.loadAvatar import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.onTextChanged import com.keylesspalace.tusky.util.onTextChanged
import com.keylesspalace.tusky.util.parcelableArrayListExtra
import com.keylesspalace.tusky.util.parcelableExtra
import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
@ -237,8 +240,7 @@ class ComposeActivity :
/* If the composer is started up as a reply to another post, override the "starting" state /* If the composer is started up as a reply to another post, override the "starting" state
* based on what the intent from the reply request passes. */ * based on what the intent from the reply request passes. */
val composeOptions: ComposeOptions? = intent.parcelableExtra(COMPOSE_OPTIONS_EXTRA)
val composeOptions: ComposeOptions? = intent.getParcelableExtra(COMPOSE_OPTIONS_EXTRA)
viewModel.setup(composeOptions) viewModel.setup(composeOptions)
@ -299,12 +301,12 @@ class ComposeActivity :
if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("audio/")) { if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("audio/")) {
when (intent.action) { when (intent.action) {
Intent.ACTION_SEND -> { Intent.ACTION_SEND -> {
intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { uri -> intent.parcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { uri ->
pickMedia(uri) pickMedia(uri)
} }
} }
Intent.ACTION_SEND_MULTIPLE -> { Intent.ACTION_SEND_MULTIPLE -> {
intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.forEach { uri -> intent.parcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.forEach { uri ->
pickMedia(uri) pickMedia(uri)
} }
} }
@ -510,6 +512,27 @@ class ComposeActivity :
binding.actionPhotoTake.setOnClickListener { initiateCameraApp() } binding.actionPhotoTake.setOnClickListener { initiateCameraApp() }
binding.actionPhotoPick.setOnClickListener { onMediaPick() } binding.actionPhotoPick.setOnClickListener { onMediaPick() }
binding.addPollTextActionTextView.setOnClickListener { openPollDialog() } binding.addPollTextActionTextView.setOnClickListener { openPollDialog() }
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (composeOptionsBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED
) {
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
handleCloseButton()
}
}
)
} }
private fun setupLanguageSpinner(initialLanguage: String?) { private fun setupLanguageSpinner(initialLanguage: String?) {
@ -1069,23 +1092,6 @@ class ComposeActivity :
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun onBackPressed() {
// Acting like a teen: deliberately ignoring parent.
if (composeOptionsBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED
) {
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
handleCloseButton()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
Log.d(TAG, event.toString()) Log.d(TAG, event.toString())
if (event.action == KeyEvent.ACTION_DOWN) { if (event.action == KeyEvent.ACTION_DOWN) {
@ -1098,7 +1104,7 @@ class ComposeActivity :
} }
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed() onBackPressedDispatcher.onBackPressed()
return true return true
} }
} }

View File

@ -38,6 +38,7 @@ import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.github.chrisbanes.photoview.PhotoView import com.github.chrisbanes.photoview.PhotoView
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.util.parcelable
// https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32 // https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500 private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500
@ -93,8 +94,7 @@ class CaptionDialog : DialogFragment() {
val window = dialog.window val window = dialog.window
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val previewUri = val previewUri: Uri = arguments?.parcelable(PREVIEW_URI_ARG) ?: error("Preview Uri is null")
arguments?.getParcelable<Uri>(PREVIEW_URI_ARG) ?: error("Preview Uri is null")
// Load the image and manually set it into the ImageView because it doesn't have a fixed size. // Load the image and manually set it into the ImageView because it doesn't have a fixed size.
Glide.with(this) Glide.with(this)
.load(previewUri) .load(previewUri)

View File

@ -23,6 +23,7 @@ import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ActivityLoginWebviewBinding import com.keylesspalace.tusky.databinding.ActivityLoginWebviewBinding
import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.parcelableExtra
import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.viewBinding
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -39,7 +40,7 @@ class OauthLogin : ActivityResultContract<LoginData, LoginResult>() {
return if (resultCode == Activity.RESULT_CANCELED) { return if (resultCode == Activity.RESULT_CANCELED) {
LoginResult.Cancel LoginResult.Cancel
} else { } else {
intent!!.getParcelableExtra(RESULT_EXTRA)!! intent!!.parcelableExtra(RESULT_EXTRA)!!
} }
} }
@ -48,7 +49,7 @@ class OauthLogin : ActivityResultContract<LoginData, LoginResult>() {
private const val DATA_EXTRA = "data" private const val DATA_EXTRA = "data"
fun parseData(intent: Intent): LoginData { fun parseData(intent: Intent): LoginData {
return intent.getParcelableExtra(DATA_EXTRA)!! return intent.parcelableExtra(DATA_EXTRA)!!
} }
fun makeResultIntent(result: LoginResult): Intent { fun makeResultIntent(result: LoginResult): Intent {

View File

@ -20,6 +20,7 @@ import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -47,7 +48,17 @@ class PreferencesActivity :
@Inject @Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any> lateinit var androidInjector: DispatchingAndroidInjector<Any>
private var restartActivitiesOnExit: Boolean = false private val restartActivitiesOnBackPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
/* Switching themes won't actually change the theme of activities on the back stack.
* Either the back stack activities need to all be recreated, or do the easier thing, which
* is hijack the back button press and use it to launch a new MainActivity and clear the
* back stack. */
val intent = Intent(this@PreferencesActivity, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivityWithSlideInAnimation(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -92,7 +103,8 @@ class PreferencesActivity :
replace(R.id.fragment_container, fragment, fragmentTag) replace(R.id.fragment_container, fragment, fragmentTag)
} }
restartActivitiesOnExit = intent.getBooleanExtra("restart", false) onBackPressedDispatcher.addCallback(this, restartActivitiesOnBackPressedCallback)
restartActivitiesOnBackPressedCallback.isEnabled = savedInstanceState?.getBoolean(EXTRA_RESTART_ON_BACK, false) ?: false
} }
override fun onResume() { override fun onResume() {
@ -106,11 +118,11 @@ class PreferencesActivity :
} }
private fun saveInstanceState(outState: Bundle) { private fun saveInstanceState(outState: Bundle) {
outState.putBoolean("restart", restartActivitiesOnExit) outState.putBoolean(EXTRA_RESTART_ON_BACK, restartActivitiesOnBackPressedCallback.isEnabled)
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean("restart", restartActivitiesOnExit) outState.putBoolean(EXTRA_RESTART_ON_BACK, restartActivitiesOnBackPressedCallback.isEnabled)
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
@ -121,16 +133,16 @@ class PreferencesActivity :
Log.d("activeTheme", theme) Log.d("activeTheme", theme)
ThemeUtils.setAppNightMode(theme) ThemeUtils.setAppNightMode(theme)
restartActivitiesOnExit = true restartActivitiesOnBackPressedCallback.isEnabled = true
this.restartCurrentActivity() this.restartCurrentActivity()
} }
"statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars", "useBlurhash", "statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars", "useBlurhash",
"showSelfUsername", "showCardsInTimelines", "confirmReblogs", "confirmFavourites", "showSelfUsername", "showCardsInTimelines", "confirmReblogs", "confirmFavourites",
"enableSwipeForTabs", "mainNavPosition", PrefKeys.HIDE_TOP_TOOLBAR -> { "enableSwipeForTabs", "mainNavPosition", PrefKeys.HIDE_TOP_TOOLBAR -> {
restartActivitiesOnExit = true restartActivitiesOnBackPressedCallback.isEnabled = true
} }
"language" -> { "language" -> {
restartActivitiesOnExit = true restartActivitiesOnBackPressedCallback.isEnabled = true
this.restartCurrentActivity() this.restartCurrentActivity()
} }
} }
@ -148,20 +160,6 @@ class PreferencesActivity :
overridePendingTransition(R.anim.fade_in, R.anim.fade_out) overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
} }
override fun onBackPressed() {
/* Switching themes won't actually change the theme of activities on the back stack.
* Either the back stack activities need to all be recreated, or do the easier thing, which
* is hijack the back button press and use it to launch a new MainActivity and clear the
* back stack. */
if (restartActivitiesOnExit) {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivityWithSlideInAnimation(intent)
} else {
super.onBackPressed()
}
}
override fun androidInjector() = androidInjector override fun androidInjector() = androidInjector
companion object { companion object {
@ -172,6 +170,7 @@ class PreferencesActivity :
const val TAB_FILTER_PREFERENCES = 3 const val TAB_FILTER_PREFERENCES = 3
const val PROXY_PREFERENCES = 4 const val PROXY_PREFERENCES = 4
private const val EXTRA_PREFERENCE_TYPE = "EXTRA_PREFERENCE_TYPE" private const val EXTRA_PREFERENCE_TYPE = "EXTRA_PREFERENCE_TYPE"
private const val EXTRA_RESTART_ON_BACK = "restart"
@JvmStatic @JvmStatic
fun newIntent(context: Context, preferenceType: Int): Intent { fun newIntent(context: Context, preferenceType: Int): Intent {

View File

@ -383,7 +383,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
} != null } != null
} }
private fun showOpenAsDialog(statusUrl: String, dialogTitle: CharSequence) { private fun showOpenAsDialog(statusUrl: String, dialogTitle: CharSequence?) {
bottomSheetActivity?.showAccountChooserDialog( bottomSheetActivity?.showAccountChooserDialog(
dialogTitle, false, dialogTitle, false,
object : AccountSelectionListener { object : AccountSelectionListener {

View File

@ -102,7 +102,7 @@ class ViewThreadFragment : SFragment(), OnRefreshListener, StatusActionListener,
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.toolbar.setNavigationOnClickListener { binding.toolbar.setNavigationOnClickListener {
activity?.onBackPressed() activity?.onBackPressedDispatcher?.onBackPressed()
} }
binding.toolbar.setOnMenuItemClickListener { menuItem -> binding.toolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) { when (menuItem.itemId) {

View File

@ -49,6 +49,7 @@ import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.util.HttpHeaderLink import com.keylesspalace.tusky.util.HttpHeaderLink
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.requireSerializable
import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.view.EndlessOnScrollListener import com.keylesspalace.tusky.view.EndlessOnScrollListener
@ -78,8 +79,8 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
type = arguments?.getSerializable(ARG_TYPE) as Type type = requireArguments().requireSerializable(ARG_TYPE)
id = arguments?.getString(ARG_ID) id = requireArguments().getString(ARG_ID)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -100,7 +101,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis) Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis)
Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis) Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis)
Type.FOLLOW_REQUESTS -> { Type.FOLLOW_REQUESTS -> {
val headerAdapter = FollowRequestsHeaderAdapter(accountManager.activeAccount!!.domain, arguments?.get(ARG_ACCOUNT_LOCKED) == true) val headerAdapter = FollowRequestsHeaderAdapter(accountManager.activeAccount!!.domain, arguments?.getBoolean(ARG_ACCOUNT_LOCKED) == true)
val followRequestsAdapter = FollowRequestsAdapter(this, animateAvatar, animateEmojis) val followRequestsAdapter = FollowRequestsAdapter(this, animateAvatar, animateEmojis)
binding.recyclerView.adapter = ConcatAdapter(headerAdapter, followRequestsAdapter) binding.recyclerView.adapter = ConcatAdapter(headerAdapter, followRequestsAdapter)
followRequestsAdapter followRequestsAdapter

View File

@ -36,6 +36,7 @@ import com.keylesspalace.tusky.ViewMediaActivity
import com.keylesspalace.tusky.databinding.FragmentViewImageBinding import com.keylesspalace.tusky.databinding.FragmentViewImageBinding
import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.parcelable
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
import io.reactivex.rxjava3.subjects.BehaviorSubject import io.reactivex.rxjava3.subjects.BehaviorSubject
import kotlin.math.abs import kotlin.math.abs
@ -92,7 +93,7 @@ class ViewImageFragment : ViewMediaFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val arguments = this.requireArguments() val arguments = this.requireArguments()
val attachment = arguments.getParcelable<Attachment>(ARG_ATTACHMENT) val attachment: Attachment? = arguments.parcelable(ARG_ATTACHMENT)
this.shouldStartTransition = arguments.getBoolean(ARG_START_POSTPONED_TRANSITION) this.shouldStartTransition = arguments.getBoolean(ARG_START_POSTPONED_TRANSITION)
val url: String? val url: String?
var description: String? = null var description: String? = null

View File

@ -31,6 +31,7 @@ import com.keylesspalace.tusky.ViewMediaActivity
import com.keylesspalace.tusky.databinding.FragmentViewVideoBinding import com.keylesspalace.tusky.databinding.FragmentViewVideoBinding
import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.parcelable
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
@ -170,7 +171,7 @@ class ViewVideoFragment : ViewMediaFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val attachment = arguments?.getParcelable<Attachment>(ARG_ATTACHMENT) val attachment: Attachment? = requireArguments().parcelable(ARG_ATTACHMENT)
if (attachment == null) { if (attachment == null) {
throw IllegalArgumentException("attachment has to be set") throw IllegalArgumentException("attachment has to be set")

View File

@ -29,6 +29,7 @@ import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.service.SendStatusService import com.keylesspalace.tusky.service.SendStatusService
import com.keylesspalace.tusky.service.StatusToSend import com.keylesspalace.tusky.service.StatusToSend
import com.keylesspalace.tusky.util.randomAlphanumericString import com.keylesspalace.tusky.util.randomAlphanumericString
import com.keylesspalace.tusky.util.requireSerializableExtra
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import javax.inject.Inject import javax.inject.Inject
@ -48,7 +49,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
val senderIdentifier = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_IDENTIFIER) val senderIdentifier = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_IDENTIFIER)
val senderFullName = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_FULL_NAME) val senderFullName = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_FULL_NAME)
val citedStatusId = intent.getStringExtra(NotificationHelper.KEY_CITED_STATUS_ID) val citedStatusId = intent.getStringExtra(NotificationHelper.KEY_CITED_STATUS_ID)
val visibility = intent.getSerializableExtra(NotificationHelper.KEY_VISIBILITY) as Status.Visibility val visibility: Status.Visibility = intent.requireSerializableExtra(NotificationHelper.KEY_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()

View File

@ -31,6 +31,7 @@ import com.keylesspalace.tusky.entity.NewPoll
import com.keylesspalace.tusky.entity.NewStatus import com.keylesspalace.tusky.entity.NewStatus
import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.parcelableExtra
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -72,7 +73,7 @@ class SendStatusService : Service(), Injectable {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
if (intent.hasExtra(KEY_STATUS)) { if (intent.hasExtra(KEY_STATUS)) {
val statusToSend = intent.getParcelableExtra<StatusToSend>(KEY_STATUS) val statusToSend: StatusToSend = intent.parcelableExtra(KEY_STATUS)
?: throw IllegalStateException("SendStatusService started without $KEY_STATUS extra") ?: throw IllegalStateException("SendStatusService started without $KEY_STATUS extra")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View File

@ -0,0 +1,49 @@
@file:Suppress("DEPRECATION")
package com.keylesspalace.tusky.util
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import java.io.Serializable
inline fun <reified T : Serializable> Intent.requireSerializableExtra(name: String?): T {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializableExtra(name, T::class.java)!!
} else {
getSerializableExtra(name) as T
}
}
inline fun <reified T : Parcelable> Intent.parcelableArrayListExtra(name: String?): ArrayList<T>? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableArrayListExtra(name, T::class.java)
} else {
getParcelableArrayListExtra(name)
}
}
inline fun <reified T : Parcelable> Bundle.parcelable(name: String?): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelable(name, T::class.java)
} else {
getParcelable(name)
}
}
inline fun <reified T : Parcelable> Intent.parcelableExtra(name: String?): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableExtra(name, T::class.java)
} else {
getParcelableExtra(name) as T?
}
}
inline fun <reified T : Serializable> Bundle.requireSerializable(name: String?): T {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializable(name, T::class.java)!!
} else {
getSerializable(name) as T
}
}

View File

@ -464,7 +464,7 @@ class ComposeActivityTest {
} }
private fun clickBack() { private fun clickBack() {
activity.onBackPressed() activity.onBackPressedDispatcher.onBackPressed()
} }
private fun insertSomeTextInContent(text: String? = null) { private fun insertSomeTextInContent(text: String? = null) {