- Remove empty file `ExampleInstrumentedTest.java`.
- Replace deprecated `MigrationTestHelper` constructor call.
- Add reified inline extension methods for `Bundle` and `Intent` to
retrieve `Parcelable` and `Serializable` objects by calling the core
`BundleCompat` and `IntentCompat` methods, to allow shorter syntax and
removing the need to pass the class explicitly.
- Replace deprecated `drawable.setColorFilter()` with simpler
`drawable.setTint()` (uses blend mode `SRC_IN` by default, has the same
effect as `SRC_ATOP` when the source is a color).
- Rename shadowed variables (mostly caught exceptions).
- Remove unnecessary `.orEmpty()` on non-null fields.
- Replace `.size() == 0` with `.isEmpty()`.
- Prevent `NullPointerException` when `account.getDisplayName()` is
`null` in `StatusBaseViewHolder.setDisplayName()`.
- Declare `customEmojis` argument as non-null in
`StatusBaseViewHolder.setDisplayName()` because it calls
`CustomEmojiHelper.emojify()` which now requires it to be non-null.
- Prevent `NullPointerException` when no matching filter is found in
`StatusBaseViewHolder.setupFilterPlaceholder()`.
- Remove deprecated call to `setTargetFragment()` (target fragment is
not used anyway).
- Remove deprecated call to `isUserVisibleHint()` and test if the view
has been destroyed instead.
- Remove some unused imports.
- Remove unnecessary casts.
- Rename arguments to supertype names when a warning is shown.
- Prevent a potential memory leak by clearing the
`toolbarVisibilityDisposable` reference in `onDestroyView()`.
This refactors the NotificationsFragment and related classes to Kotlin &
paging.
While trying to preserve as much of the original behavior as possible,
this adds the following improvements as well:
- The "show notifications filter" preference was added again
- The "load more" button now has a background ripple effect when clicked
- The "legal" report category of Mastodon 4.2 is now supported in report
notifications
- Unknown notifications now display "unknown notification type" instead
of an empty line
Other code quality improvements:
- All views from xml layouts are now referenced via ViewBindings
- the classes responsible for showing system notifications were moved to
a new package `systemnotifications` while the classes from this
refactoring are in `notifications`
- the id of the local Tusky account is now called `tuskyAccountId` in
all places I could find
closes https://github.com/tuskyapp/Tusky/issues/3429
---------
Co-authored-by: Zongle Wang <wangzongler@gmail.com>
**! ! Warning**: Do not merge before testing every API call and database
read involving JSON !
**Gson** is obsolete and has been superseded by **Moshi**. But more
importantly, parsing Kotlin objects using Gson is _dangerous_ because
Gson uses Java serialization and is **not Kotlin-aware**. This has two
main consequences:
- Fields of non-null types may end up null at runtime. Parsing will
succeed, but the code may crash later with a `NullPointerException` when
trying to access a field member;
- Default values of constructor parameters are always ignored. When
absent, reference types will be null, booleans will be false and
integers will be zero.
On the other hand, Kotlin-aware parsers like **Moshi** or **Kotlin
Serialization** will validate at parsing time that all received fields
comply with the Kotlin contract and avoid errors at runtime, making apps
more stable and schema mismatches easier to detect (as long as logs are
accessible):
- Receiving a null value for a non-null type will generate a parsing
error;
- Optional types are declared explicitly by adding a default value. **A
missing value with no default value declaration will generate a parsing
error.**
Migrating the entity declarations from Gson to Moshi will make the code
more robust but is not an easy task because of the semantic differences.
With Gson, both nullable and optional fields are represented with a null
value. After converting to Moshi, some nullable entities can become
non-null with a default value (if they are optional and not nullable),
others can stay nullable with no default value (if they are mandatory
and nullable), and others can become **nullable with a default value of
null** (if they are optional _or_ nullable _or_ both). That third option
is the safest bet when it's not clear if a field is optional or not,
except for lists which can usually be declared as non-null with a
default value of an empty list (I have yet to see a nullable array type
in the Mastodon API).
Fields that are currently declared as non-null present another
challenge. In theory, they should remain as-is and everything will work
fine. In practice, **because Gson is not aware of nullable types at
all**, it's possible that some non-null fields currently hold a null
value in some cases but the app does not report any error because the
field is not accessed by Kotlin code in that scenario. After migrating
to Moshi however, parsing such a field will now fail early if a null
value or no value is received.
These fields will have to be identified by heavily testing the app and
looking for parsing errors (`JsonDataException`) and/or by going through
the Mastodon documentation. A default value needs to be added for
missing optional fields, and their type could optionally be changed to
nullable, depending on the case.
Gson is also currently used to serialize and deserialize objects to and
from the local database, which is also challenging because backwards
compatibility needs to be preserved. Fortunately, by default Gson omits
writing null fields, so a field of type `List<T>?` could be replaced
with a field of type `List<T>` with a default value of `emptyList()` and
reading back the old data should still work. However, nullable lists
that are written directly (not as a field of another object) will still
be serialized to JSON as `"null"` so the deserializing code must still
be handling null properly.
Finally, changing the database schema is out of scope for this pull
request, so database entities that also happen to be serialized with
Gson will keep their original types even if they could be made non-null
as an improvement.
In the end this is all for the best, because the app will be more
reliable and errors will be easier to detect by showing up earlier with
a clear error message. Not to mention the performance benefits of using
Moshi compared to Gson.
- Replace Gson reflection with Moshi Kotlin codegen to generate all
parsers at compile time.
- Replace custom `Rfc3339DateJsonAdapter` with the one provided by
moshi-adapters.
- Replace custom `JsonDeserializer` classes for Enum types with
`EnumJsonAdapter.create(T).withUnknownFallback()` from moshi-adapters to
support fallback values.
- Replace `GuardedBooleanAdapter` with the more generic `GuardedAdapter`
which works with any type. Any nullable field may now be annotated with
`@Guarded`.
- Remove Proguard rules related to Json entities. Each Json entity needs
to be annotated with `@JsonClass` with no exception, and adding this
annotation will ensure that R8/Proguard will handle the entities
properly.
- Replace some nullable Boolean fields with non-null Boolean fields with
a default value where possible.
- Replace some nullable list fields with non-null list fields with a
default value of `emptyList()` where possible.
- Update `TimelineDao` to perform all Json conversions internally using
`Converters` so no Gson or Moshi instance has to be passed to its
methods.
- ~~Create a custom `DraftAttachmentJsonAdapter` to serialize and
deserialize `DraftAttachment` which is a special entity that supports
more than one json name per field. A custom adapter is necessary because
there is not direct equivalent of `@SerializedName(alternate = [...])`
in Moshi.~~ Remove alternate names for some `DraftAttachment` fields
which were used as a workaround to deserialize local data in 2-years old
builds of Tusky.
- Update tests to make them work with Moshi.
- Simplify a few `equals()` implementations.
- Change a few functions to `val`s
- Turn `NetworkModule` into an `object` (since it contains no abstract
methods).
Please test the app thoroughly before merging. There may be some fields
currently declared as mandatory that are actually optional.
This pull request removes the remaining RxJava code and replaces it with
coroutine-equivalent implementations.
- Remove all duplicate methods in `MastodonApi`:
- Methods returning a RxJava `Single` have been replaced by suspending
methods returning a `NetworkResult` in order to be consistent with the
new code.
- _sync_/_async_ method variants are replaced with the _async_ version
only (suspending method), and `runBlocking{}` is used to make the async
variant synchronous.
- Create a custom coroutine-based implementation of `Single` for usage
in Java code where launching a coroutine is not possible. This class can
be deleted after remaining Java code has been converted to Kotlin.
- `NotificationsFragment.java` can subscribe to `EventHub` events by
calling the new lifecycle-aware `EventHub.subscribe()` method. This
allows using the `SharedFlow` as single source of truth for all events.
- Rx Autodispose is replaced by `lifecycleScope.launch()` which will
automatically cancel the coroutine when the Fragment view/Activity is
destroyed.
- Background work is launched in the existing injectable
`externalScope`, since using `GlobalScope` is discouraged.
`externalScope` has been changed to be a `@Singleton` and to use the
main dispatcher by default.
- Transform `ShareShortcutHelper` to an injectable utility class so it
can use the application `Context` and `externalScope` as provided
dependencies to launch a background coroutine.
- Implement a custom Glide extension method
`RequestBuilder.submitAsync()` to do the same thing as
`RequestBuilder.submit().get()` in a non-blocking way. This way there is
no need to switch to a background dispatcher and block a background
thread, and cancellation is supported out-of-the-box.
- An utility method `Fragment.updateRelativeTimePeriodically()` has been
added to remove duplicate logic in `TimelineFragment` and
`NotificationsFragment`, and the logic is now implemented using a simple
coroutine instead of `Observable.interval()`. Note that the periodic
update now happens between onStart and onStop instead of between
onResume and onPause, since the Fragment is not interactive but is still
visible in the started state.
- Rewrite `BottomSheetActivityTest` using coroutines tests.
- Remove all RxJava library dependencies.
While working on #4249 I noticed that quick replies also don't work as
expected. The notification just stays in the sending state forever.
There are actually 2 problems:
- Notifications are sent in `NotificationFetcher` with the id of the
Mastodon notification as tag and the current account id as id. The wrong
notification id was forwarded to `SendStatusBroadcastReceiver` so it
never had a chance of updating the notification.
- Notifications containing an active remote input can't be cancelled
(they just stop their animation when doing so). So instead I update the
notification with info that the reply is being sent and have it dismiss
automatically.
I also tried replacing the original notification with the "sending"
notification of `SendStatusService`, but that doesn't work because
`Service.startForeground` doesn't have a tag parameter, only an id.
---------
Co-authored-by: Willow <charlag@tuta.io>
There are some new rules, I think they mostly make sense, except for the
max line length which I had to disable because we are over it in a lot
of places.
---------
Co-authored-by: Goooler <wangzongler@gmail.com>
- 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.
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 post editing capability
* Don't try to reprocess already uploaded attachments.
Fixes editing posts with existing media
* Don't mark post edits as modified until editing occurs
* Disable UI for things that can't be edited when editing a post
* Finally convert SFragment to kotlin
* Use api endpoint for fetching status source for editing
* Apply review feedback
* update to Api 33, fix some deprecations
* fix deprecated serializable/parcelable methods
* ask for notification permission
* fix code formatting
* add back comment in PreferencesActivity
* Attempt-zero implementation of a "focus" feature for image attachments. Choose "Set focus" in the attachment menu, tap once to select focus point (no visual feedback currently), tap "OK". Works in tests.
* Remove code duplication between 'update description' and 'update focus'
* Fix ktlint/bitrise failures
* Make updateMediaItem private
* When focus is set on a post attachment the preview focuses correctly. ProgressImageView now inherits from MediaPreviewImageView.
* Replace use of PointF for Focus where focus is represented, fix ktlint
* Substitute 'focus' for 'focus point' in strings
* First attempt draw focus point. Only updates on initial load. Modeled on code from RoundedCorners builtin from Glide
* Redraw focus after each tap
* Dark curtain where focus isn't (now looks like mastosoc)
* Correct ktlint for FocusDialog
* draft: switch to overlay for focus indicator
* Draw focus circle, but ImageView and FocusIndicatorView seem to share a single canvas
* Switch focus circle to path approach
* Correctly scale, save and load focuses. Clamp to visible area. Focus editor looks and feels right
* ktlint fixes and comments
* Focus indicator drawing should use device-independent pixels
* Shrink focus window when it gets unattractively tall (no linting, misbehaves on wide aspect ratio screens)
* Correct max-height behavior for screens in landscape mode
* Focus attachment result is are flipped on x axis; fix this
* Correctly thread focus through on scheduled posts, redrafted posts, and drafts (but draft focus is lost on post)
* More focus ktlint fixes
* Fix specific case where a draft is given a focus, then deleted, then posted in that order
* Fix accidental file change in focus PR
* ktLint fix
* Fix property style warnings in focus
* Fix remaining style warnings from focus PR
Co-authored-by: Conny Duck <k.pozniak@gmx.at>
* Add UI for selecting post language
* Apply selected language when sending status
* Save/restore post language with drafts
* Fall back to english if the configured language isn't found in the locale list (no-NB)
* Remove comment about no_NB
* Move language dropdown to top of compose view
* Preserve language when redrafting
* Set default language to target post's language when replying
* Add Tusky license header to new source file
* Tweak language dropdown button width
Fixes#793.
This is an implementation for push notifications based on UnifiedPush
for Tusky. No push gateway (other than UP itself) is needed, since
UnifiedPush is simple enough such that it can act as a catch-all
endpoint for WebPush messages. When a UnifiedPush distributor is present
on-device, we will by default register Tusky as a receiver; if no
UnifiedPush distributor is available, then pull notifications are used
as a fallback mechanism.
Because WebPush messages are encrypted, and Mastodon does not send the
keys and IV needed for decryption in the request body, for now the push
handler simply acts as a trigger for the pre-existing NotificationWorker
which is also used for pull notifications. Nevertheless, I have
implemented proper key generation and storage, just in case we would
like to implement full decryption support in the future when Mastodon
upgrades to the latest WebPush encryption scheme that includes all
information in the request body.
For users with existing accounts, push notifications will not be enabled
until all of the accounts have been re-logged in to grant the new push
OAuth scope. A small prompt will be shown (until dismissed) as a
Snackbar to explain to the user about this, and an option is added in
Account Preferences to facilitate re-login without deleting local drafts
and cache.
* make MastodonApi.createStatus suspending
* check if media processing has finished before sending status
* add backoff for retrying processed media check
* cleanup warnings, reorganize some code
* move ComposeAutoCompleteAdapter to compose package
* composeOptions doesn't need to be a class member
* add DraftsActivity and DraftsViewModel
* drafts
* remove unnecessary Unit in ComposeViewModel
* add schema/25.json
* fix db migration
* drafts
* cleanup code
* fix compose activity rotation bug
* fix media descriptions getting lost when restoring a draft
* improve deleting drafts
* fix ComposeActivityTest
* improve draft layout for almost empty drafts
* reformat code
* show toast when opening reply to deleted toot
* improve item_draft layout
* Convert ComposeActivity to Kotlin
* More ComposeActivity cleanups
* Move ComposeActivity to it's own package
* Remove ComposeActivity.IntentBuilder
* Re-do part of the media downsizing/uploading
* Add sending of status to ViewModel, draft media descriptions
* Allow uploading video, update description after uploading
* Enable camera, enable upload cancelling
* Cleanup of ComposeActivity
* Extract CaptionDialog, extract ComposeActivity methods
* Fix handling of redrafted media
* Add initial state and media uploading out of Activity
* Change ComposeOptions.mentionedUsernames to be Set rather than List
We probably don't want repeated usernames when we are writing a post
and Set provides such guarantee for free plus it tells it to the
callers. The only disadvantage is lack of order but it shouldn't be a
problem.
* Add combineOptionalLiveData. Add docs.
It it useful for nullable LiveData's. I think we cannot differentiate
between value not being set and value being null so I just added the
variant without null check.
* Add poll support to Compose.
* cleanup code
* move more classes into compose package
* cleanup code
* fix button behavior
* add error handling for media upload
* add caching for instance data again
* merge develop
* fix scheduled toots
* delete unused string
* cleanup ComposeActivity
* fix restoring media from drafts
* make media upload code a little bit clearer
* cleanup autocomplete search code
* avoid duplicate object creation in SavedTootActivity
* perf: avoid unnecessary work when initializing ComposeActivity
* add license header to new files
* use small toot button on bigger displays
* fix ComposeActivityTest
* fix bad merge
* use Singles.zip instead of Single.zip
* Scheduled toot
* Hide scheduled toot button if version < 2.7.0
* Fix timeline reloading after toot
* Add edit icon to ComposeScheduleView
* Add button to reset scheduled toot
* Close bottom sheet and change button color after time a was selected
* Fix edit icon's size
* List of scheduled toots
* Fix instance version check
* Use MaterialDatePicker
* Set date and time consecutively
* Add licenses
* upgrade to AndroidX, upgrade libraries
* move to MaterialComponents theme
* make sure the compose button looks good everywhere
* fix tollbar title/button alignment on tablet
* move to new material color theming, consolidate colors and themes
* fix build, fix imports
* set error on TextInputLayout instead of EditText
* fix imports, TootButton when
* improve snackbar style
* fix task description color
* Enforce 420-character limit on media descriptions in the UI
* Persist media descriptions with drafts
* Defer media description update until after upload finishes
* Make description field 2 lines for better visibility of hint text
* Reuse Gson instance
* Force retranslation of modified string "hint_describe_for_visually_impaired"
* Add bounds check when reading serialized media descriptions
* inject MastodonApi into LoginActivity
* inject AccountManager into MainActivity
* inject AccountManager into SplashActivity, convert to Kotlin
* inject AccountManager into AccountActivity
* inject AccountManager into LoginActivity
* inject AccountManager into NotificationsFragment and NotificationClearBroadcastReceiver, fix MainActivity
* ooops
* use same OkHttpClient for Retrofit & Picasso
* fix ordering of okhttp interceptors
* remove dependencies on TuskyApplication
* bugfix
* correctly filter notifications on Api >= 26, other fixes and refactoring
* use correct areNotificationsEnabled method in MainActivity
* change notification led color
* basic implementation
* improve LoginActivity
* darken drawer background image
* add current avatar in ComposeActivity
* add account name to logout dialog
* multi account support for notifications
* multi account support for notifications
* bugfixes & cleanup
* fix bug where somethings notifications would open with the wrong user
* correctly set active account in SFragment
* small improvements