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: 

<img
src="https://github.com/user-attachments/assets/2a1bf5d9-79fb-48eb-affc-1cbb1164d5f0"
width="280"/>
<img
src="https://github.com/user-attachments/assets/9edccdf2-c0e9-4881-a6df-bd0872934f28"
width="280"/>
<img
src="https://github.com/user-attachments/assets/2916a271-f53e-4d38-a83a-69083eb3053f"
width="280"/>
This commit is contained in:
Konrad Pozniak 2025-02-06 11:37:54 +01:00 committed by GitHub
commit 78152f0449
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 999 additions and 291 deletions

View file

@ -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"

View file

@ -107,8 +107,7 @@
<activity
android:name=".components.compose.ComposeActivity"
android:theme="@style/TuskyDialogActivityTheme"
android:alwaysRetainTaskState="true"
android:windowSoftInputMode="stateVisible|adjustResize" />
android:alwaysRetainTaskState="true" />
<activity
android:name=".components.viewthread.ViewThreadActivity"
android:configChanges="orientation|screenSize" />
@ -117,7 +116,7 @@
android:theme="@style/TuskyBaseTheme"
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize" />
<activity android:name=".components.account.AccountActivity" />
<activity android:name=".EditProfileActivity" />
<activity android:name=".EditProfileActivity"/>
<activity android:name=".components.preference.PreferencesActivity" />
<activity android:name=".StatusListActivity" />
<activity android:name=".components.accountlist.AccountListActivity" />

View file

@ -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(

View file

@ -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<ViewGroup.MarginLayoutParams> {
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)
}

View file

@ -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) }

View file

@ -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)
}

View file

@ -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(

View file

@ -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<CoordinatorLayout.LayoutParams> {
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<CoordinatorLayout.LayoutParams> {
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<TabData>) {
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
}

View file

@ -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<ViewGroup.MarginLayoutParams> {
bottomMargin = bottomInset + actionButtonMargin
}
binding.sheet.updateLayoutParams<ViewGroup.MarginLayoutParams> {
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<TabData> = mutableListOf()

View file

@ -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)

View file

@ -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<PreviewViewHolder>() {
R.drawable.ic_radio_button_unchecked_18dp
}
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, iconId, 0, 0, 0)
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(iconId, 0, 0, 0)
textView.text = options[position]

View file

@ -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<ViewGroup.MarginLayoutParams> {
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
}
}
/**

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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 {

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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()

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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?) {

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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<T : Any> :
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)
}

View file

@ -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

View file

@ -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)

View file

@ -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() {

View file

@ -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<ConstraintLayout.LayoutParams> {
bottomMargin = systemBarInsets.bottom
}
insets.inset(0, 0, 0, systemBarInsets.bottom)
}
/**
* Handle single taps, flings, and dragging
*/

View file

@ -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<ViewGroup.MarginLayoutParams> {
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<WindowInsetsAnimationCompat>,
): 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
}
}
}

View file

@ -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" />
<com.keylesspalace.tusky.view.TuskySwipeRefreshLayout
@ -27,32 +27,23 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:clipToPadding="false" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
android:src="@android:color/transparent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/errorphant_error"
tools:visibility="visible" />
</FrameLayout>
</com.keylesspalace.tusky.view.TuskySwipeRefreshLayout>
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
android:visibility="gone"
tools:src="@drawable/errorphant_error"
tools:visibility="visible" />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/topProgressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"

View file

@ -68,28 +68,24 @@
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<FrameLayout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:clipToPadding="false"
android:paddingBottom="@dimen/recyclerview_bottom_padding_actionbutton" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:clipToPadding="false"
android:paddingBottom="@dimen/recyclerview_bottom_padding_actionbutton" />
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>
</com.keylesspalace.tusky.view.TuskySwipeRefreshLayout>
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progressBar"
android:layout_width="wrap_content"

View file

@ -16,9 +16,11 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clipToPadding="false"
android:textDirection="anyRtl">
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -425,6 +425,14 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- correct size and color will be set programmatically -->
<View
android:id="@+id/accountStatusBarScrim"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@android:color/transparent"
app:layout_collapseMode="pin" />
<!-- top margin equal to statusbar size will be set programmatically -->
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/accountToolbar"
@ -444,9 +452,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="center"
app:tabIndicatorHeight="4dp"
app:tabIndicator="@drawable/tab_indicator_top"
app:tabIndicatorFullWidth="true"
app:tabIndicatorHeight="4dp"
app:tabMode="scrollable" />
</com.google.android.material.appbar.AppBarLayout>

View file

