Use `unicodeWrap` when inserting placeholders in error messages so they
set the correct text direction.
Update some strings with formatting directives to (a) include `_fmt`
in the name, and (b) use `%1$s` instead of `%s`.
The compose button isn't initially working on `MainActivity`. It is only
properly setup after changing tabs.
Fix by calling `MainActivity.refreshComposeButtonState(TabViewData)` on
creation.
Previously, modifying any tabs meant opening the left-side nav, opening
Account preferences > Tabs, and then adding / removing tabs. This is
time consuming, and difficult for new users to discover.
In addition, it was possible to remove the Home tab, and there was a
hardcoded minimum of at least two tabs.
Fix this.
When viewing a timeline that is not already in a tab an "Add to tab"
menu item is enabled, which appends the timeline to the list of existing
tabs.
When viewing a timeline in a tab (that is not the Home timeline) a
"Remove tab" menu item is enabled, which removes the tab from the list
of existing tabs.
If the user removes the active tab (either with this menu item, or
through preferences) the tab to the left of the active tab becomes the
new active tab.
A new "Manage tabs" menu item is also provided, as a shortcut to the
existing Account preferences > Tabs screen.
When managing tabs the Home timeline can not be removed; the button to
remove it is removed, and swiping is disabled on that list item. The
restriction of "at least two tabs" has also been removed.
`NotificationsActivity` has been removed, as `TimelineActivity` can
display `NotificationsFragment`.
To make the three "Trending" types (hashtags, links, and posts) more
visually distinct add two new icons for links (ic_newspaper) and posts
(ic_whatshot).
Fixes#572, #584, #585, #569
Mastodon API uses an "empty" `expires_in` value for a filter to mean
"Does not expire" (i.e., indefinite).
This was modelled as a null. Which doesn't work, because Retrofit does
not send name/value pairs in encoded forms if the value is null.
Fix this by making the API type a `String?`, and explicitly using the
empty string when indefinite expiry is used. This has to be converted
back to an Int? in a few places.
See
https://github.com/mastodon/documentation/issues/1216#issuecomment-2030222940
Previous code was inconsistent about how and when the FAB was hidden if
the user had set the relevant preference.
- Sometimes the FAB has hidden by setting the visibility to false, which
removed it with no animation.
- Sometimes the value of the preference was checked once, when the
fragment or activity was created.
- Some timelines didn't show the FAB (Hashtags, Favourites, Bookmarks,
TrendingLinks, TrendingStatuses).
- Logic for figuring out which `ComposeActivity` intent to use was
scattered across different files.
Improve this by:
- Expose changes to the `FAB_HIDE` preference in the relevant viewmodels
as a flow the UI component can collect.
- Centralise the show/hide logic in a new `ActionButtonScrollListener`
class, and always using `show()`/`hide()` to animate the transition.
- Centralise the logic for creating the `ComposeActivity` intent in
`TabViewData`.
`TabData` recorded the type of the timeline the user had added to a tab.
`TimelineKind` is another type that records general information about
configured timelines, with identical properties.
There's no need for both, so remove `TabData` and use `TimelineKind` in
its place.
`TimelineKind` is itself mis-named; it's not just the timeline's kind
but also holds data necessary to display that timeline (e.g., the list
ID if it's a `.UserList`, or the hashtags if it's a `.Hashtags`) so
rename to `Timeline` to better reflect its usage. Move it to a new
`core.model` module.
Workaround a Glide bug where the error() handler is not always called,
in this case when the URL does not resolve to an image; for example, a
misconfigured server that redirects requests for the image to an HTML
page.
Catch the exception and use the default avatar image in these cases.
Previous code would handle some expected exceptions (IO, HTTP) when
fetching a timeline, and show them to the user. Any other exception
would crash.
Now, surface all exceptions. Treat IO and HTTP exceptions as retryable
and show the "Retry" option, all others are considered non-retryable.
Provide a specific error string for exceptions caused by bad JSON.
Previous code used custom regular expressions to extract URLs, hashtags,
and mentions from text while the user was writing a post. These were
inconsistent with the ones that Mastodon uses so the derived character
count could be wrong.
As well as being visually incorrect this could prevent the user from
posting a status that was within the length limit, or allow them to
attempt to post a status that was over the length limit (which would
then fail).
Fix this by dropping the homegrown regular expressions and using the
same text parsing library that Mastodon users; twitter-text. This has
been converted to Kotlin and the functionality related to Twitter
specific features has been removed.
The hashtag handling has been adjusted, as Mastodon is more permissive
about the positions where hashtags can appear than Twitter is, in
particular, a hashtag does not need to be preceded with whitespace if
the tag appears after some scripts, such as Hirigana.
The previous bottomsheets did set a minimum height for the menu items,
so they were less than the recommended 48dp minimum. Fix that to improve
the overall accessibility.
Always highlight the "visibilty" icon, to make it clear that it's
something that is set (even if to the default).
Show the visibility icon on the "Toot" button as an additional reminder
to the user.
Other changes:
- Use the "filled" style for all icons (the visibility icons had the
"outlined" style)
- Use the `makeIcon` helper function.
- Use the `Status.Visibility` extension functions to determine the icon
for each visibility type, reducing code duplication.
Show a labelled checkbox to the bottom-right of polls that the user has
not voted in and that have votes. If checked the current vote tally (as
percentages) will be shown, along with a bar showing the relative value
of each option.
Previous code expected callers to typically provide the drawable and the
error message string resource, resulting in duplicate code at many
callsites.
Replace with three canned messages for empty containers, generic errors,
and network errors respectively. The images for these are fixed, the
caller may choose a different string resource for the error if there is
a more specific option.
Update and simplify the call sites.
Move `ListsActivity`, along with fragments and viewmodels, to a new
`feature:lists` module.
Previous code used the `item_follow_request` layout, which was not
ideal, so update it to use a dedicated layout, `item_account_in_list`.
The UI uses strings and views originally defined in the main app, so
move them elsewhere so they can be re-used.
- `BackgroundMessageView` moves to `core.ui`.
- `Lazy` moves to `core.common`.
- `ThrowableExtensions` split; the extensions specific to throwables
from network activity move to `core.network`, others move to `core.ui`.
- `BindingHolder` moves to `core.ui`
- Shared drawables and strings move to `core.ui`.
For reasons not fully understood the root of a fragment's view might not
have relevant view from the activity set as its parent. This causes
`Snackbar.make()` to throw an exception, and crash.
See https://issuetracker.google.com/issues/228215869.
For now, "fix" this by swallowing the exception. Not showing the error
is better than crashing.
The replies policy controls whether replies from members of the list
also appear in the list.
Display the replies policy as three radio buttons when a list is created
or updated, and send the chosen replies policy via the API.
Default value if not specified is always "list", for consistency with
the Mastodon API defaults.
While I'm here:
- Ensure the list dialog layout is inflated using the dialog's themed
context
- Use a `TextInputLayout` wrapper around the list name in the list
dialog for better UX
- Simplify the dialog layout, use LinearLayout, and standard padding and
margins
Modify `ListsViewModel` to keep track of the number of active network
operations and export as a flow. Collect this in `ListsActivity` and
show a `LinearProgressIndicator` while it is non-zero.
Previous code would prune any cached media every time `MainActivity` was
created, causing extra IO just as the user is trying to use the app.
Re-implement as a WorkManager worker so it can run when the device is
idle, without interfering with the responsiveness of the device.
Previous code used a normal ProgressBar and a coroutine to delay
hiding/showing the bar for a snappier UI perception.
This is built-in functionality in LinearProgressIndicator, so switch to
that.
While I'm here, implement the "Select list" dialog's layout as a layout
resource.
If the user has tabs containing one or more lists, and any of those
lists are renamed or deleted then the change should be reflected in the
tabs.
To do that:
`MainActivity`:
- Re-create tabs whenever lists are loaded and there's a list in a tab
- Compare lists-in-tabs by the ID of the list when restoring the user's
tab, so that a list rename doesn't lose their position.
`NetworkListsRepository`:
- Update the user's tab preferences whenever lists are loaded, removing
tabs that contain lists that have been deleted, and updating the
list's title for lists that have been renamed.
Fixes#192
Previously to view a list the user either had to add it to a tab, or tap
through "Lists" in the navigation menu to their list of lists, and then
tap the list they want.
Fix that, and show all their lists in a dedicated section in the menu,
with a new "Manage lists" entry that's functionality identical to the
old "Lists" entry (i.e., it shows their lists and allows them to create,
delete, and edit list settings).
To do that:
- Implement a proper `ListsRepository` as the single source of truth for
list implementation throughout the app. Expose the current list of lists
as a flow, with methods to perform operations on the lists.
- Collect the `ListsRepository` flow in `MainActivity` and use that to
populate the menu and update it whenever the user's lists change.
- Rewrite the activities and fragments that manipulate lists to use
`ListRepository`.
- Always show error snackbars when list operations fail. In particular,
the HTTP code and error are always shown.
- Delete the custom `Either` implementation, it's no longer used.
- Add types for modelling API responses and errors, `ApiResponse` and
`ApiError` respectively. The response includes the headers as well as
the body, so it can replace the use of `NetworkResult` and `Response`.
The actual result of the operation is expected to be carried in a
`com.github.michaelbull.result.Result` type. Implement a Retrofit call
adapter for these types.
Unit tests for these borrow heavily from
https://github.com/connyduck/networkresult-calladapter
Additional user-visible changes:
- Add an accessible "Refresh" menu item to `ListsActivity`.
- Adding a list to a tab has a dialog with a "Manage lists" option.
Previously that would close the dialog when clicked, so the user had to
re-open it on returning from list management. Now the dialog stays open.
- The soft keyboard automatically opens when creating or editing a list.
Previous code showed a small icon for account media that the user has
hidden.
Now determine the correct size / aspect ratio for the media and use that
to compute the placeholder (either a blurhash, or the link colour for
consistency with the view on a timeline).
Fixes#513
Previous code showed the toolbar and caption when displaying the image,
and the user has to tap the screen to dismiss it.
New code is consistent with the video viewer; the toolbar and caption
is displayed for two seconds, and then auto-hides.
- Tapping after the hide displays the toolbar and caption, which will
not auto-hide and requires a second tap to dismiss.
- Tapping the caption before it's hidden cancels the auto-hide
Fixes#505
The account logout process could fail due to API exceptions; network
errors for example, or if the user had already revoked the app's token
for that account. This would prevent the rest of the logout process
(cleaning database, etc) from completing.
Fix this by ignoring network errors during the logout process, and
always cleaning up account content in the database.
Fix a related issue where a deleted account might be recreated in a
partial state if the account's visible position was saved after it was
deleted. The recreated account couldn't do anything as it had no tokens,
but is very confusing.
Previously the only way to access notifications was to dedicate a tab to
them. Now notifications are available from the left-side navigation menu
so they're always accessible.
Add them to the top of the list, and swap the order of bookmarks and
favourites, assuming that users are more likely to want to see their
bookmarks than their favourites.
Move "Edit profile" to the bottom with the other settings options,
assuming that editing their profile does not happen very often, so
should not be at the top of the list.