Merge tag 'v25.2' into develop

# Conflicts:
#	README.md
#	app/build.gradle
#	app/lint-baseline.xml
#	app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt
#	app/src/main/java/com/keylesspalace/tusky/components/notifications/StatusNotificationViewHolder.kt
#	app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt
#	app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.kt
#	app/src/main/res/layout/activity_about.xml
#	app/src/main/res/layout/item_emoji_pref.xml
#	app/src/main/res/values-ar/strings.xml
#	app/src/main/res/values-bg/strings.xml
#	app/src/main/res/values-cy/strings.xml
#	app/src/main/res/values-de/strings.xml
#	app/src/main/res/values-fa/strings.xml
#	app/src/main/res/values-gd/strings.xml
#	app/src/main/res/values-gl/strings.xml
#	app/src/main/res/values-hu/strings.xml
#	app/src/main/res/values-is/strings.xml
#	app/src/main/res/values-it/strings.xml
#	app/src/main/res/values-ja/strings.xml
#	app/src/main/res/values-nl/strings.xml
#	app/src/main/res/values-oc/strings.xml
#	app/src/main/res/values-pt-rBR/strings.xml
#	app/src/main/res/values-pt-rPT/strings.xml
#	app/src/main/res/values-ru/strings.xml
#	app/src/main/res/values-si/strings.xml
#	app/src/main/res/values-sv/strings.xml
#	app/src/main/res/values-tr/strings.xml
#	app/src/main/res/values-uk/strings.xml
#	app/src/main/res/values-vi/strings.xml
#	app/src/main/res/values-zh-rCN/strings.xml
#	app/src/main/res/values/strings.xml
#	fastlane/metadata/android/ru/full_description.txt
#	fastlane/metadata/android/zh-Hans/full_description.txt
This commit is contained in:
Mike Barnes 2026-01-02 18:27:41 +11:00
commit 875013e47f
630 changed files with 22153 additions and 18732 deletions

View file

