From 78152f04496a8a2dd2aea8466650ac5478d6f57a Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 6 Feb 2025 11:37:54 +0100 Subject: [PATCH] Full support for Android 15, edge-to-edge mode on Android 15 (#4897) - Update to Api 35 - Update all dependencies that were blocked because they require Api 35 - fix some deprecation warnings - implement the now required edge-to-edge mode Edge-to-edge mode means that we now draw under the status bar and the navigation bar and need to make sure we don't overlap with them. Previously the system would do that for us, and we would only provide the color we want the bars in. For the edge-to-edge mode there are two Apis that are important: - `fitsSystemWindows` - some Widgets, mostly from the Material library, automatically handle system insets if you set this attribute on them to true - `ViewCompat.setOnApplyWindowInsetsListener` - this allows you to manually handle the various insets. By returning new insets from the callback it is possible to tell the system which ones are handled and which ones should be taken care of by another view. In most places edge-to-edge was straightforward to implement, except in `ComposeActivity`, `AccountActivity` and `MainActivity` which required a more custom approach, and a hacky solution to make landscape mode work in `BaseActivity`. There is also the `ViewCompat.setWindowInsetsAnimationCallback` Api which allows animating with moving insets. I used that in `LoginActivity` and `ComposeActivity` to animate the Views together with the keyboard. On Android Versions below 15 (Api <= 34) Tusky will look almost the same as before. I think the only exception is the main bottom bar, which is now slighty larger. We customized it to be smaller than the default, but in edge-to-edge mode the height needs to be `wrap_content` or it won't handle insets correctly. Screenshots: --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 5 +- .../com/keylesspalace/tusky/AboutActivity.kt | 10 + .../com/keylesspalace/tusky/BaseActivity.kt | 56 +++- .../tusky/EditProfileActivity.kt | 25 ++ .../keylesspalace/tusky/LicenseActivity.kt | 10 + .../com/keylesspalace/tusky/ListsActivity.kt | 5 + .../com/keylesspalace/tusky/MainActivity.kt | 54 +++- .../tusky/TabPreferenceActivity.kt | 24 +- .../keylesspalace/tusky/ViewMediaActivity.kt | 6 +- .../adapter/PreviewPollOptionsAdapter.kt | 3 +- .../components/account/AccountActivity.kt | 35 ++- .../accountlist/AccountListFragment.kt | 2 + .../announcements/AnnouncementsActivity.kt | 2 + .../components/compose/ComposeActivity.kt | 14 + .../conversation/ConversationsFragment.kt | 2 + .../domainblocks/DomainBlocksFragment.kt | 3 + .../tusky/components/drafts/DraftsActivity.kt | 3 + .../components/filters/EditFilterActivity.kt | 9 + .../components/filters/FiltersActivity.kt | 5 + .../followedtags/FollowedTagsActivity.kt | 5 + .../tusky/components/login/LoginActivity.kt | 9 + .../components/login/LoginWebViewActivity.kt | 12 +- .../notifications/NotificationsFragment.kt | 3 + .../preference/AccountPreferencesFragment.kt | 3 +- .../preference/BasePreferencesFragment.kt | 20 ++ .../NotificationPreferencesFragment.kt | 3 +- .../preference/PreferencesActivity.kt | 8 + .../preference/PreferencesFragment.kt | 3 +- .../preference/ProxyPreferencesFragment.kt | 3 +- .../TabFilterPreferencesFragment.kt | 3 +- .../tusky/components/report/ReportActivity.kt | 10 + .../scheduled/ScheduledStatusActivity.kt | 3 + .../search/fragments/SearchFragment.kt | 2 + .../components/timeline/TimelineFragment.kt | 15 +- .../trending/TrendingTagsFragment.kt | 3 + .../tusky/fragment/ViewImageFragment.kt | 11 + .../tusky/fragment/ViewVideoFragment.kt | 15 + .../tusky/util/ViewExtensions.kt | 107 +++++++ .../res/layout-sw640dp/fragment_timeline.xml | 37 +-- .../fragment_timeline_notifications.xml | 32 +- app/src/main/res/layout/activity_about.xml | 2 + app/src/main/res/layout/activity_account.xml | 10 +- app/src/main/res/layout/activity_compose.xml | 131 ++++---- .../main/res/layout/activity_edit_filter.xml | 2 + .../main/res/layout/activity_edit_profile.xml | 1 + app/src/main/res/layout/activity_license.xml | 46 +-- app/src/main/res/layout/activity_login.xml | 42 +-- .../res/layout/activity_login_webview.xml | 1 + app/src/main/res/layout/activity_main.xml | 13 +- app/src/main/res/layout/activity_search.xml | 4 +- .../res/layout/activity_tab_preference.xml | 4 +- .../main/res/layout/activity_view_media.xml | 35 ++- app/src/main/res/layout/fragment_timeline.xml | 47 ++- .../fragment_timeline_notifications.xml | 29 +- .../res/layout/fragment_trending_tags.xml | 28 +- .../main/res/layout/fragment_view_image.xml | 2 +- app/src/main/res/layout/toolbar_basic.xml | 1 + app/src/main/res/values-v27/styles.xml | 2 + app/src/main/res/values-v35/values.xml | 6 + app/src/main/res/values/dimens.xml | 5 + .../res/values/{integers.xml => values.xml} | 3 + gradle/libs.versions.toml | 13 +- gradle/verification-metadata.xml | 279 ++++++++++++++++++ 64 files changed, 999 insertions(+), 291 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/preference/BasePreferencesFragment.kt create mode 100644 app/src/main/res/values-v35/values.xml rename app/src/main/res/values/{integers.xml => values.xml} (64%) diff --git a/app/build.gradle b/app/build.gradle index b970cc7e1..192eff688 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 34 + compileSdk 35 namespace "com.keylesspalace.tusky" defaultConfig { applicationId APP_ID namespace "com.keylesspalace.tusky" minSdk 24 - targetSdk 34 + targetSdk 35 versionCode 129 versionName "27.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eee4b80d0..e6a7e3864 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -107,8 +107,7 @@ + android:alwaysRetainTaskState="true" /> @@ -117,7 +116,7 @@ android:theme="@style/TuskyBaseTheme" android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize" /> - + diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt index 93cb08478..73180ac93 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt @@ -10,6 +10,9 @@ import android.text.style.URLSpan import android.text.util.Linkify import android.widget.TextView import androidx.annotation.StringRes +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository import com.keylesspalace.tusky.databinding.ActivityAboutBinding @@ -41,6 +44,13 @@ class AboutActivity : BottomSheetActivity() { setTitle(R.string.about_title_activity) + ViewCompat.setOnApplyWindowInsetsListener(binding.scrollView) { scrollView, insets -> + val systemBarInsets = insets.getInsets(systemBars()) + scrollView.updatePadding(bottom = systemBarInsets.bottom) + + insets.inset(0, 0, 0, systemBarInsets.bottom) + } + binding.versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME) binding.deviceInfo.text = getString( diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.kt b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.kt index 70b158e0a..d8d02f457 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.kt @@ -21,12 +21,23 @@ import android.content.Intent import android.content.SharedPreferences import android.graphics.BitmapFactory import android.graphics.Color +import android.os.Build import android.os.Bundle import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import androidx.annotation.StyleRes import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsCompat.Type.displayCutout +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding import androidx.lifecycle.ViewModelProvider.Factory import androidx.lifecycle.lifecycleScope +import com.google.android.material.R as materialR import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.keylesspalace.tusky.MainActivity.Companion.redirectIntent @@ -88,16 +99,28 @@ abstract class BaseActivity : AppCompatActivity() { setTheme(R.style.TuskyTheme) } - /* set the taskdescription programmatically, the theme would turn it blue */ + /* Set the taskdescription programmatically - by default the primary color is used. + * On newer Android versions (or launchers?) this doesn't seem to have an effect. */ val appName = getString(R.string.app_name) - val appIcon = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) val recentsBackgroundColor = MaterialColors.getColor( this, - com.google.android.material.R.attr.colorSurface, + materialR.attr.colorSurface, Color.BLACK ) - setTaskDescription(TaskDescription(appName, appIcon, recentsBackgroundColor)) + val taskDescription = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + TaskDescription.Builder() + .setLabel(appName) + .setIcon(R.mipmap.ic_launcher) + .setPrimaryColor(recentsBackgroundColor) + .build() + } else { + val appIcon = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + @Suppress("DEPRECATION") + TaskDescription(appName, appIcon, recentsBackgroundColor) + } + + setTaskDescription(taskDescription) val style = textStyle(preferences.getString(PrefKeys.STATUS_TEXT_SIZE, "medium")) getTheme().applyStyle(style, true) @@ -107,6 +130,31 @@ abstract class BaseActivity : AppCompatActivity() { } } + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + window.decorView.setBackgroundColor(Color.BLACK) + + val contentView: View = findViewById(android.R.id.content) + contentView.setBackgroundColor(MaterialColors.getColor(contentView, android.R.attr.colorBackground)) + + // handle left/right insets. This is relevant for edge-to-edge mode in landscape orientation + ViewCompat.setOnApplyWindowInsetsListener(contentView) { _, insets -> + val systemBarInsets = insets.getInsets(systemBars()) + val displayCutoutInsets = insets.getInsets(displayCutout()) + // use padding for system bar insets so they get our background color and margin for cutout insets to turn them black + contentView.updatePadding(left = systemBarInsets.left, right = systemBarInsets.right) + contentView.updateLayoutParams { + leftMargin = displayCutoutInsets.left + rightMargin = displayCutoutInsets.right + } + + WindowInsetsCompat.Builder(insets) + .setInsets(systemBars(), Insets.of(0, systemBarInsets.top, 0, systemBarInsets.bottom)) + .setInsets(displayCutout(), Insets.of(0, displayCutoutInsets.top, 0, displayCutoutInsets.bottom)) + .build() + } + } + private fun activityTransitionWasRequested(): Boolean { return intent.getBooleanExtra(OPEN_WITH_SLIDE_IN, false) } diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt index d0a342622..87eea1340 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt @@ -27,7 +27,13 @@ import android.widget.ImageView import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog +import androidx.core.graphics.Insets +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsCompat.Type.ime +import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.isVisible +import androidx.core.view.updatePadding import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager @@ -119,6 +125,25 @@ class EditProfileActivity : BaseActivity() { setDisplayShowHomeEnabled(true) } + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { scrollView, insets -> + // if keyboard visible -> set inset on the root to push the scrollview up + // if keyboard hidden -> set inset on the scrollview so last element does not get obscured by navigation bar + // scrollview has clipToPadding set to false so it draws behind the navigation bar in edge-to-edge mode + val imeInsets = insets.getInsets(ime()) + val systemBarsInsets = insets.getInsets(systemBars()) + binding.root.updatePadding(bottom = imeInsets.bottom) + val scrollViewPadding = if (imeInsets.bottom == 0) { + systemBarsInsets.bottom + } else { + 0 + } + binding.scrollView.updatePadding(bottom = scrollViewPadding) + WindowInsetsCompat.Builder(insets) + .setInsets(ime(), Insets.of(imeInsets.left, imeInsets.top, imeInsets.right, 0)) + .setInsets(systemBars(), Insets.of(systemBarsInsets.left, systemBarsInsets.top, imeInsets.right, 0)) + .build() + } + binding.avatarButton.setOnClickListener { pickMedia(PickType.AVATAR) } binding.headerButton.setOnClickListener { pickMedia(PickType.HEADER) } diff --git a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt index 426fa1c88..e4cd82b3b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt @@ -19,6 +19,9 @@ import android.os.Bundle import android.util.Log import android.widget.TextView import androidx.annotation.RawRes +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import com.keylesspalace.tusky.databinding.ActivityLicenseBinding import dagger.hilt.android.AndroidEntryPoint @@ -45,6 +48,13 @@ class LicenseActivity : BaseActivity() { setTitle(R.string.title_licenses) + ViewCompat.setOnApplyWindowInsetsListener(binding.scrollView) { scrollView, insets -> + val systemBarInsets = insets.getInsets(systemBars()) + scrollView.updatePadding(bottom = systemBarInsets.bottom) + + insets.inset(0, 0, 0, systemBarInsets.bottom) + } + loadFileIntoTextView(R.raw.apache, binding.licenseApacheTextView) } diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt index da4af6f43..804ac5a30 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt @@ -40,6 +40,8 @@ import com.keylesspalace.tusky.databinding.DialogListBinding import com.keylesspalace.tusky.databinding.ItemListBinding import com.keylesspalace.tusky.entity.MastoList import com.keylesspalace.tusky.util.BindingHolder +import com.keylesspalace.tusky.util.ensureBottomMargin +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation @@ -78,6 +80,9 @@ class ListsActivity : BaseActivity() { setDisplayShowHomeEnabled(true) } + binding.addListButton.ensureBottomMargin() + binding.listsRecycler.ensureBottomPadding(fab = true) + binding.listsRecycler.adapter = adapter binding.listsRecycler.layoutManager = LinearLayoutManager(this) binding.listsRecycler.addItemDecoration( diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index b88125b5c..445bb3dd2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -38,6 +38,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.MenuItem.SHOW_AS_ACTION_NEVER import android.view.View +import android.view.ViewGroup.LayoutParams import android.widget.ImageView import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts @@ -49,8 +50,12 @@ import androidx.core.app.ActivityCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.MenuProvider +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.forEach import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import androidx.viewpager2.widget.MarginPageTransformer import com.bumptech.glide.Glide @@ -91,7 +96,6 @@ import com.keylesspalace.tusky.usecase.DeveloperToolsUseCase import com.keylesspalace.tusky.usecase.LogoutUsecase import com.keylesspalace.tusky.util.ActivityConstants import com.keylesspalace.tusky.util.emojify -import com.keylesspalace.tusky.util.getDimension import com.keylesspalace.tusky.util.getParcelableExtraCompat import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.overrideActivityTransitionCompat @@ -236,9 +240,45 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { } } - window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own setContentView(binding.root) + val bottomBarHeight = if (preferences.getString(PrefKeys.MAIN_NAV_POSITION, "top") == "bottom") { + resources.getDimensionPixelSize(R.dimen.bottomAppBarHeight) + } else { + 0 + } + + val fabMargin = resources.getDimensionPixelSize(R.dimen.fabMargin) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + ViewCompat.setOnApplyWindowInsetsListener(binding.viewPager) { _, insets -> + val systemBarsInsets = insets.getInsets(systemBars()) + val bottomInsets = systemBarsInsets.bottom + + binding.composeButton.updateLayoutParams { + bottomMargin = bottomBarHeight + fabMargin + bottomInsets + } + binding.mainDrawer.recyclerView.updatePadding(bottom = bottomInsets) + + if (preferences.getString(PrefKeys.MAIN_NAV_POSITION, "top") == "top") { + insets + } else { + binding.viewPager.updatePadding(bottom = bottomBarHeight + bottomInsets) + insets.inset(0, 0, 0, bottomInsets) + } + } + } else { + // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own + // on Vanilla Ice Cream (API 35) and up there is no status bar color because of edge-to-edge mode + @Suppress("DEPRECATION") + window.statusBarColor = Color.TRANSPARENT + + binding.composeButton.updateLayoutParams { + bottomMargin = bottomBarHeight + fabMargin + } + binding.viewPager.updatePadding(bottom = bottomBarHeight) + } + binding.composeButton.setOnClickListener { val composeIntent = Intent(applicationContext, ComposeActivity::class.java) startActivity(composeIntent) @@ -252,11 +292,14 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { "top" -> setSupportActionBar(binding.topNav) "bottom" -> setSupportActionBar(binding.bottomNav) } - binding.mainToolbar.hide() + // this is a bit hacky, but when the mainToolbar is GONE, the toolbar size gets messed up for some reason + binding.mainToolbar.layoutParams.height = 0 + binding.mainToolbar.visibility = View.INVISIBLE // There's not enough space in the top/bottom bars to show the title as well. supportActionBar?.setDisplayShowTitleEnabled(false) } else { setSupportActionBar(binding.mainToolbar) + binding.mainToolbar.layoutParams.height = LayoutParams.WRAP_CONTENT binding.mainToolbar.show() } @@ -793,15 +836,10 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { private fun setupTabs(tabs: List) { val activeTabLayout = if (preferences.getString(PrefKeys.MAIN_NAV_POSITION, "top") == "bottom") { - val actionBarSize = getDimension(this, androidx.appcompat.R.attr.actionBarSize) - val fabMargin = resources.getDimensionPixelSize(R.dimen.fabMargin) - (binding.composeButton.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = actionBarSize + fabMargin binding.topNav.hide() binding.bottomTabLayout } else { binding.bottomNav.hide() - (binding.viewPager.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = 0 - (binding.composeButton.layoutParams as CoordinatorLayout.LayoutParams).anchorId = R.id.viewPager binding.tabLayout } diff --git a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt index bede65b4c..90c2e1df9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt @@ -18,7 +18,11 @@ package com.keylesspalace.tusky import android.graphics.Color import android.os.Bundle import android.view.View +import android.view.ViewGroup import androidx.activity.OnBackPressedCallback +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.ItemTouchHelper @@ -34,7 +38,7 @@ import com.keylesspalace.tusky.components.account.list.ListSelectionFragment import com.keylesspalace.tusky.databinding.ActivityTabPreferenceBinding import com.keylesspalace.tusky.entity.MastoList import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.util.hashtagPattern +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.unsafeLazy import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.visible @@ -82,6 +86,19 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener, ListSelec setDisplayShowHomeEnabled(true) } + binding.currentTabsRecyclerView.ensureBottomPadding(fab = true) + ViewCompat.setOnApplyWindowInsetsListener(binding.actionButton) { _, insets -> + val bottomInset = insets.getInsets(systemBars()).bottom + val actionButtonMargin = resources.getDimensionPixelSize(R.dimen.fabMargin) + binding.actionButton.updateLayoutParams { + bottomMargin = bottomInset + actionButtonMargin + } + binding.sheet.updateLayoutParams { + bottomMargin = bottomInset + actionButtonMargin + } + insets.inset(0, 0, 0, bottomInset) + } + currentTabs = accountManager.activeAccount?.tabPreferences.orEmpty().toMutableList() currentTabsAdapter = TabAdapter(currentTabs, false, this, currentTabs.size <= MIN_TAB_COUNT) binding.currentTabsRecyclerView.adapter = currentTabsAdapter @@ -253,11 +270,6 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener, ListSelec saveTabs() } - private fun validateHashtag(input: CharSequence?): Boolean { - val trimmedInput = input?.trim() ?: "" - return trimmedInput.isNotEmpty() && hashtagPattern.matcher(trimmedInput).matches() - } - private fun updateAvailableTabs() { val addableTabs: MutableList = mutableListOf() diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index 596689b07..1430327ab 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -156,9 +156,13 @@ class ViewMediaActivity : true } + // yes it is deprecated, but it looks cool so it stays for now window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE - window.statusBarColor = Color.BLACK + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + @Suppress("DEPRECATION") + window.statusBarColor = Color.BLACK + } window.sharedElementEnterTransition.addListener(object : NoopTransitionListener { override fun onTransitionEnd(transition: Transition) { adapter.onTransitionEnd(binding.viewPager.currentItem) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PreviewPollOptionsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PreviewPollOptionsAdapter.kt index 10c2f14f7..d24cba8cd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PreviewPollOptionsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PreviewPollOptionsAdapter.kt @@ -19,7 +19,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView -import androidx.core.widget.TextViewCompat import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R @@ -58,7 +57,7 @@ class PreviewPollOptionsAdapter : RecyclerView.Adapter() { R.drawable.ic_radio_button_unchecked_18dp } - TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, iconId, 0, 0, 0) + textView.setCompoundDrawablesRelativeWithIntrinsicBounds(iconId, 0, 0, 0) textView.text = options[position] 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 0acfceff1..98602b2a0 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 @@ -21,6 +21,7 @@ import android.content.Intent import android.content.res.ColorStateList import android.graphics.Color import android.graphics.Typeface +import android.os.Build import android.os.Bundle import android.text.SpannableStringBuilder import android.text.TextWatcher @@ -40,8 +41,8 @@ import androidx.core.graphics.ColorUtils import androidx.core.view.MenuProvider import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat -import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.lifecycleScope @@ -83,6 +84,7 @@ import com.keylesspalace.tusky.util.Loading import com.keylesspalace.tusky.util.Success import com.keylesspalace.tusky.util.copyToClipboard import com.keylesspalace.tusky.util.emojify +import com.keylesspalace.tusky.util.ensureBottomMargin import com.keylesspalace.tusky.util.getDomain import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.loadAvatar @@ -285,25 +287,21 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide } private fun handleWindowInsets() { + binding.accountFloatingActionButton.ensureBottomMargin() ViewCompat.setOnApplyWindowInsetsListener(binding.accountCoordinatorLayout) { _, insets -> - val top = insets.getInsets(systemBars()).top - val toolbarParams = binding.accountToolbar.layoutParams as ViewGroup.MarginLayoutParams - toolbarParams.topMargin = top + val systemBarInsets = insets.getInsets(systemBars()) + val top = systemBarInsets.top + + binding.accountToolbar.updateLayoutParams { + topMargin = top + } - val right = insets.getInsets(systemBars()).right - val bottom = insets.getInsets(systemBars()).bottom - val left = insets.getInsets(systemBars()).left - binding.accountCoordinatorLayout.updatePadding( - right = right, - bottom = bottom, - left = left - ) binding.swipeToRefreshLayout.setProgressViewEndTarget( false, top + resources.getDimensionPixelSize(R.dimen.account_swiperefresh_distance) ) - WindowInsetsCompat.CONSUMED + insets.inset(0, top, 0, 0) } } @@ -355,7 +353,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide 1f ) - window.statusBarColor = argbEvaluator.evaluate(transparencyPercent, statusBarColorTransparent, statusBarColorOpaque) as Int + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + @Suppress("DEPRECATION") + window.statusBarColor = argbEvaluator.evaluate(transparencyPercent, statusBarColorTransparent, statusBarColorOpaque) as Int + } val evaluatedToolbarColor = argbEvaluator.evaluate( transparencyPercent, @@ -364,6 +365,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide ) as Int binding.accountToolbar.setBackgroundColor(evaluatedToolbarColor) + binding.accountStatusBarScrim.setBackgroundColor(evaluatedToolbarColor) binding.swipeToRefreshLayout.isEnabled = verticalOffset == 0 } @@ -372,7 +374,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide private fun makeNotificationBarTransparent() { WindowCompat.setDecorFitsSystemWindows(window, false) - window.statusBarColor = statusBarColorTransparent + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + @Suppress("DEPRECATION") + window.statusBarColor = statusBarColorTransparent + } } /** 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 a463ef76d..e43b87d45 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 @@ -49,6 +49,7 @@ import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.HttpHeaderLink +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.getSerializableCompat import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show @@ -92,6 +93,7 @@ class AccountListFragment : } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding.recyclerView.ensureBottomPadding() binding.recyclerView.setHasFixedSize(true) val layoutManager = LinearLayoutManager(view.context) binding.recyclerView.layoutManager = layoutManager 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 fdab56ee3..eafbdb5fa 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 @@ -38,6 +38,7 @@ import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.Error import com.keylesspalace.tusky.util.Loading import com.keylesspalace.tusky.util.Success +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation @@ -87,6 +88,7 @@ class AnnouncementsActivity : binding.swipeRefreshLayout.setOnRefreshListener(this::refreshAnnouncements) + binding.announcementsList.ensureBottomPadding() binding.announcementsList.setHasFixedSize(true) binding.announcementsList.layoutManager = LinearLayoutManager(this) val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL) 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 2b288aeb4..5a3baada5 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 @@ -50,8 +50,11 @@ import androidx.core.content.FileProvider import androidx.core.content.res.use import androidx.core.view.ContentInfoCompat import androidx.core.view.OnReceiveContentListener +import androidx.core.view.WindowInsetsCompat.Type.ime +import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.core.view.updatePadding import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doOnTextChanged import androidx.lifecycle.lifecycleScope @@ -106,6 +109,7 @@ import com.keylesspalace.tusky.util.loadAvatar import com.keylesspalace.tusky.util.map import com.keylesspalace.tusky.util.modernLanguageCode import com.keylesspalace.tusky.util.setDrawableTint +import com.keylesspalace.tusky.util.setOnWindowInsetsChangeListener import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.visible @@ -259,6 +263,16 @@ class ComposeActivity : } setContentView(binding.root) + binding.composeBottomBar.setOnWindowInsetsChangeListener { windowInsets -> + val insets = windowInsets.getInsets(systemBars() or ime()) + binding.composeBottomBar.updatePadding(bottom = insets.bottom + at.connyduck.sparkbutton.helpers.Utils.dpToPx(this@ComposeActivity, 4)) + binding.addMediaBottomSheet.updatePadding(bottom = insets.bottom + at.connyduck.sparkbutton.helpers.Utils.dpToPx(this@ComposeActivity, 50)) + binding.emojiView.updatePadding(bottom = insets.bottom + at.connyduck.sparkbutton.helpers.Utils.dpToPx(this@ComposeActivity, 50)) + binding.composeOptionsBottomSheet.updatePadding(bottom = insets.bottom + at.connyduck.sparkbutton.helpers.Utils.dpToPx(this@ComposeActivity, 50)) + binding.composeScheduleView.updatePadding(bottom = insets.bottom + at.connyduck.sparkbutton.helpers.Utils.dpToPx(this@ComposeActivity, 50)) + (binding.composeMainScrollView.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = insets.bottom + at.connyduck.sparkbutton.helpers.Utils.dpToPx(this@ComposeActivity, 58) + } + setupActionBar() setupAvatar(activeAccount) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt index 08f912b2c..1bab75ebd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt @@ -49,6 +49,7 @@ import com.keylesspalace.tusky.interfaces.StatusActionListener import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.StatusDisplayOptions +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.isAnyLoading import com.keylesspalace.tusky.util.show @@ -228,6 +229,7 @@ class ConversationsFragment : } private fun setupRecyclerView(adapter: ConversationAdapter) { + binding.recyclerView.ensureBottomPadding(fab = true) binding.recyclerView.setHasFixedSize(true) binding.recyclerView.layoutManager = LinearLayoutManager(context) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt index 10438087e..0978e06a4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.FragmentDomainBlocksBinding +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.viewBinding @@ -30,6 +31,8 @@ class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val adapter = DomainBlocksAdapter(viewModel::unblock) + binding.recyclerView.ensureBottomPadding() + binding.recyclerView.setHasFixedSize(true) binding.recyclerView.addItemDecoration( DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt index d6668c189..095bd9469 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt @@ -34,6 +34,7 @@ import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.databinding.ActivityDraftsBinding import com.keylesspalace.tusky.db.DraftsAlert import com.keylesspalace.tusky.db.entity.DraftEntity +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.isHttpNotFound import com.keylesspalace.tusky.util.parseAsMastodonHtml import com.keylesspalace.tusky.util.visible @@ -66,6 +67,8 @@ class DraftsActivity : BaseActivity(), DraftActionListener { setDisplayShowHomeEnabled(true) } + binding.draftsRecyclerView.ensureBottomPadding() + binding.draftsErrorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_drafts) val adapter = DraftsAdapter(this) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/filters/EditFilterActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/filters/EditFilterActivity.kt index 227cdcacb..81a5d5e15 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/filters/EditFilterActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/filters/EditFilterActivity.kt @@ -20,7 +20,10 @@ import android.os.Bundle import android.view.WindowManager import android.widget.AdapterView import androidx.activity.viewModels +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.size +import androidx.core.view.updatePadding import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.lifecycleScope import at.connyduck.calladapter.networkresult.fold @@ -91,6 +94,12 @@ class EditFilterActivity : BaseActivity() { } ) + ViewCompat.setOnApplyWindowInsetsListener(binding.scrollView) { scrollView, insets -> + val systemBarsInsets = insets.getInsets(systemBars()) + scrollView.updatePadding(bottom = systemBarsInsets.bottom) + insets.inset(0, 0, 0, systemBarsInsets.bottom) + } + binding.actionChip.setOnClickListener { showAddKeywordDialog() } binding.filterSaveButton.setOnClickListener { saveChanges() } binding.filterDeleteButton.setOnClickListener { 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 edfa81abc..c4087fd57 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 @@ -11,6 +11,8 @@ import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.ActivityFiltersBinding import com.keylesspalace.tusky.entity.Filter +import com.keylesspalace.tusky.util.ensureBottomMargin +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.launchAndRepeatOnLifecycle import com.keylesspalace.tusky.util.show @@ -43,6 +45,9 @@ class FiltersActivity : BaseActivity(), FiltersListener { setDisplayShowHomeEnabled(true) } + binding.filtersList.ensureBottomPadding(fab = true) + binding.addFilterButton.ensureBottomMargin() + binding.addFilterButton.setOnClickListener { launchEditFilterActivity() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/followedtags/FollowedTagsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/followedtags/FollowedTagsActivity.kt index 3c6fe5d47..c3920e801 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/followedtags/FollowedTagsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/followedtags/FollowedTagsActivity.kt @@ -18,6 +18,8 @@ import com.keylesspalace.tusky.databinding.ActivityFollowedTagsBinding import com.keylesspalace.tusky.interfaces.HashtagActionListener import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.copyToClipboard +import com.keylesspalace.tusky.util.ensureBottomMargin +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.viewBinding @@ -54,6 +56,9 @@ class FollowedTagsActivity : setDisplayShowHomeEnabled(true) } + binding.fab.ensureBottomMargin() + binding.followedTagsView.ensureBottomPadding(fab = true) + binding.fab.setOnClickListener { showDialog() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt index 3ddb0f706..9bd35af3e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt @@ -25,6 +25,9 @@ import android.view.Menu import android.view.View import android.widget.TextView import androidx.core.net.toUri +import androidx.core.view.WindowInsetsCompat.Type.ime +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import at.connyduck.calladapter.networkresult.fold import com.bumptech.glide.Glide @@ -39,6 +42,7 @@ import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.getNonNullString import com.keylesspalace.tusky.util.openLinkInCustomTab import com.keylesspalace.tusky.util.rickRoll +import com.keylesspalace.tusky.util.setOnWindowInsetsChangeListener import com.keylesspalace.tusky.util.shouldRickRoll import com.keylesspalace.tusky.util.viewBinding import dagger.hilt.android.AndroidEntryPoint @@ -75,6 +79,11 @@ class LoginActivity : BaseActivity() { setContentView(binding.root) + binding.loginScrollView.setOnWindowInsetsChangeListener { windowInsets -> + val insets = windowInsets.getInsets(systemBars() or ime()) + binding.loginScrollView.updatePadding(bottom = insets.bottom) + } + if (savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin() 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 3eb271f4e..29c63ddee 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 @@ -33,6 +33,11 @@ import android.webkit.WebViewClient import androidx.activity.result.contract.ActivityResultContract import androidx.activity.viewModels import androidx.core.net.toUri +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsCompat.Type.ime +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.keylesspalace.tusky.BaseActivity @@ -122,10 +127,15 @@ class LoginWebViewActivity : BaseActivity() { setTitle(R.string.title_login) + ViewCompat.setOnApplyWindowInsetsListener(binding.loginWebView) { _, insets -> + val bottomInsets = insets.getInsets(systemBars() or ime()).bottom + binding.root.updatePadding(bottom = bottomInsets) + WindowInsetsCompat.CONSUMED + } + val webView = binding.loginWebView webView.settings.allowContentAccess = false webView.settings.allowFileAccess = false - webView.settings.databaseEnabled = false webView.settings.displayZoomControls = false webView.settings.javaScriptCanOpenWindowsAutomatically = false // JavaScript needs to be enabled because otherwise 2FA does not work in some instances diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt index 1aa53147a..6c1a81f6c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt @@ -65,6 +65,7 @@ import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.StatusProvider +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.openLink import com.keylesspalace.tusky.util.show @@ -137,6 +138,8 @@ class NotificationsFragment : openSpoiler = accountManager.activeAccount!!.alwaysOpenSpoiler ) + binding.recyclerView.ensureBottomPadding(fab = true) + // setup the notifications filter bar showNotificationsFilterBar = preferences.getBoolean(PrefKeys.SHOW_NOTIFICATIONS_FILTER, true) updateFilterBarVisibility() 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 8f6425115..d48f92d0e 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 @@ -23,7 +23,6 @@ import android.os.Bundle import android.util.Log import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference -import androidx.preference.PreferenceFragmentCompat import at.connyduck.calladapter.networkresult.fold import com.google.android.material.color.MaterialColors import com.google.android.material.snackbar.Snackbar @@ -64,7 +63,7 @@ import javax.inject.Inject import kotlinx.coroutines.launch @AndroidEntryPoint -class AccountPreferencesFragment : PreferenceFragmentCompat() { +class AccountPreferencesFragment : BasePreferencesFragment() { @Inject lateinit var accountManager: AccountManager diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/BasePreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/BasePreferencesFragment.kt new file mode 100644 index 000000000..ba452f0bb --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/BasePreferencesFragment.kt @@ -0,0 +1,20 @@ +package com.keylesspalace.tusky.components.preference + +import android.os.Bundle +import android.view.View +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updatePadding +import androidx.preference.PreferenceFragmentCompat + +abstract class BasePreferencesFragment : PreferenceFragmentCompat() { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + ViewCompat.setOnApplyWindowInsetsListener(listView) { listView, insets -> + val systemBarsInsets = insets.getInsets(systemBars()) + listView.updatePadding(bottom = systemBarsInsets.bottom) + insets.inset(0, 0, 0, systemBarsInsets.bottom) + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt index 46c8719b2..e59ba9186 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt @@ -17,7 +17,6 @@ package com.keylesspalace.tusky.components.preference import android.os.Bundle import androidx.lifecycle.lifecycleScope -import androidx.preference.PreferenceFragmentCompat import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.systemnotifications.NotificationService import com.keylesspalace.tusky.db.AccountManager @@ -31,7 +30,7 @@ import javax.inject.Inject import kotlinx.coroutines.launch @AndroidEntryPoint -class NotificationPreferencesFragment : PreferenceFragmentCompat() { +class NotificationPreferencesFragment : BasePreferencesFragment() { @Inject lateinit var accountManager: AccountManager 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 689f83982..9a71d34bd 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 @@ -18,9 +18,11 @@ package com.keylesspalace.tusky.components.preference import android.content.Context import android.content.Intent import android.content.SharedPreferences +import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.OnBackPressedCallback +import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope @@ -66,6 +68,12 @@ class PreferencesActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + // Workaround for edge-to-edge mode not working when an activity is recreated + // https://stackoverflow.com/questions/79319740/edge-to-edge-doesnt-work-when-activity-recreated-or-appcompatdelegate-setdefaul + if (savedInstanceState != null && Build.VERSION.SDK_INT >= 35) { + WindowCompat.setDecorFitsSystemWindows(window, false) + } + val binding = ActivityPreferencesBinding.inflate(layoutInflater) setContentView(binding.root) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index ff66c9e6f..66de6335f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky.components.preference import android.os.Bundle import androidx.lifecycle.lifecycleScope import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat import com.keylesspalace.tusky.R import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Notification @@ -40,7 +39,7 @@ import javax.inject.Inject import kotlinx.coroutines.launch @AndroidEntryPoint -class PreferencesFragment : PreferenceFragmentCompat() { +class PreferencesFragment : BasePreferencesFragment() { @Inject lateinit var accountManager: AccountManager diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/ProxyPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/ProxyPreferencesFragment.kt index 84f1336f1..fba6c8ba8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/ProxyPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/ProxyPreferencesFragment.kt @@ -17,7 +17,6 @@ package com.keylesspalace.tusky.components.preference import android.os.Bundle import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat import com.keylesspalace.tusky.R import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.ProxyConfiguration @@ -30,7 +29,7 @@ import com.keylesspalace.tusky.settings.validatedEditTextPreference import com.keylesspalace.tusky.util.getNonNullString import kotlin.system.exitProcess -class ProxyPreferencesFragment : PreferenceFragmentCompat() { +class ProxyPreferencesFragment : BasePreferencesFragment() { private var pendingRestart = false override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/TabFilterPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/TabFilterPreferencesFragment.kt index 032b80ced..6d9141c93 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/TabFilterPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/TabFilterPreferencesFragment.kt @@ -19,7 +19,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.preference.PreferenceFragmentCompat import com.google.android.material.color.MaterialColors import com.keylesspalace.tusky.R import com.keylesspalace.tusky.settings.AccountPreferenceDataStore @@ -31,7 +30,7 @@ import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint -class TabFilterPreferencesFragment : PreferenceFragmentCompat() { +class TabFilterPreferencesFragment : BasePreferencesFragment() { @Inject lateinit var accountPreferenceDataStore: AccountPreferenceDataStore diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt index 7cbb97782..fa3a43251 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt @@ -19,6 +19,9 @@ import android.content.Context import android.content.Intent import android.os.Bundle import androidx.activity.viewModels +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import com.keylesspalace.tusky.BottomSheetActivity import com.keylesspalace.tusky.R @@ -58,6 +61,13 @@ class ReportActivity : BottomSheetActivity() { setHomeAsUpIndicator(R.drawable.ic_close_24dp) } + ViewCompat.setOnApplyWindowInsetsListener(binding.wizard) { wizard, insets -> + val systemBarInsets = insets.getInsets(systemBars()) + wizard.updatePadding(bottom = systemBarInsets.bottom) + + insets.inset(0, 0, 0, systemBarInsets.bottom) + } + initViewPager() if (savedInstanceState == null) { viewModel.navigateTo(Screen.Statuses) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusActivity.kt index aa59a59a9..d613d78d4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusActivity.kt @@ -36,6 +36,7 @@ import com.keylesspalace.tusky.appstore.StatusScheduledEvent import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.databinding.ActivityScheduledStatusBinding import com.keylesspalace.tusky.entity.ScheduledStatus +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.viewBinding @@ -76,6 +77,8 @@ class ScheduledStatusActivity : setDisplayShowHomeEnabled(true) } + binding.scheduledTootList.ensureBottomPadding() + binding.swipeRefreshLayout.setOnRefreshListener(this::refreshStatuses) binding.scheduledTootList.setHasFixedSize(true) 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 6dfe2c02b..7b6b3f3fd 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 @@ -26,6 +26,7 @@ import com.keylesspalace.tusky.components.search.SearchViewModel import com.keylesspalace.tusky.databinding.FragmentSearchBinding import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.network.MastodonApi +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.visible @@ -63,6 +64,7 @@ abstract class SearchFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val adapter = initAdapter() binding.swipeRefreshLayout.setOnRefreshListener(this) + binding.searchRecyclerView.ensureBottomPadding() requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) subscribeObservables(adapter) } 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 a4d98648f..30a1e5fbe 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 @@ -25,7 +25,6 @@ import android.view.View import android.view.accessibility.AccessibilityManager import androidx.core.content.getSystemService import androidx.core.view.MenuProvider -import androidx.core.view.updatePadding import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -61,6 +60,7 @@ import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate import com.keylesspalace.tusky.util.StatusDisplayOptions +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation @@ -378,6 +378,9 @@ class TimelineFragment : } private fun setupRecyclerView(adapter: TimelinePagingAdapter) { + val hasFab = (activity as? ActionButtonActivity?)?.actionButton != null + binding.recyclerView.ensureBottomPadding(fab = hasFab) + binding.recyclerView.setAccessibilityDelegateCompat( ListStatusAccessibilityDelegate(binding.recyclerView, this) { pos -> if (pos in 0 until adapter.itemCount) { @@ -387,19 +390,11 @@ class TimelineFragment : } } ) - binding.recyclerView.setHasFixedSize(true) binding.recyclerView.layoutManager = LinearLayoutManager(context) + val divider = DividerItemDecoration(context, RecyclerView.VERTICAL) binding.recyclerView.addItemDecoration(divider) - val recyclerViewBottomPadding = if ((activity as? ActionButtonActivity?)?.actionButton != null) { - resources.getDimensionPixelSize(R.dimen.recyclerview_bottom_padding_actionbutton) - } else { - resources.getDimensionPixelSize(R.dimen.recyclerview_bottom_padding_no_actionbutton) - } - - binding.recyclerView.updatePadding(bottom = recyclerViewBottomPadding) - // CWs are expanded without animation, buttons animate itself, we don't need it basically (binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false binding.recyclerView.adapter = adapter 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 b6c195679..217e47165 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 @@ -38,6 +38,7 @@ import com.keylesspalace.tusky.databinding.FragmentTrendingTagsBinding import com.keylesspalace.tusky.interfaces.ActionButtonActivity import com.keylesspalace.tusky.interfaces.RefreshableFragment import com.keylesspalace.tusky.interfaces.ReselectableFragment +import com.keylesspalace.tusky.util.ensureBottomPadding import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation @@ -121,6 +122,8 @@ class TrendingTagsFragment : } private fun setupRecyclerView(adapter: TrendingTagsAdapter) { + binding.recyclerView.ensureBottomPadding() + val columnCount = requireContext().resources.getInteger(R.integer.trending_column_count) setupLayoutManager(adapter, columnCount) diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt index a386d5524..dc9721f09 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt @@ -27,6 +27,9 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.ImageView +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource @@ -107,6 +110,14 @@ class ViewImageFragment : ViewMediaFragment() { } } + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + val systemBarInsets = insets.getInsets(systemBars()) + val mediaDescriptionBottomPadding = requireContext().resources.getDimensionPixelSize(R.dimen.media_description_sheet_bottom_padding) + binding.mediaDescription.updatePadding(bottom = mediaDescriptionBottomPadding + systemBarInsets.bottom) + + insets.inset(0, 0, 0, systemBarInsets.bottom) + } + val singleTapDetector = GestureDetector( requireContext(), object : GestureDetector.SimpleOnGestureListener() { 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 d450d0f28..e40d79be4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt @@ -31,6 +31,11 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.LinearLayout import androidx.annotation.OptIn +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.media3.common.AudioAttributes @@ -130,6 +135,16 @@ class ViewVideoFragment : ViewMediaFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + ViewCompat.setOnApplyWindowInsetsListener(binding.mediaDescriptionScrollView) { captionSheet, insets -> + val systemBarInsets = insets.getInsets(systemBars()) + captionSheet.updatePadding(bottom = systemBarInsets.bottom) + binding.videoView.updateLayoutParams { + bottomMargin = systemBarInsets.bottom + } + + insets.inset(0, 0, 0, systemBarInsets.bottom) + } + /** * Handle single taps, flings, and dragging */ diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt index 392f65b77..879d6470d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt @@ -18,9 +18,21 @@ package com.keylesspalace.tusky.util import android.util.Log import android.view.View +import android.view.ViewGroup import android.widget.TextView +import androidx.core.graphics.Insets +import androidx.core.view.OnApplyWindowInsetsListener +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsAnimationCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsCompat.Type.ime +import androidx.core.view.WindowInsetsCompat.Type.systemBars +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.keylesspalace.tusky.R fun View.show() { this.visibility = View.VISIBLE @@ -78,3 +90,98 @@ fun TextView.fixTextSelection() { setTextIsSelectable(false) post { setTextIsSelectable(true) } } + +/** + * Makes sure the [RecyclerView] has the correct bottom padding set + * and no system bars or floating action buttons cover the content when it is scrolled all the way up. + * This must be called before window insets are applied (Activity.onCreate or Fragment.onViewCreated). + * The RecyclerView needs to have clipToPadding set to false for this to work. + * @param fab true if there is a [FloatingActionButton] above the RecyclerView + */ +fun RecyclerView.ensureBottomPadding(fab: Boolean = false) { + val bottomPadding = if (fab) { + context.resources.getDimensionPixelSize(R.dimen.recyclerview_bottom_padding_actionbutton) + } else { + context.resources.getDimensionPixelSize(R.dimen.recyclerview_bottom_padding_no_actionbutton) + } + + ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets -> + val systemBarsInsets = insets.getInsets(systemBars()) + view.updatePadding(bottom = bottomPadding + systemBarsInsets.bottom) + WindowInsetsCompat.Builder(insets) + .setInsets(systemBars(), Insets.of(systemBarsInsets.left, systemBarsInsets.top, systemBarsInsets.right, 0)) + .build() + } +} + +/** Makes sure a [FloatingActionButton] has the correct bottom margin set + * so it is not covered by any system bars. + */ +fun FloatingActionButton.ensureBottomMargin() { + ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets -> + val bottomInsets = insets.getInsets(systemBars()).bottom + val actionButtonMargin = resources.getDimensionPixelSize(R.dimen.fabMargin) + view.updateLayoutParams { + bottomMargin = bottomInsets + actionButtonMargin + } + insets + } +} + +/** + * Combines WindowInsetsAnimationCompat.Callback and OnApplyWindowInsetsListener + * for easy implementation of layouts that animate with they keyboard. + * The animation callback is only called when something animates, so it isn't suitable for initial setup. + * The OnApplyWindowInsetsListener can do that, but the insets it supplies must not be used when an animation is ongoing, + * as that messes with the animation. + */ +fun View.setOnWindowInsetsChangeListener(listener: (WindowInsetsCompat) -> Unit) { + val callback = WindowInsetsCallback(listener) + + ViewCompat.setWindowInsetsAnimationCallback(this, callback) + ViewCompat.setOnApplyWindowInsetsListener(this, callback) +} + +private class WindowInsetsCallback( + private val listener: (WindowInsetsCompat) -> Unit, +) : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP), + OnApplyWindowInsetsListener { + + var imeVisible = false + var deferredInsets: WindowInsetsCompat? = null + + override fun onStart(animation: WindowInsetsAnimationCompat, bounds: WindowInsetsAnimationCompat.BoundsCompat): WindowInsetsAnimationCompat.BoundsCompat { + imeVisible = true + return super.onStart(animation, bounds) + } + + override fun onProgress( + insets: WindowInsetsCompat, + runningAnimations: List, + ): WindowInsetsCompat { + listener(insets) + return WindowInsetsCompat.CONSUMED + } + + override fun onApplyWindowInsets( + view: View, + insets: WindowInsetsCompat, + ): WindowInsetsCompat { + val ime = insets.getInsets(ime()).bottom + if (!imeVisible && ime == 0) { + listener(insets) + deferredInsets = null + } else { + deferredInsets = insets + } + return WindowInsetsCompat.CONSUMED + } + + override fun onEnd(animation: WindowInsetsAnimationCompat) { + imeVisible = deferredInsets?.isVisible(ime()) == true + deferredInsets?.let { insets -> + listener(insets) + deferredInsets = null + } + } +} diff --git a/app/src/main/res/layout-sw640dp/fragment_timeline.xml b/app/src/main/res/layout-sw640dp/fragment_timeline.xml index 415695912..19a95b21e 100644 --- a/app/src/main/res/layout-sw640dp/fragment_timeline.xml +++ b/app/src/main/res/layout-sw640dp/fragment_timeline.xml @@ -16,10 +16,10 @@ android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:indeterminate="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" - android:indeterminate="true" app:layout_constraintTop_toTopOf="parent" /> - + android:layout_height="match_parent" + android:clipToPadding="false" /> - - - - + + - + android:layout_height="match_parent" + android:background="?android:attr/colorBackground" + android:clipToPadding="false" + android:paddingBottom="@dimen/recyclerview_bottom_padding_actionbutton" /> - - - - + + + + + diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml index ec0df7c93..77ec47fa4 100644 --- a/app/src/main/res/layout/activity_compose.xml +++ b/app/src/main/res/layout/activity_compose.xml @@ -6,79 +6,87 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:background="@android:color/transparent" + android:fitsSystemWindows="true"> - - + - + + - + - + - + - + + + + + + android:layout_marginBottom="52dp" + android:clipToPadding="false" + android:paddingTop="8dp" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> + android:paddingBottom="4dp" + app:layout_insetEdge="bottom"> + android:paddingBottom="12dp" + android:textDirection="anyRtl"> @@ -37,9 +39,9 @@ @@ -47,9 +49,9 @@ @@ -57,9 +59,9 @@ @@ -67,9 +69,9 @@ @@ -77,9 +79,9 @@ @@ -87,9 +89,9 @@ @@ -97,9 +99,9 @@ @@ -107,9 +109,9 @@ @@ -117,9 +119,9 @@ @@ -127,9 +129,9 @@ @@ -137,9 +139,9 @@ @@ -147,9 +149,9 @@ @@ -157,9 +159,9 @@ @@ -167,9 +169,9 @@ @@ -177,9 +179,9 @@ @@ -187,9 +189,9 @@ @@ -197,9 +199,9 @@ @@ -207,9 +209,9 @@ @@ -218,9 +220,9 @@ android:id="@+id/licenseApacheTextView" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginEnd="18dp" android:layout_marginStart="18dp" android:layout_marginTop="12dp" + android:layout_marginEnd="18dp" android:textSize="12sp" /> diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index b885fd053..c25a28920 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,19 +1,33 @@ - + + + + + + + android:clipToPadding="false" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> + android:layout_gravity="center" + android:indeterminate="true" /> + - - - + diff --git a/app/src/main/res/layout/activity_login_webview.xml b/app/src/main/res/layout/activity_login_webview.xml index 5dee10361..e14e86d84 100644 --- a/app/src/main/res/layout/activity_login_webview.xml +++ b/app/src/main/res/layout/activity_login_webview.xml @@ -8,6 +8,7 @@ + android:fitsSystemWindows="@bool/is_edge_to_edge" + app:liftOnScroll="false" + app:statusBarForeground="?attr/colorSurface"> + app:paddingBottomSystemWindowInsets="true"> + app:liftOnScroll="false" + app:statusBarForeground="?attr/colorSurface"> @@ -37,7 +37,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" - android:layout_margin="16dp" + android:layout_margin="@dimen/fabMargin" android:visibility="invisible" app:strokeWidth="0dp" app:cardBackgroundColor="?attr/colorSurface" diff --git a/app/src/main/res/layout/activity_view_media.xml b/app/src/main/res/layout/activity_view_media.xml index 94ffa8f65..d29d34538 100644 --- a/app/src/main/res/layout/activity_view_media.xml +++ b/app/src/main/res/layout/activity_view_media.xml @@ -1,30 +1,39 @@ - + android:background="@color/black"> + + + + + + - - - + diff --git a/app/src/main/res/layout/fragment_timeline.xml b/app/src/main/res/layout/fragment_timeline.xml index 66e645dfb..7f5257a10 100644 --- a/app/src/main/res/layout/fragment_timeline.xml +++ b/app/src/main/res/layout/fragment_timeline.xml @@ -1,34 +1,11 @@ - - - - - - - - - - + + + + + + + + - - - - - - + android:layout_height="match_parent" + android:clipToPadding="false" + android:paddingBottom="@dimen/recyclerview_bottom_padding_actionbutton" /> + + - + android:layout_height="match_parent" + android:clipToPadding="false" + android:paddingBottom="@dimen/recyclerview_bottom_padding_no_actionbutton" /> - - - - + +