From 3ac78c82c9efac38986ca1699aad1f7654192440 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Mon, 8 May 2023 19:24:57 +0200 Subject: [PATCH 001/254] don't crash on unexpected json responses --- .../tusky/components/accountlist/AccountListFragment.kt | 2 +- .../tusky/components/timeline/util/TimelineUtils.kt | 3 ++- .../timeline/viewmodel/CachedTimelineRemoteMediator.kt | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt index 552744586..15f36f268 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt @@ -313,7 +313,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct val linkHeader = response.headers()["Link"] onFetchAccountsSuccess(accountList, linkHeader) - } catch (exception: IOException) { + } catch (exception: Exception) { onFetchAccountsFailure(exception) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/util/TimelineUtils.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/util/TimelineUtils.kt index c8d95fd81..4a1d75f92 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/util/TimelineUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/util/TimelineUtils.kt @@ -1,9 +1,10 @@ package com.keylesspalace.tusky.components.timeline.util +import com.google.gson.JsonParseException import retrofit2.HttpException import java.io.IOException -fun Throwable.isExpected() = this is IOException || this is HttpException +fun Throwable.isExpected() = this is IOException || this is HttpException || this is JsonParseException inline fun ifExpected( t: Throwable, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt index 89afefecd..2746b1acf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt @@ -15,6 +15,7 @@ package com.keylesspalace.tusky.components.timeline.viewmodel +import android.util.Log import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState @@ -117,6 +118,7 @@ class CachedTimelineRemoteMediator( return MediatorResult.Success(endOfPaginationReached = statuses.isEmpty()) } catch (e: Exception) { return ifExpected(e) { + Log.w(TAG, "Failed to load timeline", e) MediatorResult.Error(e) } } @@ -175,4 +177,8 @@ class CachedTimelineRemoteMediator( } return overlappedStatuses } + + companion object { + private const val TAG = "CachedTimelineRM" + } } From b18193e6c3eee776471c79e021ba673419df85d1 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Mon, 8 May 2023 19:27:00 +0200 Subject: [PATCH 002/254] add logging to NetworkTimelineRemoteMediator --- .../timeline/viewmodel/NetworkTimelineRemoteMediator.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt index 98da15bea..40b475e06 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt @@ -15,6 +15,7 @@ package com.keylesspalace.tusky.components.timeline.viewmodel +import android.util.Log import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState @@ -106,8 +107,13 @@ class NetworkTimelineRemoteMediator( return MediatorResult.Success(endOfPaginationReached = statuses.isEmpty()) } catch (e: Exception) { return ifExpected(e) { + Log.w(TAG, "Failed to load timeline", e) MediatorResult.Error(e) } } } + + companion object { + private const val TAG = "NetworkTimelineRM" + } } From 626a8760ae7c1ef3791fd2d3b9f621a6f8514dfe Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 4 Jul 2023 19:30:57 +0200 Subject: [PATCH 003/254] refactor instance blocks to paging --- .../instancemute/InstanceMutePagingSource.kt | 16 ++ .../InstanceMuteRemoteMediator.kt | 56 ++++++ .../instancemute/InstanceMuteViewModel.kt | 71 +++++++ .../adapter/DomainMutesAdapter.kt | 47 +---- .../fragment/InstanceListFragment.kt | 178 +++++++----------- .../interfaces/InstanceActionListener.kt | 5 - .../tusky/di/ViewModelFactory.kt | 6 + .../tusky/network/MastodonApi.kt | 4 +- app/src/main/res/values/strings.xml | 2 + 9 files changed, 226 insertions(+), 159 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt delete mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt new file mode 100644 index 000000000..e61617e6a --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt @@ -0,0 +1,16 @@ +package com.keylesspalace.tusky.components.instancemute + +import androidx.paging.PagingSource +import androidx.paging.PagingState + +class InstanceMutePagingSource(private val viewModel: InstanceMuteViewModel) : PagingSource() { + override fun getRefreshKey(state: PagingState): String? = null + + override suspend fun load(params: LoadParams): LoadResult { + return if (params is LoadParams.Refresh) { + LoadResult.Page(viewModel.domains.toList(), null, viewModel.nextKey) + } else { + LoadResult.Page(emptyList(), null, null) + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt new file mode 100644 index 000000000..62ac91b72 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt @@ -0,0 +1,56 @@ +package com.keylesspalace.tusky.components.instancemute + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import com.keylesspalace.tusky.network.MastodonApi +import com.keylesspalace.tusky.util.HttpHeaderLink +import retrofit2.HttpException +import retrofit2.Response + +@OptIn(ExperimentalPagingApi::class) +class InstanceMuteRemoteMediator( + private val api: MastodonApi, + private val viewModel: InstanceMuteViewModel +) : RemoteMediator() { + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + return try { + val response = request(loadType) + ?: return MediatorResult.Success(endOfPaginationReached = true) + + return applyResponse(response) + } catch (e: Exception) { + MediatorResult.Error(e) + } + } + + private suspend fun request(loadType: LoadType): Response>? { + return when (loadType) { + LoadType.PREPEND -> null + LoadType.APPEND -> api.domainBlocks(maxId = viewModel.nextKey) + LoadType.REFRESH -> { + viewModel.nextKey = null + viewModel.domains.clear() + api.domainBlocks() + } + } + } + + private fun applyResponse(response: Response>): MediatorResult { + val tags = response.body() + if (!response.isSuccessful || tags == null) { + return MediatorResult.Error(HttpException(response)) + } + + val links = HttpHeaderLink.parse(response.headers()["Link"]) + viewModel.nextKey = HttpHeaderLink.findByRelationType(links, "next")?.uri?.getQueryParameter("max_id") + viewModel.domains.addAll(tags) + viewModel.currentSource?.invalidate() + + return MediatorResult.Success(endOfPaginationReached = viewModel.nextKey == null) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt new file mode 100644 index 000000000..84444d793 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt @@ -0,0 +1,71 @@ +package com.keylesspalace.tusky.components.instancemute + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.cachedIn +import at.connyduck.calladapter.networkresult.fold +import com.keylesspalace.tusky.network.MastodonApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +class InstanceMuteViewModel @Inject constructor( + private val api: MastodonApi +) : ViewModel() { + val domains: MutableList = mutableListOf() + val uiEvents = MutableSharedFlow() + var nextKey: String? = null + var currentSource: InstanceMutePagingSource? = null + + @OptIn(ExperimentalPagingApi::class) + val pager = Pager( + config = PagingConfig(pageSize = 20), + remoteMediator = InstanceMuteRemoteMediator(api, this), + pagingSourceFactory = { + InstanceMutePagingSource( + viewModel = this + ).also { source -> + currentSource = source + } + } + ).flow.cachedIn(viewModelScope) + + fun mute(domain: String) { + viewModelScope.launch { + api.blockDomain(domain).fold({ + domains.add(domain) + currentSource?.invalidate() + }, { e -> + Log.w(TAG, "Error muting domain $domain", e) + uiEvents.emit(InstanceMuteEvent.MuteError(domain)) + }) + } + } + + fun unmute(domain: String) { + viewModelScope.launch { + api.unblockDomain(domain).fold({ + domains.remove(domain) + currentSource?.invalidate() + uiEvents.emit(InstanceMuteEvent.UnmuteSuccess(domain)) + }, { e -> + Log.w(TAG, "Error unmuting domain $domain", e) + uiEvents.emit(InstanceMuteEvent.UnmuteError(domain)) + }) + } + } + + companion object { + private const val TAG = "InstanceMuteViewModel" + } +} + +sealed class InstanceMuteEvent { + data class UnmuteSuccess(val domain: String) : InstanceMuteEvent() + data class UnmuteError(val domain: String) : InstanceMuteEvent() + data class MuteError(val domain: String) : InstanceMuteEvent() +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt index 13d8f2d83..e4f15d54b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt @@ -2,17 +2,14 @@ package com.keylesspalace.tusky.components.instancemute.adapter import android.view.LayoutInflater import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener +import androidx.paging.PagingDataAdapter +import com.keylesspalace.tusky.components.followedtags.FollowedTagsAdapter.Companion.STRING_COMPARATOR import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding import com.keylesspalace.tusky.util.BindingHolder class DomainMutesAdapter( - private val actionListener: InstanceActionListener -) : RecyclerView.Adapter>() { - - var instances: MutableList = mutableListOf() - var bottomLoading: Boolean = false + private val onUnmute: (String) -> Unit +) : PagingDataAdapter>(STRING_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { val binding = ItemMutedDomainBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -20,37 +17,11 @@ class DomainMutesAdapter( } override fun onBindViewHolder(holder: BindingHolder, position: Int) { - val instance = instances[position] - - holder.binding.mutedDomain.text = instance - holder.binding.mutedDomainUnmute.setOnClickListener { - actionListener.mute(false, instance, holder.bindingAdapterPosition) - } - } - - override fun getItemCount(): Int { - var count = instances.size - if (bottomLoading) { - ++count - } - return count - } - - fun addItems(newInstances: List) { - val end = instances.size - instances.addAll(newInstances) - notifyItemRangeInserted(end, instances.size) - } - - fun addItem(instance: String) { - instances.add(instance) - notifyItemInserted(instances.size) - } - - fun removeItem(position: Int) { - if (position >= 0 && position < instances.size) { - instances.removeAt(position) - notifyItemRemoved(position) + getItem(position)?.let { instance -> + holder.binding.mutedDomain.text = instance + holder.binding.mutedDomainUnmute.setOnClickListener { + onUnmute(instance) + } } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt index 1da0a2b7d..71e1a0482 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt @@ -4,155 +4,105 @@ import android.os.Bundle import android.util.Log import android.view.View import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle +import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope +import androidx.paging.LoadState import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import at.connyduck.calladapter.networkresult.fold -import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from -import autodispose2.autoDispose import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.components.followedtags.FollowedTagsActivity +import com.keylesspalace.tusky.components.instancemute.InstanceMuteEvent +import com.keylesspalace.tusky.components.instancemute.InstanceMuteViewModel import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter -import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener import com.keylesspalace.tusky.databinding.FragmentInstanceListBinding import com.keylesspalace.tusky.di.Injectable -import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.util.HttpHeaderLink +import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.viewBinding -import com.keylesspalace.tusky.view.EndlessOnScrollListener -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import com.keylesspalace.tusky.util.visible +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject -class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener { +class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable { @Inject - lateinit var api: MastodonApi + lateinit var viewModelFactory: ViewModelFactory private val binding by viewBinding(FragmentInstanceListBinding::bind) - private var fetching = false - private var bottomId: String? = null - private var adapter = DomainMutesAdapter(this) - private lateinit var scrollListener: EndlessOnScrollListener + private val viewModel: InstanceMuteViewModel by viewModels { viewModelFactory } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + val adapter = DomainMutesAdapter(viewModel::unmute) binding.recyclerView.setHasFixedSize(true) binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL)) binding.recyclerView.adapter = adapter + binding.recyclerView.layoutManager = LinearLayoutManager(view.context) - val layoutManager = LinearLayoutManager(view.context) - binding.recyclerView.layoutManager = layoutManager - - scrollListener = object : EndlessOnScrollListener(layoutManager) { - override fun onLoadMore(totalItemsCount: Int, view: RecyclerView) { - if (bottomId != null) { - fetchInstances(bottomId) - } - } - } - - binding.recyclerView.addOnScrollListener(scrollListener) - fetchInstances() - } - - override fun mute(mute: Boolean, instance: String, position: Int) { viewLifecycleOwner.lifecycleScope.launch { - if (mute) { - api.blockDomain(instance).fold({ - adapter.addItem(instance) - }, { e -> - Log.e(TAG, "Error muting domain $instance", e) - }) - } else { - api.unblockDomain(instance).fold({ - adapter.removeItem(position) - Snackbar.make(binding.recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG) - .setAction(R.string.action_undo) { - mute(true, instance, position) - } - .show() - }, { e -> - Log.e(TAG, "Error unmuting domain $instance", e) - }) - } - } - } - - private fun fetchInstances(id: String? = null) { - if (fetching) { - return - } - fetching = true - binding.instanceProgressBar.show() - - if (id != null) { - binding.recyclerView.post { adapter.bottomLoading = true } - } - - api.domainBlocks(id, bottomId) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe( - { response -> - val instances = response.body() - - if (response.isSuccessful && instances != null) { - onFetchInstancesSuccess(instances, response.headers()["Link"]) - } else { - onFetchInstancesFailure(Exception(response.message())) - } - }, - { throwable -> - onFetchInstancesFailure(throwable) + viewModel.uiEvents.collect { event -> + when (event) { + is InstanceMuteEvent.UnmuteError -> showUnmuteError(event.domain) + is InstanceMuteEvent.MuteError -> showMuteError(event.domain) + is InstanceMuteEvent.UnmuteSuccess -> showUnmuteSuccess(event.domain) } - ) - } - - private fun onFetchInstancesSuccess(instances: List, linkHeader: String?) { - adapter.bottomLoading = false - binding.instanceProgressBar.hide() - - val links = HttpHeaderLink.parse(linkHeader) - val next = HttpHeaderLink.findByRelationType(links, "next") - val fromId = next?.uri?.getQueryParameter("max_id") - adapter.addItems(instances) - bottomId = fromId - fetching = false - - if (adapter.itemCount == 0) { - binding.messageView.show() - binding.messageView.setup( - R.drawable.elephant_friend_empty, - R.string.message_empty, - null - ) - } else { - binding.messageView.hide() + } } - } - private fun onFetchInstancesFailure(throwable: Throwable) { - fetching = false - binding.instanceProgressBar.hide() - Log.e(TAG, "Fetch failure", throwable) + lifecycleScope.launch { + viewModel.pager.collectLatest { pagingData -> + adapter.submitData(pagingData) + } + } - if (adapter.itemCount == 0) { - binding.messageView.show() - binding.messageView.setup(throwable) { + adapter.addLoadStateListener { loadState -> + binding.instanceProgressBar.visible(loadState.refresh == LoadState.Loading && adapter.itemCount == 0) + + if (loadState.refresh is LoadState.Error) { + binding.recyclerView.hide() + binding.messageView.show() + val errorState = loadState.refresh as LoadState.Error + binding.messageView.setup(errorState.error) { adapter.retry() } + Log.w(FollowedTagsActivity.TAG, "error loading followed hashtags", errorState.error) + } else if (loadState.refresh is LoadState.NotLoading && adapter.itemCount == 0) { + binding.recyclerView.hide() + binding.messageView.show() + binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty) + } else { + binding.recyclerView.show() binding.messageView.hide() - this.fetchInstances(null) } } } - companion object { - private const val TAG = "InstanceList" // logging tag + private fun showUnmuteError(domain: String) { + showSnackbar( + getString(R.string.error_unmuting_domain, domain), + R.string.action_retry + ) { viewModel.unmute(domain) } + } + + private fun showMuteError(domain: String) { + showSnackbar( + getString(R.string.error_muting_domain, domain), + R.string.action_retry + ) { viewModel.mute(domain) } + } + + private fun showUnmuteSuccess(domain: String) { + showSnackbar( + getString(R.string.confirmation_domain_unmuted, domain), + R.string.action_undo + ) { viewModel.mute(domain) } + } + + private fun showSnackbar(message: String, actionText: Int, action: (View) -> Unit) { + Snackbar.make(binding.recyclerView, message, Snackbar.LENGTH_LONG) + .setAction(actionText, action) + .show() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt deleted file mode 100644 index 9b88ad966..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.keylesspalace.tusky.components.instancemute.interfaces - -interface InstanceActionListener { - fun mute(mute: Boolean, instance: String, position: Int) -} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt index af1972d5f..b14604c6a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt @@ -31,6 +31,7 @@ import com.keylesspalace.tusky.components.drafts.DraftsViewModel import com.keylesspalace.tusky.components.filters.EditFilterViewModel import com.keylesspalace.tusky.components.filters.FiltersViewModel import com.keylesspalace.tusky.components.followedtags.FollowedTagsViewModel +import com.keylesspalace.tusky.components.instancemute.InstanceMuteViewModel import com.keylesspalace.tusky.components.login.LoginWebViewViewModel import com.keylesspalace.tusky.components.notifications.NotificationsViewModel import com.keylesspalace.tusky.components.report.ReportViewModel @@ -185,5 +186,10 @@ abstract class ViewModelModule { @ViewModelKey(EditFilterViewModel::class) internal abstract fun editFilterViewModel(viewModel: EditFilterViewModel): ViewModel + @Binds + @IntoMap + @ViewModelKey(InstanceMuteViewModel::class) + internal abstract fun instanceMuteViewModel(viewModel: InstanceMuteViewModel): ViewModel + // Add more ViewModels here } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index a50ca7964..bfa961713 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -452,11 +452,11 @@ interface MastodonApi { ): Response> @GET("api/v1/domain_blocks") - fun domainBlocks( + suspend fun domainBlocks( @Query("max_id") maxId: String? = null, @Query("since_id") sinceId: String? = null, @Query("limit") limit: Int? = null - ): Single>> + ): Response> @FormUrlEncoded @POST("api/v1/domain_blocks") diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9fbb6fda3..1ed16f2ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,6 +44,8 @@ This instance does not support following hashtags. Error muting #%s Error unmuting #%s + Failed to mute %s + Failed to mute %s Failed to load the status source from the server. Login From 467096436239bcf4a0f4f1db62b58ad1ae93302f Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 4 Jul 2023 19:41:36 +0200 Subject: [PATCH 004/254] rename module and classes to domainblocks --- app/src/main/AndroidManifest.xml | 2 +- .../DomainBlocksActivity.kt} | 7 ++-- .../DomainBlocksAdapter.kt} | 4 +- .../DomainBlocksFragment.kt} | 40 +++++++++---------- .../DomainBlocksPagingSource.kt} | 4 +- .../DomainBlocksRemoteMediator.kt} | 6 +-- .../DomainBlocksViewModel.kt} | 36 ++++++++--------- .../preference/AccountPreferencesFragment.kt | 4 +- .../tusky/di/ActivitiesModule.kt | 4 +- .../tusky/di/FragmentBuildersModule.kt | 4 +- .../tusky/di/ViewModelFactory.kt | 6 +-- ...ce_list.xml => fragment_domain_blocks.xml} | 4 +- app/src/main/res/values/strings.xml | 4 +- 13 files changed, 62 insertions(+), 63 deletions(-) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/InstanceListActivity.kt => domainblocks/DomainBlocksActivity.kt} (78%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/adapter/DomainMutesAdapter.kt => domainblocks/DomainBlocksAdapter.kt} (92%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/fragment/InstanceListFragment.kt => domainblocks/DomainBlocksFragment.kt} (68%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/InstanceMutePagingSource.kt => domainblocks/DomainBlocksPagingSource.kt} (73%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/InstanceMuteRemoteMediator.kt => domainblocks/DomainBlocksRemoteMediator.kt} (92%) rename app/src/main/java/com/keylesspalace/tusky/components/{instancemute/InstanceMuteViewModel.kt => domainblocks/DomainBlocksViewModel.kt} (57%) rename app/src/main/res/layout/{fragment_instance_list.xml => fragment_domain_blocks.xml} (93%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 232bf491c..f88f1fc80 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -148,7 +148,7 @@ - + diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksActivity.kt similarity index 78% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksActivity.kt index 667360c53..618174907 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksActivity.kt @@ -1,15 +1,14 @@ -package com.keylesspalace.tusky.components.instancemute +package com.keylesspalace.tusky.components.domainblocks import android.os.Bundle import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment import com.keylesspalace.tusky.databinding.ActivityAccountListBinding import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector import javax.inject.Inject -class InstanceListActivity : BaseActivity(), HasAndroidInjector { +class DomainBlocksActivity : BaseActivity(), HasAndroidInjector { @Inject lateinit var androidInjector: DispatchingAndroidInjector @@ -28,7 +27,7 @@ class InstanceListActivity : BaseActivity(), HasAndroidInjector { supportFragmentManager .beginTransaction() - .replace(R.id.fragment_container, InstanceListFragment()) + .replace(R.id.fragment_container, DomainBlocksFragment()) .commit() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt similarity index 92% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt index e4f15d54b..a4b21fc79 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt @@ -1,4 +1,4 @@ -package com.keylesspalace.tusky.components.instancemute.adapter +package com.keylesspalace.tusky.components.domainblocks import android.view.LayoutInflater import android.view.ViewGroup @@ -7,7 +7,7 @@ import com.keylesspalace.tusky.components.followedtags.FollowedTagsAdapter.Compa import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding import com.keylesspalace.tusky.util.BindingHolder -class DomainMutesAdapter( +class DomainBlocksAdapter( private val onUnmute: (String) -> Unit ) : PagingDataAdapter>(STRING_COMPARATOR) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt similarity index 68% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt index 71e1a0482..9d2d8686c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt @@ -1,4 +1,4 @@ -package com.keylesspalace.tusky.components.instancemute.fragment +package com.keylesspalace.tusky.components.domainblocks import android.os.Bundle import android.util.Log @@ -11,11 +11,7 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.components.followedtags.FollowedTagsActivity -import com.keylesspalace.tusky.components.instancemute.InstanceMuteEvent -import com.keylesspalace.tusky.components.instancemute.InstanceMuteViewModel -import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter -import com.keylesspalace.tusky.databinding.FragmentInstanceListBinding +import com.keylesspalace.tusky.databinding.FragmentDomainBlocksBinding import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.util.hide @@ -26,17 +22,17 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject -class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable { +class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectable { @Inject lateinit var viewModelFactory: ViewModelFactory - private val binding by viewBinding(FragmentInstanceListBinding::bind) + private val binding by viewBinding(FragmentDomainBlocksBinding::bind) - private val viewModel: InstanceMuteViewModel by viewModels { viewModelFactory } + private val viewModel: DomainBlocksViewModel by viewModels { viewModelFactory } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val adapter = DomainMutesAdapter(viewModel::unmute) + val adapter = DomainBlocksAdapter(viewModel::unblock) binding.recyclerView.setHasFixedSize(true) binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL)) @@ -46,9 +42,9 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab viewLifecycleOwner.lifecycleScope.launch { viewModel.uiEvents.collect { event -> when (event) { - is InstanceMuteEvent.UnmuteError -> showUnmuteError(event.domain) - is InstanceMuteEvent.MuteError -> showMuteError(event.domain) - is InstanceMuteEvent.UnmuteSuccess -> showUnmuteSuccess(event.domain) + is DomainBlockEvent.UnblockError -> showUnmuteError(event.domain) + is DomainBlockEvent.BlockError -> showMuteError(event.domain) + is DomainBlockEvent.BlockSuccess -> showUnmuteSuccess(event.domain) } } } @@ -60,14 +56,14 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab } adapter.addLoadStateListener { loadState -> - binding.instanceProgressBar.visible(loadState.refresh == LoadState.Loading && adapter.itemCount == 0) + binding.progressBar.visible(loadState.refresh == LoadState.Loading && adapter.itemCount == 0) if (loadState.refresh is LoadState.Error) { binding.recyclerView.hide() binding.messageView.show() val errorState = loadState.refresh as LoadState.Error binding.messageView.setup(errorState.error) { adapter.retry() } - Log.w(FollowedTagsActivity.TAG, "error loading followed hashtags", errorState.error) + Log.w(TAG, "error loading blocked domains", errorState.error) } else if (loadState.refresh is LoadState.NotLoading && adapter.itemCount == 0) { binding.recyclerView.hide() binding.messageView.show() @@ -81,23 +77,23 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab private fun showUnmuteError(domain: String) { showSnackbar( - getString(R.string.error_unmuting_domain, domain), + getString(R.string.error_unblocking_domain, domain), R.string.action_retry - ) { viewModel.unmute(domain) } + ) { viewModel.unblock(domain) } } private fun showMuteError(domain: String) { showSnackbar( - getString(R.string.error_muting_domain, domain), + getString(R.string.error_blocking_domain, domain), R.string.action_retry - ) { viewModel.mute(domain) } + ) { viewModel.block(domain) } } private fun showUnmuteSuccess(domain: String) { showSnackbar( getString(R.string.confirmation_domain_unmuted, domain), R.string.action_undo - ) { viewModel.mute(domain) } + ) { viewModel.block(domain) } } private fun showSnackbar(message: String, actionText: Int, action: (View) -> Unit) { @@ -105,4 +101,8 @@ class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectab .setAction(actionText, action) .show() } + + companion object { + private const val TAG = "DomainBlocksFragment" + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt similarity index 73% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt index e61617e6a..a0c1c5b54 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMutePagingSource.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt @@ -1,9 +1,9 @@ -package com.keylesspalace.tusky.components.instancemute +package com.keylesspalace.tusky.components.domainblocks import androidx.paging.PagingSource import androidx.paging.PagingState -class InstanceMutePagingSource(private val viewModel: InstanceMuteViewModel) : PagingSource() { +class DomainBlocksPagingSource(private val viewModel: DomainBlocksViewModel) : PagingSource() { override fun getRefreshKey(state: PagingState): String? = null override suspend fun load(params: LoadParams): LoadResult { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt similarity index 92% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt index 62ac91b72..ecbb61104 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt @@ -1,4 +1,4 @@ -package com.keylesspalace.tusky.components.instancemute +package com.keylesspalace.tusky.components.domainblocks import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType @@ -10,9 +10,9 @@ import retrofit2.HttpException import retrofit2.Response @OptIn(ExperimentalPagingApi::class) -class InstanceMuteRemoteMediator( +class DomainBlocksRemoteMediator( private val api: MastodonApi, - private val viewModel: InstanceMuteViewModel + private val viewModel: DomainBlocksViewModel ) : RemoteMediator() { override suspend fun load( loadType: LoadType, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt similarity index 57% rename from app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt rename to app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt index 84444d793..c70fa5077 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceMuteViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt @@ -1,4 +1,4 @@ -package com.keylesspalace.tusky.components.instancemute +package com.keylesspalace.tusky.components.domainblocks import android.util.Log import androidx.lifecycle.ViewModel @@ -13,20 +13,20 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import javax.inject.Inject -class InstanceMuteViewModel @Inject constructor( +class DomainBlocksViewModel @Inject constructor( private val api: MastodonApi ) : ViewModel() { val domains: MutableList = mutableListOf() - val uiEvents = MutableSharedFlow() + val uiEvents = MutableSharedFlow() var nextKey: String? = null - var currentSource: InstanceMutePagingSource? = null + var currentSource: DomainBlocksPagingSource? = null @OptIn(ExperimentalPagingApi::class) val pager = Pager( config = PagingConfig(pageSize = 20), - remoteMediator = InstanceMuteRemoteMediator(api, this), + remoteMediator = DomainBlocksRemoteMediator(api, this), pagingSourceFactory = { - InstanceMutePagingSource( + DomainBlocksPagingSource( viewModel = this ).also { source -> currentSource = source @@ -34,38 +34,38 @@ class InstanceMuteViewModel @Inject constructor( } ).flow.cachedIn(viewModelScope) - fun mute(domain: String) { + fun block(domain: String) { viewModelScope.launch { api.blockDomain(domain).fold({ domains.add(domain) currentSource?.invalidate() }, { e -> - Log.w(TAG, "Error muting domain $domain", e) - uiEvents.emit(InstanceMuteEvent.MuteError(domain)) + Log.w(TAG, "Error blocking domain $domain", e) + uiEvents.emit(DomainBlockEvent.BlockError(domain)) }) } } - fun unmute(domain: String) { + fun unblock(domain: String) { viewModelScope.launch { api.unblockDomain(domain).fold({ domains.remove(domain) currentSource?.invalidate() - uiEvents.emit(InstanceMuteEvent.UnmuteSuccess(domain)) + uiEvents.emit(DomainBlockEvent.BlockSuccess(domain)) }, { e -> - Log.w(TAG, "Error unmuting domain $domain", e) - uiEvents.emit(InstanceMuteEvent.UnmuteError(domain)) + Log.w(TAG, "Error unblocking domain $domain", e) + uiEvents.emit(DomainBlockEvent.UnblockError(domain)) }) } } companion object { - private const val TAG = "InstanceMuteViewModel" + private const val TAG = "DomainBlocksViewModel" } } -sealed class InstanceMuteEvent { - data class UnmuteSuccess(val domain: String) : InstanceMuteEvent() - data class UnmuteError(val domain: String) : InstanceMuteEvent() - data class MuteError(val domain: String) : InstanceMuteEvent() +sealed class DomainBlockEvent { + data class BlockSuccess(val domain: String) : DomainBlockEvent() + data class UnblockError(val domain: String) : DomainBlockEvent() + data class BlockError(val domain: String) : DomainBlockEvent() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt index b0fedb2b1..629c3709d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt @@ -30,9 +30,9 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.TabPreferenceActivity import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.components.accountlist.AccountListActivity +import com.keylesspalace.tusky.components.domainblocks.DomainBlocksActivity import com.keylesspalace.tusky.components.filters.FiltersActivity import com.keylesspalace.tusky.components.followedtags.FollowedTagsActivity -import com.keylesspalace.tusky.components.instancemute.InstanceListActivity import com.keylesspalace.tusky.components.login.LoginActivity import com.keylesspalace.tusky.components.notifications.currentAccountNeedsMigration import com.keylesspalace.tusky.db.AccountManager @@ -156,7 +156,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setTitle(R.string.title_domain_mutes) setIcon(R.drawable.ic_mute_24dp) setOnPreferenceClickListener { - val intent = Intent(context, InstanceListActivity::class.java) + val intent = Intent(context, DomainBlocksActivity::class.java) activity?.startActivity(intent) activity?.overridePendingTransition( R.anim.slide_from_right, diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index 2ceb97213..02b4f6ea7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -29,11 +29,11 @@ import com.keylesspalace.tusky.components.account.AccountActivity import com.keylesspalace.tusky.components.accountlist.AccountListActivity import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity import com.keylesspalace.tusky.components.compose.ComposeActivity +import com.keylesspalace.tusky.components.domainblocks.DomainBlocksActivity import com.keylesspalace.tusky.components.drafts.DraftsActivity import com.keylesspalace.tusky.components.filters.EditFilterActivity import com.keylesspalace.tusky.components.filters.FiltersActivity import com.keylesspalace.tusky.components.followedtags.FollowedTagsActivity -import com.keylesspalace.tusky.components.instancemute.InstanceListActivity import com.keylesspalace.tusky.components.login.LoginActivity import com.keylesspalace.tusky.components.login.LoginWebViewActivity import com.keylesspalace.tusky.components.preference.PreferencesActivity @@ -113,7 +113,7 @@ abstract class ActivitiesModule { abstract fun contributesReportActivity(): ReportActivity @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) - abstract fun contributesInstanceListActivity(): InstanceListActivity + abstract fun contributesInstanceListActivity(): DomainBlocksActivity @ContributesAndroidInjector abstract fun contributesScheduledStatusActivity(): ScheduledStatusActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt index aee1feab4..3292bbfe8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt @@ -20,7 +20,7 @@ import com.keylesspalace.tusky.components.account.list.ListsForAccountFragment import com.keylesspalace.tusky.components.account.media.AccountMediaFragment import com.keylesspalace.tusky.components.accountlist.AccountListFragment import com.keylesspalace.tusky.components.conversation.ConversationsFragment -import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment +import com.keylesspalace.tusky.components.domainblocks.DomainBlocksFragment import com.keylesspalace.tusky.components.notifications.NotificationsFragment import com.keylesspalace.tusky.components.preference.AccountPreferencesFragment import com.keylesspalace.tusky.components.preference.NotificationPreferencesFragment @@ -84,7 +84,7 @@ abstract class FragmentBuildersModule { abstract fun reportDoneFragment(): ReportDoneFragment @ContributesAndroidInjector - abstract fun instanceListFragment(): InstanceListFragment + abstract fun instanceListFragment(): DomainBlocksFragment @ContributesAndroidInjector abstract fun searchStatusesFragment(): SearchStatusesFragment diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt index b14604c6a..f2ae58893 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt @@ -27,11 +27,11 @@ import com.keylesspalace.tusky.components.account.media.AccountMediaViewModel import com.keylesspalace.tusky.components.announcements.AnnouncementsViewModel import com.keylesspalace.tusky.components.compose.ComposeViewModel import com.keylesspalace.tusky.components.conversation.ConversationsViewModel +import com.keylesspalace.tusky.components.domainblocks.DomainBlocksViewModel import com.keylesspalace.tusky.components.drafts.DraftsViewModel import com.keylesspalace.tusky.components.filters.EditFilterViewModel import com.keylesspalace.tusky.components.filters.FiltersViewModel import com.keylesspalace.tusky.components.followedtags.FollowedTagsViewModel -import com.keylesspalace.tusky.components.instancemute.InstanceMuteViewModel import com.keylesspalace.tusky.components.login.LoginWebViewViewModel import com.keylesspalace.tusky.components.notifications.NotificationsViewModel import com.keylesspalace.tusky.components.report.ReportViewModel @@ -188,8 +188,8 @@ abstract class ViewModelModule { @Binds @IntoMap - @ViewModelKey(InstanceMuteViewModel::class) - internal abstract fun instanceMuteViewModel(viewModel: InstanceMuteViewModel): ViewModel + @ViewModelKey(DomainBlocksViewModel::class) + internal abstract fun instanceMuteViewModel(viewModel: DomainBlocksViewModel): ViewModel // Add more ViewModels here } diff --git a/app/src/main/res/layout/fragment_instance_list.xml b/app/src/main/res/layout/fragment_domain_blocks.xml similarity index 93% rename from app/src/main/res/layout/fragment_instance_list.xml rename to app/src/main/res/layout/fragment_domain_blocks.xml index 8270cee33..f82573e24 100644 --- a/app/src/main/res/layout/fragment_instance_list.xml +++ b/app/src/main/res/layout/fragment_domain_blocks.xml @@ -5,7 +5,7 @@ android:layout_height="match_parent"> @@ -22,4 +22,4 @@ android:layout_gravity="center" android:visibility="gone" tools:visibility="visible" /> - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1ed16f2ea..6c1df77d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,8 +44,8 @@ This instance does not support following hashtags. Error muting #%s Error unmuting #%s - Failed to mute %s - Failed to mute %s + Failed to mute %s + Failed to unmute %s Failed to load the status source from the server. Login From 2393ff1e4d3ff99235ba89c4c697c0ace04e483f Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 19:23:04 +0200 Subject: [PATCH 005/254] rename item_muted_domain to item_blocked_domain --- .../components/domainblocks/DomainBlocksAdapter.kt | 14 +++++++------- ...em_muted_domain.xml => item_blocked_domain.xml} | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) rename app/src/main/res/layout/{item_muted_domain.xml => item_blocked_domain.xml} (91%) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt index a4b21fc79..e37aa917c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksAdapter.kt @@ -4,22 +4,22 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.paging.PagingDataAdapter import com.keylesspalace.tusky.components.followedtags.FollowedTagsAdapter.Companion.STRING_COMPARATOR -import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding +import com.keylesspalace.tusky.databinding.ItemBlockedDomainBinding import com.keylesspalace.tusky.util.BindingHolder class DomainBlocksAdapter( private val onUnmute: (String) -> Unit -) : PagingDataAdapter>(STRING_COMPARATOR) { +) : PagingDataAdapter>(STRING_COMPARATOR) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { - val binding = ItemMutedDomainBinding.inflate(LayoutInflater.from(parent.context), parent, false) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { + val binding = ItemBlockedDomainBinding.inflate(LayoutInflater.from(parent.context), parent, false) return BindingHolder(binding) } - override fun onBindViewHolder(holder: BindingHolder, position: Int) { + override fun onBindViewHolder(holder: BindingHolder, position: Int) { getItem(position)?.let { instance -> - holder.binding.mutedDomain.text = instance - holder.binding.mutedDomainUnmute.setOnClickListener { + holder.binding.blockedDomain.text = instance + holder.binding.blockedDomainUnblock.setOnClickListener { onUnmute(instance) } } diff --git a/app/src/main/res/layout/item_muted_domain.xml b/app/src/main/res/layout/item_blocked_domain.xml similarity index 91% rename from app/src/main/res/layout/item_muted_domain.xml rename to app/src/main/res/layout/item_blocked_domain.xml index 3147fca56..e043e1a3b 100644 --- a/app/src/main/res/layout/item_muted_domain.xml +++ b/app/src/main/res/layout/item_blocked_domain.xml @@ -11,7 +11,7 @@ > - \ No newline at end of file + From 8e56b5429b18a96a5a3741df8964229070f8d2b3 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 19:42:30 +0200 Subject: [PATCH 006/254] introduce SnackbarEvent instead of DomainBlockEvent --- .../domainblocks/DomainBlocksFragment.kt | 37 ++++--------- .../domainblocks/DomainBlocksViewModel.kt | 54 +++++++++++++------ app/src/main/res/values/strings.xml | 4 +- 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt index 9d2d8686c..66b59bfab 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt @@ -41,11 +41,7 @@ class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectab viewLifecycleOwner.lifecycleScope.launch { viewModel.uiEvents.collect { event -> - when (event) { - is DomainBlockEvent.UnblockError -> showUnmuteError(event.domain) - is DomainBlockEvent.BlockError -> showMuteError(event.domain) - is DomainBlockEvent.BlockSuccess -> showUnmuteSuccess(event.domain) - } + showSnackbar(event) } } @@ -75,30 +71,17 @@ class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectab } } - private fun showUnmuteError(domain: String) { - showSnackbar( - getString(R.string.error_unblocking_domain, domain), - R.string.action_retry - ) { viewModel.unblock(domain) } - } + private fun showSnackbar(event: SnackbarEvent) { + val message = if (event.throwable == null) { + getString(event.message, event.domain) + } else { + Log.w(TAG, event.throwable) + val error = event.throwable.localizedMessage ?: getString(R.string.ui_error_unknown) + getString(event.message, event.domain, error) + } - private fun showMuteError(domain: String) { - showSnackbar( - getString(R.string.error_blocking_domain, domain), - R.string.action_retry - ) { viewModel.block(domain) } - } - - private fun showUnmuteSuccess(domain: String) { - showSnackbar( - getString(R.string.confirmation_domain_unmuted, domain), - R.string.action_undo - ) { viewModel.block(domain) } - } - - private fun showSnackbar(message: String, actionText: Int, action: (View) -> Unit) { Snackbar.make(binding.recyclerView, message, Snackbar.LENGTH_LONG) - .setAction(actionText, action) + .setAction(event.actionText, event.action) .show() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt index c70fa5077..cf0d42578 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt @@ -1,6 +1,7 @@ package com.keylesspalace.tusky.components.domainblocks -import android.util.Log +import android.view.View +import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.ExperimentalPagingApi @@ -8,6 +9,7 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.cachedIn import at.connyduck.calladapter.networkresult.fold +import com.keylesspalace.tusky.R import com.keylesspalace.tusky.network.MastodonApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch @@ -17,7 +19,7 @@ class DomainBlocksViewModel @Inject constructor( private val api: MastodonApi ) : ViewModel() { val domains: MutableList = mutableListOf() - val uiEvents = MutableSharedFlow() + val uiEvents = MutableSharedFlow() var nextKey: String? = null var currentSource: DomainBlocksPagingSource? = null @@ -40,8 +42,15 @@ class DomainBlocksViewModel @Inject constructor( domains.add(domain) currentSource?.invalidate() }, { e -> - Log.w(TAG, "Error blocking domain $domain", e) - uiEvents.emit(DomainBlockEvent.BlockError(domain)) + uiEvents.emit( + SnackbarEvent( + message = R.string.error_blocking_domain, + domain = domain, + throwable = e, + actionText = R.string.action_retry, + action = { block(domain) } + ) + ) }) } } @@ -51,21 +60,34 @@ class DomainBlocksViewModel @Inject constructor( api.unblockDomain(domain).fold({ domains.remove(domain) currentSource?.invalidate() - uiEvents.emit(DomainBlockEvent.BlockSuccess(domain)) + uiEvents.emit( + SnackbarEvent( + message = R.string.confirmation_domain_unmuted, + domain = domain, + throwable = null, + actionText = R.string.action_undo, + action = { block(domain) } + ) + ) }, { e -> - Log.w(TAG, "Error unblocking domain $domain", e) - uiEvents.emit(DomainBlockEvent.UnblockError(domain)) + uiEvents.emit( + SnackbarEvent( + message = R.string.error_unblocking_domain, + domain = domain, + throwable = e, + actionText = R.string.action_retry, + action = { unblock(domain) } + ) + ) }) } } - - companion object { - private const val TAG = "DomainBlocksViewModel" - } } -sealed class DomainBlockEvent { - data class BlockSuccess(val domain: String) : DomainBlockEvent() - data class UnblockError(val domain: String) : DomainBlockEvent() - data class BlockError(val domain: String) : DomainBlockEvent() -} +class SnackbarEvent( + @StringRes val message: Int, + val domain: String, + val throwable: Throwable?, + @StringRes val actionText: Int, + val action: (View) -> Unit +) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c1df77d4..1e68f6612 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,8 +44,8 @@ This instance does not support following hashtags. Error muting #%s Error unmuting #%s - Failed to mute %s - Failed to unmute %s + Failed to mute %1$s: %2$s + Failed to unmute %1$s: %2$s Failed to load the status source from the server. Login From f8a6e0d8b801637a53072b65b501ea2ef958bff5 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 20:09:16 +0200 Subject: [PATCH 007/254] introduce DomainBlocksRepository --- .../domainblocks/DomainBlocksFragment.kt | 2 +- .../domainblocks/DomainBlocksPagingSource.kt | 7 +- .../DomainBlocksRemoteMediator.kt | 17 ++--- .../domainblocks/DomainBlocksRepository.kt | 69 +++++++++++++++++++ .../domainblocks/DomainBlocksViewModel.kt | 37 +++------- .../NotificationsViewModelTestBase.kt | 1 + 6 files changed, 93 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRepository.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt index 66b59bfab..4d689b3e4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt @@ -46,7 +46,7 @@ class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectab } lifecycleScope.launch { - viewModel.pager.collectLatest { pagingData -> + viewModel.domainPager.collectLatest { pagingData -> adapter.submitData(pagingData) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt index a0c1c5b54..0438a268f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksPagingSource.kt @@ -3,12 +3,15 @@ package com.keylesspalace.tusky.components.domainblocks import androidx.paging.PagingSource import androidx.paging.PagingState -class DomainBlocksPagingSource(private val viewModel: DomainBlocksViewModel) : PagingSource() { +class DomainBlocksPagingSource( + private val domains: List, + private val nextKey: String? +) : PagingSource() { override fun getRefreshKey(state: PagingState): String? = null override suspend fun load(params: LoadParams): LoadResult { return if (params is LoadParams.Refresh) { - LoadResult.Page(viewModel.domains.toList(), null, viewModel.nextKey) + LoadResult.Page(domains, null, nextKey) } else { LoadResult.Page(emptyList(), null, null) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt index ecbb61104..09f99044e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRemoteMediator.kt @@ -12,8 +12,9 @@ import retrofit2.Response @OptIn(ExperimentalPagingApi::class) class DomainBlocksRemoteMediator( private val api: MastodonApi, - private val viewModel: DomainBlocksViewModel + private val repository: DomainBlocksRepository ) : RemoteMediator() { + override suspend fun load( loadType: LoadType, state: PagingState @@ -31,10 +32,10 @@ class DomainBlocksRemoteMediator( private suspend fun request(loadType: LoadType): Response>? { return when (loadType) { LoadType.PREPEND -> null - LoadType.APPEND -> api.domainBlocks(maxId = viewModel.nextKey) + LoadType.APPEND -> api.domainBlocks(maxId = repository.nextKey) LoadType.REFRESH -> { - viewModel.nextKey = null - viewModel.domains.clear() + repository.nextKey = null + repository.domains.clear() api.domainBlocks() } } @@ -47,10 +48,10 @@ class DomainBlocksRemoteMediator( } val links = HttpHeaderLink.parse(response.headers()["Link"]) - viewModel.nextKey = HttpHeaderLink.findByRelationType(links, "next")?.uri?.getQueryParameter("max_id") - viewModel.domains.addAll(tags) - viewModel.currentSource?.invalidate() + repository.nextKey = HttpHeaderLink.findByRelationType(links, "next")?.uri?.getQueryParameter("max_id") + repository.domains.addAll(tags) + repository.invalidate() - return MediatorResult.Success(endOfPaginationReached = viewModel.nextKey == null) + return MediatorResult.Success(endOfPaginationReached = repository.nextKey == null) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRepository.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRepository.kt new file mode 100644 index 000000000..bdc9b9367 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksRepository.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2023 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . + */ + +package com.keylesspalace.tusky.components.domainblocks + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.InvalidatingPagingSourceFactory +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingSource +import at.connyduck.calladapter.networkresult.NetworkResult +import at.connyduck.calladapter.networkresult.onSuccess +import com.keylesspalace.tusky.network.MastodonApi +import javax.inject.Inject + +class DomainBlocksRepository @Inject constructor( + private val api: MastodonApi +) { + val domains: MutableList = mutableListOf() + var nextKey: String? = null + + private var factory = InvalidatingPagingSourceFactory { + DomainBlocksPagingSource(domains.toList(), nextKey) + } + + @OptIn(ExperimentalPagingApi::class) + val domainPager = Pager( + config = PagingConfig(pageSize = PAGE_SIZE, initialLoadSize = PAGE_SIZE), + remoteMediator = DomainBlocksRemoteMediator(api, this), + pagingSourceFactory = factory + ).flow + + /** Invalidate the active paging source, see [PagingSource.invalidate] */ + fun invalidate() { + factory.invalidate() + } + + suspend fun block(domain: String): NetworkResult { + return api.blockDomain(domain).onSuccess { + domains.add(domain) + factory.invalidate() + } + } + + suspend fun unblock(domain: String): NetworkResult { + return api.unblockDomain(domain).onSuccess { + domains.remove(domain) + factory.invalidate() + } + } + + companion object { + private const val PAGE_SIZE = 20 + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt index cf0d42578..6458977f0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksViewModel.kt @@ -4,44 +4,25 @@ import android.view.View import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.paging.ExperimentalPagingApi -import androidx.paging.Pager -import androidx.paging.PagingConfig import androidx.paging.cachedIn import at.connyduck.calladapter.networkresult.fold +import at.connyduck.calladapter.networkresult.onFailure import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.network.MastodonApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import javax.inject.Inject class DomainBlocksViewModel @Inject constructor( - private val api: MastodonApi + private val repo: DomainBlocksRepository ) : ViewModel() { - val domains: MutableList = mutableListOf() - val uiEvents = MutableSharedFlow() - var nextKey: String? = null - var currentSource: DomainBlocksPagingSource? = null - @OptIn(ExperimentalPagingApi::class) - val pager = Pager( - config = PagingConfig(pageSize = 20), - remoteMediator = DomainBlocksRemoteMediator(api, this), - pagingSourceFactory = { - DomainBlocksPagingSource( - viewModel = this - ).also { source -> - currentSource = source - } - } - ).flow.cachedIn(viewModelScope) + val domainPager = repo.domainPager.cachedIn(viewModelScope) + + val uiEvents = MutableSharedFlow() fun block(domain: String) { viewModelScope.launch { - api.blockDomain(domain).fold({ - domains.add(domain) - currentSource?.invalidate() - }, { e -> + repo.block(domain).onFailure { e -> uiEvents.emit( SnackbarEvent( message = R.string.error_blocking_domain, @@ -51,15 +32,13 @@ class DomainBlocksViewModel @Inject constructor( action = { block(domain) } ) ) - }) + } } } fun unblock(domain: String) { viewModelScope.launch { - api.unblockDomain(domain).fold({ - domains.remove(domain) - currentSource?.invalidate() + repo.unblock(domain).fold({ uiEvents.emit( SnackbarEvent( message = R.string.confirmation_domain_unmuted, diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt index 773c67662..6574e5cc2 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt @@ -21,6 +21,7 @@ import android.content.SharedPreferences import android.os.Looper import androidx.test.ext.junit.runners.AndroidJUnit4 import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.components.domainblocks.NotificationsRepository import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.settings.PrefKeys From c84815525fde3b6b9af884a2d0d8925af10a56ba Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 20:11:12 +0200 Subject: [PATCH 008/254] ui improvements --- .../tusky/components/domainblocks/DomainBlocksFragment.kt | 1 + app/src/main/res/layout/fragment_domain_blocks.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt index 4d689b3e4..896e81ead 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/domainblocks/DomainBlocksFragment.kt @@ -81,6 +81,7 @@ class DomainBlocksFragment : Fragment(R.layout.fragment_domain_blocks), Injectab } Snackbar.make(binding.recyclerView, message, Snackbar.LENGTH_LONG) + .setTextMaxLines(5) .setAction(event.actionText, event.action) .show() } diff --git a/app/src/main/res/layout/fragment_domain_blocks.xml b/app/src/main/res/layout/fragment_domain_blocks.xml index f82573e24..65fdf1d3e 100644 --- a/app/src/main/res/layout/fragment_domain_blocks.xml +++ b/app/src/main/res/layout/fragment_domain_blocks.xml @@ -17,7 +17,7 @@ Date: Wed, 5 Jul 2023 20:21:32 +0200 Subject: [PATCH 009/254] update lint-baseline.xml --- app/lint-baseline.xml | 274 +++++++++++++++--------------------------- 1 file changed, 95 insertions(+), 179 deletions(-) diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index bf4a0466a..76e26c38f 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -817,7 +817,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -828,7 +828,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -839,7 +839,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -850,7 +850,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -861,7 +861,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2031,17 +2031,6 @@ column="5"/> - - - - - - - - - - - - - - - - - - - - - - - - @@ -2390,7 +2324,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2401,7 +2335,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -2412,7 +2346,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2423,7 +2357,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2434,7 +2368,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2445,7 +2379,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2456,7 +2390,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2467,7 +2401,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2478,7 +2412,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2489,7 +2423,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2500,7 +2434,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2511,7 +2445,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2522,7 +2456,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -2533,7 +2467,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2544,7 +2478,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -2555,7 +2489,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -2566,7 +2500,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -2577,7 +2511,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2588,7 +2522,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2599,7 +2533,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -2610,7 +2544,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2621,7 +2555,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -2632,7 +2566,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2643,7 +2577,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2654,7 +2588,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -2665,7 +2599,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2676,7 +2610,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2687,7 +2621,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2698,7 +2632,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -2709,7 +2643,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2720,7 +2654,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2731,7 +2665,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2742,7 +2676,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2962,7 +2896,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2973,7 +2907,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2984,7 +2918,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> @@ -2995,7 +2929,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3006,7 +2940,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3017,7 +2951,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3028,7 +2962,7 @@ errorLine2=" ~~~~~~"> @@ -3039,7 +2973,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> @@ -3050,7 +2984,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -3061,7 +2995,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3072,7 +3006,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3083,7 +3017,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3094,7 +3028,7 @@ errorLine2=" ~~~~~~"> @@ -3204,7 +3138,7 @@ errorLine2=" ~~~~~~~"> @@ -3215,7 +3149,7 @@ errorLine2=" ~~~~~~~"> @@ -3270,7 +3204,7 @@ errorLine2=" ~~~~~~~"> @@ -3281,7 +3215,7 @@ errorLine2=" ~~~~~~~"> @@ -3292,7 +3226,7 @@ errorLine2=" ~~~~~~~"> @@ -3303,7 +3237,7 @@ errorLine2=" ~~~~~~~"> @@ -3314,18 +3248,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - @@ -3589,7 +3512,7 @@ errorLine2=" ~~~~~~~~~"> @@ -3600,7 +3523,7 @@ errorLine2=" ~~~~~~~~~"> @@ -4491,7 +4414,7 @@ errorLine2=" ~~~~~~~~~"> @@ -4502,7 +4425,7 @@ errorLine2=" ~~~~~~~~"> @@ -4513,7 +4436,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -4524,7 +4447,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -4535,7 +4458,7 @@ errorLine2=" ~~~~~~~"> @@ -4546,7 +4469,7 @@ errorLine2=" ~~~~~~~"> @@ -4557,7 +4480,7 @@ errorLine2=" ~~~~~~~"> @@ -4568,7 +4491,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -5096,17 +5019,10 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - + + + + @@ -5631,18 +5558,7 @@ errorLine2=" ~~~~~~~~"> - - - - @@ -6137,7 +6053,7 @@ errorLine2=" ~~~~~~~~"> @@ -6445,7 +6361,7 @@ errorLine2=" ~~~~~~~~~"> @@ -6500,7 +6416,7 @@ errorLine2=" ~~~~~~~~~"> @@ -6819,7 +6735,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -6830,7 +6746,7 @@ errorLine2=" ~~~~~~"> @@ -6841,7 +6757,7 @@ errorLine2=" ~~~~~~~~"> @@ -6852,7 +6768,7 @@ errorLine2=" ~~~~"> @@ -6863,7 +6779,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -6874,7 +6790,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -6885,7 +6801,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -6896,7 +6812,7 @@ errorLine2=" ~~~~~~~~"> @@ -6907,7 +6823,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> From f919252ede9ae1d60db2b2a292233b44f97fca24 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Wed, 5 Jul 2023 20:43:24 +0200 Subject: [PATCH 010/254] remove import wrongly added by auto refactoring --- .../components/notifications/NotificationsViewModelTestBase.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt index 6574e5cc2..773c67662 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt @@ -21,7 +21,6 @@ import android.content.SharedPreferences import android.os.Looper import androidx.test.ext.junit.runners.AndroidJUnit4 import com.keylesspalace.tusky.appstore.EventHub -import com.keylesspalace.tusky.components.domainblocks.NotificationsRepository import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.settings.PrefKeys From 9824694b86884b9ea1fce71d66247c10425b985a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Mon, 24 Jul 2023 13:35:54 +0000 Subject: [PATCH 011/254] Translated using Weblate (Icelandic) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (609 of 609 strings) Co-authored-by: Sveinn í Felli Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/is/ Translation: Tusky/Tusky --- app/src/main/res/values-is/strings.xml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 0e9aecb23..0412db2ef 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -13,7 +13,7 @@ Listar Listar Villa kom upp. - Villa í netkerfi: Athugaðu nettenginguna þína og prófaðu svo aftur! + Villa kom upp í netkerfi. Athugaðu nettenginguna þína og prófaðu svo aftur. Þetta má ekki vera tómt. Ógilt lén sett inn Mistókst að auðkenna gagnvart þessu tilviki. Ef þetta er viðvarandi skaltu prófa \'Skrá inn í vafra\' úr valmyndinni. @@ -74,7 +74,7 @@ Fjarlægja eftirlæti Meira Semja skilaboð - Ertu viss um að þú viljir skrá þig út af notandaaðgangnum %1$s\? + Ertu viss um að þú viljir skrá þig út af notandaaðgangnum %1$s\? Þetta mun eyða öllum staðværum gögnum af aðgangnum, þar með talið drögum og kjörstillingum. Fylgja Hætta að fylgjast með Útiloka @@ -657,4 +657,14 @@ Þetta er tímalínan þín. Hún sýnir nýlegar færslur þeirra sem þú fylgist með. \n \nTil að skoða hvað aðrir eru að gera getur þú til dæmis uppgötvað viðkomandi í einni af hinum tímalínunum. Til dæmis á staðværu tímalínu netþjónsins þíns [iconics gmd_group]. Eða að þú leitar að þeim eftir nafni [iconics gmd_search]; til dæmis geturðu leitað að Tusky til að finna Mastodon-aðganginn okkar. + Textastærð viðmóts + Bakgrunnsvirkni + Tilkynningar þegar Tusky er að vinna í bakgrunni + Sæki tilkynningar… + Viðhaldsvinna biðminnis… + Hlaða inn nýjustu tilkynningum + Eyða drögum\? + Þjónninn þinn veit að þessari færslu hefur verið breytt, en er hins vegar ekki með afrit af breytingunum, þannig að ekki er hægt að sýna þér þær. +\n +\nÞetta er Mastodon verkbeiðni #25398. \ No newline at end of file From 0b01efed5eac3b71b669810fc410bdbb61cedbb3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:52:28 +0200 Subject: [PATCH 012/254] Update coroutines to v1.7.3 (#3879) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [org.jetbrains.kotlinx:kotlinx-coroutines-test](https://togithub.com/Kotlin/kotlinx.coroutines) | `1.7.2` -> `1.7.3` | [![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlinx:kotlinx-coroutines-test/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.kotlinx:kotlinx-coroutines-test/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.kotlinx:kotlinx-coroutines-test/1.7.2/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlinx:kotlinx-coroutines-test/1.7.2/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [org.jetbrains.kotlinx:kotlinx-coroutines-rx3](https://togithub.com/Kotlin/kotlinx.coroutines) | `1.7.2` -> `1.7.3` | [![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlinx:kotlinx-coroutines-rx3/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.kotlinx:kotlinx-coroutines-rx3/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.kotlinx:kotlinx-coroutines-rx3/1.7.2/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlinx:kotlinx-coroutines-rx3/1.7.2/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [org.jetbrains.kotlinx:kotlinx-coroutines-android](https://togithub.com/Kotlin/kotlinx.coroutines) | `1.7.2` -> `1.7.3` | [![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlinx:kotlinx-coroutines-android/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.kotlinx:kotlinx-coroutines-android/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.kotlinx:kotlinx-coroutines-android/1.7.2/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlinx:kotlinx-coroutines-android/1.7.2/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
Kotlin/kotlinx.coroutines (org.jetbrains.kotlinx:kotlinx-coroutines-test) ### [`v1.7.3`](https://togithub.com/Kotlin/kotlinx.coroutines/blob/HEAD/CHANGES.md#Version-173) [Compare Source](https://togithub.com/Kotlin/kotlinx.coroutines/compare/1.7.2...1.7.3) - Disabled the publication of the multiplatform library metadata for the old (1.6 and earlier) KMP Gradle plugin ([#​3809](https://togithub.com/Kotlin/kotlinx.coroutines/issues/3809)). - Fixed a bug introduced in 1.7.2 that disabled the coroutine debugger in IDEA ([#​3822](https://togithub.com/Kotlin/kotlinx.coroutines/issues/3822)).
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/tuskyapp/Tusky). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d52f28c96..94cf958c2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ androidx-room = "2.5.1" autodispose = "2.1.1" bouncycastle = "1.70" conscrypt = "2.5.2" -coroutines = "1.7.2" +coroutines = "1.7.3" dagger = "2.47" diffx = "1.1.1" emoji2 = "1.3.0" From 406152d5b92b52662d15cdb9701e22a90bc76efc Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Wed, 26 Jul 2023 17:00:08 +0200 Subject: [PATCH 013/254] Clear listeners, hide icons when appropriate (#3870) Preferences are shown using view holders. The previous code did not clear the listeners or hide the icons if necessary. The practical upshot of this was that if you had two or more slider preferences, *and* they were situated more than a screen's height apart, the viewholder from the first one would get reused. And if the first one enabled icons then the second one would show them. And clicking on the second one would also call the listeners for the first one. --- .../keylesspalace/tusky/view/SliderPreference.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/view/SliderPreference.kt b/app/src/main/java/com/keylesspalace/tusky/view/SliderPreference.kt index 742a2cd76..12b0b990e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/SliderPreference.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/SliderPreference.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.res.TypedArray import android.graphics.drawable.Drawable import android.util.AttributeSet -import android.view.View.VISIBLE import androidx.appcompat.content.res.AppCompatResources import androidx.preference.Preference import androidx.preference.PreferenceViewHolder @@ -12,6 +11,8 @@ import com.google.android.material.slider.LabelFormatter.LABEL_GONE import com.google.android.material.slider.Slider import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.PrefSliderBinding +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show import java.lang.Float.max import java.lang.Float.min @@ -130,6 +131,8 @@ class SliderPreference @JvmOverloads constructor( binding.root.isClickable = false + binding.slider.clearOnChangeListeners() + binding.slider.clearOnSliderTouchListeners() binding.slider.addOnChangeListener(this) binding.slider.addOnSliderTouchListener(this) binding.slider.value = value // sliderValue @@ -141,24 +144,24 @@ class SliderPreference @JvmOverloads constructor( binding.slider.labelBehavior = LABEL_GONE binding.slider.isEnabled = isEnabled - binding.summary.visibility = VISIBLE + binding.summary.show() binding.summary.text = formatter(value) decrementIcon?.let { icon -> binding.decrement.icon = icon - binding.decrement.visibility = VISIBLE + binding.decrement.show() binding.decrement.setOnClickListener { value -= stepSize } - } + } ?: binding.decrement.hide() incrementIcon?.let { icon -> binding.increment.icon = icon - binding.increment.visibility = VISIBLE + binding.increment.show() binding.increment.setOnClickListener { value += stepSize } - } + } ?: binding.increment.hide() } override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) { From e35cc2dcef268ecf0446ddd7757ba73da16dbc5e Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Wed, 26 Jul 2023 22:54:30 +0200 Subject: [PATCH 014/254] Enable gradle build cache for Bitrise builds (#3840) Read/write to the cache for nightly / release builds, read from the cache for other builds. --- bitrise.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bitrise.yml b/bitrise.yml index a0e36a41b..1a01709ee 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -15,6 +15,9 @@ workflows: run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - git-clone@8.0: {} - cache-pull@2.7: {} + - activate-build-cache-for-gradle: + inputs: + - push: 'true' - install-missing-android-tools: inputs: - gradlew_path: $PROJECT_LOCATION/gradlew @@ -51,6 +54,7 @@ workflows: run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - git-clone: {} - cache-pull@2.7: {} + - activate-build-cache-for-gradle: {} - install-missing-android-tools: inputs: - gradlew_path: $PROJECT_LOCATION/gradlew @@ -87,6 +91,9 @@ workflows: run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - git-clone: {} - cache-pull@2.7: {} + - activate-build-cache-for-gradle: + inputs: + - push: 'true' - install-missing-android-tools@3.1: inputs: - gradlew_path: $PROJECT_LOCATION/gradlew From 0694764b339fcd0a08afbff9b677a823b978612e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Jul 2023 22:55:31 +0200 Subject: [PATCH 015/254] Update dependency androidx.fragment:fragment-ktx to v1.6.1 (#3884) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [androidx.fragment:fragment-ktx](https://developer.android.com/jetpack/androidx/releases/fragment#1.6.1) ([source](https://cs.android.com/androidx/platform/frameworks/support)) | `1.6.0` -> `1.6.1` | [![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.fragment:fragment-ktx/1.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.fragment:fragment-ktx/1.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.fragment:fragment-ktx/1.6.0/1.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.fragment:fragment-ktx/1.6.0/1.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/tuskyapp/Tusky). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94cf958c2..eac981804 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ androidx-cardview = "1.0.0" androidx-constraintlayout = "2.1.4" androidx-core = "1.10.1" androidx-exifinterface = "1.3.6" -androidx-fragment = "1.6.0" +androidx-fragment = "1.6.1" androidx-junit = "1.1.5" androidx-lifecycle = "2.6.1" androidx-paging = "3.1.1" From 8d612375e5d4493b69f15c8ecf926129d3540902 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Jul 2023 23:30:41 +0200 Subject: [PATCH 016/254] Update dependency androidx.paging:paging-runtime-ktx to v3.2.0 (#3887) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [androidx.paging:paging-runtime-ktx](https://developer.android.com/jetpack/androidx/releases/paging#3.2.0) ([source](https://cs.android.com/androidx/platform/frameworks/support)) | `3.1.1` -> `3.2.0` | [![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.paging:paging-runtime-ktx/3.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.paging:paging-runtime-ktx/3.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.paging:paging-runtime-ktx/3.1.1/3.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.paging:paging-runtime-ktx/3.1.1/3.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/tuskyapp/Tusky). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eac981804..c06b34834 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ androidx-exifinterface = "1.3.6" androidx-fragment = "1.6.1" androidx-junit = "1.1.5" androidx-lifecycle = "2.6.1" -androidx-paging = "3.1.1" +androidx-paging = "3.2.0" androidx-preference = "1.2.0" androidx-recyclerview = "1.3.0" androidx-sharetarget = "1.2.0" From e852aa64a5962701dad8ab10c3c4150716465be4 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Wed, 26 Jul 2023 23:36:29 +0200 Subject: [PATCH 017/254] Show the throwable error message when an upload fails (#3838) --- .../tusky/components/compose/ComposeActivity.kt | 7 ++++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index c3476821d..f26279712 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -485,7 +485,12 @@ class ComposeActivity : if (throwable is UploadServerError) { displayTransientMessage(throwable.errorMessage) } else { - displayTransientMessage(R.string.error_media_upload_sending) + displayTransientMessage( + getString( + R.string.error_media_upload_sending_fmt, + throwable.message + ) + ) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 18baffcd4..ac381127d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,6 +38,7 @@ Permission to store media is required. Images and videos cannot both be attached to the same post. The upload failed. + The upload failed: %s Error sending post. Error following #%s Error unfollowing #%s From 1bdf19407ce58b667f07f04122fe4cdd26afe68b Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 27 Jul 2023 06:06:26 +0800 Subject: [PATCH 018/254] Use org.gradle.configuration-cache flag (#3792) Follow up #3528. https://docs.gradle.org/8.1/userguide/upgrading_version_8.html#configuration_caching_options_renamed --- gradle.properties | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index f7cd3016d..45c7b7e09 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,8 +3,7 @@ org.gradle.caching=true org.gradle.jvmargs=-XX:+UseParallelGC -Xmx4g -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError -Xms256m # use parallel execution org.gradle.parallel=true -# https://docs.gradle.org/7.6/userguide/configuration_cache.html -org.gradle.unsafe.configuration-cache=true +org.gradle.configuration-cache=true # https://blog.jetbrains.com/kotlin/2022/07/a-new-approach-to-incremental-compilation-in-kotlin/ kotlin.incremental.useClasspathSnapshot=true From ee711598c74954e81d867fd5d50c8c5112de3991 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 27 Jul 2023 00:09:26 +0200 Subject: [PATCH 019/254] Cache locked state of connected accounts (#3790) Small improvement to the behavior on bad/disconnected networks Fixes https://github.com/tuskyapp/Tusky/issues/3773 --- .../52.json | 1009 +++++++++++++++++ .../com/keylesspalace/tusky/MainActivity.kt | 8 +- .../accountlist/AccountListActivity.kt | 7 +- .../accountlist/AccountListFragment.kt | 10 +- .../keylesspalace/tusky/db/AccountEntity.kt | 6 +- .../keylesspalace/tusky/db/AccountManager.kt | 1 + .../keylesspalace/tusky/db/AppDatabase.java | 5 +- 7 files changed, 1027 insertions(+), 19 deletions(-) create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/52.json diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/52.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/52.json new file mode 100644 index 000000000..18290c93e --- /dev/null +++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/52.json @@ -0,0 +1,1009 @@ +{ + "formatVersion": 1, + "database": { + "version": 52, + "identityHash": "233a8680f540e9a89950da21532ce85d", + "entities": [ + { + "tableName": "DraftEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountId` INTEGER NOT NULL, `inReplyToId` TEXT, `content` TEXT, `contentWarning` TEXT, `sensitive` INTEGER NOT NULL, `visibility` INTEGER NOT NULL, `attachments` TEXT NOT NULL, `poll` TEXT, `failedToSend` INTEGER NOT NULL, `failedToSendNew` INTEGER NOT NULL, `scheduledAt` TEXT, `language` TEXT, `statusId` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentWarning", + "columnName": "contentWarning", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sensitive", + "columnName": "sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "failedToSend", + "columnName": "failedToSend", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "failedToSendNew", + "columnName": "failedToSendNew", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scheduledAt", + "columnName": "scheduledAt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "statusId", + "columnName": "statusId", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `domain` TEXT NOT NULL, `accessToken` TEXT NOT NULL, `clientId` TEXT, `clientSecret` TEXT, `isActive` INTEGER NOT NULL, `accountId` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `profilePictureUrl` TEXT NOT NULL, `notificationsEnabled` INTEGER NOT NULL, `notificationsMentioned` INTEGER NOT NULL, `notificationsFollowed` INTEGER NOT NULL, `notificationsFollowRequested` INTEGER NOT NULL, `notificationsReblogged` INTEGER NOT NULL, `notificationsFavorited` INTEGER NOT NULL, `notificationsPolls` INTEGER NOT NULL, `notificationsSubscriptions` INTEGER NOT NULL, `notificationsSignUps` INTEGER NOT NULL, `notificationsUpdates` INTEGER NOT NULL, `notificationsReports` INTEGER NOT NULL, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `defaultPostLanguage` TEXT NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `notificationMarkerId` TEXT NOT NULL DEFAULT '0', `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL, `oauthScopes` TEXT NOT NULL, `unifiedPushUrl` TEXT NOT NULL, `pushPubKey` TEXT NOT NULL, `pushPrivKey` TEXT NOT NULL, `pushAuth` TEXT NOT NULL, `pushServerKey` TEXT NOT NULL, `lastVisibleHomeTimelineStatusId` TEXT, `locked` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "domain", + "columnName": "domain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accessToken", + "columnName": "accessToken", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "clientId", + "columnName": "clientId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "clientSecret", + "columnName": "clientSecret", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isActive", + "columnName": "isActive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profilePictureUrl", + "columnName": "profilePictureUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationsEnabled", + "columnName": "notificationsEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsMentioned", + "columnName": "notificationsMentioned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFollowed", + "columnName": "notificationsFollowed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFollowRequested", + "columnName": "notificationsFollowRequested", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsReblogged", + "columnName": "notificationsReblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFavorited", + "columnName": "notificationsFavorited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsPolls", + "columnName": "notificationsPolls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsSubscriptions", + "columnName": "notificationsSubscriptions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsSignUps", + "columnName": "notificationsSignUps", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsUpdates", + "columnName": "notificationsUpdates", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsReports", + "columnName": "notificationsReports", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationSound", + "columnName": "notificationSound", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationVibration", + "columnName": "notificationVibration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationLight", + "columnName": "notificationLight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultPostPrivacy", + "columnName": "defaultPostPrivacy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultMediaSensitivity", + "columnName": "defaultMediaSensitivity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultPostLanguage", + "columnName": "defaultPostLanguage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "alwaysShowSensitiveMedia", + "columnName": "alwaysShowSensitiveMedia", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "alwaysOpenSpoiler", + "columnName": "alwaysOpenSpoiler", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mediaPreviewEnabled", + "columnName": "mediaPreviewEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastNotificationId", + "columnName": "lastNotificationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationMarkerId", + "columnName": "notificationMarkerId", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'0'" + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tabPreferences", + "columnName": "tabPreferences", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationsFilter", + "columnName": "notificationsFilter", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "oauthScopes", + "columnName": "oauthScopes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unifiedPushUrl", + "columnName": "unifiedPushUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pushPubKey", + "columnName": "pushPubKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pushPrivKey", + "columnName": "pushPrivKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pushAuth", + "columnName": "pushAuth", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pushServerKey", + "columnName": "pushServerKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastVisibleHomeTimelineStatusId", + "columnName": "lastVisibleHomeTimelineStatusId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "locked", + "columnName": "locked", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_AccountEntity_domain_accountId", + "unique": true, + "columnNames": [ + "domain", + "accountId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_AccountEntity_domain_accountId` ON `${TABLE_NAME}` (`domain`, `accountId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "InstanceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`instance` TEXT NOT NULL, `emojiList` TEXT, `maximumTootCharacters` INTEGER, `maxPollOptions` INTEGER, `maxPollOptionLength` INTEGER, `minPollDuration` INTEGER, `maxPollDuration` INTEGER, `charactersReservedPerUrl` INTEGER, `version` TEXT, `videoSizeLimit` INTEGER, `imageSizeLimit` INTEGER, `imageMatrixLimit` INTEGER, `maxMediaAttachments` INTEGER, `maxFields` INTEGER, `maxFieldNameLength` INTEGER, `maxFieldValueLength` INTEGER, PRIMARY KEY(`instance`))", + "fields": [ + { + "fieldPath": "instance", + "columnName": "instance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojiList", + "columnName": "emojiList", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maximumTootCharacters", + "columnName": "maximumTootCharacters", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptions", + "columnName": "maxPollOptions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptionLength", + "columnName": "maxPollOptionLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "minPollDuration", + "columnName": "minPollDuration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollDuration", + "columnName": "maxPollDuration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "charactersReservedPerUrl", + "columnName": "charactersReservedPerUrl", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "videoSizeLimit", + "columnName": "videoSizeLimit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "imageSizeLimit", + "columnName": "imageSizeLimit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "imageMatrixLimit", + "columnName": "imageMatrixLimit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxMediaAttachments", + "columnName": "maxMediaAttachments", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxFields", + "columnName": "maxFields", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxFieldNameLength", + "columnName": "maxFieldNameLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxFieldValueLength", + "columnName": "maxFieldValueLength", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "instance" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimelineStatusEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `editedAt` INTEGER, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `repliesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT NOT NULL, `visibility` INTEGER NOT NULL, `attachments` TEXT, `mentions` TEXT, `tags` TEXT, `application` TEXT, `reblogServerId` TEXT, `reblogAccountId` TEXT, `poll` TEXT, `muted` INTEGER, `expanded` INTEGER NOT NULL, `contentCollapsed` INTEGER NOT NULL, `contentShowing` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `card` TEXT, `language` TEXT, `filtered` TEXT, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authorServerId", + "columnName": "authorServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToAccountId", + "columnName": "inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "editedAt", + "columnName": "editedAt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogsCount", + "columnName": "reblogsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favouritesCount", + "columnName": "favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repliesCount", + "columnName": "repliesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reblogged", + "columnName": "reblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookmarked", + "columnName": "bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favourited", + "columnName": "favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sensitive", + "columnName": "sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "spoilerText", + "columnName": "spoilerText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "application", + "columnName": "application", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogServerId", + "columnName": "reblogServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogAccountId", + "columnName": "reblogAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "muted", + "columnName": "muted", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "expanded", + "columnName": "expanded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contentCollapsed", + "columnName": "contentCollapsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contentShowing", + "columnName": "contentShowing", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pinned", + "columnName": "pinned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "card", + "columnName": "card", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "filtered", + "columnName": "filtered", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "serverId", + "timelineUserId" + ] + }, + "indices": [ + { + "name": "index_TimelineStatusEntity_authorServerId_timelineUserId", + "unique": false, + "columnNames": [ + "authorServerId", + "timelineUserId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TimelineStatusEntity_authorServerId_timelineUserId` ON `${TABLE_NAME}` (`authorServerId`, `timelineUserId`)" + } + ], + "foreignKeys": [ + { + "table": "TimelineAccountEntity", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "authorServerId", + "timelineUserId" + ], + "referencedColumns": [ + "serverId", + "timelineUserId" + ] + } + ] + }, + { + "tableName": "TimelineAccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `timelineUserId` INTEGER NOT NULL, `localUsername` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `url` TEXT NOT NULL, `avatar` TEXT NOT NULL, `emojis` TEXT NOT NULL, `bot` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `timelineUserId`))", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localUsername", + "columnName": "localUsername", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bot", + "columnName": "bot", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "serverId", + "timelineUserId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ConversationEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `id` TEXT NOT NULL, `order` INTEGER NOT NULL, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_editedAt` INTEGER, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_repliesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_tags` TEXT, `s_showingHiddenContent` INTEGER NOT NULL, `s_expanded` INTEGER NOT NULL, `s_collapsed` INTEGER NOT NULL, `s_muted` INTEGER NOT NULL, `s_poll` TEXT, `s_language` TEXT, PRIMARY KEY(`id`, `accountId`))", + "fields": [ + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accounts", + "columnName": "accounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.id", + "columnName": "s_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.url", + "columnName": "s_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToId", + "columnName": "s_inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToAccountId", + "columnName": "s_inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.account", + "columnName": "s_account", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.content", + "columnName": "s_content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.createdAt", + "columnName": "s_createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.editedAt", + "columnName": "s_editedAt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastStatus.emojis", + "columnName": "s_emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.favouritesCount", + "columnName": "s_favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.repliesCount", + "columnName": "s_repliesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.favourited", + "columnName": "s_favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.bookmarked", + "columnName": "s_bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.sensitive", + "columnName": "s_sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.spoilerText", + "columnName": "s_spoilerText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.attachments", + "columnName": "s_attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.mentions", + "columnName": "s_mentions", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.tags", + "columnName": "s_tags", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.showingHiddenContent", + "columnName": "s_showingHiddenContent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.expanded", + "columnName": "s_expanded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsed", + "columnName": "s_collapsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.muted", + "columnName": "s_muted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.poll", + "columnName": "s_poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.language", + "columnName": "s_language", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "accountId" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '233a8680f540e9a89950da21532ce85d')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index c70e83680..2c6cd1a15 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -167,8 +167,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje private lateinit var glide: RequestManager - private var accountLocked: Boolean = false - // We need to know if the emoji pack has been changed private var selectedEmojiPack: String? = null @@ -236,7 +234,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje // user clicked a notification, show follow requests for type FOLLOW_REQUEST, // otherwise show notification tab if (intent.getStringExtra(NotificationHelper.TYPE) == Notification.Type.FOLLOW_REQUEST.name) { - val intent = AccountListActivity.newIntent(this, AccountListActivity.Type.FOLLOW_REQUESTS, accountLocked = true) + val intent = AccountListActivity.newIntent(this, AccountListActivity.Type.FOLLOW_REQUESTS) startActivityWithSlideInAnimation(intent) } else { showNotificationTab = true @@ -538,7 +536,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje nameRes = R.string.action_view_follow_requests iconicsIcon = GoogleMaterial.Icon.gmd_person_add onClick = { - val intent = AccountListActivity.newIntent(context, AccountListActivity.Type.FOLLOW_REQUESTS, accountLocked = accountLocked) + val intent = AccountListActivity.newIntent(context, AccountListActivity.Type.FOLLOW_REQUESTS) startActivityWithSlideInAnimation(intent) } }, @@ -872,8 +870,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje disableAllNotifications(this, accountManager) } - accountLocked = me.locked - updateProfiles() updateShortcut(this, accountManager.activeAccount!!) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListActivity.kt index f5981c0bc..2419dc915 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListActivity.kt @@ -48,7 +48,6 @@ class AccountListActivity : BottomSheetActivity(), HasAndroidInjector { val type = intent.getSerializableExtra(EXTRA_TYPE) as Type val id: String? = intent.getStringExtra(EXTRA_ID) - val accountLocked: Boolean = intent.getBooleanExtra(EXTRA_ACCOUNT_LOCKED, false) setSupportActionBar(binding.includedToolbar.toolbar) supportActionBar?.apply { @@ -66,7 +65,7 @@ class AccountListActivity : BottomSheetActivity(), HasAndroidInjector { } supportFragmentManager.commit { - replace(R.id.fragment_container, AccountListFragment.newInstance(type, id, accountLocked)) + replace(R.id.fragment_container, AccountListFragment.newInstance(type, id)) } } @@ -75,13 +74,11 @@ class AccountListActivity : BottomSheetActivity(), HasAndroidInjector { companion object { private const val EXTRA_TYPE = "type" private const val EXTRA_ID = "id" - private const val EXTRA_ACCOUNT_LOCKED = "acc_locked" - fun newIntent(context: Context, type: Type, id: String? = null, accountLocked: Boolean = false): Intent { + fun newIntent(context: Context, type: Type, id: String? = null): Intent { return Intent(context, AccountListActivity::class.java).apply { putExtra(EXTRA_TYPE, type) putExtra(EXTRA_ID, id) - putExtra(EXTRA_ACCOUNT_LOCKED, accountLocked) } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt index 68eba3c76..30d75402d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/accountlist/AccountListFragment.kt @@ -107,13 +107,15 @@ class AccountListFragment : val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) val showBotOverlay = pm.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true) + val activeAccount = accountManager.activeAccount!! + adapter = when (type) { Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis, showBotOverlay) Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis, showBotOverlay) Type.FOLLOW_REQUESTS -> { val headerAdapter = FollowRequestsHeaderAdapter( - instanceName = accountManager.activeAccount!!.domain, - accountLocked = arguments?.getBoolean(ARG_ACCOUNT_LOCKED) == true + instanceName = activeAccount.domain, + accountLocked = activeAccount.locked ) val followRequestsAdapter = FollowRequestsAdapter(this, this, animateAvatar, animateEmojis, showBotOverlay) binding.recyclerView.adapter = ConcatAdapter(headerAdapter, followRequestsAdapter) @@ -404,14 +406,12 @@ class AccountListFragment : private const val TAG = "AccountList" // logging tag private const val ARG_TYPE = "type" private const val ARG_ID = "id" - private const val ARG_ACCOUNT_LOCKED = "acc_locked" - fun newInstance(type: Type, id: String? = null, accountLocked: Boolean = false): AccountListFragment { + fun newInstance(type: Type, id: String? = null): AccountListFragment { return AccountListFragment().apply { arguments = Bundle(3).apply { putSerializable(ARG_TYPE, type) putString(ARG_ID, id) - putBoolean(ARG_ACCOUNT_LOCKED, accountLocked) } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt index cdde765f7..853003e75 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt @@ -100,7 +100,11 @@ data class AccountEntity( * ID of the status at the top of the visible list in the home timeline when the * user navigated away. */ - var lastVisibleHomeTimelineStatusId: String? = null + var lastVisibleHomeTimelineStatusId: String? = null, + + /** true if the connected Mastodon account is locked (has to manually approve all follow requests **/ + @ColumnInfo(defaultValue = "0") + var locked: Boolean = false ) { val identifier: String diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt index 61ce076a5..9c7999fd9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt @@ -156,6 +156,7 @@ class AccountManager @Inject constructor(db: AppDatabase) { it.defaultPostLanguage = account.source?.language.orEmpty() it.defaultMediaSensitivity = account.source?.sensitive ?: false it.emojis = account.emojis.orEmpty() + it.locked = account.locked Log.d(TAG, "updateActiveAccount: saving account with id " + it.id) accountDao.insertOrReplace(it) diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java index a51273545..86ce13cb1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java @@ -42,11 +42,12 @@ import java.io.File; TimelineAccountEntity.class, ConversationEntity.class }, - version = 51, + version = 52, autoMigrations = { @AutoMigration(from = 48, to = 49), @AutoMigration(from = 49, to = 50, spec = AppDatabase.MIGRATION_49_50.class), - @AutoMigration(from = 50, to = 51) + @AutoMigration(from = 50, to = 51), + @AutoMigration(from = 51, to = 52) } ) public abstract class AppDatabase extends RoomDatabase { From 500271d4b380b276d40ba113ec4035363a730b85 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Wed, 26 Jul 2023 21:17:52 +0000 Subject: [PATCH 020/254] Translated using Weblate (Ukrainian) Currently translated at 100.0% (609 of 609 strings) Co-authored-by: Ihor Hordiichuk Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/uk/ Translation: Tusky/Tusky --- app/src/main/res/values-uk/strings.xml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 744dc67b8..5621c6ad6 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -24,7 +24,7 @@ Допис задовгий! Не вдалося знайти браузер, який можна використати. Не може бути порожнім. - Сталася помилка мережі! Перевірте інтернет-з\'єднання та спробуйте знову! + Сталася помилка мережі. Перевірте інтернет-з\'єднання та спробуйте знову. Списки Списки Про застосунок @@ -85,7 +85,7 @@ Заблокувати Відписатися Підписатися - Ви впевнені, що хочете вийти з облікового запису %1$s\? + Ви впевнені, що хочете вийти %1$s\? Це призведе до видалення всіх локальних даних облікового запису, включно з чернетками та вподобаннями. Написати Не подобається Додати в закладки @@ -681,4 +681,14 @@ Помилка завантаження списків У вас ще немає списків Керувати списками - + Розмір шрифту інтерфейсу + Фонова діяльність + Сповіщення, коли Tusky працює у фоновому режимі + Отримання сповіщень… + Обслуговування кешу… + Завантажити найновіші сповіщення + Видалити чернетку\? + Ваш сервер знає, що цей допис було змінено, але не має копії редагувань, тому вони не можуть бути вам показані. +\n +\nЦе помилка #25398 у Mastodon. + \ No newline at end of file From 97713b06da91b5b1734a09c6ebfdf3960545b7aa Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 27 Jul 2023 13:44:28 +0200 Subject: [PATCH 021/254] Always update the filter display, even when the list is empty (#3880) Fixes #3839, https://github.com/tuskyapp/Tusky/issues/3525 --- .../keylesspalace/tusky/components/filters/FiltersActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt index 9230fdae2..c6d3328f0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/filters/FiltersActivity.kt @@ -72,6 +72,7 @@ class FiltersActivity : BaseActivity(), FiltersListener { binding.messageView.show() } FiltersViewModel.LoadingState.LOADED -> { + binding.filtersList.adapter = FiltersAdapter(this@FiltersActivity, state.filters) if (state.filters.isEmpty()) { binding.messageView.setup( R.drawable.elephant_friend_empty, @@ -81,7 +82,6 @@ class FiltersActivity : BaseActivity(), FiltersListener { binding.messageView.show() } else { binding.messageView.hide() - binding.filtersList.adapter = FiltersAdapter(this@FiltersActivity, state.filters) } } } From 28f31efa21a6ab7d12f13e4ed542051e958f58a1 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 28 Jul 2023 12:35:55 +0000 Subject: [PATCH 022/254] Translated using Weblate (Ukrainian) Currently translated at 100.0% (31 of 31 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/uk/ --- .../metadata/android/uk/changelogs/107.txt | 6 ++++++ .../metadata/android/uk/changelogs/108.txt | 5 +++++ .../metadata/android/uk/changelogs/109.txt | 10 ++++++++++ .../metadata/android/uk/changelogs/110.txt | 20 +++++++++++++++++++ .../metadata/android/uk/changelogs/111.txt | 15 ++++++++++++++ .../metadata/android/uk/changelogs/112.txt | 6 ++++++ .../metadata/android/uk/changelogs/113.txt | 15 ++++++++++++++ 7 files changed, 77 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/107.txt create mode 100644 fastlane/metadata/android/uk/changelogs/108.txt create mode 100644 fastlane/metadata/android/uk/changelogs/109.txt create mode 100644 fastlane/metadata/android/uk/changelogs/110.txt create mode 100644 fastlane/metadata/android/uk/changelogs/111.txt create mode 100644 fastlane/metadata/android/uk/changelogs/112.txt create mode 100644 fastlane/metadata/android/uk/changelogs/113.txt diff --git a/fastlane/metadata/android/uk/changelogs/107.txt b/fastlane/metadata/android/uk/changelogs/107.txt new file mode 100644 index 000000000..37fad826b --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/107.txt @@ -0,0 +1,6 @@ +Tusky 22.0 beta 5 + +Виправлення: + +- Відкотили бібліотеку APNG для виправлення несправних анімованих емодзі +- Збереження локальної копії маркера сповіщень на випадок, якщо сервер не підтримує API diff --git a/fastlane/metadata/android/uk/changelogs/108.txt b/fastlane/metadata/android/uk/changelogs/108.txt new file mode 100644 index 000000000..a2c23e949 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/108.txt @@ -0,0 +1,5 @@ +Tusky 22.0 beta 6 + +Виправлення: + +- Частіше збереження позиції читання на вкладці Сповіщення diff --git a/fastlane/metadata/android/uk/changelogs/109.txt b/fastlane/metadata/android/uk/changelogs/109.txt new file mode 100644 index 000000000..05e00e530 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/109.txt @@ -0,0 +1,10 @@ +Tusky 22.0 beta 7 + +Виправлення: + + +### Значні виправлення помилок + +- Під час створення сповіщень Mastodon для Android виводитиме всі сповіщення Mastodon, що залишилися без відповіді +- Натискання кнопки "Створити" у сповіщенні могло призвести до встановлення неправильного облікового запису +- "ID останнього прочитаного сповіщення" відтепер зберігатиметься в правильному обліковому записі diff --git a/fastlane/metadata/android/uk/changelogs/110.txt b/fastlane/metadata/android/uk/changelogs/110.txt new file mode 100644 index 000000000..b89b7d0ea --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/110.txt @@ -0,0 +1,20 @@ +Tusky 22.0 + +Нові функції: + +- Перегляд популярних хештегів +- Стеження за новими хештегами +- Краще впорядкування вибору мов +- Показ різниці між версіями допису +- Підтримка фільтрів Mastodon v4 +- Можливість показу статистики дописів у стрічці +- Та інше... + +Виправлення: + +- Запам'ятовування вибраної вкладки та позиції +- Збереження сповіщень до прочитання +- Правильний показ змішаного RTL і LTR тексту в профілях +- Правильне обчислення довжини допису +- Підписи зображень публікуються завжди +- Та інше... diff --git a/fastlane/metadata/android/uk/changelogs/111.txt b/fastlane/metadata/android/uk/changelogs/111.txt new file mode 100644 index 000000000..da49c84bb --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/111.txt @@ -0,0 +1,15 @@ +Tusky 23.0 beta 1 + +Нові функції: + +- Новий параметр масштабу тексту + +Виправлення: + +- Правильне збереження інформації про обліковий запис +- Сповіщення "потягнути" на пристроях з Android <= 11 +- Усунено помилку в Android, коли з текстових полів не можна копіювати/вставляти +- Перегляд "розбіжностей" історії змін не виходитиме за межі екрана +- Якщо на вашому сервері немає історії зміни повідомлень не стається збоїв +- Додано кнопку "Видалити" за зміни фільтра +- Правильний показ неквадратних емодзі diff --git a/fastlane/metadata/android/uk/changelogs/112.txt b/fastlane/metadata/android/uk/changelogs/112.txt new file mode 100644 index 000000000..90f2af626 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/112.txt @@ -0,0 +1,6 @@ +Tusky 23.0 beta 2 + +Виправлено: + +- Потенційний збій під час редагування полів профілю +- Завелике контекстне меню під час редагування описів зображень diff --git a/fastlane/metadata/android/uk/changelogs/113.txt b/fastlane/metadata/android/uk/changelogs/113.txt new file mode 100644 index 000000000..f2722e490 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/113.txt @@ -0,0 +1,15 @@ +Tusky 23.0 + +Нове: + +- Новий параметр масштабу тексту + +Виправлення: + +- Правильне збереження інформації про обліковий запис +- Сповіщення "потягнути" на пристроях з Android <= 11 +- Усунено помилку в Android, коли з текстових полів не можна копіювати/вставляти +- Перегляд розбіжностей історії змін не виходитиме за межі екрана +- Якщо на вашому сервері немає історії зміни повідомлень не стається збоїв +- Правильний показ неквадратних емодзі +- Потенційний збій під час редагування полів профілю From 6601ecc020c4696bcb0c629a7ba337ebea8bf121 Mon Sep 17 00:00:00 2001 From: Yannis D Date: Fri, 28 Jul 2023 12:35:55 +0000 Subject: [PATCH 023/254] Translated using Weblate (Greek) Currently translated at 12.9% (4 of 31 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/el/ --- fastlane/metadata/android/el/changelogs/100.txt | 7 +++++++ fastlane/metadata/android/el/full_description.txt | 12 ++++++++++++ fastlane/metadata/android/el/short_description.txt | 1 + fastlane/metadata/android/el/title.txt | 1 + 4 files changed, 21 insertions(+) create mode 100644 fastlane/metadata/android/el/changelogs/100.txt create mode 100644 fastlane/metadata/android/el/full_description.txt create mode 100644 fastlane/metadata/android/el/short_description.txt create mode 100644 fastlane/metadata/android/el/title.txt diff --git a/fastlane/metadata/android/el/changelogs/100.txt b/fastlane/metadata/android/el/changelogs/100.txt new file mode 100644 index 000000000..37b1d5c56 --- /dev/null +++ b/fastlane/metadata/android/el/changelogs/100.txt @@ -0,0 +1,7 @@ +Tusky 21.0 + +- Υποστήριξη για επεξεργασία αναρτήσεων +- Νέες ρυθμίσεις για έλεγχο των προτιμητέων διευθύνσεων ανάγνωσης +- Προεπισκόπηση μεγαλύτερων πολυμέσων και νέα εμφάνιση για την αναγνώριση των πολυμέσων με περιγραφή +- Πλέον είναι δυνατή η εισαγωγή λογαριασμών σε λίστες από τα προφιλ τους +και πολλά περισσότερα diff --git a/fastlane/metadata/android/el/full_description.txt b/fastlane/metadata/android/el/full_description.txt new file mode 100644 index 000000000..71bd11ef8 --- /dev/null +++ b/fastlane/metadata/android/el/full_description.txt @@ -0,0 +1,12 @@ +Το Tusky είνια μια ελαφριά εφαρμογή για το Mastodon, ένα δωρεάν και ανοικτού κώδικα διακομιστή κοινωνικού δικτύου. + +• Material Design +• Τα περισσότερα API του Mastodon είναι προσβάσιμα +• Υποστήριξη πολλαπλών λογαριασμών +• Σκούρο και φωτεινό θέμα με δυνατότητα αυτόματης εναλλαγής ανάλογα την ώρα της ημέρας +• Πρόχειρα - γράψτε toots και αποθηκεύστε τα για αργότερα +• Επιλέξτε μεταξύ διαφορετικών στιλ emoji +• Προσαρμόζεται σε όλα τα μεγέθη οθόνης +• Απόλυτα ανοικτού-κώδια - χωρίς μη-ελεύθερες εξαρτήσεις όπως υπηρεσίες της Google + +Για να μάθαιτε περισσότερα για το Mastodon, επισκεφτείτε το https://joinmastodon.org/ diff --git a/fastlane/metadata/android/el/short_description.txt b/fastlane/metadata/android/el/short_description.txt new file mode 100644 index 000000000..a32eb465b --- /dev/null +++ b/fastlane/metadata/android/el/short_description.txt @@ -0,0 +1 @@ +Μια εφαρμογή πολλαπλών λογαριασμών του κοινωνικού δικτύου Mastodon diff --git a/fastlane/metadata/android/el/title.txt b/fastlane/metadata/android/el/title.txt new file mode 100644 index 000000000..0238ffc0a --- /dev/null +++ b/fastlane/metadata/android/el/title.txt @@ -0,0 +1 @@ +Tusky From 31af6c3e0c464af93bd64e13de0a64bb14cf4eef Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Fri, 28 Jul 2023 21:35:55 +0000 Subject: [PATCH 024/254] Translated using Weblate (Persian) Currently translated at 100.0% (610 of 610 strings) Co-authored-by: Danial Behzadi Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fa/ Translation: Tusky/Tusky --- app/src/main/res/values-fa/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index ef594ec5f..99588764f 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -674,4 +674,5 @@ آگاهی‌ها هنگامی که تاسکی در پس‌زمینه کار می‌کند واکشی آگاهی‌ها… نگه‌داری انباره… + بارگذاری شکست خورد: %s \ No newline at end of file From 18dfc39bd1c5b0651e35cb8cf10cb708ec957e73 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 28 Jul 2023 21:35:55 +0000 Subject: [PATCH 025/254] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (610 of 610 strings) Co-authored-by: Eric Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hans/ Translation: Tusky/Tusky --- app/src/main/res/values-zh-rCN/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 49ba6086a..5fdf44a1a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -674,4 +674,5 @@ 获取通知中… 缓存维护… 后台活动 + 上传失败了:%s \ No newline at end of file From c4a7532ee5cdc043da2f7b1789832e6ade166c3e Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 28 Jul 2023 21:35:55 +0000 Subject: [PATCH 026/254] Translated using Weblate (Ukrainian) Currently translated at 100.0% (610 of 610 strings) Co-authored-by: Ihor Hordiichuk Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/uk/ Translation: Tusky/Tusky --- app/src/main/res/values-uk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 5621c6ad6..4ff42002e 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -691,4 +691,5 @@ Ваш сервер знає, що цей допис було змінено, але не має копії редагувань, тому вони не можуть бути вам показані. \n \nЦе помилка #25398 у Mastodon. + Не вдалося вивантажити: %s \ No newline at end of file From fb94585a850a9ab75ff49e93fb1b08f956219ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Nh=E1=BA=A5t=20Duy?= Date: Fri, 28 Jul 2023 21:35:55 +0000 Subject: [PATCH 027/254] Translated using Weblate (Vietnamese) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (610 of 610 strings) Co-authored-by: Hồ Nhất Duy Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translation: Tusky/Tusky --- app/src/main/res/values-vi/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 1d4ccf662..35476221b 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -656,4 +656,5 @@ Thông báo khi Tusky hoạt động ngầm Đang nạp thông báo… Bảo trì bộ nhớ đệm… + Không thể tải lên: %s \ No newline at end of file From b58219f8cf21b084b3e13cd9859ea759a474bdd6 Mon Sep 17 00:00:00 2001 From: XoseM Date: Fri, 28 Jul 2023 21:35:55 +0000 Subject: [PATCH 028/254] Translated using Weblate (Galician) Currently translated at 100.0% (610 of 610 strings) Co-authored-by: XoseM Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gl/ Translation: Tusky/Tusky --- app/src/main/res/values-gl/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 862e4f4bc..9a0738340 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -667,4 +667,5 @@ Notificacións cando Tusky está a funcionar en segundo plano Obtendo as notificacións… Mantemento da caché… + Fallou a subida: %s \ No newline at end of file From b5bed897c60c05a6d38f83326c448c28c42eaedc Mon Sep 17 00:00:00 2001 From: Yannis D Date: Fri, 28 Jul 2023 21:35:55 +0000 Subject: [PATCH 029/254] Translated using Weblate (Greek) Currently translated at 17.2% (105 of 610 strings) Co-authored-by: Yannis D Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/el/ Translation: Tusky/Tusky --- app/src/main/res/values-el/strings.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index cef008748..c87d00e71 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1,7 +1,7 @@ Αυτό δεν μπορεί να είναι κενό. - Προέκυψε σφάλμα δικτύου! Παρακαλώ ελέγξτε τη σύνδεσή σας και προσπαθήστε ξανά! + Προέκυψε σφάλμα δικτύου! Παρακαλώ ελέγξτε τη σύνδεσή σας και προσπαθήστε ξανά. Προέκυψε ένα σφάλμα. Αποκλεισμένοι χρήστες Ακύρωση αιτήματος ακολούθησης; @@ -103,4 +103,12 @@ Κλικ για να δείτε ο/η %s επεξεργάστηκε τη δημοσίευσή του/της Διαγραφή και αναδιατύπωση αυτής της δημοσίευσης; + Σφάλμα ακολουθίας #%s + Σφάλμα μη-ακολουθίας #%s + Το ανέβασμα απέτυχε: %s + Το ανέβασμα αρχείου απέτυχε. + Η φόρτωση λεπτομερειών λογαριασμού απέτυχε + Αρχική Σελίδα + Σφάλμα αποστολής ανάρτησης. + Δεν ήταν δυνατή η φόρτωση της σελίδας εισόδου χρήστη. \ No newline at end of file From 41fc480ef72c6158a21234aadff8dfb6c58a70c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 29 Jul 2023 15:55:54 +0200 Subject: [PATCH 030/254] Update androidx.room to v2.5.2 (#3768) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [androidx.room:room-testing](https://developer.android.com/jetpack/androidx/releases/room#2.5.2) ([source](https://cs.android.com/androidx/platform/frameworks/support)) | `2.5.1` -> `2.5.2` | [![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.room:room-testing/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.room:room-testing/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.room:room-testing/2.5.1/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.room:room-testing/2.5.1/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [androidx.room:room-ktx](https://developer.android.com/jetpack/androidx/releases/room#2.5.2) ([source](https://cs.android.com/androidx/platform/frameworks/support)) | `2.5.1` -> `2.5.2` | [![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.room:room-ktx/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.room:room-ktx/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.room:room-ktx/2.5.1/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.room:room-ktx/2.5.1/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [androidx.room:room-paging](https://developer.android.com/jetpack/androidx/releases/room#2.5.2) ([source](https://cs.android.com/androidx/platform/frameworks/support)) | `2.5.1` -> `2.5.2` | [![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.room:room-paging/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.room:room-paging/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.room:room-paging/2.5.1/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.room:room-paging/2.5.1/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [androidx.room:room-compiler](https://developer.android.com/jetpack/androidx/releases/room#2.5.2) ([source](https://cs.android.com/androidx/platform/frameworks/support)) | `2.5.1` -> `2.5.2` | [![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.room:room-compiler/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.room:room-compiler/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.room:room-compiler/2.5.1/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.room:room-compiler/2.5.1/2.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/tuskyapp/Tusky). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c06b34834..4a97a287f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ androidx-swiperefresh-layout = "1.1.0" androidx-testing = "2.2.0" androidx-viewpager2 = "1.0.0" androidx-work = "2.8.1" -androidx-room = "2.5.1" +androidx-room = "2.5.2" autodispose = "2.1.1" bouncycastle = "1.70" conscrypt = "2.5.2" From 0f74a6b3dcbdd6c84792416a4651ed72a01bfbd7 Mon Sep 17 00:00:00 2001 From: Connyduck Date: Sat, 29 Jul 2023 19:29:22 +0000 Subject: [PATCH 031/254] Added translation using Weblate (Hebrew) Co-authored-by: Connyduck --- app/src/main/res/values-iw/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-iw/strings.xml diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/app/src/main/res/values-iw/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 839d8bcc044f7f4ca4cefe5e9e781f377cf05160 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Sun, 30 Jul 2023 15:50:04 +0200 Subject: [PATCH 032/254] Migrate to AGP 8.0.2 / Android Studio Flamingo / Java 17 (#3541) - Update AGP in version catalog to 8.0.2 - Set Java version to 17 - Enable non-final resource IDs --- .github/workflows/ci.yml | 10 +++++----- app/build.gradle | 4 ++-- .../keylesspalace/tusky/usecase/TimelineCasesTest.kt | 2 +- bitrise.yml | 9 +++++++++ build.gradle | 2 +- gradle.properties | 5 ----- gradle/libs.versions.toml | 2 +- 7 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9f5ac74f..8e36ee795 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Gradle Wrapper Validation @@ -27,15 +27,15 @@ jobs: uses: gradle/gradle-build-action@v2 with: cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} - + - name: ktlint run: ./gradlew clean ktlintCheck - + - name: Regular lint run: ./gradlew app:lintGreenDebug - + - name: Test run: ./gradlew app:testGreenDebugUnitTest - + - name: Build run: ./gradlew app:buildGreenDebug diff --git a/app/build.gradle b/app/build.gradle index 88c2ad7b3..b7f60b3a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -103,8 +103,8 @@ android { // Can remove this once https://issuetracker.google.com/issues/260059413 is fixed. // https://kotlinlang.org/docs/gradle-configure-project.html#gradle-java-toolchains-support compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } applicationVariants.configureEach { variant -> variant.outputs.configureEach { diff --git a/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt b/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt index 688622853..bcb690641 100644 --- a/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt @@ -19,7 +19,7 @@ import org.mockito.kotlin.stub import org.robolectric.annotation.Config import retrofit2.HttpException import retrofit2.Response -import java.util.* +import java.util.Date @Config(sdk = [28]) @RunWith(AndroidJUnit4::class) diff --git a/bitrise.yml b/bitrise.yml index 1a01709ee..714b3dd89 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -11,6 +11,9 @@ trigger_map: workflows: nightly: steps: + - set-java-version@1: + inputs: + - set_java_version: '17' - activate-ssh-key: run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - git-clone@8.0: {} @@ -50,6 +53,9 @@ workflows: - cache-push@2.7: {} primary: steps: + - set-java-version@1: + inputs: + - set_java_version: '17' - activate-ssh-key: run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - git-clone: {} @@ -87,6 +93,9 @@ workflows: - cache-push@2.7: {} release: steps: + - set-java-version@1: + inputs: + - set_java_version: '17' - activate-ssh-key: run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - git-clone: {} diff --git a/build.gradle b/build.gradle index 7689e2fb6..fb0ac0197 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ allprojects { plugins.withType(JavaBasePlugin).configureEach { java { - toolchain.languageVersion = JavaLanguageVersion.of(11) + toolchain.languageVersion = JavaLanguageVersion.of(17) } } } diff --git a/gradle.properties b/gradle.properties index 45c7b7e09..84a07ffd3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,11 +9,6 @@ org.gradle.configuration-cache=true kotlin.incremental.useClasspathSnapshot=true # Disable buildFeatures flags by default -android.defaults.buildfeatures.aidl=false -android.defaults.buildfeatures.buildconfig=false -android.defaults.buildfeatures.renderscript=false android.defaults.buildfeatures.resvalues=false android.defaults.buildfeatures.shaders=false -android.enableR8.fullMode=true -android.nonTransitiveRClass=true android.useAndroidX=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4a97a287f..1d855edfa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "7.4.2" +agp = "8.0.2" androidx-activity = "1.7.2" androidx-appcompat = "1.6.1" androidx-browser = "1.5.0" From 30be3ce1f0d59692252a58a939f64b0a1ecc7039 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Sun, 30 Jul 2023 21:45:43 +0200 Subject: [PATCH 033/254] Use lint version 8.1.0 to fix erroneous warning about forEach (#3903) Android lint was erroneously warning that the forEach construct in Kotlin required API 24+, which is incorrect, see https://issuetracker.google.com/issues/185418482. Work around that by forcing the Android lint version to 8.1.0. This triggered some additional checks, which have been ignored, and a new baseline. --- app/lint-baseline.xml | 1052 ++++++----------------------------------- app/lint.xml | 6 + gradle.properties | 3 + 3 files changed, 150 insertions(+), 911 deletions(-) diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index 056acea99..1dc283511 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -1,5 +1,5 @@ - + @@ -30,7 +30,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -41,7 +41,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -84,6 +84,17 @@ column="21"/> + + + + @@ -788,6 +799,17 @@ column="293"/> + + + + @@ -828,7 +850,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -839,7 +861,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -850,7 +872,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -861,535 +883,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2148,7 +1642,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2159,7 +1653,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2313,7 +1807,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2324,7 +1818,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2335,7 +1829,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -2346,7 +1840,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2357,7 +1851,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2368,7 +1862,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2379,7 +1873,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2390,7 +1884,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2401,7 +1895,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2412,7 +1906,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2423,7 +1917,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2434,7 +1928,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2445,7 +1939,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2456,7 +1950,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -2467,7 +1961,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2478,7 +1972,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -2489,7 +1983,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -2500,7 +1994,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -2511,7 +2005,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2522,7 +2016,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2533,7 +2027,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -2544,7 +2038,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2555,7 +2049,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -2566,7 +2060,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2577,7 +2071,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2588,7 +2082,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -2599,7 +2093,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2610,7 +2104,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2621,7 +2115,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2632,7 +2126,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -2643,7 +2137,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2654,7 +2148,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2665,7 +2159,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2676,7 +2170,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2863,7 +2357,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3050,7 +2544,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3061,7 +2555,7 @@ errorLine2=" ~~~~~~~~~"> @@ -3072,7 +2566,7 @@ errorLine2=" ~~~~~~~~~"> @@ -3644,7 +3138,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -3655,7 +3149,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3666,7 +3160,7 @@ errorLine2=" ~~~~~~~"> @@ -3677,7 +3171,7 @@ errorLine2=" ~~~~~~~"> @@ -3688,7 +3182,7 @@ errorLine2=" ~~~~~~~"> @@ -3699,7 +3193,7 @@ errorLine2=" ~~~~~~~"> @@ -3710,7 +3204,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3721,7 +3215,7 @@ errorLine2=" ~~~~~~~"> @@ -3732,7 +3226,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3743,7 +3237,7 @@ errorLine2=" ~~~~~~~"> @@ -3754,7 +3248,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3765,7 +3259,7 @@ errorLine2=" ~~~~~~~"> @@ -3776,7 +3270,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3787,7 +3281,7 @@ errorLine2=" ~~~~~~~"> @@ -3798,7 +3292,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3809,7 +3303,7 @@ errorLine2=" ~~~~~~~"> @@ -3820,7 +3314,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3831,7 +3325,7 @@ errorLine2=" ~~~~~~~"> @@ -3842,7 +3336,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3853,7 +3347,7 @@ errorLine2=" ~~~~~~~"> @@ -3864,7 +3358,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3875,7 +3369,7 @@ errorLine2=" ~~~~~~~"> @@ -3886,7 +3380,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -3897,7 +3391,7 @@ errorLine2=" ~~~~~~~"> @@ -3908,7 +3402,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -3919,7 +3413,7 @@ errorLine2=" ~~~~~~~"> @@ -3930,7 +3424,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -3941,7 +3435,7 @@ errorLine2=" ~~~~~~~"> @@ -3952,7 +3446,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -3963,7 +3457,7 @@ errorLine2=" ~~~~~~~"> @@ -3974,7 +3468,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -3985,7 +3479,7 @@ errorLine2=" ~~~~~~~"> @@ -3996,7 +3490,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -4007,7 +3501,7 @@ errorLine2=" ~~~~~~~"> @@ -4018,7 +3512,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -4029,7 +3523,7 @@ errorLine2=" ~~~~~~~"> @@ -4038,6 +3532,17 @@ message="Access to `private` method `getBinding` of class `MainActivity` requires synthetic accessor" errorLine1=" binding.mainToolbar.title = tab.contentDescription" errorLine2=" ~~~~~~~"> + + + + - - - - @@ -4073,7 +3567,7 @@ errorLine2=" ~~~~~~~"> @@ -4084,7 +3578,7 @@ errorLine2=" ~~~~~~~"> @@ -4095,7 +3589,7 @@ errorLine2=" ~~~~~~~"> @@ -4106,7 +3600,7 @@ errorLine2=" ~~~~~~~"> @@ -4117,7 +3611,7 @@ errorLine2=" ~~~~~~~"> @@ -4128,7 +3622,7 @@ errorLine2=" ~~~~~~~"> @@ -4425,7 +3919,7 @@ errorLine2=" ~~~~~~~~~"> @@ -4436,7 +3930,7 @@ errorLine2=" ~~~~~~~~"> @@ -4447,7 +3941,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -4458,7 +3952,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -4759,281 +4253,6 @@ column="54"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -6339,6 +5558,17 @@ column="6"/> + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index 84a07ffd3..dc6a0c69a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,3 +12,6 @@ kotlin.incremental.useClasspathSnapshot=true android.defaults.buildfeatures.resvalues=false android.defaults.buildfeatures.shaders=false android.useAndroidX=true + +# Upgrade lint to a newer version to work around https://issuetracker.google.com/issues/185418482. +android.experimental.lint.version=8.1.0 From 79ee2dc32ccf3a03a1649ab0423e529c9f61e2aa Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Mon, 31 Jul 2023 12:44:01 +0200 Subject: [PATCH 034/254] Fix image zoom / pan / scroll / swipe (#3894) Migrate to touchimageview from photoview, and adjust the touch logic to correctly handle single finger drag, two finger pinch/stretch, flings, taps, and swipes. As before, the features are: - Single tap, show/hide controls and media description - Double tap, zoom in/out - Single finger drag up/down, scale/translate image, dismiss if scrolled too far - Single finger drag left/right - When not zoomed, swipe to next image if multiple images present - When zoomed, scroll to edge of image, then to next image if multiple images present - Two finger pinch/zoom, zoom in/out on the image Behaviour differences to previous code 1. Bug fix: The image can't get "stuck" when zoomed, and impossible to scroll 2. Bug fix: Pinching is not mis-interpreted as a fling, closing the image 3. Bug fix: The zoom state of images is not lost or misinterpreted when the user swipes through multiple images 4. Bug fix: Double-tap zooms all the way, instead of stopping 5. Tapping outside the image does not dismiss it, controls and description show/hide Fixes https://github.com/tuskyapp/Tusky/issues/3562, https://github.com/tuskyapp/Tusky/issues/2297 --- app/build.gradle | 19 +- .../compose/dialog/CaptionDialog.kt | 2 +- .../tusky/fragment/ViewImageFragment.kt | 201 +++++++++++------- .../res/layout/dialog_image_description.xml | 2 +- .../main/res/layout/fragment_view_image.xml | 4 +- gradle/libs.versions.toml | 4 +- 6 files changed, 145 insertions(+), 87 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b7f60b3a8..1e67b551b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -158,7 +158,7 @@ dependencies { implementation libs.sparkbutton - implementation libs.photoview + implementation libs.touchimageview implementation libs.bundles.material.drawer implementation libs.material.typeface @@ -186,3 +186,20 @@ dependencies { androidTestImplementation libs.androidx.room.testing androidTestImplementation libs.androidx.test.junit } + +// Work around warnings of: +// WARNING: Illegal reflective access by org.jetbrains.kotlin.kapt3.util.ModuleManipulationUtilsKt (file:/C:/Users/Andi/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-annotation-processing-gradle/1.8.22/28dab7e0ee9ce62c03bf97de3543c911dc653700/kotlin-annotation-processing-gradle-1.8.22.jar) to constructor com.sun.tools.javac.util.Context() +// See https://youtrack.jetbrains.com/issue/KT-30589/Kapt-An-illegal-reflective-access-operation-has-occurred +tasks.withType(org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask) { + kaptProcessJvmArgs.addAll([ + "--add-opens", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-opens", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-opens", "jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED", + "--add-opens", "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-opens", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "--add-opens", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-opens", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-opens", "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-opens", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED"]) +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt index a1dc032cd..c066b3d9f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt @@ -51,7 +51,7 @@ class CaptionDialog : DialogFragment() { input = binding.imageDescriptionText val imageView = binding.imageDescriptionView - imageView.maximumScale = 6f + imageView.maxZoom = 6f input.hint = resources.getQuantityString( R.plurals.hint_describe_for_visually_impaired, diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt index 24c8feb03..bbbfdf219 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt @@ -19,25 +19,31 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.annotation.SuppressLint import android.content.Context +import android.graphics.PointF import android.graphics.drawable.Drawable import android.os.Bundle +import android.view.GestureDetector import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.core.os.BundleCompat +import androidx.core.view.GestureDetectorCompat import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target -import com.github.chrisbanes.photoview.PhotoViewAttacher +import com.keylesspalace.tusky.R import com.keylesspalace.tusky.ViewMediaActivity import com.keylesspalace.tusky.databinding.FragmentViewImageBinding import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.visible +import com.ortiz.touchview.OnTouchCoordinatesListener +import com.ortiz.touchview.TouchImageView import io.reactivex.rxjava3.subjects.BehaviorSubject import kotlin.math.abs @@ -48,10 +54,8 @@ class ViewImageFragment : ViewMediaFragment() { fun onPhotoTap() } - private var _binding: FragmentViewImageBinding? = null - private val binding get() = _binding!! + private val binding by viewBinding(FragmentViewImageBinding::bind) - private lateinit var attacher: PhotoViewAttacher private lateinit var photoActionsListener: PhotoActionsListener private lateinit var toolbar: View private var transition = BehaviorSubject.create() @@ -84,8 +88,7 @@ class ViewImageFragment : ViewMediaFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { toolbar = (requireActivity() as ViewMediaActivity).toolbar this.transition = BehaviorSubject.create() - _binding = FragmentViewImageBinding.inflate(inflater, container, false) - return binding.root + return inflater.inflate(R.layout.fragment_view_image, container, false) } @SuppressLint("ClickableViewAccessibility") @@ -108,95 +111,139 @@ class ViewImageFragment : ViewMediaFragment() { } } - attacher = PhotoViewAttacher(binding.photoView).apply { - // This prevents conflicts with ViewPager - setAllowParentInterceptOnEdge(true) + val singleTapDetector = GestureDetectorCompat( + requireContext(), + object : GestureDetector.SimpleOnGestureListener() { + override fun onDown(e: MotionEvent) = true + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + photoActionsListener.onPhotoTap() + return false + } + } + ) - // Clicking outside the photo closes the viewer. - setOnOutsidePhotoTapListener { photoActionsListener.onDismiss() } - setOnClickListener { onMediaTap() } + binding.photoView.setOnTouchCoordinatesListener(object : OnTouchCoordinatesListener { + /** Y coordinate of the last single-finger drag */ + var lastDragY: Float? = null - /* A vertical swipe motion also closes the viewer. This is especially useful when the photo - * mostly fills the screen so clicking outside is difficult. */ - setOnSingleFlingListener { _, _, velocityX, velocityY -> - var result = false - if (abs(velocityY) > abs(velocityX)) { + override fun onTouchCoordinate(view: View, event: MotionEvent, bitmapPoint: PointF) { + singleTapDetector.onTouchEvent(event) + + // Two fingers have gone down after a single finger drag. Finish the drag + if (event.pointerCount == 2 && lastDragY != null) { + onGestureEnd(view) + lastDragY = null + } + + // Stop the parent view from handling touches if either (a) the user has 2+ + // fingers on the screen, or (b) the image has been zoomed in, and can be scrolled + // horizontally in both directions. + // + // This stops things like ViewPager2 from trying to intercept a left/right swipe + // and ensures that the image does not appear to "stick" to the screen as different + // views fight over who should be handling the swipe. + // + // If the view can be scrolled in one direction it's OK to let the parent intercept, + // which allows the user to swipe between images even if one or more of them have + // been zoomed in. + if (event.pointerCount >= 2 || view.canScrollHorizontally(1) && view.canScrollHorizontally(-1)) { + when (event.action) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { + view.parent.requestDisallowInterceptTouchEvent(true) + } + + MotionEvent.ACTION_UP -> { + view.parent.requestDisallowInterceptTouchEvent(false) + } + } + return + } + + // The user is dragging the image around + if (event.pointerCount == 1) { + // If the image is zoomed then the swipe-to-dismiss functionality is disabled + if ((view as TouchImageView).isZoomed) return + + // The user's finger just went down, start recording where they are dragging from + if (event.action == MotionEvent.ACTION_DOWN) { + lastDragY = event.rawY + return + } + + // The user is dragging the un-zoomed image to possibly fling it up or down + // to dismiss. + if (event.action == MotionEvent.ACTION_MOVE) { + // lastDragY may be null; e.g., the user was performing a two-finger drag, + // and has lifted one finger. In this case do nothing + lastDragY ?: return + + // Compute the Y offset of the drag, and scale/translate the photoview + // accordingly. + val diff = event.rawY - lastDragY!! + if (view.translationY != 0f || abs(diff) > 40) { + // Drag has definitely started, stop the parent from interfering + view.parent.requestDisallowInterceptTouchEvent(true) + view.translationY += diff + val scale = (-abs(view.translationY) / 720 + 1).coerceAtLeast(0.5f) + view.scaleY = scale + view.scaleX = scale + lastDragY = event.rawY + } + return + } + + // The user has finished dragging. Allow the parent to handle touch events if + // appropriate, and end the gesture. + if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) { + view.parent.requestDisallowInterceptTouchEvent(false) + if (lastDragY != null) onGestureEnd(view) + lastDragY = null + return + } + } + } + + /** + * Handle the end of the user's gesture. + * + * If the user was previously dragging, and the image has been dragged a sufficient + * distance then we are done. Otherwise, animate the image back to its starting position. + */ + private fun onGestureEnd(view: View) { + if (abs(view.translationY) > 180) { photoActionsListener.onDismiss() - result = true + } else { + view.animate().translationY(0f).scaleX(1f).start() } - result } - } - - var lastY = 0f - - binding.photoView.setOnTouchListener { v, event -> - // This part is for scaling/translating on vertical move. - // We use raw coordinates to get the correct ones during scaling - - if (event.action == MotionEvent.ACTION_DOWN) { - lastY = event.rawY - } else if (event.pointerCount == 1 && - attacher.scale == 1f && - event.action == MotionEvent.ACTION_MOVE - ) { - val diff = event.rawY - lastY - // This code is to prevent transformations during page scrolling - // If we are already translating or we reached the threshold, then transform. - if (binding.photoView.translationY != 0f || abs(diff) > 40) { - binding.photoView.translationY += (diff) - val scale = (-abs(binding.photoView.translationY) / 720 + 1).coerceAtLeast(0.5f) - binding.photoView.scaleY = scale - binding.photoView.scaleX = scale - lastY = event.rawY - return@setOnTouchListener true - } - } else if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) { - onGestureEnd() - } - attacher.onTouch(v, event) - } + }) finalizeViewSetup(url, attachment?.previewUrl, description) } - private fun onGestureEnd() { - if (_binding == null) { - return - } - if (abs(binding.photoView.translationY) > 180) { - photoActionsListener.onDismiss() - } else { - binding.photoView.animate().translationY(0f).scaleX(1f).scaleY(1f).start() - } - } - - private fun onMediaTap() { - photoActionsListener.onPhotoTap() - } - override fun onToolbarVisibilityChange(visible: Boolean) { - if (_binding == null || !userVisibleHint) { - return - } + if (!userVisibleHint) return + isDescriptionVisible = showingDescription && visible val alpha = if (isDescriptionVisible) 1.0f else 0.0f binding.captionSheet.animate().alpha(alpha) .setListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { - if (_binding != null) { - binding.captionSheet.visible(isDescriptionVisible) - } + view ?: return + binding.captionSheet.visible(isDescriptionVisible) animation.removeListener(this) } }) .start() } - override fun onDestroyView() { + override fun onStop() { + super.onStop() Glide.with(this).clear(binding.photoView) + } + + override fun onDestroyView() { transition.onComplete() - _binding = null super.onDestroyView() } @@ -270,7 +317,7 @@ class ViewImageFragment : ViewMediaFragment() { photoActionsListener.onBringUp() } // Hide progress bar only on fail request from internet - if (!isCacheRequest && _binding != null) binding.progressBar.hide() + if (!isCacheRequest) binding.progressBar.hide() // We don't want to overwrite preview with null when main image fails to load return !isCacheRequest } @@ -283,9 +330,7 @@ class ViewImageFragment : ViewMediaFragment() { dataSource: DataSource, isFirstResource: Boolean ): Boolean { - if (_binding != null) { - binding.progressBar.hide() // Always hide the progress bar on success - } + binding.progressBar.hide() // Always hide the progress bar on success if (!startedTransition || !shouldStartTransition) { // Set this right away so that we don't have to concurrent post() requests @@ -303,10 +348,6 @@ class ViewImageFragment : ViewMediaFragment() { .take(1) .subscribe { target.onResourceReady(resource, null) - // It's needed. Don't ask why, I don't know, setImageDrawable() should - // do it by itself but somehow it doesn't work automatically. - // Just do it. If you don't, image will jump around when touched. - attacher.update() } } return true diff --git a/app/src/main/res/layout/dialog_image_description.xml b/app/src/main/res/layout/dialog_image_description.xml index 4d749a8f5..d20d8795f 100644 --- a/app/src/main/res/layout/dialog_image_description.xml +++ b/app/src/main/res/layout/dialog_image_description.xml @@ -6,7 +6,7 @@ android:orientation="vertical" android:paddingBottom="0dp"> - - @@ -76,4 +76,4 @@ - \ No newline at end of file + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d855edfa..1ef4a5ceb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -47,8 +47,8 @@ robolectric = "4.10.3" rxandroid3 = "3.0.2" rxjava3 = "3.1.6" rxkotlin3 = "3.0.1" -photoview = "2.3.0" sparkbutton = "4.1.0" +touchimageview = "3.4" truth = "1.1.5" turbine = "1.0.0" unified-push = "2.1.1" @@ -127,7 +127,6 @@ mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = " networkresult-calladapter = { module = "at.connyduck:networkresult-calladapter", version.ref = "networkresult-calladapter" } okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } -photoview = { module = "com.github.chrisbanes:PhotoView", version.ref = "photoview" } retrofit-adapter-rxjava3 = { module = "com.squareup.retrofit2:adapter-rxjava3", version.ref = "retrofit" } retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" } retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } @@ -136,6 +135,7 @@ rxjava3-android = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rx rxjava3-core = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava3" } rxjava3-kotlin = { module = "io.reactivex.rxjava3:rxkotlin", version.ref = "rxkotlin3" } sparkbutton = { module = "com.github.connyduck:sparkbutton", version.ref = "sparkbutton" } +touchimageview = { module = "com.github.MikeOrtiz:TouchImageView", version.ref = "touchimageview" } truth = { module = "com.google.truth:truth", version.ref = "truth" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } unified-push = { module = "com.github.UnifiedPush:android-connector", version.ref = "unified-push" } From 5391b5f797e8bafb66455e289cbe03ae3325ebcd Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 31 Jul 2023 10:35:54 +0000 Subject: [PATCH 035/254] Translated using Weblate (Japanese) Currently translated at 97.7% (596 of 610 strings) Co-authored-by: Suguru Hirahara Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ja/ Translation: Tusky/Tusky --- app/src/main/res/values-ja/strings.xml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index e21f2b5f3..c9e7247e5 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -58,7 +58,7 @@ 投稿する TUsky でログイン ログアウト - アカウント %1$s からログアウトしてもよろしいですか? + アカウント %1$s からログアウトしてもよろしいですか?ログアウトすると、下書きや詳細設定を含めて、ローカルに保存されているアカウントの全てのデータが削除されます。 フォローする フォロー解除 ブロック @@ -256,7 +256,7 @@ リスト リスト名を変更できませんでした リスト名の変更 - %1$sで投稿 + %1$sとして投稿 視覚障害者のための説明 (%d文字まで) @@ -320,7 +320,7 @@ ダイレクト リスト名 - ネットワークエラーが発生しました!接続を確認してもう一度試してください! + ネットワークエラーが発生しました。接続を確認してもう一度試してください。 \@%s ブーストを解除 お気に入りを解除 @@ -443,7 +443,7 @@ %sのミュートを解除 タブ間の切り替えにスワイプのジェスチャーを有効化 リンクのプレビューをタイムラインに表示 - ブーストする前に確認ダイアログを表示 + ブーストする前に確認を表示 ハッシュタグ (#をつけない) リストを選択 %1$s • %2$s @@ -532,7 +532,7 @@ アカウントのリストからの削除に失敗しました アナウンスはありません。 編集しました - お気に入り前に確認ダイアログを表示する + お気に入りに追加する前に確認を表示する トップバーのタイトルを隠す ウェルビーイング このアカウントに関するプライベートメモ @@ -657,4 +657,9 @@ %s: %s 最新の通知を読み込む 下書きを削除しますか? + リストを読み込む際のエラー + アップロードに失敗しました: %s + あなたのサーバーは、この投稿が変更されたことを把握していますが、編集履歴のコピーを備えていないので、表示できません。 +\n +\nこれはMastodonのissue #25398です。 \ No newline at end of file From 40bd95d7527c2f815d82c41133fba5f7e9078f5a Mon Sep 17 00:00:00 2001 From: Goooler Date: Wed, 2 Aug 2023 15:04:24 +0800 Subject: [PATCH 036/254] Kotlin 1.9.0 (#3835) Update to Kotlin 1.9.0 and migrate to newer language idioms. - Remove unnecessary @OptIn for features migrated to mainstream - Use `data object` where appropriate - Use new enum `entries` property --- .../main/java/com/keylesspalace/tusky/TabData.kt | 4 +--- .../tusky/adapter/StatusBaseViewHolder.java | 12 +++++------- .../tusky/components/compose/ComposeViewModel.kt | 4 +--- .../compose/dialog/AddPollOptionsAdapter.kt | 6 +----- .../tusky/components/login/LoginWebViewActivity.kt | 2 +- .../notifications/NotificationHelper.java | 2 +- .../notifications/NotificationsFragment.kt | 2 +- .../notifications/NotificationsPagingAdapter.kt | 2 +- .../notifications/NotificationsViewModel.kt | 14 ++++++-------- .../components/viewthread/ViewThreadViewModel.kt | 4 ++-- .../viewthread/edits/ViewEditsAdapter.kt | 4 ++-- .../viewthread/edits/ViewEditsViewModel.kt | 6 +++--- .../com/keylesspalace/tusky/db/AccountEntity.kt | 4 +--- .../com/keylesspalace/tusky/util/FlowExtensions.kt | 2 -- .../keylesspalace/tusky/util/NoUnderlineURLSpan.kt | 2 +- .../java/com/keylesspalace/tusky/util/SpanUtils.kt | 2 +- .../tusky/viewmodel/EditProfileViewModel.kt | 2 -- .../notifications/NotificationsPagingSourceTest.kt | 2 -- .../NotificationsViewModelTestBase.kt | 5 ++--- ...NotificationsViewModelTestClearNotifications.kt | 2 -- .../NotificationsViewModelTestFilter.kt | 2 -- ...NotificationsViewModelTestNotificationAction.kt | 2 -- .../NotificationsViewModelTestStatusAction.kt | 2 -- ...tificationsViewModelTestStatusDisplayOptions.kt | 2 -- .../NotificationsViewModelTestUiState.kt | 2 -- .../NotificationsViewModelTestVisibleId.kt | 2 -- gradle.properties | 3 --- gradle/libs.versions.toml | 5 ++--- renovate.json | 12 ++++++++++++ 29 files changed, 44 insertions(+), 71 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/TabData.kt b/app/src/main/java/com/keylesspalace/tusky/TabData.kt index 09d1f1cf4..6bff4a82b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabData.kt @@ -52,9 +52,7 @@ data class TabData( other as TabData if (id != other.id) return false - if (arguments != other.arguments) return false - - return true + return arguments == other.arguments } override fun hashCode() = Objects.hash(id, arguments) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index de4d23802..ca9d5f32f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -114,10 +114,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { private final TextView cardDescription; private final TextView cardUrl; private final PollAdapter pollAdapter; - protected LinearLayout filteredPlaceholder; - protected TextView filteredPlaceholderLabel; - protected Button filteredPlaceholderShowButton; - protected ConstraintLayout statusContainer; + protected final LinearLayout filteredPlaceholder; + protected final TextView filteredPlaceholderLabel; + protected final Button filteredPlaceholderShowButton; + protected final ConstraintLayout statusContainer; private final NumberFormat numberFormat = NumberFormat.getNumberInstance(); private final AbsoluteTimeFormatter absoluteTimeFormatter = new AbsoluteTimeFormatter(); @@ -838,9 +838,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } filteredPlaceholderLabel.setText(itemView.getContext().getString(R.string.status_filter_placeholder_label_format, matchedFilter.getTitle())); - filteredPlaceholderShowButton.setOnClickListener(view -> { - listener.clearWarningAction(getBindingAdapterPosition()); - }); + filteredPlaceholderShowButton.setOnClickListener(view -> listener.clearWarningAction(getBindingAdapterPosition())); } protected static boolean hasPreviewableAttachment(List attachments) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index 90389d28c..0919d29d0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -39,7 +39,6 @@ import com.keylesspalace.tusky.service.ServiceClient import com.keylesspalace.tusky.service.StatusToSend import com.keylesspalace.tusky.util.randomAlphanumericString import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -53,7 +52,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject -@OptIn(FlowPreview::class) class ComposeViewModel @Inject constructor( private val api: MastodonApi, private val accountManager: AccountManager, @@ -95,7 +93,7 @@ class ComposeViewModel @Inject constructor( val media: MutableStateFlow> = MutableStateFlow(emptyList()) val uploadError = MutableSharedFlow(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - lateinit var composeKind: ComposeKind + private lateinit var composeKind: ComposeKind // Used in ComposeActivity to pass state to result function when cropImage contract inflight var cropImageItemOld: QueuedMedia? = null diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollOptionsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollOptionsAdapter.kt index ded1b7cfd..4d7ecdca8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollOptionsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollOptionsAdapter.kt @@ -75,10 +75,6 @@ class AddPollOptionsAdapter( } private fun validateInput(): Boolean { - if (options.contains("") || options.distinct().size != options.size) { - return false - } - - return true + return !(options.contains("") || options.distinct().size != options.size) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt index be7e323ad..ed5744165 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt @@ -99,7 +99,7 @@ sealed class LoginResult : Parcelable { data class Err(val errorMessage: String) : LoginResult() @Parcelize - object Cancel : LoginResult() + data object Cancel : LoginResult() } /** Activity to do Oauth process using WebView. */ diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java index 6a42e8977..481098934 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java @@ -285,7 +285,7 @@ public class NotificationHelper { int accountId = (int) account.getId(); // Initialise the map with all channel IDs. - for (Notification.Type ty : Notification.Type.values()) { + for (Notification.Type ty : Notification.Type.getEntries()) { channelGroups.put(getChannelId(account, ty), new ArrayList<>()); } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt index 78f7a1565..3c0f0da4d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt @@ -293,7 +293,7 @@ class NotificationsFragment : val position = adapter.snapshot().indexOfFirst { it?.statusViewData?.status?.id == (action as StatusAction).statusViewData.id } - if (position != RecyclerView.NO_POSITION) { + if (position != NO_POSITION) { adapter.notifyItemChanged(position) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingAdapter.kt index faa1aefce..8f45a56f2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingAdapter.kt @@ -124,7 +124,7 @@ class NotificationsPagingAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) - return when (NotificationViewKind.values()[viewType]) { + return when (NotificationViewKind.entries[viewType]) { NotificationViewKind.STATUS -> { StatusViewHolder( ItemStatusBinding.inflate(inflater, parent, false), diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt index d1d41a947..1f3f982b9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt @@ -46,7 +46,6 @@ import com.keylesspalace.tusky.util.toViewData import com.keylesspalace.tusky.viewdata.NotificationViewData import com.keylesspalace.tusky.viewdata.StatusViewData import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -70,7 +69,6 @@ import kotlinx.coroutines.rx3.await import retrofit2.HttpException import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.ExperimentalTime data class UiState( /** Filtered notification types */ @@ -103,7 +101,7 @@ sealed class UiAction /** Actions the user can trigger from the UI. These actions may fail. */ sealed class FallibleUiAction : UiAction() { /** Clear all notifications */ - object ClearNotifications : FallibleUiAction() + data object ClearNotifications : FallibleUiAction() } /** @@ -129,7 +127,7 @@ sealed class InfallibleUiAction : UiAction() { // Resets the account's `lastNotificationId`, which can't fail, which is why this is // infallible. Reloading the data may fail, but that's handled by the paging system / // adapter refresh logic. - object LoadNewest : InfallibleUiAction() + data object LoadNewest : InfallibleUiAction() } /** Actions the user can trigger on an individual notification. These may fail. */ @@ -146,13 +144,13 @@ sealed class UiSuccess { // of these three should trigger the UI to refresh. /** A user was blocked */ - object Block : UiSuccess() + data object Block : UiSuccess() /** A user was muted */ - object Mute : UiSuccess() + data object Mute : UiSuccess() /** A conversation was muted */ - object MuteConversation : UiSuccess() + data object MuteConversation : UiSuccess() } /** The result of a successful action on a notification */ @@ -286,7 +284,7 @@ sealed class UiError( } } -@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class, ExperimentalTime::class) +@OptIn(ExperimentalCoroutinesApi::class) class NotificationsViewModel @Inject constructor( private val repository: NotificationsRepository, private val preferences: SharedPreferences, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt index f4b94400b..da8a91e4d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModel.kt @@ -516,7 +516,7 @@ class ViewThreadViewModel @Inject constructor( sealed interface ThreadUiState { /** The initial load of the detailed status for this thread */ - object Loading : ThreadUiState + data object Loading : ThreadUiState /** Loading the detailed status has completed, now loading ancestors/descendants */ data class LoadingThread( @@ -535,7 +535,7 @@ sealed interface ThreadUiState { ) : ThreadUiState /** Refreshing the thread with a swipe */ - object Refreshing : ThreadUiState + data object Refreshing : ThreadUiState } enum class RevealButtonState { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsAdapter.kt index 33085f82b..56bf1ffc4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsAdapter.kt @@ -51,10 +51,10 @@ class ViewEditsAdapter( private val absoluteTimeFormatter = AbsoluteTimeFormatter() /** Size of large text in this theme, in px */ - var largeTextSizePx: Float = 0f + private var largeTextSizePx: Float = 0f /** Size of medium text in this theme, in px */ - var mediumTextSizePx: Float = 0f + private var mediumTextSizePx: Float = 0f override fun onCreateViewHolder( parent: ViewGroup, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsViewModel.kt index 93f663587..85786efa2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/viewthread/edits/ViewEditsViewModel.kt @@ -132,12 +132,12 @@ class ViewEditsViewModel @Inject constructor(private val api: MastodonApi) : Vie } sealed interface EditsUiState { - object Initial : EditsUiState - object Loading : EditsUiState + data object Initial : EditsUiState + data object Loading : EditsUiState // "Refreshing" state is necessary, otherwise a refresh state transition is Success -> Success, // and state flows don't emit repeated states, so the UI never updates. - object Refreshing : EditsUiState + data object Refreshing : EditsUiState class Error(val throwable: Throwable) : EditsUiState data class Success( val edits: List diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt index 853003e75..84c8874f5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt @@ -129,9 +129,7 @@ data class AccountEntity( other as AccountEntity if (id == other.id) return true - if (domain == other.domain && accountId == other.accountId) return true - - return false + return domain == other.domain && accountId == other.accountId } override fun hashCode(): Int { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/FlowExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/FlowExtensions.kt index 7fcf77359..8de7129ce 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/FlowExtensions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/FlowExtensions.kt @@ -20,7 +20,6 @@ package com.keylesspalace.tusky.util import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlin.time.Duration -import kotlin.time.ExperimentalTime import kotlin.time.TimeMark import kotlin.time.TimeSource @@ -54,7 +53,6 @@ import kotlin.time.TimeSource * @param timeout Emissions within this duration of the last emission are filtered * @param timeSource Used to measure elapsed time. Normally only overridden in tests */ -@OptIn(ExperimentalTime::class) fun Flow.throttleFirst( timeout: Duration, timeSource: TimeSource = TimeSource.Monotonic diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NoUnderlineURLSpan.kt b/app/src/main/java/com/keylesspalace/tusky/util/NoUnderlineURLSpan.kt index 12be84d8a..59d8bbabe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/NoUnderlineURLSpan.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/NoUnderlineURLSpan.kt @@ -19,7 +19,7 @@ import android.text.TextPaint import android.text.style.URLSpan import android.view.View -open class NoUnderlineURLSpan constructor(val url: String) : URLSpan(url) { +open class NoUnderlineURLSpan(val url: String) : URLSpan(url) { // This should not be necessary. But if you don't do this the [StatusLengthTest] tests // fail. Without this, accessing the `url` property, or calling `getUrl()` (which should diff --git a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt index f89f5fea5..4b1235148 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt @@ -128,7 +128,7 @@ private fun findPattern(string: String, fromIndex: Int): FindCharsResult { val result = FindCharsResult() for (i in fromIndex..string.lastIndex) { val c = string[i] - for (matchType in FoundMatchType.values()) { + for (matchType in FoundMatchType.entries) { val finder = finders[matchType] if (finder!!.searchCharacter == c && ( diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt index fe5110381..21a8a01d7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt @@ -35,7 +35,6 @@ import com.keylesspalace.tusky.util.Resource import com.keylesspalace.tusky.util.Success import com.keylesspalace.tusky.util.getServerErrorMessage import com.keylesspalace.tusky.util.randomAlphanumericString -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asFlow @@ -64,7 +63,6 @@ class EditProfileViewModel @Inject constructor( val headerData = MutableLiveData() val saveData = MutableLiveData>() - @OptIn(FlowPreview::class) val instanceData: Flow = instanceInfoRepo::getInstanceInfo.asFlow() .shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1) diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingSourceTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingSourceTest.kt index 450f702a7..b91e07161 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingSourceTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingSourceTest.kt @@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.gson.Gson import com.keylesspalace.tusky.entity.Notification import com.keylesspalace.tusky.network.MastodonApi -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import okhttp3.ResponseBody.Companion.toResponseBody import org.junit.Assert.assertEquals @@ -38,7 +37,6 @@ import retrofit2.Response @Config(sdk = [28]) @RunWith(AndroidJUnit4::class) -@OptIn(ExperimentalCoroutinesApi::class) class NotificationsPagingSourceTest { @Test fun `load() returns error message on HTTP error`() = runTest { diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt index 773c67662..666d591cd 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestBase.kt @@ -48,7 +48,7 @@ import retrofit2.HttpException import retrofit2.Response @OptIn(ExperimentalCoroutinesApi::class) -class MainCoroutineRule constructor(private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()) : TestWatcher() { +class MainCoroutineRule(private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()) : TestWatcher() { override fun starting(description: Description) { super.starting(description) Dispatchers.setMain(dispatcher) @@ -62,7 +62,6 @@ class MainCoroutineRule constructor(private val dispatcher: TestDispatcher = Unc @Config(sdk = [28]) @RunWith(AndroidJUnit4::class) -@OptIn(ExperimentalCoroutinesApi::class) abstract class NotificationsViewModelTestBase { protected lateinit var notificationsRepository: NotificationsRepository protected lateinit var sharedPreferencesMap: MutableMap @@ -73,7 +72,7 @@ abstract class NotificationsViewModelTestBase { protected lateinit var viewModel: NotificationsViewModel /** Empty success response, for API calls that return one */ - protected var emptySuccess = Response.success("".toResponseBody()) + protected var emptySuccess: Response = Response.success("".toResponseBody()) /** Empty error response, for API calls that return one */ protected var emptyError: Response = Response.error(404, "".toResponseBody()) diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestClearNotifications.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestClearNotifications.kt index 03d850617..e6b765be4 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestClearNotifications.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestClearNotifications.kt @@ -19,7 +19,6 @@ package com.keylesspalace.tusky.components.notifications import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.mockito.kotlin.doReturn @@ -34,7 +33,6 @@ import org.mockito.kotlin.verify * This is only tested in the success case; if it passed there it must also * have passed in the error case. */ -@OptIn(ExperimentalCoroutinesApi::class) class NotificationsViewModelTestClearNotifications : NotificationsViewModelTestBase() { @Test fun `clearing notifications succeeds && invalidate the repository`() = runTest { diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestFilter.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestFilter.kt index a2be13e2a..98737e431 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestFilter.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestFilter.kt @@ -21,7 +21,6 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.entity.Notification -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.mockito.kotlin.argumentCaptor @@ -33,7 +32,6 @@ import org.mockito.kotlin.verify * - Is the [UiState] updated correctly? * - Are the correct [AccountManager] functions called, with the correct arguments? */ -@OptIn(ExperimentalCoroutinesApi::class) class NotificationsViewModelTestFilter : NotificationsViewModelTestBase() { @Test diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestNotificationAction.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestNotificationAction.kt index b6177c556..3c48dd2b1 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestNotificationAction.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestNotificationAction.kt @@ -21,7 +21,6 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import com.keylesspalace.tusky.entity.Relationship import io.reactivex.rxjava3.core.Single -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.mockito.kotlin.any @@ -39,7 +38,6 @@ import org.mockito.kotlin.verify * This is only tested in the success case; if it passed there it must also * have passed in the error case. */ -@OptIn(ExperimentalCoroutinesApi::class) class NotificationsViewModelTestNotificationAction : NotificationsViewModelTestBase() { /** Dummy relationship */ private val relationship = Relationship( diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusAction.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusAction.kt index 3a102c12a..cf7ce2dc1 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusAction.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusAction.kt @@ -22,7 +22,6 @@ import at.connyduck.calladapter.networkresult.NetworkResult import com.google.common.truth.Truth.assertThat import com.keylesspalace.tusky.FilterV1Test.Companion.mockStatus import com.keylesspalace.tusky.viewdata.StatusViewData -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.mockito.kotlin.any @@ -40,7 +39,6 @@ import org.mockito.kotlin.verify * This is only tested in the success case; if it passed there it must also * have passed in the error case. */ -@OptIn(ExperimentalCoroutinesApi::class) class NotificationsViewModelTestStatusAction : NotificationsViewModelTestBase() { private val status = mockStatus(pollOptions = listOf("Choice 1", "Choice 2", "Choice 3")) private val statusViewData = StatusViewData.Concrete( diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusDisplayOptions.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusDisplayOptions.kt index 29e3c7a18..f3dd3c478 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusDisplayOptions.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestStatusDisplayOptions.kt @@ -23,7 +23,6 @@ import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.StatusDisplayOptions -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test @@ -34,7 +33,6 @@ import org.junit.Test * - Does the make() function correctly use an updated preference? * - Is the correct update emitted when a relevant preference changes? */ -@OptIn(ExperimentalCoroutinesApi::class) class NotificationsViewModelTestStatusDisplayOptions : NotificationsViewModelTestBase() { private val defaultStatusDisplayOptions = StatusDisplayOptions( diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestUiState.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestUiState.kt index a77e503ca..e295dda05 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestUiState.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestUiState.kt @@ -22,7 +22,6 @@ import com.google.common.truth.Truth.assertThat import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.entity.Notification import com.keylesspalace.tusky.settings.PrefKeys -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test @@ -32,7 +31,6 @@ import org.junit.Test * - Is the initial value taken from values in sharedPreferences and account? * - Is the correct update emitted when a relevant preference changes? */ -@OptIn(ExperimentalCoroutinesApi::class) class NotificationsViewModelTestUiState : NotificationsViewModelTestBase() { private val initialUiState = UiState( diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestVisibleId.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestVisibleId.kt index 03a9a8708..f6b7360e6 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestVisibleId.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModelTestVisibleId.kt @@ -19,13 +19,11 @@ package com.keylesspalace.tusky.components.notifications import com.google.common.truth.Truth.assertThat import com.keylesspalace.tusky.db.AccountEntity -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.verify -@OptIn(ExperimentalCoroutinesApi::class) class NotificationsViewModelTestVisibleId : NotificationsViewModelTestBase() { @Test diff --git a/gradle.properties b/gradle.properties index dc6a0c69a..84fe860c9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,9 +5,6 @@ org.gradle.jvmargs=-XX:+UseParallelGC -Xmx4g -Dfile.encoding=UTF-8 -XX:MaxMetasp org.gradle.parallel=true org.gradle.configuration-cache=true -# https://blog.jetbrains.com/kotlin/2022/07/a-new-approach-to-incremental-compilation-in-kotlin/ -kotlin.incremental.useClasspathSnapshot=true - # Disable buildFeatures flags by default android.defaults.buildfeatures.resvalues=false android.defaults.buildfeatures.shaders=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1ef4a5ceb..27c4b7fa1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ glide = "4.15.1" # Deliberate downgrade, https://github.com/tuskyapp/Tusky/issues/3631 glide-animation-plugin = "2.23.0" gson = "2.10.1" -kotlin = "1.8.22" +kotlin = "1.9.0" image-cropper = "4.3.2" material = "1.9.0" material-drawer = "8.4.5" @@ -56,7 +56,7 @@ xmlwriter = "1.0.4" [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -google-ksp = "com.google.devtools.ksp:1.8.22-1.0.11" +google-ksp = "com.google.devtools.ksp:1.9.0-1.0.12" kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } @@ -113,7 +113,6 @@ glide-compiler = { module = "com.github.bumptech.glide:ksp", version.ref = "glid glide-core = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } glide-okhttp3-integration = { module = "com.github.bumptech.glide:okhttp3-integration", version.ref = "glide" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" } -kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-coroutines-rx3 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx3", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } diff --git a/renovate.json b/renovate.json index f9c2c3270..79fe8ab4e 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,17 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" + ], + "packageRules": [ + { + "groupName": "Kotlin", + "groupSlug": "kotlin", + "matchPackagePrefixes": [ + "com.google.devtools.ksp" + ], + "matchPackagePatterns": [ + "org.jetbrains.kotlin.*" + ] + } ] } From 561af5f1054d4a75741a5e49d41c2a5f1772763f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 12:19:50 +0200 Subject: [PATCH 037/254] Update dependency com.github.MikeOrtiz:TouchImageView to v3.5 (#3914) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.github.MikeOrtiz:TouchImageView](https://togithub.com/MikeOrtiz/TouchImageView) | `3.4` -> `3.5` | [![age](https://developer.mend.io/api/mc/badges/age/maven/com.github.MikeOrtiz:TouchImageView/3.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.github.MikeOrtiz:TouchImageView/3.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.github.MikeOrtiz:TouchImageView/3.4/3.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.github.MikeOrtiz:TouchImageView/3.4/3.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
MikeOrtiz/TouchImageView (com.github.MikeOrtiz:TouchImageView) ### [`v3.5`](https://togithub.com/MikeOrtiz/TouchImageView/releases/tag/3.5) [Compare Source](https://togithub.com/MikeOrtiz/TouchImageView/compare/3.4...3.5) #### What's Changed ##### Exciting New Features 🎉 - Java 17 by [@​hannesa2](https://togithub.com/hannesa2) in [https://github.com/MikeOrtiz/TouchImageView/pull/541](https://togithub.com/MikeOrtiz/TouchImageView/pull/541) - Android Studio Flamingo by [@​hannesa2](https://togithub.com/hannesa2) in [https://github.com/MikeOrtiz/TouchImageView/pull/542](https://togithub.com/MikeOrtiz/TouchImageView/pull/542) - Android Studio Giraffe by [@​hannesa2](https://togithub.com/hannesa2) in [https://github.com/MikeOrtiz/TouchImageView/pull/547](https://togithub.com/MikeOrtiz/TouchImageView/pull/547) ##### Other Changes - Bump gradle/wrapper-validation-action from 1.0.6 to 1.1.0 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/MikeOrtiz/TouchImageView/pull/544](https://togithub.com/MikeOrtiz/TouchImageView/pull/544) **Full Changelog**: https://github.com/MikeOrtiz/TouchImageView/compare/3.4...3.5
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/tuskyapp/Tusky). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 27c4b7fa1..28e6e21f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -48,7 +48,7 @@ rxandroid3 = "3.0.2" rxjava3 = "3.1.6" rxkotlin3 = "3.0.1" sparkbutton = "4.1.0" -touchimageview = "3.4" +touchimageview = "3.5" truth = "1.1.5" turbine = "1.0.0" unified-push = "2.1.1" From 9cda091d03917a7da504145e93c362e7cb801bb5 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Thu, 3 Aug 2023 12:20:35 +0200 Subject: [PATCH 038/254] Show additional bug report info in AboutActivity (#3802) Make it easier for people to find information we need for a bug report, and show it on AboutActivity. New info is: - Device manufacturer (e.g., "Google") and model (e.g., "Pixel 4a (5G)") - Android version (e.g., "13") - SDK version (e.g., "33") - Active account (e.g., "@Tusky@mastodon.social") - Server's version (e.g., "4.1.2+nightly-20230627") All info is copyable to make it easy to include in a bug report. A button to copy the information is also shown. --- .../com/keylesspalace/tusky/AboutActivity.kt | 44 +++++ .../components/instanceinfo/InstanceInfo.kt | 3 +- .../instanceinfo/InstanceInfoRepository.kt | 3 +- .../main/res/drawable/ic_content_copy_24.xml | 5 + app/src/main/res/layout/activity_about.xml | 158 ++++++++++++++---- app/src/main/res/values/strings.xml | 16 +- 6 files changed, 183 insertions(+), 46 deletions(-) create mode 100644 app/src/main/res/drawable/ic_content_copy_24.xml diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt index 7c2eedccd..b8b64692e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt @@ -1,6 +1,10 @@ package com.keylesspalace.tusky +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.content.Intent +import android.os.Build import android.os.Bundle import android.text.SpannableString import android.text.SpannableStringBuilder @@ -8,13 +12,21 @@ import android.text.method.LinkMovementMethod import android.text.style.URLSpan import android.text.util.Linkify import android.widget.TextView +import android.widget.Toast import androidx.annotation.StringRes +import androidx.lifecycle.lifecycleScope +import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository import com.keylesspalace.tusky.databinding.ActivityAboutBinding import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.util.NoUnderlineURLSpan import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show +import kotlinx.coroutines.launch +import javax.inject.Inject class AboutActivity : BottomSheetActivity(), Injectable { + @Inject + lateinit var instanceInfoRepository: InstanceInfoRepository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -32,6 +44,28 @@ class AboutActivity : BottomSheetActivity(), Injectable { binding.versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME) + binding.deviceInfo.text = getString( + R.string.about_device_info, + Build.MANUFACTURER, + Build.MODEL, + Build.VERSION.RELEASE, + Build.VERSION.SDK_INT + ) + + lifecycleScope.launch { + accountManager.activeAccount?.let { account -> + val instanceInfo = instanceInfoRepository.getInstanceInfo() + binding.accountInfo.text = getString( + R.string.about_account_info, + account.username, + account.domain, + instanceInfo.version + ) + binding.accountInfoTitle.show() + binding.accountInfo.show() + } + } + if (BuildConfig.CUSTOM_INSTANCE.isBlank()) { binding.aboutPoweredByTusky.hide() } @@ -47,6 +81,16 @@ class AboutActivity : BottomSheetActivity(), Injectable { binding.aboutLicensesButton.setOnClickListener { startActivityWithSlideInAnimation(Intent(this, LicenseActivity::class.java)) } + + binding.copyDeviceInfo.setOnClickListener { + val text = "${binding.versionTextView.text}\n\nDevice:\n\n${binding.deviceInfo.text}\n\nAccount:\n\n${binding.accountInfo.text}" + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("Tusky version information", text) + clipboard.setPrimaryClip(clip) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + Toast.makeText(this, getString(R.string.about_copied), Toast.LENGTH_SHORT).show() + } + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt index bb97621c1..582df02e8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt @@ -28,5 +28,6 @@ data class InstanceInfo( val maxMediaAttachments: Int, val maxFields: Int, val maxFieldNameLength: Int?, - val maxFieldValueLength: Int? + val maxFieldValueLength: Int?, + val version: String? ) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt index bfc5a9b8c..1045fe480 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt @@ -99,7 +99,8 @@ class InstanceInfoRepository @Inject constructor( maxMediaAttachments = instanceInfo?.maxMediaAttachments ?: DEFAULT_MAX_MEDIA_ATTACHMENTS, maxFields = instanceInfo?.maxFields ?: DEFAULT_MAX_ACCOUNT_FIELDS, maxFieldNameLength = instanceInfo?.maxFieldNameLength, - maxFieldValueLength = instanceInfo?.maxFieldValueLength + maxFieldValueLength = instanceInfo?.maxFieldValueLength, + version = instanceInfo?.version ) } } diff --git a/app/src/main/res/drawable/ic_content_copy_24.xml b/app/src/main/res/drawable/ic_content_copy_24.xml new file mode 100644 index 000000000..bac0f6001 --- /dev/null +++ b/app/src/main/res/drawable/ic_content_copy_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index 91919bc6b..0b46cda4c 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -21,104 +21,190 @@ android:layout_gravity="center" android:textDirection="anyRtl"> - + + + + + android:textAppearance="@style/TextAppearance.AppCompat.Medium" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/versionTextView" + tools:text="Your device" /> + + + + + + + + - + android:textAppearance="@style/TextAppearance.AppCompat.Subhead" + app:layout_constraintEnd_toEndOf="@+id/copyDeviceInfo" + app:layout_constraintStart_toStartOf="@+id/deviceInfo" + app:layout_constraintTop_toBottomOf="@+id/accountInfo" /> + app:layout_constraintEnd_toEndOf="@+id/aboutWebsiteInfoTextView" + app:layout_constraintStart_toStartOf="@+id/aboutWebsiteInfoTextView" + app:layout_constraintTop_toBottomOf="@id/aboutWebsiteInfoTextView" />