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" /> - - - - + +