@ -6,79 +6,87 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="@android:color/transparent">
android:background="@android:color/transparent"
android:fitsSystemWindows="true">
<ImageView
android:id="@+id/composeAvatar"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="end"
android:padding="8dp"
tools:ignore="ContentDescription" />
<!--content description will be set in code -->
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Spinner
android:id="@+id/composePostLanguageButton"
style="@style/TuskyImageButton"
android:layout_width="52dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:contentDescription="@string/description_post_language"
android:padding="0dp"
android:textColor="?android:attr/textColorTertiary"
android:textSize="?attr/status_text_large"
android:textStyle="bold"
app:tooltipText="@string/description_post_language" />
<ImageView
android:id="@+id/composeAvatar"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="end"
android:padding="8dp"
tools:ignore="ContentDescription" />
<!--content description will be set in code -->
<com.google.android.material.button.MaterialButton
android:id="@+id/atButton"
style="@style/TuskyImageButton"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:padding="8dp"
android:text="@string/at_symbol"
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_large"
android:textStyle="bold" />
<Spinner
android:id="@+id/composePostLanguageButton"
style="@style/TuskyImageButton"
android:layout_width="52dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:contentDescription="@string/description_post_language"
android:padding="0dp"
android:textColor="?android:attr/textColorTertiary"
android:textSize="?attr/status_text_large"
android:textStyle="bold"
app:tooltipText="@string/description_post_language" />
<com.google.android.material.button.MaterialButton
android:id="@+id/hashButton"
style="@style/TuskyImageButton"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:padding="8dp"
android:text="@string/hash_symbol"
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_large"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
android:id="@+id/atButton"
style="@style/TuskyImageButton"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:padding="8dp"
android:text="@string/at_symbol"
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_large"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
android:id="@+id/descriptionMissingWarningButton"
style="@style/TuskyImageButton"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:contentDescription="@string/hint_media_description_missing"
android:padding="8dp"
app:icon="@drawable/ic_missing_description_24dp"
app:iconTint="@color/tusky_orange_light"
app:tooltipText="@string/hint_media_description_missing" />
<com.google.android.material.button.MaterialButton
android:id="@+id/hashButton"
style="@style/TuskyImageButton"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:padding="8dp"
android:text="@string/hash_symbol"
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_large"
android:textStyle="bold" />
</com.google.android.material.appbar.MaterialToolbar>
<com.google.android.material.button.MaterialButton
android:id="@+id/descriptionMissingWarningButton"
style="@style/TuskyImageButton"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:contentDescription="@string/hint_media_description_missing"
android:padding="8dp"
app:icon="@drawable/ic_missing_description_24dp"
app:iconTint="@color/tusky_orange_light"
app:tooltipText="@string/hint_media_description_missing" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/composeMainScrollView"
android:layout_width="match_parent"
android:layout_height="@dimen/compose_activity_scrollview_height"
android:layout_marginTop="?attr/actionBarSize"
android:layout_marginBottom="52dp">
android:layout_marginBottom="52dp"
android:clipToPadding="false"
android:paddingTop="8dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout
android:layout_width="match_parent"
@ -290,7 +298,8 @@
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp">
android:paddingBottom="4dp"
app:layout_insetEdge="bottom">
<ImageButton
android:id="@+id/composeAddMediaButton"

View file

@ -11,8 +11,10 @@
layout="@layout/toolbar_basic" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout

View file

@ -14,6 +14,7 @@
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout

View file

@ -12,8 +12,10 @@
layout="@layout/toolbar_basic" />
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
@ -21,15 +23,15 @@
android:layout_height="wrap_content"
android:clipChildren="false"
android:orientation="vertical"
android:textDirection="anyRtl"
android:paddingBottom="12dp">
android:paddingBottom="12dp"
android:textDirection="anyRtl">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="18dp"
android:layout_marginStart="18dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="18dp"
android:gravity="center_vertical"
android:lineSpacingMultiplier="1.1"
android:text="@string/license_description" />
@ -37,9 +39,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://kotlinlang.org/"
license:name="Kotlin" />
@ -47,9 +49,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://developer.android.com/jetpack/androidx"
license:name="AndroidX" />
@ -57,9 +59,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://github.com/material-components/material-components-android"
license:name="Material Components for Android" />
@ -67,9 +69,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://square.github.io/okhttp/"
license:name="OkHttp" />
@ -77,9 +79,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://github.com/google/conscrypt"
license:name="Conscrypt" />
@ -87,9 +89,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://square.github.io/retrofit/"
license:name="Retrofit" />
@ -97,9 +99,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://github.com/square/moshi"
license:name="Moshi" />
@ -107,9 +109,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://bumptech.github.io/glide/"
license:name="Glide" />
@ -117,9 +119,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://dagger.dev/"
license:name="Dagger 2" />
@ -127,9 +129,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://github.com/mikepenz/MaterialDrawer"
license:name="MaterialDrawer" />
@ -137,9 +139,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://github.com/connyduck/SparkButton"
license:name="SparkButton" />
@ -147,9 +149,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://github.com/chrisbanes/PhotoView"
license:name="PhotoView" />
@ -157,9 +159,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://github.com/CanHub/Android-Image-Cropper"
license:name="Android Image Cropper" />
@ -167,9 +169,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://github.com/penfeizhou/APNG4Android"
license:name="APNG4Android" />
@ -177,9 +179,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_apache_2"
license:link="https://github.com/C1710/FileMojiCompat"
license:name="FileMojiCompat" />
@ -187,9 +189,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_cc_by_4"
license:link="https://twemoji.twitter.com/"
license:name="Twemoji" />
@ -197,9 +199,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_cc_by_4"
license:link="https://github.com/c1710/blobmoji"
license:name="Blobmoji" />
@ -207,9 +209,9 @@
<com.keylesspalace.tusky.view.LicenseCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
license:license="@string/license_cc_by_sa_4"
license:link="https://github.com/tuskyapp/artwork"
license:name="Tusky elephant artwork" />
@ -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" />
</LinearLayout>

View file

@ -1,19 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context="com.keylesspalace.tusky.components.login.LoginActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:background="@android:color/transparent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:minHeight="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:id="@+id/loginScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
@ -23,11 +37,11 @@
android:padding="16dp">
<ImageView
android:id="@+id/loginLogo"
android:layout_width="160dp"
android:layout_height="178dp"
android:layout_marginBottom="50dp"
android:contentDescription="@null"
android:id="@+id/loginLogo"
app:srcCompat="@drawable/elephant_friend" />
<LinearLayout
@ -35,6 +49,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipChildren="false"
android:layout_marginBottom="16dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
@ -81,8 +96,8 @@
<com.google.android.material.progressindicator.CircularProgressIndicator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_gravity="center" />
android:layout_gravity="center"
android:indeterminate="true" />
<TextView
android:layout_width="250dp"
@ -93,14 +108,7 @@
</LinearLayout>
</LinearLayout>
</ScrollView>
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentTop="true"
android:background="@android:color/transparent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -8,6 +8,7 @@
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar

View file

