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