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.
ktlint 1.3.0 promoted some experimental rules to the standard set, and
they some significant code churn if applied:
- Rewriting class signatures
- Rewriting conditions
- Rewriting single statement functions
Disable them for the time being.
The user has to specify the language they're posting in, and sometimes
they might get it wrong (e.g., replying to a post that also had the
language set incorrectly, forgetfulness, etc).
This has accessiblity issues (only following statuses in a given
language fails, translation can fail, etc).
Prevent this by trying to detect the language the status is written in
when the user tries to post it. If the detected language and the set
language do not match, and the detection is 60+% confident, warn the
user the status language might be incorrect, and offer to correct it
before posting.
How this works differs by device and API level.
- API 23 - 28, fdroid and github build flavours
- Not supported. A no-op language detector is used.
- API 29 and above, fdroid and github build flavours
- Uses Android TextClassifier to detect the likely language
- AP 23 and above, google build flavour
- Uses ML Kit language identification
To do this:
- Add `LanguageIdentifier`, with methods to do the identification, and
`LanguageIdentifier.Factory` to create the identifiers.
- Inject the factory in `ComposeActivity`
- Detect the language when the user posts, showing a dialog if there's a
sufficiently large discrepancy.
The ML Kit dependencies (language models) will be installed by the Play
libraries, so there's some machinery to check that they're installed,
and kick off the installation if not. If they can't be installed then
the language check is bypassed.
Update the privacy policy, as the ML Kit libraries may send some data to
Google.
This reverts commit 3870267701.
With this commit Pachli Current crash reports in Google Play showed
instances of:
```
Exception java.lang.RuntimeException:
at android.app.ActivityThread.handleBindApplication (ActivityThread.java:7716)
at android.app.ActivityThread.-$$Nest$mhandleBindApplication
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2478)
at android.os.Handler.dispatchMessage (Handler.java:106)
at android.os.Looper.loopOnce (Looper.java:230)
at android.os.Looper.loop (Looper.java:319)
at android.app.ActivityThread.main (ActivityThread.java:8919)
at java.lang.reflect.Method.invoke
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:578)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1103)
Caused by java.lang.IllegalStateException: WorkManager is not initialized properly. You have explicitly disabled WorkManagerInitializer in your manifest, have not manually called WorkManager#initialize at this point, and your Application does not implement Configuration.Provider.
at androidx.work.impl.WorkManagerImpl.getInstance (WorkManagerImpl.java:170)
at androidx.work.WorkManager.getInstance (WorkManager.java:184)
at app.pachli.PachliApplication.onCreate (PachliApplication.kt:96)
at android.app.Instrumentation.callApplicationOnCreate (Instrumentation.java:1316)
at android.app.ActivityThread.handleBindApplication (ActivityThread.java:7711)
```
on Samsung devices at API 34.
By my understanding this is a "can't happen" issue, the `WorkManager` is
supposed to be initialised by the androidx.startup content provider
before `PachliApplication` starts.
But it clearly does, so revert this change to be safe.
Implementing predictive back support inadvertently broke the code
ensuring activities are restarted when the font size or family changes.
So the user could change the font in Preferences, see the change be
immediately reflected in the preferences screen, then go back to the
previous activity and the change wouldn't be reflected.
Fix this by restoring some of the previous code.
Previous code set the initial status text, and then set up the callbacks
which meant that the status' length was initially 0, even when editing a
status.
This meant that, e.g., editing a status to change its language would
erroneously report the status body was empty. It also meant that editing
a status and changing just the language would not prompt to save or
discard the changes if moving back.
Fix this.
First, only set the status content after the callbacks that compute the
status length.
Second, provide a function that sets the status' language, and update
the close confirmation state when the language changes. Modify isDirty()
to compare the original and current language when determining if the
status is dirty.
Fixes#701
Previous code did not provide whitespace between different media labels
when media is not loaded.
In addition, the icon for the media was centre-aligned vertically with
the text, making it difficult to scan and determine when one media label
ends and another one starts.
Fix this by adding an 8dp margin between the media labels, and using a
TextView subclass that vertically aligns the media icon with the first
line of text.
Set the compound drawables with relative alignment, so they behave
appropriately in RTL layouts.
Fixes#751.
The user might have set a profile header image that is close to the
colour of the text in the account header in the left navigation menu.
This can make the text difficult or impossible to see.
Work around this by drawing a partially transparent scrim behind the
text so it's always displayed over a background that makes the text
legible.
Fixes#298
Previous code created one shortcut per account, which could exceed the
maximum number of shortcuts allowed, causing a crash.
Fix this by creating no more than the max number of shortcuts while
ensuring that the active account is always included.
Fixes#752
By Christophe Beyls in https://github.com/tuskyapp/Tusky/pull/4515.
Their commit notes:
Improve the performance of `BlurHashDecoder` while also reducing 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.
`ImageDownsizer.downsizeImage()`:
- Remove the return value, it was ignored
- Throw `FileNotFoundException` when `openInputStream` returns null
`ImageDownsizer.getImageOrientation()`:
- Throw `FileNotFoundException` when `openInputStream` returns null
`MediaUploader.prepareMedia()`:
- Copy URI contents using Okio buffers / source / sink
`UriExtensions`:
- Rename from `IOUtils`
- Implement `Uri.copyToFile()` using Okio buffers / source / sink
- Replace `ProgressRequestBody()` with `Uri.asRequestBody()` using Okio
buffers / source / sink
`DraftHelper.copyToFolder()`
- Use Okio buffers / source / sink
`CompositeWithOpaqueBackground`
- Use constants `SIZE_BYTES` and `CHARSET` instead of magic values
- Use `Objects.hash` when hashing multiple objects
Based on work by Christophe Beyls in
- https://github.com/tuskyapp/Tusky/pull/4366
- https://github.com/tuskyapp/Tusky/pull/4372