@ -5,7 +5,7 @@
android:id="@+id/mainDrawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:fitsSystemWindows="@bool/is_not_edge_to_edge"
tools:context="com.keylesspalace.tusky.MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
@ -17,7 +17,9 @@
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="false">
android:fitsSystemWindows="@bool/is_edge_to_edge"
app:liftOnScroll="false"
app:statusBarForeground="?attr/colorSurface">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/mainToolbar"
@ -58,14 +60,13 @@
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
android:background="?attr/windowBackgroundColor"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:backgroundTint="?attr/colorSurface"
app:contentInsetStart="0dp"
@ -73,13 +74,13 @@
app:fabAlignmentMode="end"
app:menuAlignmentMode="auto"
app:navigationContentDescription="@string/action_open_drawer"
app:paddingBottomSystemWindowInsets="false">
app:paddingBottomSystemWindowInsets="true">
<com.keylesspalace.tusky.view.AdaptiveTabLayout
android:id="@+id/bottomTabLayout"
style="@style/TuskyTabAppearance"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_height="@dimen/bottomAppBarHeight"
android:background="?attr/colorSurface"
app:tabGravity="fill"
app:tabIndicator="@drawable/tab_indicator_bottom"

View file

@ -9,8 +9,10 @@
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_collapseMode="pin"
app:liftOnScroll="false">
app:liftOnScroll="false"
app:statusBarForeground="?attr/colorSurface">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"

View file

@ -27,7 +27,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:contentDescription="@string/action_add_tab"
app:srcCompat="@drawable/ic_plus_24dp"
app:tint="?attr/colorOnPrimary" />
@ -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"

View file

@ -1,30 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical">
android:background="@color/black">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/transparent_black"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ViewMediaActivity.AppBarLayout"
app:titleTextColor="@color/white" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:layout_height="match_parent" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/transparent_black"
android:theme="@style/ViewMediaActivity.AppBarLayout"
app:titleTextColor="@color/white" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progressBarShare"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,34 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground">
<com.keylesspalace.tusky.view.TuskySwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
android:visibility="gone" />
</FrameLayout>
</com.keylesspalace.tusky.view.TuskySwipeRefreshLayout>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progressBar"
android:layout_width="wrap_content"
@ -39,6 +16,28 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.keylesspalace.tusky.view.TuskySwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
</com.keylesspalace.tusky.view.TuskySwipeRefreshLayout>
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
android:visibility="gone"
tools:src="@drawable/errorphant_error"
tools:visibility="visible" />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/topProgressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"

View file

@ -61,27 +61,22 @@
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<FrameLayout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/recyclerview_bottom_padding_actionbutton" />
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
android:visibility="gone" />
</FrameLayout>
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/recyclerview_bottom_padding_actionbutton" />
</com.keylesspalace.tusky.view.TuskySwipeRefreshLayout>
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
android:visibility="gone" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progressBar"
android:layout_width="wrap_content"

View file

@ -11,26 +11,22 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/recyclerview_bottom_padding_no_actionbutton" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/recyclerview_bottom_padding_no_actionbutton" />
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/messageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>
</com.keylesspalace.tusky.view.TuskySwipeRefreshLayout>
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/messageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progressBar"
android:layout_width="wrap_content"

View file

@ -69,7 +69,7 @@
android:lineSpacingMultiplier="1.1"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="8dp"
android:paddingBottom="@dimen/media_description_sheet_bottom_padding"
android:textColor="?android:textColorPrimary"
android:textIsSelectable="true"
android:textSize="?attr/status_text_medium"

View file

@ -4,6 +4,7 @@
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar

View file

@ -1,6 +1,7 @@
<resources>
<style name="TuskyTheme" parent="TuskyBaseTheme">
<item name="android:windowLightStatusBar">@bool/lightNavigationBar</item>
<item name="android:windowLightNavigationBar">@bool/lightNavigationBar</item>
<item name="android:navigationBarColor">@color/colorBackground</item>
<item name="android:navigationBarDividerColor">?attr/dividerColor</item>
@ -8,6 +9,7 @@
<!--Black Application Theme Styles-->
<style name="TuskyBlackTheme" parent="TuskyBlackThemeBase">
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar">false</item>
<item name="android:navigationBarColor">@color/black</item>
<item name="android:navigationBarDividerColor">?attr/dividerColor</item>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="is_edge_to_edge">true</bool>
<bool name="is_not_edge_to_edge">false</bool>
</resources>

View file

@ -82,4 +82,9 @@
<dimen name="caption_dialog_inset">24dp</dimen>
<dimen name="media_description_sheet_bottom_padding">8dp</dimen>
<!-- height of an BottomAppBar according to Material Design guidelines -->
<dimen name="bottomAppBarHeight">80dp</dimen>
</resources>

View file

@ -3,4 +3,7 @@
<integer name="profile_media_column_count">3</integer>
<integer name="trending_column_count">1</integer>
<bool name="is_edge_to_edge">false</bool>
<bool name="is_not_edge_to_edge">true</bool>
</resources>

View file

@ -1,27 +1,27 @@
[versions]
agp = "8.8.0"
androidx-activity = "1.9.3"
androidx-activity = "1.10.0"
androidx-appcompat = "1.7.0"
androidx-browser = "1.8.0"
androidx-cardview = "1.0.0"
androidx-constraintlayout = "2.2.0"
androidx-core = "1.13.1"
androidx-core = "1.15.0"
androidx-drawerlayout = "1.2.0"
androidx-exifinterface = "1.3.7"
androidx-fragment = "1.8.5"
androidx-hilt = "1.2.0"
androidx-junit = "1.2.1"
androidx-lifecycle = "2.8.7"
androidx-media3 = "1.4.1"
androidx-media3 = "1.5.1"
androidx-paging = "3.3.5"
androidx-preference = "1.2.1"
androidx-recyclerview = "1.3.2"
androidx-recyclerview = "1.4.0"
androidx-sharetarget = "1.2.0"
androidx-splashscreen = "1.2.0-alpha01"
androidx-splashscreen = "1.2.0-alpha02"
androidx-swiperefresh-layout = "1.1.0"
androidx-testing = "2.2.0"
androidx-viewpager2 = "1.1.0"
androidx-work = "2.9.1"
androidx-work = "2.10.0"
androidx-room = "2.6.1"
bouncycastle = "1.70"
conscrypt = "2.5.3"
@ -30,7 +30,6 @@ diffx = "1.1.1"
emoji2 = "1.4.0"
filemoji-compat = "3.2.7"
glide = "4.16.0"
# Deliberate downgrade, https://github.com/tuskyapp/Tusky/issues/3631
glide-animation-plugin = "3.0.2"
hilt = "2.55"
kotlin = "2.1.10"