@ -15,18 +15,10 @@
package com.keylesspalace.tusky
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatEditText
@ -38,34 +30,29 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
import at.connyduck.calladapter.networkresult.fold
import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.transition.MaterialArcMotion
import com.google.android.material.transition.MaterialContainerTransform
import com.keylesspalace.tusky.adapter.ItemInteractionListener
import com.keylesspalace.tusky.adapter.TabAdapter
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
import com.keylesspalace.tusky.components.account.list.ListSelectionFragment
import com.keylesspalace.tusky.databinding.ActivityTabPreferenceBinding
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.MastoList
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.getDimension
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.unsafeLazy
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import java.util.regex.Pattern
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListener {
class TabPreferenceActivity : BaseActivity(), Injectable, HasAndroidInjector, ItemInteractionListener, ListSelectionFragment.ListSelectionListener {
@Inject
lateinit var mastodonApi: MastodonApi
@ -73,6 +60,9 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
@Inject
lateinit var eventHub: EventHub
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
private val binding by viewBinding(ActivityTabPreferenceBinding::inflate)
private lateinit var currentTabs: MutableList<TabData>
@ -82,9 +72,13 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
private var tabsChanged = false
private val selectedItemElevation by unsafeLazy { resources.getDimension(R.dimen.selected_drag_item_elevation) }
private val selectedItemElevation by unsafeLazy {
resources.getDimension(R.dimen.selected_drag_item_elevation)
}
private val hashtagRegex by unsafeLazy { Pattern.compile("([\\w_]*[\\p{Alpha}_][\\w_]*)", Pattern.CASE_INSENSITIVE) }
private val hashtagRegex by unsafeLazy {
Pattern.compile("([\\w_]*[\\p{Alpha}_][\\w_]*)", Pattern.CASE_INSENSITIVE)
}
private val onFabDismissedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
@ -109,14 +103,19 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
currentTabsAdapter = TabAdapter(currentTabs, false, this, currentTabs.size <= MIN_TAB_COUNT)
binding.currentTabsRecyclerView.adapter = currentTabsAdapter
binding.currentTabsRecyclerView.layoutManager = LinearLayoutManager(this)
binding.currentTabsRecyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
binding.currentTabsRecyclerView.addItemDecoration(
DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
)
addTabAdapter = TabAdapter(listOf(createTabDataFromId(DIRECT)), true, this)
binding.addTabRecyclerView.adapter = addTabAdapter
binding.addTabRecyclerView.layoutManager = LinearLayoutManager(this)
touchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.END)
}
@ -128,7 +127,11 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
return MIN_TAB_COUNT < currentTabs.size
}
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val temp = currentTabs[viewHolder.bindingAdapterPosition]
currentTabs[viewHolder.bindingAdapterPosition] = currentTabs[target.bindingAdapterPosition]
currentTabs[target.bindingAdapterPosition] = temp
@ -148,7 +151,10 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
}
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.elevation = 0f
}
@ -164,18 +170,12 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
toggleFab(false)
}
binding.maxTabsInfo.text = resources.getQuantityString(R.plurals.max_tab_number_reached, MAX_TAB_COUNT, MAX_TAB_COUNT)
updateAvailableTabs()
onBackPressedDispatcher.addCallback(onFabDismissedCallback)
}
override fun onTabAdded(tab: TabData) {
if (currentTabs.size >= MAX_TAB_COUNT) {
return
}
toggleFab(false)
if (tab.id == HASHTAG) {
@ -273,81 +273,24 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
editText.requestFocus()
}
private var listSelectDialog: ListSelectionFragment? = null
private fun showSelectListDialog() {
val adapter = object : ArrayAdapter<MastoList>(this, android.R.layout.simple_list_item_1) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = super.getView(position, convertView, parent)
getItem(position)?.let { item -> (view as TextView).text = item.title }
return view
}
}
listSelectDialog = ListSelectionFragment.newInstance(null)
listSelectDialog?.show(supportFragmentManager, null)
val statusLayout = LinearLayout(this)
statusLayout.gravity = Gravity.CENTER
val progress = ProgressBar(this)
val preferredPadding = getDimension(this, androidx.appcompat.R.attr.dialogPreferredPadding)
progress.setPadding(preferredPadding, 0, preferredPadding, 0)
progress.visible(false)
val noListsText = TextView(this)
noListsText.setPadding(preferredPadding, 0, preferredPadding, 0)
noListsText.text = getText(R.string.select_list_empty)
noListsText.visible(false)
statusLayout.addView(progress)
statusLayout.addView(noListsText)
val dialogBuilder = AlertDialog.Builder(this)
.setTitle(R.string.select_list_title)
.setNeutralButton(R.string.select_list_manage) { _, _ ->
val listIntent = Intent(applicationContext, ListsActivity::class.java)
startActivity(listIntent)
}
.setNegativeButton(android.R.string.cancel, null)
.setView(statusLayout)
.setAdapter(adapter) { _, position ->
adapter.getItem(position)?.let { item ->
val newTab = createTabDataFromId(LIST, listOf(item.id, item.title))
currentTabs.add(newTab)
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
updateAvailableTabs()
saveTabs()
}
}
val showProgressBarJob = getProgressBarJob(progress, 500)
showProgressBarJob.start()
val dialog = dialogBuilder.show()
lifecycleScope.launch {
mastodonApi.getLists().fold(
{ lists ->
showProgressBarJob.cancel()
adapter.addAll(lists)
if (lists.isEmpty()) {
noListsText.show()
}
},
{ throwable ->
dialog.hide()
Log.e("TabPreferenceActivity", "failed to load lists", throwable)
Snackbar.make(binding.root, R.string.error_list_load, Snackbar.LENGTH_LONG).show()
}
)
}
return
}
private fun getProgressBarJob(progressView: View, delayMs: Long) = this.lifecycleScope.launch(
start = CoroutineStart.LAZY
) {
try {
delay(delayMs)
progressView.show()
awaitCancellation()
} finally {
progressView.hide()
}
override fun onListSelected(list: MastoList) {
listSelectDialog?.dismiss()
listSelectDialog = null
val newTab = createTabDataFromId(LIST, listOf(list.id, list.title))
currentTabs.add(newTab)
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
updateAvailableTabs()
saveTabs()
}
private fun validateHashtag(input: CharSequence?): Boolean {
@ -378,17 +321,23 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
if (!currentTabs.contains(directMessagesTab)) {
addableTabs.add(directMessagesTab)
}
val trendingTab = createTabDataFromId(TRENDING)
if (!currentTabs.contains(trendingTab)) {
addableTabs.add(trendingTab)
val trendingTagsTab = createTabDataFromId(TRENDING_TAGS)
if (!currentTabs.contains(trendingTagsTab)) {
addableTabs.add(trendingTagsTab)
}
val bookmarksTab = createTabDataFromId(BOOKMARKS)
if (!currentTabs.contains(bookmarksTab)) {
addableTabs.add(bookmarksTab)
}
val trendingStatusesTab = createTabDataFromId(TRENDING_STATUSES)
if (!currentTabs.contains(trendingStatusesTab)) {
addableTabs.add(trendingStatusesTab)
}
addableTabs.add(createTabDataFromId(HASHTAG))
addableTabs.add(createTabDataFromId(LIST))
addTabAdapter.updateData(addableTabs)
binding.maxTabsInfo.visible(addableTabs.size == 0 || currentTabs.size >= MAX_TAB_COUNT)
currentTabsAdapter.setRemoveButtonVisible(currentTabs.size > MIN_TAB_COUNT)
}
@ -419,8 +368,9 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
}
}
override fun androidInjector() = dispatchingAndroidInjector
companion object {
private const val MIN_TAB_COUNT = 2
private const val MAX_TAB_COUNT = 5
}
}