Previous code was missing the JSON name of the event, so it wasn't
present when it should have been, resulting in a crash.
Also, the Mastodon documentation is incorrect about the relationship
count, which is instead represented as two properties, one for followers
and one for following. So model that, and display them separately in the
UI.
Fixes#1086
Continue the work to remove the "activeAccount" idiom.
- Uses a new PachliAccount type through most of the app. This holds
information that was previously accessed separately (e.g., content
filters, lists) in one place. The information is loaded when the app
launches or the active account switches.
- Fetching data when the account is switched / loaded simplifies error
handling, as more code can now assume the data has already been loaded.
If it hasn't the code path is simply unreachable.
- This opens up the possibility of "acting as one account while logged
in as another". E.g., have two accounts, and be logged in to one account
and boost a post you've seen from your other account.
- Add a database migration to populate existing accounts with default
data when the user updates the app.
- Refactor code that used those list and filter repositories to get the
data from the PachliAccount instead. New local and remote data sources
are implemented, and the list and filter repositories mediate between
those sources.
- Start a ViewModel for MainActivity, which includes:
- Sending user actions as UiAction objects
- Providing a flow of uiState for MainActivity to react to
- Remove most uses of SharedPreferencesRepository from MainActivity
- Show messages about errors that occur when logging in
- Refactor intent routing in MainActivity to make the logic clearer.
- Add new `core.data` types to push more `core.network` types out of the
UI code
- `core.data.model.MastodonList` for `core.network.model.MastoList`
- `core.data.model.Server` for `core.network.model.Server`
- Continue the work to send the Pachli account ID to the code that uses
it.
- Most view models now get the account ID via assisted injection.
- QueuedMedia now includes the AccountEntity so it can operate with any
account. Modify the `uploadMedia` API call to include explicit
authentication details.
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Previous code would remove image attachments from the compose editor if
there was a problem uploading or updating them.
This caused a particular problem with image captions. You could attach a
valid image, then write a caption that was too long for the server. The
server would reject the status, and the status was saved to drafts.
Then you open the draft, which tries to upload the image again with a
too-long caption. The upload is rejected, and the image, along with the
caption, is removed.
Fix this.
- Change `QueuedMedia` to track the upload state as a `Result<_,_>`, so
any error messages are preserved and available to the UI.
- The different `Ok` types for the upload state contain the upload
progress percentage (if appropriate) or the server's ID for the uploaded
media.
- Change `ProgressImageView` to accept the upload state `Result`. If the
result is an error the image is drawn with a red overlay and white
"error" icon.
- If an upload is in an error state allow the user to click on it. That
shows a dialog explaining the error, and provides options to edit the
image, change the caption, etc.
- When changing the caption make the API call to change it on the server
(if the attachment has been uploaded). This makes the user aware of any
errors sooner in the process, so they can correct them.
Fixes#879
Previous code would remove image attachments from the compose editor
if there was a problem uploading or updating them.
This caused a particular problem with image captions. You could attach
a valid image, then write a caption that was too long for the server.
The server would reject the status, and the status was saved to drafts.
Then you open the draft, which tries to upload the image again with a
too-long caption. The upload is rejected, and the image, along with the
caption, is removed.
Fix this.
- Change `QueuedMedia` to track the upload state as a `Result<_,_>`,
so any error messages are preserved and available to the UI.
- The different `Ok` types for the upload state contain the upload
progress percentage (if appropriate) or the server's ID for the
uploaded media.
- Change `ProgressImageView` to accept the upload state `Result`.
If the result is an error the image is drawn with a red overlay and
white "error" icon.
- If an upload is in an error state allow the user to click on it.
That shows a dialog explaining the error, and provides options to
edit the image, change the caption, etc.
- When changing the caption make the API call to change it on the
server (if the attachment has been uploaded). This makes the user
aware of any errors sooner in the process, so they can correct them.
Users report that copying items can be difficult using Talkback.
Make this easier in the dialogs that appear for links, mentions, and
hashtags by using a dedicated adapter that displays a "Copy" button at
the end of each item.
Fixes#1038
Previous code didn't send the language to ComposeActivity when editing a
scheduled status so it always appeared to be dirty. This prompted the
user to save/discard changes when backing out, even if they hadn't made
any changes.
Fix this by collecting the language code when fetching scheduled posts
and passing it in `ComposeOptions`.
Previous code accepted the `scheduledAt` value as a String, and kept it
as a String (including when serialising as part of a draft). Then it was
converted to an actual Date for display.
Refactor to keep it as a Date for as long as possible. Moshi decodes
Dates correctly over the network, and the database is configured to
serialise Dates as Longs.
This necessitates two migration steps to preserve any existing
`scheduledAt` values for drafts. The first step adds a new column to
store the date as a Long and copies over existing data. The second step
replaces the old column with the new column.
GoToSocial servers don't support scheduled posts; they return the wrong
type, and this can cause a loop of posting.
The GoToSocial bug to implement scheduled posts is
https://github.com/superseriousbusiness/gotosocial/issues/1006.
Fix this by adding a new server capability for scheduled post support,
using it for most servers, and disabling it for GoToSocial.
If scheduled post support is not available for an account:
- The "Scheduled posts" menu option is not shown.
- The scheduling button (clock) when composing a post is hidden, so the
user cannot set scheduling parameters.
Fixes#963
The `Status` type associated with an announcement is entirely different
to the regular `Status` type, with a different JSON shape. The incorrect
type meant a deserialisation error when loading announcements with
associated statuses.
Previous code assumed the active account could always be determined from
the account manager.
This causes a few problems.
1. The account manager has to handle the case where there is no active
account (e.g., the user is logging out of the last account). This meant
the `activeAccount` property had to be nullable, so every consumer of
that property either used it with a `let` or `!!` expression.
2. The active account can change over the life of the UI component, for
example, when the user is switching accounts. There's a theoretical race
condition where the UI component has started an operation for one
account, then the account changes and the network authentication code
uses the new account.
3. All operations assume they operate on whatever the active account is,
making it difficult to provide any features that allow the user to
temporarily operate as another account ("Boost as...", etc).
This "ambient account" was effectively global mutable state, with all
the problems that can cause.
Start to fix this. The changes in this commit do not fix the problem
completely, but they are some progress.
Each activity (except LoginActivity) is expected to be launched with an
intent that includes the ID of the Pachli account it defaults to
operating with. This is `pachliAccountId`, and is the *database ID*
(not the server ID) of the account. This is non-null, which removes one
class of bugs.
This account is passed to each fragment and any piece of code that has
to perform an operation on behalf of this account. It's not used in
most of those places yet, that will be done over a number of followup
PRs as part of modernising each module.
Previous code didn't encode v2 filter keywords, so created v2 filters by
first creating the filter with no keywords (one API call) then making
1-N API calls to add each keyword to the filter.
Fix this by adding a dedicated converter for the `NewContentFilter` type
that encodes it correctly so the filter can be created with a single API
call.
This necessitates moving some types around,
The new anti-harassment features will add several different types of
filtering options through the UI.
To ensure there is no confusion, rename the existing "Filters" UI and
code to "Content filters" to accurately describe what they operate on,
distinct from new filters which will act on account metadata.
Fixes#926.
Previous code showed any JSON-wrapped errors from notification fetches
as the JSON string, instead of the error message.
Fix this by switching to `ApiResult` and using the formatted error
message.
Fixes 937
Previous code used `Response`. Convert to `ApiResult` as part of the
work to implement anti-harassment controls, which will need to query the
user's list of accounts they are following.
Converting just `accountFollowing` wasn't practical, as all the methods
are called by a single function in `AccountListFragment` which expects
the return type to be the same.
veganism.social is doing something weird that injects statuses into the
user's timeline that are missing the fields that are normally present on
authenticated statuses.
So far I've noticed:
- `reblogged`
- `favourited`
- `bookmarked`
on the top level `Status` are missing, and if the status contains a poll
the `voted` field is also missing.
Cover up this breakage by setting defaults for these fields.
Clean up the notification handling code and fix a lot of bugs, hopefully
without introducing new ones in the process.
Specific bugs discovered and fixed:
- The code that tried to sync notification filtering state between the
server and Pachli could fail, leaving things in an inconsistent state,
resulting in dropped notifications. Remove that code, do filtering
client-side.
- Logging out of an account would disable push notifications for all
accounts.
- If any account did not support push notifications then push
notifications were disabled for all accounts.
- If any account did not support push notifications the user was
prompted to log out of all accounts. Drop that entirely.
- The UnifiedPush library could get to a state where configuring the
notification mechanism would silently fail,
The preferences UI now has a section for notifications, showing:
- The Unified Push distributor in use (if any)
- A mechanism to change the distributor
- Per-account configuration and notification fetch details
- Battery optimisation state
General changes:
- Update to UnifiedPush library 2.4.0.
- NotificationFetcher.fetchAndShow() can now fetch a single account's
notifications, or all accounts, depending on data passed to the worker.
- Use ApiResult for `push/subscription` responses.
- Drop the "needs migration" terminology for the more specific "has push
scope", to make it clear what the issue with the account is.
Mastodon supports in-query search operators, such as `has:image`,
`language:en`, or `in:library`. Previously the user had to enter them in
to the query directly.
This provides a chip-based UI that allows the user to set values for
these operators.
## Server
- Add new search capabilities to record the faceted search features the
server reports.
- Update definitions for Mastodon, Friendica, and GoToSocial to specify
which versions of the operations they support.
## SearchOperator / SearchOperatorViewData
- Represents each supported operator and associated viewdata.
## SearchActivity / activity_search.xml
- Conditionally display a chip for each facet depending on the server's
level of support.
- Implement the UI for each chip. They display dialogs of varying levels
of complexity depending on the underlying operation.
## FragmentSearch
- Display the progress as a LinearProgressIndicator instead of an
indeterminate ProgressBar. This makes it more visible under the search
facets.
The Glitch fork reports the same version number as stock Mastodon and is
supposed to have the same baseline capabilities as stock Mastodon at
that version number.
The previous code had a number of problems, including:
- Calls to the filters API were scattered through UI and viewmodel code.
- Repeated places where the differences between the v1 and v2 Mastodon
filters API had to be handled.
- UI and viewmodel code using the network filter classes, which tied
them to the API implementation.
- Error handling was inconsistent.
Fix this.
## FiltersRepository
- All filter management now goes through `FiltersRepository`.
- `FiltersRepository` exposes the current set of filters as a
`StateFlow`, and automatically updates it when the current server
changes or any changes to filters are made. This makes
`FilterChangeEvent` obsolete.
- Other operations on filters are exposed through `FiltersRepository` as
functions for viewmodels to call.
- Within the bulk of the app a new `Filter` class is used to represent a
filter; handling the differences between the v1 and v2 APIs is
encapsulated in `FiltersRepository`.
- Represent errors when handling filters as subclasses of `PachliError`,
and use `Result<V, E>` throughout, including using `ApiResult` for all
filter API results.
- Provide different types to distinguish between new-and-unsaved
filters, new-and-unsaved keywords, and in-progress edits to filters.
## Editing filters
- Accept an optional complete filter, or filter ID, as parameters in the
intent that launches `EditFilterActivity`. Pass those to the viewmodel
using assisted injection so the viewmodel has the info immediately.
- In the viewmodel use a new `FilterViewData` type to model the data
used to display and edit the filter.
- Start using the UiSuccess/UiError model. Refrain from cutting over to
full the action implementation as that would be a much larger change.
- Use `FiltersRepository` instead of making any API calls directly.
## Listing filters
- Use `FiltersRepository` instead of making any API calls directly.
## EventHub
- Remove `FilterChangedEvent`. Update everywhere that used it to use the
flow from `FiltersRepository`.
Previous code blindly inserted commas and semi-colons as separators
between the components of a content description. If some of those
components were null you could have a content description that looked
like "... , , , ..." or similar, and the repeated reading of "comma" by
screen readers was jarring and reduced accessibility.
Fix this by inserting punctuation only where necessary, building up the
string piece by piece instead of using a string resource with hardcoded
punctuation.
Fixes#791.
When autocompleting hashtags while composing a status the previous code
showed the hashtags in the same order they're returned by the server,
with no additional information.
This doesn't allow the user to make an informed choice about which
hashtag might be better to use. For example, trying to choose between
"#nivenly" and "#NivenlyFoundation".
To fix that, include the hashtag's usage when receiving data from the
server. Sum that, and show it to the user in the hashtag list. Sort the
hashtags by popularity, most popular first.
Mastodon now supports additional (optional) author information to show
as a byline on preview cards.
Use this (if included), to show the author's avatar, name, and link to
their profile. If tapped a click on a new `Target.BYLINE` target is
registered allowing fragments/activities to launch
`ViewProfileActivity`.
Include this as an action in `ListStatusAccessibilityDelegate`, and
provide `TrendingLinksAccessibilityDelegate` to provide accessibility
actions when viewing trending links.
Previous code assumed server responses would always be JSON, and had no
special handling for mis-configured servers that sometimes return HTML;
for example, if the server has a bug, or there's a reverse proxy in
front of the server issuing DoS-prevention challenges.
This could cause errors to show with no useful debugging information.
Update `ApiResult` to check the content-type in the response and return
one of two new errors if the content-type is missing or wrong. Also
include the HTTP code in `ApiResponse` for use elsewhere.
Update `ThrowableExtensions` to pull the `error` and optional
`description` out of the error body.
Update `PachliError` so `formatArgs` can be an array of arbitrary types,
not just strings.
Update `MediaUploader`; expose the different errors as new
`MediaUploaderError` types instead of `Exception` subclasses, and return
`Result<V, E>` where appropriate.
Update `ComposeViewModel` to use the new `MediaUploaderError` types and
create new `PickMediaError` to report issues there, replacing
`VideoOrImageException`.
Update `ComposeActivity` to use the new error types and show errors
until the user dismisses them, so they're better able to see and report
problems.
Fixes#704.
Implement suggestions as a new `feature:suggestions` module, with
associated activity, fragment, etc.
Suggested accounts are shown with their normal information, as well as
information about the number of follows / followers, and a guide to
posting frequency, so the user can make a more informed decision about
whether to follow or not.
In the previous code `PachliError` could correctly chain errors and
generate error messages, `ApiError` didn't, which is why there was the
temporary `ApiError.fmt()` extension function.
Rewrite `ApiError` to implement `PachliError` so it gets these benefits
and to reduce the number of different error-handling mechanisms in the
code.
Main changes:
- `PachliError` is now an interface so it can be extended by other
error interfaces.
- All the `ApiError` subclasses implement `PachliError`, and can
specify the error string and interpolated variables at the point of
declaration.
- Update `ListsRepository` and `ServerRepository` to return
`PachliError` subclasses.
Edited polls only include the list of options with titles; no other
metadata (poll ID, single/multiple choice, vote counts, etc). Since the
data shape didn't match Moshi wasn't decoding the data.
Provide dedicated data classes to model the response, and add a fourth
poll display option to represent viewing an edit history snapshot.
Crash was occuring because the instance info hadn't been fetched, trying
to take the last item of an empty list.
To fix:
- Expose the instance info as a state flow, with a default. New instance
info is fetched whenever the active account changes.
- Do the same for the emojis supported by the server.
- Update call sites as appropriate.
- Mark `InstanceInfoRepository` as `@Singleton` so it isn't repeatedly
created causing fresh content fetches.
The tests needed updating to get this to work.
- Extract the network fake modules in to a network-test module so
multiple other modules can use them.
- Rewrite `InstanceInfoRepositoryTest` to use Hilt and use Turbine to
test the new flows.
Checking this showed cosmetic bugs in the About layout when instance
info is missing, clean those up.