View file

@ -11,6 +11,14 @@
</trusted-artifacts>
</configuration>
<components>
<component group="androidx.activity" name="activity" version="1.10.0">
<artifact name="activity-1.10.0.aar">
<sha256 value="c72301bc7307769cd6c3e0edb8da5837e7dea6fd89a5cdedfcfc87bd581850e1" origin="Generated by Gradle"/>
</artifact>
<artifact name="activity-1.10.0.module">
<sha256 value="334315c325c3bad343fc5f70b7a5126e268ff0fea38b0f5dc068106a5286898f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity" version="1.9.0">
<artifact name="activity-1.9.0-sources.jar">
<sha256 value="3b95a45ba13704e4872ba0f2755b13ab7013e7f7c08bb8a208417a2b66622e74" origin="Generated by Gradle"/>
@ -46,6 +54,14 @@
<sha256 value="7e6a24dda205e1cb1e9c96fa35ff6f261608087f31d323d4cff42b9716d7ed57" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-ktx" version="1.10.0">
<artifact name="activity-ktx-1.10.0.aar">
<sha256 value="3b8e0c51e8aa1a32efb6b2d4181a67dc80784789c166bfe307f70ee44185d510" origin="Generated by Gradle"/>
</artifact>
<artifact name="activity-ktx-1.10.0.module">
<sha256 value="b905385b69b275b0b1bd3c765de9964e64e8fe105c23daa4a776bde66c2c3159" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-ktx" version="1.8.1">
<artifact name="activity-ktx-1.8.1.module">
<sha256 value="bea2f811b8d8c1b8a835e11d3c68219097c597c458429d3ceafd728e0ca2c6d1" origin="Generated by Gradle"/>
@ -181,6 +197,14 @@
<sha256 value="5930ea7f21fcb6d0deb2ba32748a0ef7c8fd2c42384860582ba7cd20deb90379" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation-experimental" version="1.4.1">
<artifact name="annotation-experimental-1.4.1.aar">
<sha256 value="6bd4c7c7476f8260cd3bdbb81183583e93fc9f790c27dea7dc314181cbf87aa0" origin="Generated by Gradle"/>
</artifact>
<artifact name="annotation-experimental-1.4.1.module">
<sha256 value="2ac2f7106e12f263425b4a4dfc80989447fb895675fe902d86759aa74fd12b7d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation-jvm" version="1.6.0">
<artifact name="annotation-jvm-1.6.0.module">
<sha256 value="3f5a8faa19de667e63dca9730ff8ef0e478e4bafb5feeb8258e5c086246dc90c" origin="Generated by Gradle"/>
@ -281,6 +305,9 @@
</artifact>
</component>
<component group="androidx.collection" name="collection" version="1.0.0">
<artifact name="collection-1.0.0.jar">
<sha256 value="9c8d117b5c2bc120a1cdfeb857e05b495b16c36013570372a708f7827e3ac9f9" origin="Generated by Gradle"/>
</artifact>
<artifact name="collection-1.0.0.pom">
<sha256 value="a7913a5275ad68e555d2612ebe8c14c367b153e14ca48a1872a64899020e54ef" origin="Generated by Gradle"/>
</artifact>
@ -306,6 +333,11 @@
<sha256 value="2fd3b523e8276c0254c417b66d8e7fecc5b0b975af5d178afa7f5b813812cfab" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection" version="1.4.2">
<artifact name="collection-1.4.2.module">
<sha256 value="0326d2cf5adbe592312810ca1f71c6c0cc30f753c43f07c7402361b788a7784c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-jvm" version="1.4.0">
<artifact name="collection-jvm-1.4.0.jar">
<sha256 value="d5cf7b72647c7995071588fe870450ff9c8f127f253d2d4851e161b800f67ae0" origin="Generated by Gradle"/>
@ -314,6 +346,14 @@
<sha256 value="21b0b02ea68abe418f3dd4e4d42876ecf3bd9a1cada458b460cae1cd9d58ef6d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-jvm" version="1.4.2">
<artifact name="collection-jvm-1.4.2.jar">
<sha256 value="984ce9bd780055eafb8a105aca3f514686bf7b1b6272ec7c645a98ce40fc7db4" origin="Generated by Gradle"/>
</artifact>
<artifact name="collection-jvm-1.4.2.module">
<sha256 value="aad6b3536c0f0e570aa733d5141d70fa6b9b3add370f73a311ca4ca5dee25448" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-ktx" version="1.1.0">
<artifact name="collection-ktx-1.1.0.jar">
<sha256 value="2bfc54475c047131913361f56d0f7f019c6e5bee53eeb0eb7d94a7c499a05227" origin="Generated by Gradle"/>
@ -330,6 +370,14 @@
<sha256 value="3770999ec32d1c082d1a34cf1d64d16d9eca9b6b1263c979954407f461fba82b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-ktx" version="1.4.2">
<artifact name="collection-ktx-1.4.2.jar">
<sha256 value="c6deada2fac53b8ea6523dbda77597b128006674616f140f04df23264c6d1aa3" origin="Generated by Gradle"/>
</artifact>
<artifact name="collection-ktx-1.4.2.module">
<sha256 value="8a68e297cd92c80789acc033e2b3d7cbd7941f9234335f00c037205df200bcf5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.runtime" name="runtime" version="1.6.7">
<artifact name="runtime-1.6.7.module">
<sha256 value="2922f1c5cd195b2d852461cde71c0ed36877e4da5b5cd7cacceb3042fc2604ea" origin="Generated by Gradle"/>
@ -415,6 +463,14 @@
<sha256 value="dae46132cdcd46b798425f7cb78fd65890869b6d26101ccdcd43461a4f51754c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core" version="1.12.0">
<artifact name="core-1.12.0.aar">
<sha256 value="42ffa7ca47d7ba8fe1d874c57ef9c7111bc41a2b0c0c21518a39e07d222ded8b" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-1.12.0.module">
<sha256 value="2f63fbeda23ca0919738d09e406de661f21bac583d6e04a1797dcb77e3b6ae95" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core" version="1.13.1">
<artifact name="core-1.13.1.aar">
<sha256 value="2c27de199535675005553066597a4b20fa1eea7c228ab4ef6b32b5fe39ca1f59" origin="Generated by Gradle"/>
@ -423,6 +479,14 @@
<sha256 value="2a10979bbb3bcd7b25b7f664ab4e9b016fabf2174a26768b677e12e4bea4c7c4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core" version="1.15.0">
<artifact name="core-1.15.0.aar">
<sha256 value="432b85a1974076e14b487ece4a28c59a84f1b9efc3fc8be72cd7f05d32055e51" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-1.15.0.module">
<sha256 value="e8a6c386e1765d870012fed2221173d4a2e8d2fd5aec732c51afb4a9f4684519" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-ktx" version="1.13.1">
<artifact name="core-ktx-1.13.1-sources.jar">
<sha256 value="b6646f76cd2d59210561dffb16b32a4e5fcca6de37ca027f1dae82bfb0cf6493" origin="Generated by Gradle"/>
@ -434,6 +498,14 @@
<sha256 value="ab530b04e2fbe73205484899a3be56c90968873eb496fc52e5ea7596d91035d1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-ktx" version="1.15.0">
<artifact name="core-ktx-1.15.0.aar">
<sha256 value="c72c97ff4a32308bcaf3061eea5ddf33455ce413cee4aa1d5c82440c5759d4bc" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-ktx-1.15.0.module">
<sha256 value="bee9b44664ff37e2e222ebcde4b30e38294a3f927451edc611fec7f454ffa442" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-splashscreen" version="1.2.0-alpha01">
<artifact name="core-splashscreen-1.2.0-alpha01-sources.jar">
<sha256 value="5e82448d6f1c3aad499ff0f5f9d24602f880eb8d26fb13525821a3669b3466de" origin="Generated by Gradle"/>
@ -445,6 +517,14 @@
<sha256 value="dc11ea4e05d403739592d854f833860df7321d7f4262b40463ee54f51bce0107" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-splashscreen" version="1.2.0-alpha02">
<artifact name="core-splashscreen-1.2.0-alpha02.aar">
<sha256 value="fd85f37cbac803f3818bb0b4e763a5db93b8d61bd43ab8746164db82270168b1" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-splashscreen-1.2.0-alpha02.module">
<sha256 value="81c50181738c236c79de1867018a1ac25098ecf4a322fdd0864565d2b7d1af0c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.cursoradapter" name="cursoradapter" version="1.0.0">
<artifact name="cursoradapter-1.0.0.aar">
<sha256 value="a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564" origin="Generated by Gradle"/>
@ -1021,6 +1101,14 @@
<sha256 value="7d4bc2961cd5bd399e3621d434f0c453dd6cadf891f917a946cc291abdda8f1a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.6.2">
<artifact name="lifecycle-common-2.6.2.jar">
<sha256 value="f34831b6c71cd844e1d35d1be49d5e79447c5ab856346531b1e8676fda7374b1" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-common-2.6.2.module">
<sha256 value="0fa7f28f5cff8a406a4f7870b243cb0d6d7a7c20faa7a2becaff5907ae12f9cc" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.8.1">
<artifact name="lifecycle-common-2.8.1.module">
<sha256 value="4442d73add4e6862086c8f979be651c0fa24f3a61f3e30368daaa9ce99fc57d6" origin="Generated by Gradle"/>
@ -1203,6 +1291,14 @@
<sha256 value="b5f4a08193d7802ac3574d91ef442ae31c633ff3095f1c4973c80d68908a48bc" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata" version="2.6.2">
<artifact name="lifecycle-livedata-2.6.2.aar">
<sha256 value="67359f609dfc2bf65da1270b23033f856064ec279f058e0a70c715f7c9003031" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-livedata-2.6.2.module">
<sha256 value="19a287b46d1b1ffe297bee0df7dee5183aeba95600ec0de8742adeb5b67c8bab" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata" version="2.8.1">
<artifact name="lifecycle-livedata-2.8.1.aar">
<sha256 value="1a280b5d6351515907aaa19d3179ec8076095985e979e323a5972bdc5d6b7945" origin="Generated by Gradle"/>
@ -1267,6 +1363,14 @@
<sha256 value="3f388e9e078901970c2bfcfc02fecae948de4b46be5211919ae07d012ca2980d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.6.2">
<artifact name="lifecycle-livedata-core-2.6.2.aar">
<sha256 value="2256780a3cff4a1e57fbb3d442557c17dc363ab8af105bcaf5261d8e2d5db949" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-livedata-core-2.6.2.module">
<sha256 value="527d0e1ac467d1f47cc20ed7c30db170217299fabb868154cf5d1765e4e4dad9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.8.1">
<artifact name="lifecycle-livedata-core-2.8.1.aar">
<sha256 value="ed204afc11b8db2b154bee54a2b1af28d46acc54dc05dce463b4fc61e3ee3995" origin="Generated by Gradle"/>
@ -1501,11 +1605,24 @@
<sha256 value="98a1c08c10cfd4bdaa89e9c256a9b96c09cdd5bcfbb03a9260817a6d89cc2378" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.3.1">
<artifact name="lifecycle-runtime-2.3.1.module">
<sha256 value="2a7b90e5049b674b36bccfd68677b3a0b3178b3f7c2ef7ddf618d3895598c4ce" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.6.1">
<artifact name="lifecycle-runtime-2.6.1.module">
<sha256 value="a4cbb01a42d07047bd8d870017c96a1b0b7b4673320e86b66317a13be2ec10c7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.6.2">
<artifact name="lifecycle-runtime-2.6.2.aar">
<sha256 value="4867fd5279742fba8388821930cb2affe06d81a52814e7e41e70392ea0ef887c" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-runtime-2.6.2.module">
<sha256 value="ea0131846abe1fe9dea59ac6dfe1f0fb9d8b6d600c9eff9a1fd4ad5ee5e7cbc7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.8.1">
<artifact name="lifecycle-runtime-2.8.1.aar">
<sha256 value="d4ae4e8dc0ca6265b683d7b2333a102a3ee84ad97ccb9fbec4334f5cd0e3f54d" origin="Generated by Gradle"/>
@ -2160,6 +2277,14 @@
<sha256 value="ed4a5a918d5febf5c36f8f8bf86ed41ebd51c633f7f6e366a17daa0faab4a587" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-common" version="1.5.1">
<artifact name="media3-common-1.5.1.aar">
<sha256 value="ee69fc48b9ea9c5e1381cbf89bcab96e254fd7bd1f31891642bb4e1c1f6c6fd7" origin="Generated by Gradle"/>
</artifact>
<artifact name="media3-common-1.5.1.module">
<sha256 value="c01afb3a57e3d171e41d98d71220ef9cbb7a508118a189d0c79e55c6337fb796" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-container" version="1.3.1">
<artifact name="media3-container-1.3.1.aar">
<sha256 value="c6103c11666e03ceabeb6454b5bf0b9b51fdc93647f84dd02ee869a9c086269e" origin="Generated by Gradle"/>
@ -2184,6 +2309,14 @@
<sha256 value="333a0fa420c00ef66bbbbdc8ec6cb24bcb85cf8b1d0c730aeb8dfce3bb5615fa" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-container" version="1.5.1">
<artifact name="media3-container-1.5.1.aar">
<sha256 value="b163c43acbb47244b3ffedc5f216f349cfc8ca9585d3b73b7f32b7228fd63dff" origin="Generated by Gradle"/>
</artifact>
<artifact name="media3-container-1.5.1.module">
<sha256 value="369c92fe1ec4d80aa3bd9cbec4fe6757cc4bea24b3a4cddea328ad9629c56d5f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-database" version="1.3.1">
<artifact name="media3-database-1.3.1.aar">
<sha256 value="8386d2c445d194268d4af2d63abb31b028da5f742fc38c6365bc2c40ab641383" origin="Generated by Gradle"/>
@ -2208,6 +2341,14 @@
<sha256 value="405dceb759e14d60667f536011c7c2ad43c39b63fa54939b68719f991c5f738f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-database" version="1.5.1">
<artifact name="media3-database-1.5.1.aar">
<sha256 value="13bf4ccce6bc9e8ef8e04a62e90fa6c1589b89bee8633f0db9f61ae2c23f6ef6" origin="Generated by Gradle"/>
</artifact>
<artifact name="media3-database-1.5.1.module">
<sha256 value="21239585d06e7c673002e3da85ab6358ce427d64f8ab4253c932d2f4fbddb065" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-datasource" version="1.3.1">
<artifact name="media3-datasource-1.3.1.aar">
<sha256 value="ad94437e3c3dc5b9fb4a5c2dc64aa912a91125519742ec2fc3ae9ebfacc62381" origin="Generated by Gradle"/>
@ -2232,6 +2373,14 @@
<sha256 value="52e072c78f2eb1d51b1b7a7de32282722362f1be05f4bd79f6b301f794e60899" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-datasource" version="1.5.1">
<artifact name="media3-datasource-1.5.1.aar">
<sha256 value="b86cfb137a94bf471d6b2802415edd4fe66038dab6986682133786ca7dd43de2" origin="Generated by Gradle"/>
</artifact>
<artifact name="media3-datasource-1.5.1.module">
<sha256 value="a94825f509e053042c918b7e1b4b7752f5bafaf9ba2968c4c3350b6222c5af64" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-datasource-okhttp" version="1.3.1">
<artifact name="media3-datasource-okhttp-1.3.1-sources.jar">
<sha256 value="35c112f4e1c6c02d13e4f714c14971fb59c236cad507ff7f4f5a879c993144f7" origin="Generated by Gradle"/>
@ -2259,6 +2408,14 @@
<sha256 value="4bde6bba18cd131e3027289e92de1ae22597b7d88b77ac831cc73cdcc2717e36" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-datasource-okhttp" version="1.5.1">
<artifact name="media3-datasource-okhttp-1.5.1.aar">
<sha256 value="f778c8943d5e9fbc1bd271339a9ed7b2e8708e18b160e7cabd8f979c4e5d32bf" origin="Generated by Gradle"/>
</artifact>
<artifact name="media3-datasource-okhttp-1.5.1.module">
<sha256 value="39f2c03eb7a0832872676e6d6cd0339a0118452528eefcef2d727cee9d6194f8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-decoder" version="1.3.1">
<artifact name="media3-decoder-1.3.1.aar">
<sha256 value="c4799cd411061a654ad9c73fbc3d1a95945fce40f1bbceaf219e17b8a56fa1c1" origin="Generated by Gradle"/>
@ -2283,6 +2440,14 @@
<sha256 value="c8a5ffe14418ce0ef005521c37bc9810a2bb01c2e5bd66874553788d14262512" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-decoder" version="1.5.1">
<artifact name="media3-decoder-1.5.1.aar">
<sha256 value="6cd78cad4c1ef8aa16876fcd923eefc3894b538960798698a891cc7f419b8b34" origin="Generated by Gradle"/>
</artifact>
<artifact name="media3-decoder-1.5.1.module">
<sha256 value="fc9f6c7dde342308ec395d3db7c517fb089c22978e0465f1eb870ee0570ee88f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-exoplayer" version="1.3.1">
<artifact name="media3-exoplayer-1.3.1-sources.jar">
<sha256 value="c30282058a61aa6934ca6b2eaeb47383e233622fbda98977a8893c914f2d5907" origin="Generated by Gradle"/>
@ -2310,6 +2475,14 @@
<sha256 value="4e153240eae009f97ef4e8aec012443a3ae792545c5354e51b1d232c97f2daae" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-exoplayer" version="1.5.1">
<artifact name="media3-exoplayer-1.5.1.aar">
<sha256 value="11bb9687acd2c0bdd0a99a32006c8ac632ac138ffd996ae7293f375514721b51" origin="Generated by Gradle"/>
</artifact>
<artifact name="media3-exoplayer-1.5.1.module">
<sha256 value="d7b09ec60b5eee17041c7b7cd7233e6964db598ec73ae2037661691454430a75" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-extractor" version="1.3.1">
<artifact name="media3-extractor-1.3.1.aar">
<sha256 value="54400e411960da947d85567bbce3e752fade6bcb7d075c122a4e2cd05379dc62" origin="Generated by Gradle"/>
@ -2334,6 +2507,14 @@
<sha256 value="cac1d03702f3835342287bd40f47d80bd08d8cdbaf2d472df3fdc2e5f2138bc4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-extractor" version="1.5.1">
<artifact name="media3-extractor-1.5.1.aar">
<sha256 value="f2a146209e040409ed2cea3daa74932011c3b4ac451427f1738569821b21d084" origin="Generated by Gradle"/>
</artifact>
<artifact name="media3-extractor-1.5.1.module">
<sha256 value="86b2a4e43762808d57f331c25b5f7967bc7fab3cffe580563108f9149afd3d9b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-ui" version="1.3.1">
<artifact name="media3-ui-1.3.1-sources.jar">
<sha256 value="c3d8d9a0c76996f07e9978e9e1176e344783b93ab3967d9c8b23ea414171a747" origin="Generated by Gradle"/>
@ -2361,6 +2542,14 @@
<sha256 value="4287c7a4a885b77f332dff61e1d0c81754dcd6be3918f0cbc1a7bb46ba9c1ede" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.media3" name="media3-ui" version="1.5.1">
<artifact name="media3-ui-1.5.1.aar">
<sha256 value="01caff80faaf5cbc4304d70f971a41004865228b0e31188a34b9bd219bb1cdd8" origin="Generated by Gradle"/>
</artifact>
<artifact name="media3-ui-1.5.1.module">
<sha256 value="67d598c80cd09c09919ad6822af8d285b5bdcaafc5f92656d729e6b8a35e0ee6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.multidex" name="multidex" version="2.0.1">
<artifact name="multidex-2.0.1.aar">
<sha256 value="42dd32ff9f97f85771b82a20003a8d70f68ab7b4ba328964312ce0732693db09" origin="Generated by Gradle"/>
@ -2626,6 +2815,14 @@
<sha256 value="179fc4d2d022c0a8247e7612feab40b951bd8710f6b452e3ed6dc03a1e52769d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.recyclerview" name="recyclerview" version="1.4.0">
<artifact name="recyclerview-1.4.0.aar">
<sha256 value="0f32ccaffcda74c3f87f0b4cfe6ae78e8aa4eb3ad7dda4f2edf06eedc947a5cf" origin="Generated by Gradle"/>
</artifact>
<artifact name="recyclerview-1.4.0.module">
<sha256 value="631d996322198595129c1d02805346361d811c06f25545fa8bbca567355f6f98" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.resourceinspection" name="resourceinspection-annotation" version="1.0.1">
<artifact name="resourceinspection-annotation-1.0.1.jar">
<sha256 value="8cff870ec6fb31db48a52f4a792335b4bf8de07e03bd37823181526433ccd5cb" origin="Generated by Gradle"/>
@ -2971,6 +3168,22 @@
<sha256 value="b1fed4309623b6f20bc817d8fbd70e4ea7085e40647694cd399ae58d2f0049e3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.tracing" name="tracing" version="1.2.0">
<artifact name="tracing-1.2.0.aar">
<sha256 value="6faa90390d1fdbf0adb9a99bf99de67b94c6c6f35aea9510593a9d17973736a2" origin="Generated by Gradle"/>
</artifact>
<artifact name="tracing-1.2.0.module">
<sha256 value="d0d8d486b6bd33206dbf3f1a6d167e9b43c268ea63c3321c886b1543ad05ece3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.tracing" name="tracing-ktx" version="1.2.0">
<artifact name="tracing-ktx-1.2.0.aar">
<sha256 value="c33f9cbd931e6190ca38aa09bf8a7a8a19391d4b0fb7a7605a6b59f2d42580d1" origin="Generated by Gradle"/>
</artifact>
<artifact name="tracing-ktx-1.2.0.module">
<sha256 value="39288b23c71a4391132aec9951f5e27f41727a2cdff69c6e4b4ece49f63e540c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.transition" name="transition" version="1.5.0">
<artifact name="transition-1.5.0.aar">
<sha256 value="0aa66a0ea406d25a1091f96a3b753b4b12e44fdc43b91ec52c17831e9c31f54b" origin="Generated by Gradle"/>
@ -3041,6 +3254,14 @@
<sha256 value="536773d2b2d65c26ce06b8c95e0fb415f1ad25d9b87330170f508689d3ad5ffb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.work" name="work-runtime" version="2.10.0">
<artifact name="work-runtime-2.10.0.aar">
<sha256 value="2ee10cd3345a7e6e37d50898fd0ebdd91e4a59267aa928effa8e507dae85c88b" origin="Generated by Gradle"/>
</artifact>
<artifact name="work-runtime-2.10.0.module">
<sha256 value="465c1b4750092ebf0b031e06ecfd8011f7e15ace8dccb1e4c47d409f77becba0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.work" name="work-runtime" version="2.9.0">
<artifact name="work-runtime-2.9.0.aar">
<sha256 value="8b85f38aa826d902e8a88d2af9dc73a5f43f6618720485f16e742692c5a18f6f" origin="Generated by Gradle"/>
@ -3057,6 +3278,14 @@
<sha256 value="d544ed64c5cba1861f96f48acdf15f7a06894f3675a16aa815a99f3aa290946b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.work" name="work-runtime-ktx" version="2.10.0">
<artifact name="work-runtime-ktx-2.10.0.aar">
<sha256 value="8273692ccc17094d746cafd04b679f259cbf501b2ea8475e349fba24ed3976b9" origin="Generated by Gradle"/>
</artifact>
<artifact name="work-runtime-ktx-2.10.0.module">
<sha256 value="80aa6dc6ec0cc210475c820a6d36dc8100d5da9aef7cccac081169a1a2502b04" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.work" name="work-runtime-ktx" version="2.9.0">
<artifact name="work-runtime-ktx-2.9.0-sources.jar">
<sha256 value="c6deada2fac53b8ea6523dbda77597b128006674616f140f04df23264c6d1aa3" origin="Generated by Gradle"/>
@ -3076,6 +3305,14 @@
<sha256 value="711fb95228d07347da52089dfb5d195c51a07dd75528ad51474c397196afb35c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.work" name="work-testing" version="2.10.0">
<artifact name="work-testing-2.10.0.aar">
<sha256 value="80eeb8a0ba60089ef0af4a006fa0a0be2b5b627497c332c2b07226f213261690" origin="Generated by Gradle"/>
</artifact>
<artifact name="work-testing-2.10.0.module">
<sha256 value="4a6dcc76dd6030f939904acb1f829c3056774ccd209ba3ede4a322b2191f7a14" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.work" name="work-testing" version="2.9.0">
<artifact name="work-testing-2.9.0-sources.jar">
<sha256 value="7ed3ec16b4203d209cec89f6e467d28359eba77de5cd9165b6430eea0a64eebf" origin="Generated by Gradle"/>
@ -10688,6 +10925,17 @@
<sha256 value="452b2d9787b7d366fa8cf5ed9a1c40404542d05effa7a598da03bbbbb76d9f31" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="guava" version="33.3.1-android">
<artifact name="guava-33.3.1-android.jar">
<sha256 value="2c3e41d1b380f2044d257947a3aa82dabf3ae4b978622745254aa18b6cf89ab0" origin="Generated by Gradle"/>
</artifact>
<artifact name="guava-33.3.1-android.module">
<sha256 value="6971614ddeee003e54d2b69c6b0c94ce8a5b5eba980d16455c81df9089a7acb7" origin="Generated by Gradle"/>
</artifact>
<artifact name="guava-33.3.1-jre.jar">
<sha256 value="4bf0e2c5af8e4525c96e8fde17a4f7307f97f8478f11c4c8e35a0e3298ae4e90" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="guava" version="33.3.1-jre">
<artifact name="guava-33.3.1-android.jar">
<sha256 value="2c3e41d1b380f2044d257947a3aa82dabf3ae4b978622745254aa18b6cf89ab0" origin="Generated by Gradle"/>
@ -10739,6 +10987,11 @@
<sha256 value="55441db27e8869dfefe053059bdf478bdc7e95585642bf391f0023345fd56287" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="guava-parent" version="33.3.1-android">
<artifact name="guava-parent-33.3.1-android.pom">
<sha256 value="6e11986ea7250b51f847157e2dc937f32a306804dfce0007a5e81ddb9b95c579" origin="Generated by Conny Duck"/>
</artifact>
</component>
<component group="com.google.guava" name="listenablefuture" version="1.0">
<artifact name="listenablefuture-1.0-sources.jar">
<sha256 value="3d1bd8d4a39b293612a40e547ec51d9ce34fa638d7adeae83871cdbe2923b161" origin="Generated by Gradle"/>
@ -12527,6 +12780,11 @@
<sha256 value="e718289ad63441bde3c61ab25b907d9b3152b9a181120832305f309e4d0e8274" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-bom" version="1.8.0">
<artifact name="kotlin-bom-1.8.0.pom">
<sha256 value="681f9878548c0a149c678092fd8b1f4cc7c5c41ba1b24397101c1073a6c76dda" origin="Generated by Conny Duck"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-bom" version="1.8.21">
<artifact name="kotlin-bom-1.8.21.pom">
<sha256 value="43f52cd839d8fd872f240491d38fa7df170a87a36e105e700dd939d15572a8ef" origin="Generated by Gradle"/>
@ -14411,6 +14669,14 @@
<sha256 value="beb7ff0f5ebc63a0b30af2ae1214e0b622a7b7e408240e64a8ea5b213c4d5334" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-android" version="1.7.3">
<artifact name="kotlinx-coroutines-android-1.7.3.jar">
<sha256 value="59fffb26bee12c32dadcfa5d420c2a7db85d3253518128b170efda726613256d" origin="Generated by Gradle"/>
</artifact>
<artifact name="kotlinx-coroutines-android-1.7.3.module">
<sha256 value="48dfd8139edee5481bcc8b25e24d7586aca615f0d1ed2beb242dc7478ed4cee0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-android" version="1.8.1">
<artifact name="kotlinx-coroutines-android-1.8.1.jar">
<sha256 value="a134dacf4e6578b29b32e97aa50548d09cb45e3cb3551ce77ac27e55e265d8f5" origin="Generated by Gradle"/>
@ -14442,6 +14708,11 @@
<sha256 value="b925aa988c40a5c7aa0c77b21373feb18c39414926bd51c76ef44434f5bae9c2" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.7.3">
<artifact name="kotlinx-coroutines-bom-1.7.3.pom">
<sha256 value="4e5d1900e6379ef3f5970d04a8f30529adc82f859e8cc107c21ce8149ef666c4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.8.0">
<artifact name="kotlinx-coroutines-bom-1.8.0.pom">
<sha256 value="1239e9dbe1397cd5971342956b2511bc3ace7b641842e4372a088dcfa8b9ad55" origin="Generated by Gradle"/>
@ -14531,6 +14802,14 @@
<sha256 value="db1fe0110fa82b565d332d39d3f3e62c1dc7eb6238333e3b15e4377fa56e39cb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core-jvm" version="1.7.3">
<artifact name="kotlinx-coroutines-core-jvm-1.7.3.jar">
<sha256 value="1ab3acc38f3e7355c4f9d1ec62107a46fa73c899f3070d055e5d4373dfe67e12" origin="Generated by Gradle"/>
</artifact>
<artifact name="kotlinx-coroutines-core-jvm-1.7.3.module">
<sha256 value="34d6ee99b76ac062b51555b4a70be18349fe5566da79a190614f171c80b6538e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core-jvm" version="1.8.0">
<artifact name="kotlinx-coroutines-core-jvm-1.8.0.module">
<sha256 value="ff6a22da40040938751db0ae21177e76517dbf126a76796f5426727bf76c1228" origin="Generated by Gradle"/>