Closes#4353 by addressing the last edge case: The swipe to refresh
won't be enabled before something is loaded, preventing multiple loads
at the same time. After the first load, the swipe to refresh itself
prevents multiple loads: It can be swiped only once.
---------
Co-authored-by: Goooler <wangzongler@gmail.com>
Adds auto keyboard popup to ListsActivity, FollowedTagsActivity,
EditFilterActivity, CaptionDialog, and AddPollDialog.kt, and
AddHashtagDialog in TabPreferenceActivity. These are all dialog screens
which editing texts are the main purposes
---------
Co-authored-by: Weblate <42475313+nailyk-weblate@users.noreply.github.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Manuel <mannivuwiki@gmail.com>
Co-authored-by: Eric <ekhfcxwuvxqfdb@hldrive.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
regression from https://github.com/tuskyapp/Tusky/pull/4599https://pug.ninja/@motoridersd/113102603349423738
The problem is, `MediaUploader` is no longer a singleton, so the state
is not correctly shared between `ComposeActivity` and
`SendStatusService.` But it can't be a singleton, since `MediaUploadApi`
(injected into `MediaUploader`) needs to be recreated on account change.
Let's share the state in the companion object instead.
This one is odd.
I find that if I
- Add an image to a post
- Set a caption on the image
- Tap the caption dialog to bring up the keyboard
- Tap the navbar (very easy to do by accident while typing, just tap
under the spacebar)
The caption dialog disappears and you lose your caption.
(This doesn't happen in ANY other dialog in the app, and in the caption
dialog it ONLY happens when the keyboard is up.)
I got a tablet and on the tablet this happens ALL THE TIME.
I was considering adding a "really cancel?" dialog to the caption
dialog. But then I discovered (thank you @memorion@mastodon.social) by
setting getCanceledOnTouchOutside(false), I could make the bad behavior
go ahead completely. In my tests now I can only make the dialog go away
by tapping the actual back button.
I believe the potential negative impact of this is low because the
caption dialog is full screen, so it was already impossible to trigger
the touch outside behavior on *purpose*.
I might attempt a "really cancel caption?" dialog later because it sucks
to lose a long caption you have typed, but I think this fixes my
personal problem by itself.
---------
Co-authored-by: Konrad Pozniak <connyduck@users.noreply.github.com>
```
android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:961)
at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:790)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:89)
at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.kt:42)
at androidx.room.EntityInsertionAdapter.insertAndReturnId(EntityInsertionAdapter.kt:101)
at com.keylesspalace.tusky.db.dao.TimelineStatusDao_Impl$insert$2.call(TimelineStatusDao_Impl.kt:345)
at com.keylesspalace.tusky.db.dao.TimelineStatusDao_Impl$insert$2.call(TimelineStatusDao_Impl.kt:340)
at androidx.room.CoroutinesRoom$Companion.execute(CoroutinesRoom.kt:56)
at com.keylesspalace.tusky.db.dao.TimelineStatusDao_Impl.insert(TimelineStatusDao_Impl.kt:340)
at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator.replaceNotificationRange(NotificationsRemoteMediator.kt:169)
at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator.access$replaceNotificationRange(NotificationsRemoteMediator.kt:36)
at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator$load$3.invokeSuspend(NotificationsRemoteMediator.kt:109)
at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator$load$3.invoke(Unknown Source:8)
at com.keylesspalace.tusky.components.notifications.NotificationsRemoteMediator$load$3.invoke(Unknown Source:2)
at androidx.room.RoomDatabaseKt$withTransaction$transactionBlock$1.invokeSuspend(RoomDatabaseExt.kt:62)
at androidx.room.RoomDatabaseKt$withTransaction$transactionBlock$1.invoke(Unknown Source:8)
at androidx.room.RoomDatabaseKt$withTransaction$transactionBlock$1.invoke(Unknown Source:4)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:61)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:163)
at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
at androidx.room.RoomDatabaseKt$startTransactionCoroutine$2$1$1.invokeSuspend(RoomDatabaseExt.kt:103)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:95)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:69)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source:1)
at androidx.room.RoomDatabaseKt$startTransactionCoroutine$2$1.run(RoomDatabaseExt.kt:99)
at androidx.room.TransactionExecutor.execute$lambda$1$lambda$0(TransactionExecutor.kt:36)
at androidx.room.TransactionExecutor.$r8$lambda$FZWr2PGmP3sgXLCiri-DCcePXSs(Unknown Source:0)
at androidx.room.TransactionExecutor$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at java.lang.Thread.run(Thread.java:1012)
```
It looks kinda weird because "x just posted" has a different user than
the actual post, but it works for groups I guess? And definitely better
than crashing.
<img
src="https://github.com/user-attachments/assets/8110ff17-674d-4f36-8df0-453a666856a6"
width="320"/>
closes#4563
closes#4631closes#4629
and other weirdness introduced in Tusky 26.1.
I did a lot of testing on 2 physical devices and multiple emulators. It
definitely is better than before, but probably still not perfect.
Adds feature if user clicks on toolbar, on the pages box, or on the tabs
buttons that the keyboard will minimize and focus will be taken off of
search bar.
Further addresses issue #4573
This fixes a bug where the timeline would jump to the position of a
previously clicked "load more" when new post are inserted at the top
after swiping to refresh.
This only happens when "Reading order" is set to "Oldest first".
The problem is that updateReadingPositionForOldestFirst (which consumes
the marker to which it should jump) is called from onItemRangeInserted.
But onItemRangeInserted is not called everytime a "load more" is
clicked, leaving the marker active, causing a jump the next time
onItemRangeInserted is called.
I am not sure this fixes all cases of the various jumping bugs that have
been reported recently as this was the only one I could reliably
reproduce.
Related to https://github.com/tuskyapp/Tusky/issues/4493
This does 2 things:
- Removes `AccountSwitchInterceptor`, the main culprit for the bug. APIs
can no longer change their base url after they have been created. As a
result they are not Singletons anymore.
- Additionally, I refactored how MainActivity handles Intents to make it
less likely to have multiple instances of it active.
Here is how I could reliably reproduce the bug:
- Be logged in with account A and B
- Write a post with account A, cancel it before it sends (go into flight
mode for that)
- Switch to account B
- Open the "this post failed to send" notification from account A,
drafts will open
- Go back. You are in the MainActivity of account A, everything seems
fine.
- Go back again. You are in the old, now broken MainActivity of account
B. It uses the database of account B but the network of account A.
Refreshing will show posts from A.
closes#4567closes#4554closes#4402closes#4148closes#2663
and possibly #4588
This will make it less likely that data from different accounts will get
mixed up.
I checked the codepaths and we shouldn't hit viewModel if account is not
there but this is still a bit of risk to assume that so I'll investigate
if we can pass in more things and use DI to make it more secure
---------
Co-authored-by: Conny Duck <git@connyduck.at>
Addresses issue #4573. Updates searchView in SearchActivity to clear
focus when a user makes a search. This allows the keyboard to not keep
popping up after a user goes back to search screen after clicking on a
searched item.
This is the third attempt to fix `RequestBuilder.submitAsync()`. For the
rationale, see the comments of #4436.
We now clear the continuation reference after resuming it, to make sure
that:
1) It will only be resumed once
2) It will not leak the coroutine when Glide keeps the `Request` around.
Without the `isPersistent = false` flag the preference is saved to
SharedPreferences in addition to the account and overrides the shown
value. So it would always show the value of the last account it was set
to. It was working fine though, only the shown value was wrong.
https://social.sitedethib.com/@Claire/112769984398891581
https://social.froonix.org/@cs/112767747835228296
We were caching the instance info with the instance name as the key and
then look it up with the actual domain and those do not always match so
the check if translation is supported fails. fnx.li vs
social.froonix.org in this case.
re: https://infosec.exchange/@webhat/112745609655586468
Boost were not correctly handled here, probably because they are only on
profile timelines which I rarely check. This makes sure likes and boosts
get correctly set to posts even when they are boosts.
```
Exception java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setClipToOutline(boolean)' on a null object reference
at com.keylesspalace.tusky.adapter.StatusBaseViewHolder.<init> (StatusBaseViewHolder.java:150)
at com.keylesspalace.tusky.adapter.StatusViewHolder.<init> (StatusViewHolder.java:55)
at com.keylesspalace.tusky.components.notifications.UnknownNotificationViewHolder.<init> (UnknownNotificationViewHolder.java:27)
at com.keylesspalace.tusky.components.notifications.NotificationsPagingAdapter.onCreateViewHolder (NotificationsPagingAdapter.kt:139)
at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder (RecyclerView.java:7788)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline (RecyclerView.java:6873)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition (RecyclerView.java:6757)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition (RecyclerView.java:6753)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next (LinearLayoutManager.java:2362)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk (LinearLayoutManager.java:1662)
at androidx.recyclerview.widget.LinearLayoutManager.fill (LinearLayoutManager.java:1622)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren (LinearLayoutManager.java:687)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2 (RecyclerView.java:4645)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout (RecyclerView.java:4348)
at androidx.recyclerview.widget.RecyclerView.onLayout (RecyclerView.java:4919)
...
```
A user still on Mastodon 3 complained that since my change in
https://github.com/tuskyapp/Tusky/pull/4539 the timeline reloads on
starting Tusky. Well, we need to do some reloading of the timeline after
loading the v1 filters or they won't work. Changing the `fullReload` to
`invalidate` improves the situation by not loading everything from the
network again (just the database) but there is still some noticeable
loading.
(I'm not willing to invest any more time to support these old instances)
This is way more efficient than before as less views need to be inflated
and bound for a filtered status to be rendered. It also should fix the
bug where sometimes a `StatusViewHolder` that is set up for showing a
status gets bound to a status that is filtered, leading to a crash.
Instead of calling the endpoint every time filters are needed, it will
be called only once and the result cached. This will result in quite
some requests less on instances supporting v2.
I also tested v1 filters and made some small improvements. We should
[remove filters v1
support](https://github.com/tuskyapp/Tusky/issues/4538) some time in the
future though.
```
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
at com.keylesspalace.tusky.adapter.StatusBaseViewHolder.setupFilterPlaceholder(StatusBaseViewHolder.java:894)
at com.keylesspalace.tusky.adapter.StatusBaseViewHolder.setupWithStatus(StatusBaseViewHolder.java:825)
at com.keylesspalace.tusky.adapter.StatusViewHolder.setupWithStatus(StatusViewHolder.java:91)
at com.keylesspalace.tusky.components.notifications.StatusViewHolder.bind(StatusViewHolder.kt:46)
at com.keylesspalace.tusky.components.notifications.NotificationsPagingAdapter.bindViewHolder(NotificationsPagingAdapter.kt:150)
at com.keylesspalace.tusky.components.notifications.NotificationsPagingAdapter.onBindViewHolder(NotificationsPagingAdapter.kt:143)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7847)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6646)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6917)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6757)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6753)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2362)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1662)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1622)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:687)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4645)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:4348)
at androidx.recyclerview.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:2106)
at androidx.recyclerview.widget.RecyclerView$1.run(RecyclerView.java:468)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1231)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239)
at android.view.Choreographer.doCallbacks(Choreographer.java:899)
at android.view.Choreographer.doFrame(Choreographer.java:827)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7872)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
```
Emojis are sorted by category within the emoji picker. The original
alphabetical sorting is preserved within categories.
This partially addresses #1868 and is only a small part of what is done
in #3300, but I think it is still enough to improve quality of life for
users.
When investigating #4517, I found that sharing to Tusky sometimes just
does nothing.
This is because when `MainActivity` is already open, it isn't always
restarted (`onCreate` not called) but instead the Intent is delivered to
`onNewIntent` of the existing `MainActivity`. This fixes the issue by
overriding `onNewIntent`.
The problem does not always reproduce, it seems to depend on what is
shared from where and on the Android version. `MainActivity` must be
open for the problem to occur though.
This probably also fixes the issue that sometimes Tusky does not show
the right tab when clicking on a notification
(https://github.com/tuskyapp/Tusky/issues/2691)
Found this crash in the Google Play reports:
```
Exception java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 0
at jdk.internal.util.Preconditions.outOfBounds (Preconditions.java:64)
at jdk.internal.util.Preconditions.outOfBoundsCheckIndex (Preconditions.java:70)
at jdk.internal.util.Preconditions.checkIndex (Preconditions.java:266)
at java.util.Objects.checkIndex (Objects.java:359)
at java.util.ArrayList.get (ArrayList.java:434)
at java.util.Collections$UnmodifiableList.get (Collections.java:1394)
at com.keylesspalace.tusky.components.compose.MediaPreviewAdapter.onMediaClick (MediaPreviewAdapter.java:45)
at com.keylesspalace.tusky.components.compose.MediaPreviewAdapter.access$onMediaClick (MediaPreviewAdapter.java:32)
at com.keylesspalace.tusky.components.compose.MediaPreviewAdapter$PreviewViewHolder._init_$lambda$0 (MediaPreviewAdapter.java:144)
at android.view.View.performClick (View.java:7535)
at android.view.View.performClickInternal (View.java:7512)
at android.view.View.-$$Nest$mperformClickInternal
at android.view.View$PerformClick.run (View.java:29488)
at android.os.Handler.handleCallback (Handler.java:984)
at android.os.Handler.dispatchMessage (Handler.java:104)
at android.os.Looper.loopOnce (Looper.java:238)
at android.os.Looper.loop (Looper.java:357)
at android.app.ActivityThread.main (ActivityThread.java:8118)
at java.lang.reflect.Method.invoke
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:957)
```
Can't reproduce, but seems to be some kind of race condition where the
view is clicked at the same time as it is being removed from the
`RecyclerView`. Not using the index in the click listener should resolve
the problem. Also refactored to `ListAdapter` to not deal with the
`AsyncListDiffer` manually.
Follow up to https://github.com/tuskyapp/Tusky/pull/3921
- no more hardcoded `tusky_blue`, instead the `colorPrimary` attribute
is used. This will help us when adding more themes, e.g a dynamic color
one.
- The `colorPrimary` of the dark theme is now lighter for more contrast
and subsequently the `colorOnPrimary` is now dark grey instead of white.
- `tusky_red_lighter` is now a bit more red than before
- Tweaked color usage in a few places for better contrast
I think this looks a bit unfamiliar but overall better and the higher
contrast makes things noticeably easier to read.
<img
src="https://github.com/tuskyapp/Tusky/assets/10157047/4cbb92d8-b772-4e94-bc15-c4baf0e5473f"
width="260"/>
This pull request aims to dramatically improve the performance of
`BlurHashDecoder` while also reducing its memory allocations.
- Precompute cosines tables before composing the image so each cosine
value is only computed once.
- Compute cosines tables once if both are identical (for square images
with the same number of colors in both dimensions).
- Store colors in a one-dimension array instead of a two-dimension array
to reduce memory allocations.
- Use a simple `String.indexOf()` to find the index of a Base83 char,
which is both faster and needs less memory than a `HashMap` thanks to
better locality and no boxing of chars.
- No cache is used, so computations may be performed in parallel on
background threads without the need for synchronization which limits
throughput.
## Benchmarks
Simple: 4x4 colors, 32x32 pixels output. (This is what Mastodon and
Tusky currently use)
Complex: 9x9 colors, 256x256 pixels output.
**Pixel 7 (Android 14)**
```
365 738 ns 23 allocs Trace BlurHashDecoderBenchmark.tuskySimple
109 577 ns 8 allocs Trace BlurHashDecoderBenchmark.newSimple
108 771 647 ns 88 allocs Trace BlurHashDecoderBenchmark.tuskyComplex
12 932 076 ns 8 allocs Trace BlurHashDecoderBenchmark.newComplex
```
**Nexus 5 (Android 6)**
```
4 600 937 ns 22 allocs Trace BlurHashDecoderBenchmark.tuskySimple
1 391 487 ns 7 allocs Trace BlurHashDecoderBenchmark.newSimple
1 260 644 948 ns 87 allocs Trace BlurHashDecoderBenchmark.tuskyComplex
125 274 063 ns 7 allocs Trace BlurHashDecoderBenchmark.newComplex
```
Conclusion: The new implementation is **3 times faster** than the old
one for the current usage and up to **9 times faster** if we decide to
increase the BlurHash quality in the future.
The source code of the benchmark comparing the original untouched Kotlin
implementation to the new one can be found
[here](https://github.com/cbeyls/BlurHashAndroidBenchmark).
closes#4499
This restores support for v1 filters. The problem was that the state was
uncoditionally set to error instead of checking the v1 response.
While checking the code I found some other problems:
- Two error messages that were shown to users were not translatable
- When filters were updated sometimes `PreferenceChangedEvent` was sent
instead of `FilterUpdatedEvent`
- The notifications fragment was not listening to the
`FilterUpdatedEvent`
This PR fixes https://github.com/tuskyapp/Tusky/issues/2798 and is
mostly based on and supersedes
https://github.com/tuskyapp/Tusky/pull/2826 but I have fixed all merge
conflicts and unit tests.
I tested the changes locally and the setting takes effect immediately
for replies, and persists across killing the app.
---------
Co-authored-by: Eva Tatarka <eva@tatarka.me>
Co-authored-by: Konrad Pozniak <connyduck@users.noreply.github.com>
As discussed in our contributors meeting.
Advantages:
- last element of list never obscured by action button
- less code that runs on every scroll
- less settings to worry about
Additionally:
- Added a (smaller) padding to the bottom of lists without action
button, I think it looks nice if there is a bit of white space and the
nav bar divider and the last list divider don't touch.
- The list of filters had no dividers, I added them.
- Recyclerviews with fixed height (Drafts, Filters, edits) now have
scrollbars
- code formatted all touched xml files
closes https://github.com/tuskyapp/Tusky/issues/1563
<img
src="https://github.com/tuskyapp/Tusky/assets/10157047/cd50199f-e84f-4402-93e4-a5a1beba2a08"
width="280"/>
At first I thought simply changing the regex might help, but then I
found more and more differences between Mastodon and Tusky, so I decided
to reimplement the thing. I added 74 testcases that I all compared to
Mastodon to make sure they are correct.
On an Fairphone 4 the new implementation is faster, on an Samsung Galaxy
Tab S3 slower.
Testcases for the benchmark:
```
test of a status with #one hashtag http
```
```
test
http:// #hashtag https://connyduck.at/http://example.org
this is a #test
and this is a @mention@test.com @test @test@test456@test.com
```
```
@mention@test.social Just your ordinary mention with a hashtag
#test
```
```
@mention@test.social Just your ordinary mention with a url
https://riot.im/app/#/room/#Tusky:matrix.org
```
FP4:
```
11.159 ns 15 allocs Benchmark.new_1
119.701 ns 43 allocs Benchmark.new_2
21.895 ns 24 allocs Benchmark.new_3
87.512 ns 32 allocs Benchmark.new_4
16.592 ns 46 allocs Benchmark.old_1
134.381 ns 169 allocs Benchmark.old_2
28.355 ns 68 allocs Benchmark.old_3
45.221 ns 77 allocs Benchmark.old_4
```
SGT3:
```
43,785 ns 18 allocs Benchmark.new_1
446,074 ns 43 allocs Benchmark.new_2
78,802 ns 26 allocs Benchmark.new_3
315,478 ns 32 allocs Benchmark.new_4
42,186 ns 45 allocs Benchmark.old_1
353,570 ns 157 allocs Benchmark.old_2
72,376 ns 66 allocs Benchmark.old_3
122,985 ns 74 allocs Benchmark.old_4
```
benchmark code is here: https://github.com/tuskyapp/tusky-span-benchmark
closes https://github.com/tuskyapp/Tusky/issues/4425
This pull request fixes the following issues:
- `FiltersActivity` launches a new coroutine to collect the ViewModel
state every time the Activity is resumed, without cancelling the
previous coroutine.
- `FiltersActivity` reloads the filters in `onResume()`, even if loading
is already in progress (without cancelling the current loading). This
can lead to inconsistent state.
List of improvements:
- Implement `launchAndRepeatOnLifecycle()` to combine
`coroutineScope.launch()` with `repeatOnLifecycle()` for the same
`Lifecycle`. Use it in `FiltersActivity` to update the view only when
the Activity is visible.
- Optimize the filters loading: load them when `FiltersViewModel` is
created and when returning from `EditFilterActivity` (when receiving the
Activity result). Cancel the load already in progress, if any.
- use `MutableStateFlow.update()` to update the state in a thread-safe
way.
- Turn `FiltersViewModel.deleteFilter()` into a suspending function in
order to perform the update in the coroutinescope of the Activity
lifecycle, so the View passed as argument doesn't leak.
- Wait for an ongoing load operation to complete before performing a
delete filter operation, so the state stays consistent.
- Add `Intent.withSlideInAnimation()` as a simpler and more flexible
alternative to `Activity.startActivityWithSlideInAnimation(Intent)`.