The previous code unilaterally enabled filter functionality. Some
Mastodon-like servers -- like GoToSocial -- do not support filters, and
this resulted in user visible error messages when connecting to those
servers.
To fix this:
- Extend the set of supported server capabilities to include client and
server side filtering.
- Disable the filter preferences if the server does not support filters
and show a message explaining why it's disabled.
Extend the capabilities model to support this:
- Fetch server software name and version from the nodeinfo endpoints
(implementing the nodeinfo API and schema)
- Extend the use of kotlin-result to provide hierarchies of Error
classes and demonstrate how to chain errors and display more informative
messages without using exceptions.
Fixes#343
The previous code was serialising the `TabKind` enum without the `_`, so
when it was converted back to the enum name (which has a `_`) it failed
and immediately crashed.
Fixes#306
If a status was part of a thread, and it was not the "detailed" status,
and it had been translated, then the view data was marked as "show the
translation". But the translation was not loaded, so the status content
appeared as empty.
Fix that by loading the translated content of all statuses in the thead
and ensure that the translated content is rendered.
Throw an `IllegalStateException` in debug builds to catch any future
occurrences of this.
Fixes#281
Mastodon counts post lengths by considering emojis to be single
characters, no matter how many unicode code points they are composed of.
So "😜" has length 1.
Pachli was using `String.length`, which considers "😜" as length 2.
Correct the calculation by using a BreakIterator to count the characters
in the string, which treats multi-character emojis as a length 1.
Poll options had a similar problem, exacerbated by the Mastodon web UI
also having the same problem, see
https://github.com/mastodon/mastodon/issues/28336.
Fix that by creating `MastodonLengthFilter`, an `InputFilter` that does
the right thing for regular text that may contain emojis.
See also https://github.com/tuskyapp/Tusky/pull/4152, which has the fix
for status length but not polls.
---------
Co-authored-by: Konrad Pozniak <opensource@connyduck.at>
Roles for the logged in user appeared in Mastodon 4.0.0 and can be
displayed on the user's profile screen.
Show them as chips, adjusting the display of the existing "Follows you"
and "Bot" indicators to make allowances for this.
Roles can have a custom colour assigned by the server admin. This is
blended with the app colour so it is not too jarring in the display.
See also https://github.com/tuskyapp/Tusky/pull/4029
Co-authored-by: Konrad Pozniak <opensource@connyduck.at>
The previous code generally started an activity by having the activity
provide a method in a companion object that returns the relevant intent,
possibly taking additional parameters that will be included in the
intent as extras.
E.g., if A wants to start B, B provides the method that returns the
intent that starts B.
This introduces a dependency between A and B.
This is worse if B also wants to start A.
For example, if A is `StatusListActivity` and B is`ViewThreadActivity`.
The user might click a status in `StatusListActivity` to view the
thread, starting `ViewThreadActivity`. But from the thread they might
click a hashtag to view the list of statuses with that hashtag. Now
`StatusListActivity` and `ViewThreadActivity` have a circular
dependency.
Even if that doesn't happen the dependency means that any changes to B
will trigger a rebuild of A, even if the changes to B are not relevant.
Break this dependency by adding a `:core:navigation` module with an
`app.pachli.core.navigation` package that contains `Intent` subclasses
that should be used instead. The `quadrant` plugin is used to generate
constants that can be used to launch activities by name instead of by
class, breaking the dependency chain.
The plugin uses the `Activity` names from the manifest, so when an
activity is moved in the future the constant will automatically update
to reflect the new package name.
If the activity's intent requires specific extras those are passed via
the constructor, with companion object methods to extract them from the
intent.
Using the intent classes from this package is enforced by a lint
`IntentDetector` which will warn if any intents are created using a
class literal.
See #291
The package wasn't renamed when it was moved, so was still
`app.pachli.components.timeline`, instead of the new location,
`app.pachli.core.network.model`.
The existing code base is a single monolithic module. This is relatively
simple to configure, but many of the tasks to compile the module and
produce the final app have to run in series.
This is unnecessarily slow.
This change starts to split the code in to multiple modules, which are:
- :core:account - AccountManager, to break a dependency cycle
- :core:common - low level types or utilities used in many other modules
- :core:database - database types, DAOs, and DI infrastructure
- :core:network - network types, API definitions, and DI infrastructure
- :core:preferences - shared preferences definitions and DI
infrastructure
- :core:testing - fakes and rules used across different modules
Benchmarking with gradle-profiler shows a ~ 17% reduction in incremental
build times after an ABI change. That will improve further as more code
is moved to modules.
The rough mechanics of the changes are:
- Create the modules, and move existing files in to them. This causes a
lot of churn in import arguments.
- Convert build.gradle files to build.gradle.kts
- Separate out the data required to display a tab (`TabViewData`) from
the data required to configure a tab (`TabData`) to avoid circular
dependencies.
- Abstract the repeated build logic shared between the modules in to
a set of plugins under `build-logic/`, to simplify configuration of
the application and library builds.
- Be explicit that some nullable types are non-null at time of use.
Nullable properties in types imported from modules generally can't be
smart cast to non-null. There's a detailed discussion of why this
restriction exists at
https://discuss.kotlinlang.org/t/what-is-the-reason-behind-smart-cast-being-impossible-to-perform-when-referenced-class-is-in-another-module/2201.
The changes highlight design problems with the current code, including:
- The main application code is too tightly coupled to the network types
- Too many values are declared unnecessarily nullable
- Dependency cycles between code that make modularisation difficult
Future changes will add more modules.
See #291.