From b976fe52969b1b2ed1b25115f81a2b740be0468b Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Fri, 23 Feb 2024 10:27:19 +0100 Subject: [PATCH] full sdk 34 support (#4224) builds upon work from #4082 Additionally fixes some deprecations and adds support for [predictive back](https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture). I also refactored how the activity transitions work because they are closely related to predictive back. The awkward `finishWithoutSlideOutAnimation` is gone, activities that have been started with slide in will now automatically close with slide out. To test predictive back you need an emulator or device with Sdk 34 (Android 14) and then enable it in the developer settings. Predictive back requires the back action to be determined before it actually occurs so the system can play the right predictive animation, which made a few reorganisations necessary. closes #4082 closes #4005 unlocks a bunch of dependency upgrades that require sdk 34 --------- Co-authored-by: Goooler --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 3 +- .../com/keylesspalace/tusky/AboutActivity.kt | 1 + .../com/keylesspalace/tusky/BaseActivity.java | 34 +++---- .../tusky/BottomSheetActivity.kt | 1 + .../tusky/EditProfileActivity.kt | 34 +++++-- .../com/keylesspalace/tusky/ListsActivity.kt | 1 + .../com/keylesspalace/tusky/MainActivity.kt | 60 ++++++++----- .../keylesspalace/tusky/StatusListActivity.kt | 1 + .../keylesspalace/tusky/ViewMediaActivity.kt | 1 + .../tusky/adapter/AccountFieldEditAdapter.kt | 6 +- .../components/account/AccountActivity.kt | 1 + .../accountlist/AccountListFragment.kt | 14 ++- .../announcements/AnnouncementsActivity.kt | 1 + .../components/compose/ComposeActivity.kt | 90 ++++++++++++------- .../components/compose/ComposeViewModel.kt | 36 ++++++-- .../components/filters/FiltersActivity.kt | 4 +- .../components/login/LoginWebViewActivity.kt | 6 +- .../preference/AccountPreferencesFragment.kt | 39 ++------ .../preference/PreferencesActivity.kt | 23 ++--- .../tusky/components/search/SearchActivity.kt | 4 - .../search/fragments/SearchFragment.kt | 1 + .../fragments/SearchStatusesFragment.kt | 1 + .../components/timeline/TimelineFragment.kt | 6 +- .../trending/TrendingTagsFragment.kt | 4 +- .../viewthread/ViewThreadFragment.kt | 6 +- .../viewthread/edits/ViewEditsFragment.kt | 1 + .../tusky/fragment/ViewVideoFragment.kt | 2 +- .../tusky/service/TuskyTileService.kt | 11 ++- .../tusky/util/ActivityExensions.kt | 21 +++++ .../tusky/view/ClickableSpanTextView.kt | 10 +-- .../com/keylesspalace/tusky/view/GraphView.kt | 4 +- .../tusky/viewmodel/EditProfileViewModel.kt | 13 +-- app/src/main/res/anim/fade_in.xml | 6 -- app/src/main/res/anim/fade_out.xml | 6 -- .../components/compose/StatusLengthTest.kt | 2 + 36 files changed, 272 insertions(+), 186 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/ActivityExensions.kt delete mode 100644 app/src/main/res/anim/fade_in.xml delete mode 100644 app/src/main/res/anim/fade_out.xml diff --git a/app/build.gradle b/app/build.gradle index 708ee7e9a..63b103366 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,13 +22,13 @@ final def CUSTOM_INSTANCE = "" final def SUPPORT_ACCOUNT_URL = "https://mastodon.social/@Tusky" android { - compileSdk 33 + compileSdk 34 namespace "com.keylesspalace.tusky" defaultConfig { applicationId APP_ID namespace "com.keylesspalace.tusky" minSdk 24 - targetSdk 33 + targetSdk 34 versionCode 117 versionName "24.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8c03d7a9b..1d11f1513 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,8 @@ android:supportsRtl="true" android:theme="@style/TuskyTheme" android:usesCleartextTraffic="false" - android:localeConfig="@xml/locales_config"> + android:localeConfig="@xml/locales_config" + android:enableOnBackInvokedCallback="true"> = Build.VERSION_CODES.UPSIDE_DOWN_CAKE && getIntent().getBooleanExtra(OPEN_WITH_SLIDE_IN, false)) { + overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, R.anim.slide_from_right, R.anim.slide_to_left); + overrideActivityTransition(OVERRIDE_TRANSITION_CLOSE, R.anim.slide_from_left, R.anim.slide_to_right); + } + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); /* There isn't presently a way to globally change the theme of a whole application at @@ -166,11 +176,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab return style; } - public void startActivityWithSlideInAnimation(@NonNull Intent intent) { - super.startActivity(intent); - overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left); - } - @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == android.R.id.home) { @@ -183,11 +188,10 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab @Override public void finish() { super.finish(); - overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right); - } - - public void finishWithoutSlideOutAnimation() { - super.finish(); + // if this activity was opened with slide-in, close it with slide out + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE && getIntent().getBooleanExtra(OPEN_WITH_SLIDE_IN, false)) { + overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right); + } } protected void redirectIfNotLoggedIn() { @@ -195,7 +199,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab if (account == null) { Intent intent = new Intent(this, LoginActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityWithSlideInAnimation(intent); + ActivityExtensions.startActivityWithSlideInAnimation(this, intent); finish(); } } @@ -235,9 +239,9 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab adapter.addAll(accounts); new AlertDialog.Builder(this) - .setTitle(dialogTitle) - .setAdapter(adapter, (dialogInterface, index) -> listener.onAccountSelected(accounts.get(index))) - .show(); + .setTitle(dialogTitle) + .setAdapter(adapter, (dialogInterface, index) -> listener.onAccountSelected(accounts.get(index))) + .show(); } public @Nullable String getOpenAsText() { @@ -263,7 +267,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab Intent intent = MainActivity.redirectIntent(this, account.getId(), url); startActivity(intent); - finishWithoutSlideOutAnimation(); + finish(); } @Override diff --git a/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt b/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt index ac5ae4c4d..8d62ed6b6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt @@ -31,6 +31,7 @@ import com.keylesspalace.tusky.components.viewthread.ViewThreadActivity import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.looksLikeMastodonUrl import com.keylesspalace.tusky.util.openLink +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import javax.inject.Inject diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt index f447984ef..2ccf27823 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt @@ -29,6 +29,7 @@ import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible +import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.LiveData import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager @@ -230,18 +231,33 @@ class EditProfileActivity : BaseActivity(), Injectable { } } - val onBackCallback = object : OnBackPressedCallback(enabled = true) { - override fun handleOnBackPressed() = checkForUnsavedChanges() + binding.displayNameEditText.doAfterTextChanged { + viewModel.dataChanged(currentProfileData) + } + + binding.displayNameEditText.doAfterTextChanged { + viewModel.dataChanged(currentProfileData) + } + + binding.lockedCheckBox.setOnCheckedChangeListener { _, _ -> + viewModel.dataChanged(currentProfileData) + } + + accountFieldEditAdapter.onFieldsChanged = { + viewModel.dataChanged(currentProfileData) + } + + val onBackCallback = object : OnBackPressedCallback(enabled = false) { + override fun handleOnBackPressed() { + showUnsavedChangesDialog() + } } onBackPressedDispatcher.addCallback(this, onBackCallback) - } - - fun checkForUnsavedChanges() { - if (viewModel.hasUnsavedChanges(currentProfileData)) { - showUnsavedChangesDialog() - } else { - finish() + lifecycleScope.launch { + viewModel.isChanged.collect { dataWasChanged -> + onBackCallback.isEnabled = dataWasChanged + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt index 44e073ec0..f8a2e9465 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt @@ -43,6 +43,7 @@ import com.keylesspalace.tusky.entity.MastoList import com.keylesspalace.tusky.util.BindingHolder import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.viewmodel.ListsViewModel diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 9e445d102..0b6e54b3b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -51,6 +51,7 @@ import androidx.core.view.GravityCompat import androidx.core.view.MenuProvider import androidx.core.view.forEach import androidx.core.view.isVisible +import androidx.drawerlayout.widget.DrawerLayout import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceManager import androidx.viewpager2.widget.MarginPageTransformer @@ -107,6 +108,7 @@ import com.keylesspalace.tusky.util.getDimension import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.reduceSwipeSensitivity import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.unsafeLazy import com.keylesspalace.tusky.util.updateShortcut import com.keylesspalace.tusky.util.viewBinding @@ -185,6 +187,19 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje private var directMessageTab: TabLayout.Tab? = null + private val onBackPressedCallback = object : OnBackPressedCallback(false) { + override fun handleOnBackPressed() { + when { + binding.mainDrawerLayout.isOpen -> { + binding.mainDrawerLayout.close() + } + binding.viewPager.currentItem != 0 -> { + binding.viewPager.currentItem = 0 + } + } + } + } + @SuppressLint("RestrictedApi") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -373,24 +388,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje 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() - } - } - } - } - ) + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) if ( Build.VERSION.SDK_INT >= 33 && @@ -616,6 +614,19 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje ) setSavedInstance(savedInstanceState) } + binding.mainDrawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener { + override fun onDrawerSlide(drawerView: View, slideOffset: Float) { } + + override fun onDrawerOpened(drawerView: View) { + onBackPressedCallback.isEnabled = true + } + + override fun onDrawerClosed(drawerView: View) { + onBackPressedCallback.isEnabled = binding.tabLayout.selectedTabPosition > 0 + } + + override fun onDrawerStateChanged(newState: Int) { } + }) } private fun refreshMainDrawerItems( @@ -876,6 +887,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje onTabSelectedListener = object : OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { + onBackPressedCallback.isEnabled = tab.position > 0 || binding.mainDrawerLayout.isOpen + binding.mainToolbar.title = tab.contentDescription refreshComposeButtonState(tabAdapter, tab.position) @@ -964,8 +977,13 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje intent.putExtras(forward) } startActivity(intent) - finishWithoutSlideOutAnimation() - overridePendingTransition(R.anim.explode, R.anim.explode) + finish() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, R.anim.explode, R.anim.explode) + } else { + @Suppress("DEPRECATION") + overridePendingTransition(R.anim.explode, R.anim.explode) + } } private fun logout() { @@ -988,7 +1006,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje LoginActivity.getIntent(this@MainActivity, LoginActivity.MODE_DEFAULT) } startActivity(intent) - finishWithoutSlideOutAnimation() + finish() } } .setNegativeButton(android.R.string.cancel, null) diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt index 44aa5064f..c844f2256 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt @@ -35,6 +35,7 @@ import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.entity.FilterV1 import com.keylesspalace.tusky.util.isHttpNotFound +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.viewBinding import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index df0250994..736f5a915 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -58,6 +58,7 @@ import com.keylesspalace.tusky.fragment.ViewVideoFragment import com.keylesspalace.tusky.pager.ImagePagerAdapter import com.keylesspalace.tusky.pager.SingleImagePagerAdapter import com.keylesspalace.tusky.util.getTemporaryMediaFilename +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.viewdata.AttachmentViewData import dagger.android.DispatchingAndroidInjector diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldEditAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldEditAdapter.kt index d6aa6016c..890e956f1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldEditAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldEditAdapter.kt @@ -24,7 +24,9 @@ import com.keylesspalace.tusky.entity.StringField import com.keylesspalace.tusky.util.BindingHolder import com.keylesspalace.tusky.util.fixTextSelection -class AccountFieldEditAdapter : RecyclerView.Adapter>() { +class AccountFieldEditAdapter( + var onFieldsChanged: () -> Unit = { } +) : RecyclerView.Adapter>() { private val fieldData = mutableListOf() private var maxNameLength: Int? = null @@ -90,10 +92,12 @@ class AccountFieldEditAdapter : RecyclerView.Adapter fieldData.getOrNull(holder.bindingAdapterPosition)?.first = newText.toString() + onFieldsChanged() } holder.binding.accountFieldValueText.doAfterTextChanged { newText -> fieldData.getOrNull(holder.bindingAdapterPosition)?.second = newText.toString() + onFieldsChanged() } // Ensure the textview contents are selectable diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt index 0d21e3a79..70f6669ba 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt @@ -92,6 +92,7 @@ import com.keylesspalace.tusky.util.parseAsMastodonHtml import com.keylesspalace.tusky.util.reduceSwipeSensitivity import com.keylesspalace.tusky.util.setClickableText import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.unsafeLazy import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.visible diff --git a/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt index 6df9b387f..66b226144 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt @@ -31,7 +31,6 @@ import at.connyduck.calladapter.networkresult.fold import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from import autodispose2.autoDispose import com.google.android.material.snackbar.Snackbar -import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.BottomSheetActivity import com.keylesspalace.tusky.PostLookupFallbackBehavior import com.keylesspalace.tusky.R @@ -56,6 +55,7 @@ import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.HttpHeaderLink import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.view.EndlessOnScrollListener import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers @@ -144,17 +144,13 @@ class AccountListFragment : } override fun onViewTag(tag: String) { - (activity as BaseActivity?) - ?.startActivityWithSlideInAnimation( - StatusListActivity.newHashtagIntent(requireContext(), tag) - ) + activity?.startActivityWithSlideInAnimation( + StatusListActivity.newHashtagIntent(requireContext(), tag) + ) } override fun onViewAccount(id: String) { - (activity as BaseActivity?)?.let { - val intent = AccountActivity.getIntent(it, id) - it.startActivityWithSlideInAnimation(intent) - } + activity?.startActivityWithSlideInAnimation(AccountActivity.getIntent(requireContext(), id)) } override fun onViewUrl(url: String) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt index 13526fe21..2c41a1ef4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt @@ -44,6 +44,7 @@ import com.keylesspalace.tusky.util.Loading import com.keylesspalace.tusky.util.Success import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.unsafeLazy import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.view.EmojiPicker diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 07bd2d2ae..91383e381 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -70,6 +70,7 @@ import com.canhub.cropper.CropImage import com.canhub.cropper.CropImageContract import com.canhub.cropper.options import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback import com.google.android.material.color.MaterialColors import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.BaseActivity @@ -215,6 +216,24 @@ class ComposeActivity : viewModel.cropImageItemOld = null } + private val onBackPressedCallback = object : OnBackPressedCallback(false) { + 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() + } + } + public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -425,7 +444,10 @@ class ComposeActivity : if (startingContentWarning != null) { binding.composeContentWarningField.setText(startingContentWarning) } - binding.composeContentWarningField.doOnTextChanged { _, _, _, _ -> updateVisibleCharactersLeft() } + binding.composeContentWarningField.doOnTextChanged { newContentWarning, _, _, _ -> + updateVisibleCharactersLeft() + viewModel.updateContentWarning(newContentWarning?.toString()) + } } private fun setupComposeField(preferences: SharedPreferences, startingText: String?) { @@ -456,6 +478,7 @@ class ComposeActivity : binding.composeEditField.doAfterTextChanged { editable -> highlightSpans(editable!!, mentionColour) updateVisibleCharactersLeft() + viewModel.updateContent(editable.toString()) } // work around Android platform bug -> https://issuetracker.google.com/issues/67102093 @@ -547,6 +570,12 @@ class ComposeActivity : } } } + + lifecycleScope.launch { + viewModel.closeConfirmation.collect { + updateOnBackPressedCallbackState() + } + } } private fun setupButtons() { @@ -557,6 +586,17 @@ class ComposeActivity : scheduleBehavior = BottomSheetBehavior.from(binding.composeScheduleView) emojiBehavior = BottomSheetBehavior.from(binding.emojiView) + val bottomSheetCallback = object : BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, newState: Int) { + updateOnBackPressedCallbackState() + } + override fun onSlide(bottomSheet: View, slideOffset: Float) { } + } + composeOptionsBehavior.addBottomSheetCallback(bottomSheetCallback) + addMediaBehavior.addBottomSheetCallback(bottomSheetCallback) + scheduleBehavior.addBottomSheetCallback(bottomSheetCallback) + emojiBehavior.addBottomSheetCallback(bottomSheetCallback) + enableButton(binding.composeEmojiButton, clickable = false, colorActive = false) // Setup the interface buttons. @@ -618,26 +658,7 @@ class ComposeActivity : binding.actionPhotoPick.setOnClickListener { onMediaPick() } 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() - } - } - ) + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) } private fun setupLanguageSpinner(initialLanguages: List) { @@ -690,6 +711,15 @@ class ComposeActivity : ) } + private fun updateOnBackPressedCallbackState() { + val confirmation = viewModel.closeConfirmation.value + onBackPressedCallback.isEnabled = confirmation != ConfirmationKind.NONE || + composeOptionsBehavior.state != BottomSheetBehavior.STATE_HIDDEN || + addMediaBehavior.state != BottomSheetBehavior.STATE_HIDDEN || + emojiBehavior.state != BottomSheetBehavior.STATE_HIDDEN || + scheduleBehavior.state != BottomSheetBehavior.STATE_HIDDEN + } + private fun replaceTextAtCaret(text: CharSequence) { // If you select "backward" in an editable, you get SelectionStart > SelectionEnd val start = binding.composeEditField.selectionStart.coerceAtMost( @@ -1004,7 +1034,7 @@ class ComposeActivity : } private fun removePoll() { - viewModel.poll.value = null + viewModel.updatePoll(null) binding.pollPreview.hide() } @@ -1219,7 +1249,7 @@ class ComposeActivity : } private fun pickMedia(uri: Uri, description: String? = null) { - var sanitizedDescription = sanitizePickMediaDescription(description) + val sanitizedDescription = sanitizePickMediaDescription(description) lifecycleScope.launch { viewModel.pickMedia(uri, sanitizedDescription).onFailure { throwable -> @@ -1292,10 +1322,10 @@ class ComposeActivity : private fun handleCloseButton() { val contentText = binding.composeEditField.text.toString() val contentWarning = binding.composeContentWarningField.text.toString() - when (viewModel.handleCloseButton(contentText, contentWarning)) { + when (viewModel.closeConfirmation.value) { ConfirmationKind.NONE -> { viewModel.stopUploads() - finishWithoutSlideOutAnimation() + finish() } ConfirmationKind.SAVE_OR_DISCARD -> getSaveAsDraftOrDiscardDialog(contentText, contentWarning).show() @@ -1355,7 +1385,7 @@ class ComposeActivity : } .setNegativeButton(R.string.action_discard) { _, _ -> viewModel.stopUploads() - finishWithoutSlideOutAnimation() + finish() } } @@ -1371,7 +1401,7 @@ class ComposeActivity : } .setNegativeButton(R.string.action_discard) { _, _ -> viewModel.stopUploads() - finishWithoutSlideOutAnimation() + finish() } } @@ -1385,7 +1415,7 @@ class ComposeActivity : .setPositiveButton(R.string.action_delete) { _, _ -> viewModel.deleteDraft() viewModel.stopUploads() - finishWithoutSlideOutAnimation() + finish() } .setNegativeButton(R.string.action_continue_edit) { _, _ -> // Do nothing, dialog will dismiss, user can continue editing @@ -1394,7 +1424,7 @@ class ComposeActivity : private fun deleteDraftAndFinish() { viewModel.deleteDraft() - finishWithoutSlideOutAnimation() + finish() } private fun saveDraftAndFinish(contentText: String, contentWarning: String) { @@ -1412,7 +1442,7 @@ class ComposeActivity : } viewModel.saveDraft(contentText, contentWarning) dialog?.cancel() - finishWithoutSlideOutAnimation() + finish() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index 50bd3fdfc..74e840f78 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -76,6 +76,9 @@ class ComposeViewModel @Inject constructor( private var modifiedInitialState: Boolean = false private var hasScheduledTimeChanged: Boolean = false + private var currentContent: String? = "" + private var currentContentWarning: String? = "" + val instanceInfo: SharedFlow = instanceInfoRepo::getInstanceInfo.asFlow() .shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1) @@ -99,6 +102,8 @@ class ComposeViewModel @Inject constructor( onBufferOverflow = BufferOverflow.DROP_OLDEST ) + val closeConfirmation = MutableStateFlow(ConfirmationKind.NONE) + private lateinit var composeKind: ComposeKind // Used in ComposeActivity to pass state to result function when cropImage contract inflight @@ -199,6 +204,7 @@ class ComposeViewModel @Inject constructor( } } } + updateCloseConfirmation() return mediaItem } @@ -228,21 +234,37 @@ class ComposeViewModel @Inject constructor( fun removeMediaFromQueue(item: QueuedMedia) { mediaUploader.cancelUploadScope(item.localId) media.update { mediaList -> mediaList.filter { it.localId != item.localId } } + updateCloseConfirmation() } fun toggleMarkSensitive() { this.markMediaAsSensitive.value = this.markMediaAsSensitive.value != true } - fun handleCloseButton(contentText: String?, contentWarning: String?): ConfirmationKind { - return if (didChange(contentText, contentWarning)) { + fun updateContent(newContent: String?) { + currentContent = newContent + updateCloseConfirmation() + } + + fun updateContentWarning(newContentWarning: String?) { + currentContentWarning = newContentWarning + updateCloseConfirmation() + } + + private fun updateCloseConfirmation() { + val contentWarning = if (showContentWarning.value) { + currentContentWarning + } else { + "" + } + this.closeConfirmation.value = if (didChange(currentContent, contentWarning)) { when (composeKind) { - ComposeKind.NEW -> if (isEmpty(contentText, contentWarning)) { + ComposeKind.NEW -> if (isEmpty(currentContent, contentWarning)) { ConfirmationKind.NONE } else { ConfirmationKind.SAVE_OR_DISCARD } - ComposeKind.EDIT_DRAFT -> if (isEmpty(contentText, contentWarning)) { + ComposeKind.EDIT_DRAFT -> if (isEmpty(currentContent, contentWarning)) { ConfirmationKind.CONTINUE_EDITING_OR_DISCARD_DRAFT } else { ConfirmationKind.UPDATE_OR_DISCARD @@ -272,6 +294,7 @@ class ComposeViewModel @Inject constructor( fun contentWarningChanged(value: Boolean) { showContentWarning.value = value contentWarningStateChanged = true + updateCloseConfirmation() } fun deleteDraft() { @@ -511,11 +534,14 @@ class ComposeViewModel @Inject constructor( replyingStatusContent = composeOptions?.replyingStatusContent replyingStatusAuthor = composeOptions?.replyingStatusAuthor + updateCloseConfirmation() + setupComplete = true } - fun updatePoll(newPoll: NewPoll) { + fun updatePoll(newPoll: NewPoll?) { poll.value = newPoll + updateCloseConfirmation() } fun updateScheduledAt(newScheduledAt: String?) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt index cc3afc70e..0acc043ae 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt @@ -12,6 +12,7 @@ import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.visible import javax.inject.Inject @@ -110,8 +111,7 @@ class FiltersActivity : BaseActivity(), FiltersListener { putExtra(EditFilterActivity.FILTER_TO_EDIT, filter) } } - startActivity(intent) - overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) + startActivityWithSlideInAnimation(intent) } override fun deleteFilter(filter: Filter) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt index 75735b471..b8c113b90 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt @@ -227,14 +227,10 @@ class LoginWebViewActivity : BaseActivity(), Injectable { super.onDestroy() } - override fun finish() { - super.finishWithoutSlideOutAnimation() - } - override fun requiresLogin() = false private fun sendResult(result: LoginResult) { setResult(Activity.RESULT_OK, OauthLogin.makeResultIntent(result)) - finishWithoutSlideOutAnimation() + finish() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt index bdbed2fc5..f369f51eb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt @@ -51,6 +51,7 @@ import com.keylesspalace.tusky.util.getInitialLanguages import com.keylesspalace.tusky.util.getLocaleList import com.keylesspalace.tusky.util.getTuskyDisplayName import com.keylesspalace.tusky.util.makeIcon +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.unsafeLazy import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial @@ -100,11 +101,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setIcon(R.drawable.ic_tabs) setOnPreferenceClickListener { val intent = Intent(context, TabPreferenceActivity::class.java) - activity?.startActivity(intent) - activity?.overridePendingTransition( - R.anim.slide_from_right, - R.anim.slide_to_left - ) + activity?.startActivityWithSlideInAnimation(intent) true } } @@ -114,11 +111,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setIcon(R.drawable.ic_hashtag) setOnPreferenceClickListener { val intent = Intent(context, FollowedTagsActivity::class.java) - activity?.startActivity(intent) - activity?.overridePendingTransition( - R.anim.slide_from_right, - R.anim.slide_to_left - ) + activity?.startActivityWithSlideInAnimation(intent) true } } @@ -129,11 +122,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setOnPreferenceClickListener { val intent = Intent(context, AccountListActivity::class.java) intent.putExtra("type", AccountListActivity.Type.MUTES) - activity?.startActivity(intent) - activity?.overridePendingTransition( - R.anim.slide_from_right, - R.anim.slide_to_left - ) + activity?.startActivityWithSlideInAnimation(intent) true } } @@ -147,11 +136,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setOnPreferenceClickListener { val intent = Intent(context, AccountListActivity::class.java) intent.putExtra("type", AccountListActivity.Type.BLOCKS) - activity?.startActivity(intent) - activity?.overridePendingTransition( - R.anim.slide_from_right, - R.anim.slide_to_left - ) + activity?.startActivityWithSlideInAnimation(intent) true } } @@ -161,11 +146,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setIcon(R.drawable.ic_mute_24dp) setOnPreferenceClickListener { val intent = Intent(context, DomainBlocksActivity::class.java) - activity?.startActivity(intent) - activity?.overridePendingTransition( - R.anim.slide_from_right, - R.anim.slide_to_left - ) + activity?.startActivityWithSlideInAnimation(intent) true } } @@ -176,7 +157,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setIcon(R.drawable.ic_logout) setOnPreferenceClickListener { val intent = LoginActivity.getIntent(context, LoginActivity.MODE_MIGRATION) - (activity as BaseActivity).startActivityWithSlideInAnimation(intent) + activity?.startActivityWithSlideInAnimation(intent) true } } @@ -300,8 +281,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { it, PreferencesActivity.NOTIFICATION_PREFERENCES ) - it.startActivity(intent) - it.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) + it.startActivityWithSlideInAnimation(intent) } } } @@ -368,8 +348,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { private fun launchFilterActivity() { val intent = Intent(context, FiltersActivity::class.java) - activity?.startActivity(intent) - activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) + (activity as? BaseActivity)?.startActivityWithSlideInAnimation(intent) } companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt index 478e07ce8..c608f102a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt @@ -38,6 +38,7 @@ import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.PrefKeys.APP_THEME import com.keylesspalace.tusky.util.getNonNullString import com.keylesspalace.tusky.util.setAppNightMode +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector import javax.inject.Inject @@ -139,16 +140,14 @@ class PreferencesActivity : ).unregisterOnSharedPreferenceChangeListener(this) } - private fun saveInstanceState(outState: Bundle) { - outState.putBoolean(EXTRA_RESTART_ON_BACK, restartActivitiesOnBackPressedCallback.isEnabled) - } - override fun onSaveInstanceState(outState: Bundle) { outState.putBoolean(EXTRA_RESTART_ON_BACK, restartActivitiesOnBackPressedCallback.isEnabled) super.onSaveInstanceState(outState) } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + sharedPreferences ?: return + key ?: return when (key) { APP_THEME -> { val theme = sharedPreferences.getNonNullString(APP_THEME, AppTheme.DEFAULT.value) @@ -156,11 +155,11 @@ class PreferencesActivity : setAppNightMode(theme) restartActivitiesOnBackPressedCallback.isEnabled = true - this.restartCurrentActivity() + this.recreate() } PrefKeys.UI_TEXT_SCALE_RATIO -> { restartActivitiesOnBackPressedCallback.isEnabled = true - this.restartCurrentActivity() + this.recreate() } PrefKeys.STATUS_TEXT_SIZE, PrefKeys.ABSOLUTE_TIME_VIEW, PrefKeys.SHOW_BOT_OVERLAY, PrefKeys.ANIMATE_GIF_AVATARS, PrefKeys.USE_BLURHASH, PrefKeys.SHOW_SELF_USERNAME, PrefKeys.SHOW_CARDS_IN_TIMELINES, PrefKeys.CONFIRM_REBLOGS, PrefKeys.CONFIRM_FAVOURITES, @@ -173,16 +172,6 @@ class PreferencesActivity : } } - private fun restartCurrentActivity() { - intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK - val savedInstanceState = Bundle() - saveInstanceState(savedInstanceState) - intent.putExtras(savedInstanceState) - startActivityWithSlideInAnimation(intent) - finish() - overridePendingTransition(R.anim.fade_in, R.anim.fade_out) - } - override fun androidInjector() = androidInjector companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt index 1b3de0f88..7cdff23fa 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt @@ -97,10 +97,6 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector, MenuProvider, return false } - override fun finish() { - super.finishWithoutSlideOutAnimation() - } - private fun getPageTitle(position: Int): CharSequence { return when (position) { 0 -> getString(R.string.title_posts) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt index 5a59c790b..1b0a92466 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt @@ -28,6 +28,7 @@ import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.network.MastodonApi +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.visible import com.mikepenz.iconics.IconicsDrawable diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index d0f14c771..24f3065de 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -58,6 +58,7 @@ import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.openLink +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.view.showMuteAccountDialog import com.keylesspalace.tusky.viewdata.AttachmentViewData import com.keylesspalace.tusky.viewdata.StatusViewData diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt index b817ab59e..649b4010f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt @@ -39,7 +39,6 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import at.connyduck.sparkbutton.helpers.Utils import autodispose2.androidx.lifecycle.autoDispose import com.google.android.material.color.MaterialColors -import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.adapter.StatusBaseViewHolder import com.keylesspalace.tusky.appstore.EventHub @@ -66,6 +65,7 @@ import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.unsafeLazy import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.viewdata.AttachmentViewData @@ -463,13 +463,13 @@ class TimelineFragment : override fun onShowReblogs(position: Int) { val statusId = adapter.peek(position)?.asStatusOrNull()?.id ?: return val intent = newIntent(requireContext(), AccountListActivity.Type.REBLOGGED, statusId) - (activity as BaseActivity).startActivityWithSlideInAnimation(intent) + activity?.startActivityWithSlideInAnimation(intent) } override fun onShowFavs(position: Int) { val statusId = adapter.peek(position)?.asStatusOrNull()?.id ?: return val intent = newIntent(requireContext(), AccountListActivity.Type.FAVOURITED, statusId) - (activity as BaseActivity).startActivityWithSlideInAnimation(intent) + activity?.startActivityWithSlideInAnimation(intent) } override fun onLoadMore(position: Int) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsFragment.kt index ba5e7821f..6b0a62af9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/trending/TrendingTagsFragment.kt @@ -30,7 +30,6 @@ import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.SimpleItemAnimator import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import at.connyduck.sparkbutton.helpers.Utils -import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.StatusListActivity import com.keylesspalace.tusky.components.trending.viewmodel.TrendingTagsViewModel @@ -42,6 +41,7 @@ import com.keylesspalace.tusky.interfaces.RefreshableFragment import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.viewdata.TrendingViewData import javax.inject.Inject @@ -136,7 +136,7 @@ class TrendingTagsFragment : } fun onViewTag(tag: String) { - (requireActivity() as BaseActivity).startActivityWithSlideInAnimation( + requireActivity().startActivityWithSlideInAnimation( StatusListActivity.newHashtagIntent(requireContext(), tag) ) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt index d4fd0c8b9..ab7b84020 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadFragment.kt @@ -36,7 +36,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.SimpleItemAnimator import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import com.google.android.material.snackbar.Snackbar -import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.accountlist.AccountListActivity import com.keylesspalace.tusky.components.accountlist.AccountListActivity.Companion.newIntent @@ -53,6 +52,7 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.openLink import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.viewdata.AttachmentViewData.Companion.list import com.keylesspalace.tusky.viewdata.StatusViewData @@ -386,13 +386,13 @@ class ViewThreadFragment : override fun onShowReblogs(position: Int) { val statusId = adapter.currentList[position].id val intent = newIntent(requireContext(), AccountListActivity.Type.REBLOGGED, statusId) - (requireActivity() as BaseActivity).startActivityWithSlideInAnimation(intent) + requireActivity().startActivityWithSlideInAnimation(intent) } override fun onShowFavs(position: Int) { val statusId = adapter.currentList[position].id val intent = newIntent(requireContext(), AccountListActivity.Type.FAVOURITED, statusId) - (requireActivity() as BaseActivity).startActivityWithSlideInAnimation(intent) + requireActivity().startActivityWithSlideInAnimation(intent) } override fun onContentCollapsedChange(isCollapsed: Boolean, position: Int) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsFragment.kt index 9114fae7b..a2af2831c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsFragment.kt @@ -46,6 +46,7 @@ import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.loadAvatar import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.unicodeWrap import com.keylesspalace.tusky.util.viewBinding import com.mikepenz.iconics.IconicsDrawable diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt index 0967bb766..36c663c45 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt @@ -171,7 +171,7 @@ class ViewVideoFragment : ViewMediaFragment(), Injectable { /** A fling up/down should dismiss the fragment */ override fun onFling( - e1: MotionEvent, + e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float diff --git a/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt b/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt index 9d1b90a2f..74a58714b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt @@ -15,7 +15,9 @@ package com.keylesspalace.tusky.service +import android.app.PendingIntent import android.content.Intent +import android.os.Build import android.service.quicksettings.TileService import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.components.compose.ComposeActivity @@ -29,6 +31,13 @@ class TuskyTileService : TileService() { override fun onClick() { val intent = MainActivity.composeIntent(this, ComposeActivity.ComposeOptions()) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivityAndCollapse(intent) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + val pendingIntent = PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_IMMUTABLE) + startActivityAndCollapse(pendingIntent) + } else { + @Suppress("DEPRECATION") + startActivityAndCollapse(intent) + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ActivityExensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/ActivityExensions.kt new file mode 100644 index 000000000..df34fcdf3 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/ActivityExensions.kt @@ -0,0 +1,21 @@ +@file:JvmName("ActivityExtensions") + +package com.keylesspalace.tusky.util + +import android.app.Activity +import android.content.Intent +import android.os.Build +import com.keylesspalace.tusky.BaseActivity +import com.keylesspalace.tusky.R + +fun Activity.startActivityWithSlideInAnimation(intent: Intent) { + // the new transition api needs to be called by the activity that is the result of the transition, + // so we pass a flag that BaseActivity will respect. + intent.putExtra(BaseActivity.OPEN_WITH_SLIDE_IN, true) + startActivity(intent) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // the old api needs to be called by the activity that starts the transition + @Suppress("DEPRECATION") + overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/view/ClickableSpanTextView.kt b/app/src/main/java/com/keylesspalace/tusky/view/ClickableSpanTextView.kt index a5d65716f..1fc01c999 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/ClickableSpanTextView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/ClickableSpanTextView.kt @@ -373,21 +373,21 @@ class ClickableSpanTextView @JvmOverloads constructor( return firstDiff < secondDiff } - override fun onDraw(canvas: Canvas?) { + override fun onDraw(canvas: Canvas) { super.onDraw(canvas) // Paint span boundaries. Optimised out on release builds, or debug builds where // showSpanBoundaries is false. if (BuildConfig.DEBUG && showSpanBoundaries) { - canvas?.save() + canvas.save() for (entry in delegateRects) { - canvas?.drawRect(entry.key, paddingDebugPaint) + canvas.drawRect(entry.key, paddingDebugPaint) } for (entry in spanRects) { - canvas?.drawRect(entry.key, spanDebugPaint) + canvas.drawRect(entry.key, spanDebugPaint) } - canvas?.restore() + canvas.restore() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/view/GraphView.kt b/app/src/main/java/com/keylesspalace/tusky/view/GraphView.kt index 2aecad08d..e6a54a2bc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/GraphView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/GraphView.kt @@ -265,14 +265,14 @@ class GraphView @JvmOverloads constructor( private fun dataSpacing(data: List) = width.toFloat() / max(data.size - 1, 1).toFloat() - override fun onDraw(canvas: Canvas?) { + override fun onDraw(canvas: Canvas) { super.onDraw(canvas) if (primaryLinePath.isEmpty && width > 0) { initializeVertices() } - canvas?.apply { + canvas.apply { drawRect(sizeRect, graphPaint) val pointDistance = dataSpacing(primaryLineData) diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt index 5cbc0e57d..9c74a3d85 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt @@ -38,6 +38,7 @@ import com.keylesspalace.tusky.util.randomAlphanumericString import java.io.File import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.shareIn @@ -72,6 +73,8 @@ class EditProfileViewModel @Inject constructor( val instanceData: Flow = instanceInfoRepo::getInstanceInfo.asFlow() .shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1) + val isChanged = MutableStateFlow(false) + private var apiProfileAccount: Account? = null fun obtainProfile() = viewModelScope.launch { @@ -102,6 +105,10 @@ class EditProfileViewModel @Inject constructor( headerData.value = getHeaderUri() } + internal fun dataChanged(newProfileData: ProfileDataInUi) { + isChanged.value = getProfileDiff(apiProfileAccount, newProfileData).hasChanges() + } + internal fun save(newProfileData: ProfileDataInUi) { if (saveData.value is Loading || profileData.value !is Success) { return @@ -178,12 +185,6 @@ class EditProfileViewModel @Inject constructor( } } - internal fun hasUnsavedChanges(newProfileData: ProfileDataInUi): Boolean { - val diff = getProfileDiff(apiProfileAccount, newProfileData) - - return diff.hasChanges() - } - private fun getProfileDiff( oldProfileAccount: Account?, newProfileData: ProfileDataInUi diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml deleted file mode 100644 index 972e757ec..000000000 --- a/app/src/main/res/anim/fade_in.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml deleted file mode 100644 index 9b48ae8f6..000000000 --- a/app/src/main/res/anim/fade_out.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/test/java/com/keylesspalace/tusky/components/compose/StatusLengthTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/compose/StatusLengthTest.kt index 5f8096305..0a28539a7 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/compose/StatusLengthTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/compose/StatusLengthTest.kt @@ -23,8 +23,10 @@ import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.robolectric.ParameterizedRobolectricTestRunner +import org.robolectric.annotation.Config @RunWith(ParameterizedRobolectricTestRunner::class) +@Config(sdk = [33]) class StatusLengthTest( private val text: String, private val expectedLength: Int