Previously, the thread indicator would start at the top of the avatar
for the status at the start of the thread, and end at the top of the
avatar for the status at the end of the thread.
If these avatars were partially transparent the thread indicator could
either (a) poke out of the top of the avatar at the start of the thread,
(b) not properly connect with the avatar at the end of the thread, or
(c) both.
Partially fix this by making the divider start/stop in the middle of the
avatar. This assumes that this area will typically have opaque content,
even if some of the rest of the avatar is transparent. This is not
always true, but it's still better than the current behaviour.
Avatars that are semi-transparent are a problem when viewing a thread,
as the line that connects different statuses in the same thread is drawn
underneath the avatar and is visible.
Fix this with a CompositeWithOpaqueBackground Glide transformation that:
1. Extracts the alpha channel from the avatar image
2. Converts the alpha to a 1bpp mask
3. Draws that mask on a new bitmap, with the appropriate background
colour
4. Draws the original bitmap on top of that
So any partially transparent areas of the original image are drawn over
a solid background colour, so anything drawn under them will not appear.
If:
1. You're viewing an account's media tab
2. Some of the media was marked sensitivei
3. The `alwaysShowSensitiveMedia` setting was `true`
tapping on the image (once) would do nothing visible, because it was
treated as the "reveal sensitive media" tap. You had to tap on it a
second time to open it.
Fix this, by passing the preference value through to the relevant code.
---------
Co-authored-by: Tiga! <maxiinne@proton.me>
To determine the earliest day to show in the calendar, take the current
date/time, add the minimum scheduled seconds buffer (which may roll the
date/time over to the next day), and then clamp to the start of that
day. So it's either today (if the current time + minimum scheduled
seconds is less than midnight) or it's tomorrow.
When displaying the calendar work around a misfeature in Material Date
Picker. It accepts UTC seconds-since-epoch, but does not convert it to
the local time for display.
While I'm here, show the selected day in the time picker's title.
Fixes https://github.com/tuskyapp/Tusky/issues/3916
The previous code used `notificationTabPosition`, which was never
changed, so always 0.
This meant that if you e.g., got to `MainActivity` by clicking on a
notification, and the notification tab was current, the title would
still show "Home".
Fix that by using the existing `position` variable which represents the
currently selected tab, and ensure the correct title is shown.
Fixes#3864.
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.
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
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
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.
GBoard and other IME's support pasting images, which are converted to attachments.
Sometimes these have labels that describe the image. If present, set it as the default alt-text.
Fixes#3799
Fix a crash where workers, in some conditions, should show a notification. These are sent to a dedicated channel with no importance.
Convert NotificationWorker to a CoroutineWorker and remove its use of `runBlocking`.
Fixes#3754
Most lists in the app use (explicitly or implicitly) platform metrics for dimensions, text size, colour, and so on, possibly via styles.
A few don't, inadvertently using the user's setting for status text size
Fix these, and simplify code where possible.
- Use android attributes for padding and height, for consistent UX.
- Remove explicit usage of app:tabTextAppearance, rely on the style.
- Remove ListSelectionAdapter and item_picker_list.xml, and adjust TabPreferenceActivity to use an ArrayAdapter with simple_list_item_1.xml
- Simplify item_followed_hashtag.xml, consistent with item_list.xml.
Fixes https://github.com/tuskyapp/Tusky/issues/3131
Font scaling is applied in addition to any scaling set in Android system preferences. So if the user set the Android font size to largest (a 1.3x increase) and then sets the preference to 120%, the total change is 1.56x.
Create SliderPreference to adjust the preference.
- Use Slider, which supports float values and step sizes > 1
- Display the selected value in the preference's summary
- Provide buttons to increment / decrement the value
Restart the activity if the preference changes so that the user sees the impact of the change immediately. Fix a bug in PreferencesActivity where the "EXTRA_RESTART_ON_BACK" intent was never processed. Fix this to ensure that other activities are restarted so the new font scale takes effect.
Implement the scaling in BaseActivity by overriding onAttachBaseContext, and providing a wrapped context with the font scaling applied.
Fixes https://github.com/tuskyapp/Tusky/issues/2982, https://github.com/tuskyapp/Tusky/issues/2461
Instead of repeating the same if/else check on the error type when setting up the background message, move this in to BackgroundMessageView.
Provide different `setup()` variants, including one that just takes a throwable and a handler, and figures out the correct drawables and error message.
Update and simplify call sites.
This can happen if the edit history has not been propogated to the user's server.
If the edit history is missing then show an error with a link to the specifc Mastodon issue.
Fixes#3743
When the user is closing the compose view,
if it's new and empty, don't show a prompt.
if it's an existing draft and now empty, ask if the user wants to delete it or continue editing. I don't think there is much value in saving an empty draft.
---------
When NotificationWorker was moved from ...components.notifications to
...worker.
Installing Tusky with this change doesn't remove any future periodic
jobs queued under the old class name. So when Class.forName() is
called the old class name is not found, and the exception is thrown.
Handle this the same way androidx.work.WorkerFactory does -- catch
the exception, log it, and return null.
Fixes#3740
- Create a flow with new items (arbitrary ints) when a reload from the top should happen
- Combine this flow with notificationFilter, so changes to either of them trigger a reload
- Provide a menu item in NotificationsFragment to initiate the reload
- Handle the action in the view model
Remove the use of ReplacementSpan. It turns out this span type is incompatible with spans that occupy more than one line, and the result is that a longer diff can run off the end of the screen. The alternative means that the diff'd text doesn't have additional padding and rounded corners, but it's better than not being visible.
Display the most recent version of the status with larger text. Again, consistent with the thread view.
Display the avatar, name, and username of the poster in a pinned header at the top of the screen, instead of duplicating the information on every edit. This reduces the amount of redundant information on the screen.
The Android libraries have a bug where a TextView can forget that it contains selectable text, can be pasted in to, etc.
See https://issuetracker.google.com/issues/37095917
Fix this with an extension method that toggles the selectable state to re-enable it, and use this on the profile fields when editing an account.
Fixes https://github.com/tuskyapp/Tusky/issues/3706
- Use NO_POSITION instead of hardcoding 0.
- Don't set a state restoration policy, PagingDataAdapter already does that
- Return the closest item, not just the closest page, in getRefreshKey
This will make tests that need it easier.
- Rename from AccountPreferenceHandler
- Inject its dependencies
- Create an injectable CoroutineScope it can use for launching coroutines
- Use it in AccountPreferences
In the previous code any errors that occured *before* a subscriber was
listening to `uiError` would be dropped, so the user would be unware
of them.
By implementing as a channel these errors will be shown to the user,
with an opportunity to retry the operation or report the error.
Introduce Flow<T>.throttleFirst(). In a flow this emits the first value,
and each value afterwards that is > some timeout after the previous
value.
This prevents accidental double-taps on UI elements from generating
multiple-actions.
The previous code used debounce(). That has a similar effect, but with
debounce() the code has to wait until after the timeout period has
elapsed before it can process the action, leading to an unnecessary
UI delay.
With throttleFirst a value is emitted immediately, there's no need
to wait. It's subsequent values that are potentially throttled.
- Extend what was `NotificationWorkerFactory` to `WorkerFactory`. This
can construct arbitrary Workers as long as they provide their own
Factory for construction.
The per-Worker factory contains any injected components just for that
worker type, keeping `WorkerFactory` clean.
- Move `NotificationWorkerFactory` to the new model.
- Implement `PruneCacheWorker`, and remove the code from
`CachedTimelineViewModel`.
- Create the periodic worker in `TuskyApplication`, ensuring that the
database is only pruned when the device is idle.
formatNumber() was existing code to show numbers with suffixes like K, M, etc, so re-use that code and delete shortNumber().
Update the tests to (a) test formatNumber(), and (b) be parameterised.
* Fetch all outstanding Mastodon notifications when creating Android notifications
Previous code fetched the oldest page of unfetched Mastodon notifications.
If you had more than a page of Mastodon notifications you'd get Android notifications for that page, then ~ 15 minutes later Android notifications for the next page, and so on.
This code fetches all the outstanding notifications at once.
If this results in more than 40 total notifications the list is still trimmed so that a maximum of 40 Android notifications is displayed.
Fixes https://github.com/tuskyapp/Tusky/issues/3648
* Build the list using buildList
Don't use `accountManager.activeAccount` throughout the code.
Instead, get the active account once, and use that over the life of the viewmodel.
As shown in https://github.com/tuskyapp/Tusky/issues/3689#issuecomment-1567219338 the active account can change before the view model is destroyed, and if that happens account information for the account will be written to the wrong account.
Fix a bug where the active account can be overwritten.
1. Have two accounts logged in to Tusky, A and B
2. Open Tusky as account A
3. Send a DM to account B (doesn't have to be a DM, just anything that creates a notification for account B)
4. Restart Tusky so the Android notification for the DM is displayed immediately. You are still acting as account A.
5. Drag down the Android notification, you should see two options, "Quick reply" and "Compose"
6. Tap "Compose"
7. ComposeActivity will start. You are now acting as account B. Compose and send a reply
8. You'll be returned to the "Home" tab.
The UI will show you are still account A (i.e., it's account A's avatar at the top of the screen, if you have the "Show username in toolbars" option turned on it will be account A's username in the toolbar).
But you are now seeing the home timeline for account B.
Fix this.
ComposeViewModel
- Do not rely on the active account in sendStatus(), receive the account ID as a parameter
ComposeActivity
- Use either the account ID from the intent, or the current active account. **Do not** change the active account
- Pass the account ID to use in the sendStatus() call
The previous code gets the user's reading position *once*, when the
viewmodel is created, and uses that whenever it needs to be restored.
This is a problem. Suppose the user is a few days behind on their
notifications, and opens the app.
The reading position is restored, as expected. They scroll up to start
reading newer notifications.
Then they change their notification filters. This causes the
notifications list to change, and when it does their reading position
is set back to what it was when they first switched to the Notifications
tab.
Fix this by:
NotificationsFragment:
- Save the reading position whenever the user stops scrolling.
NotificationsViewModel:
- Use the saved reading position whenever the list of notifications
can change, not just when the view model is created.
Not all servers support the marker API. If they don't the user will
potentially get duplicate Android notifications.
To resolve this, store a copy of the notification marker ID locally as
well. Defer to the remote marker if it exists (and is newer than the
local marker).
Fixes https://github.com/tuskyapp/Tusky/issues/3671
When fetching:
- Maintain a marker with the position of the newest fetched notification
- Use the marker to determine which notifications to fetch
- Fetch notifications with min_id to ensure that none are lost
- Update the marker as necessary
- Perform a one-time immediate fetch of notifications on startup
When creating notifications:
- Identify each notification with tag=${MastodonNotificationId}, id=${account.id}
- Remove activeNotifications field, it's no longer necessary
- Use the tag/id tuple to reliably identify existing notifications and avoid creating duplicates
- Cancelling notifications for an account must iterate over all the notifications, and individually remove the notifications that exist for that account.
- Limit notifications to a maximum of 40 (excluding summary notifications)
- Remove notifications (oldest first) to get under this limit
- Rate limit notification creation to 1 per second, so the OS won't drop them
Adjust the summary notification:
- Ensure the summary notification and the child notifications have the same group key
- Dismiss the summary notification if there is only one child notification
NotificationClearBroadcastReceiver is no longer needed, so remove it, and the need for deletePendingIntent.
Fixes#3625, #3539
- Add a field to AccountEntity to hold the reading position
- Provide a method to save in the viewmodel to save the position
- Save the position when TimelineFragment pauses
Does not restore the position yet.
Requesting a non-existent notification ID will cause the fall-back requests to succeed but return empty lists which stops pagination.
Check for this, and in the worst case, fall back to returning the most recent notifications.
On Account preferences > Filters
With the list hidden on an empty view the FAB is erroneouly placed on the top left of the screen.
(Introduced with #3430)
The pageSize is large enough that there's no need for the default 3 x pageSize initialLoadSize value, and this improves performance.
Fixes https://github.com/tuskyapp/Tusky/issues/3578
This makes the notification view for a follow request contain more info about the new follower, and makes the layout (of their name / username) consistent with other notifications that show names/usernames.
Previous code would discard the image alt-text if the user finished writing the text before the image had finished uploading.
This code ensures the text is set after the image has completed uploading.
* 3416: Call the list activity when empty; show failure on loading
* 3416: Revert include grouping
* 3416: Remove faulty include after merge
* 3416: Added a list loading progress
* 3416: Add proper padding to progress
* 3416: Show a text if there are no lists, do not change dialog title
* 3416: Center things in layout
* 3416: Appease linter (?)
* 3416: Do not hide manage lists button
* 3416: Use ThemeUtils
* 3434: Make description dialog (text field) more usable
* 3434: Close dialog on back button
* 3434: Use a TextInputLayout
* 3434: Adapt German plurals text
* 3434: Remove unused id
* 3434: Disable counter officially