Previous code had to distinguish between showing an attachment or
showing an image by URL.
Simplify this by -- in the image URL case -- creating a fake attachment
that references the image URL.
Move the code that unmarshalls the Bundle arguments to
`ViewMediaFragment` to share between `ViewImageFragment` and
`ViewVideoFragment`.
Previous code finalised the view setup in `onViewCreated`, so if you
opened some media, switched away from the app, and switched back you'd
get a blank screen.
Fix this by doing the finalisation in `onResume()`, so the media is
displayed correctly when returning to the fragment.
Fixes#161
Android 14 (SDK 34) requires a `foregroundServiceType` and `onTimeout()`
implementation for foreground services, otherwise creating the service
will crash.
Do this. If `SendStatusService` does timeout then any pending statuses
are marked as failed, saved to drafts, and the user is informed.
Fixes#162
Use the Material colour for `conversation_thread_line` (which is
`colorOutlineVariant`) instead of a custom attribute.
Elsewhere, use the Material attribute directly (in code), or replace the
custom divider with a `MaterialDivider`.
This makes some colour definitions unused, so remove them.
Fixes#148
Previously the tests mocked shared preferences with a map and a mock
that had to be implemented for each test that needed it.
Replace this with `InMemorySharedPreferences`, which provides the normal
`SharedPreferences` interface so can be used as a drop-in replacement.
Associated changes:
- Handle new null/non-null type signatures in overriden methods
- Configure Robolectric to use SDK 33 (current highest supported
version)
- Remove `Injectable` interface, use `@AndroidEntryPoint`
- Remove `DispatchingAndroidInjector`
- Remove `viewModelFactory`, use `@HiltViewModel`
- Create providers for the different DAOs, and inject those instead of
`AppDatabase`
- Create provider for a database transaction, inject that instead of
`AppDatabase`
- Update tests
Instead of linking to the privacy policy embed it in the app as a string
of HTML.
The string is created with a new `markdown2resource` plugin, which
converts `PRIVACY.md` to HTML and generates a Java class with the HTML
content.
Create `PrivacyPolicyActivity` to display the HTML in a `WebView`, and
link to it from `AboutActivity`.
The previous code did not always work when the user returned to the app
after a lengthy absence (e.g., overnight).
Instead of restoring by scrolling in `TimelineFragment`, restore by
working with the platform.
Determine the initial page to fetch by looking half a page ahead of the
saved saved status ID, and fetch that status and the page immediately
prior. This seems to match the view's expectations about what will be
immediately available.
Set `jumpThreshold` and `enablePlaceholders` in the `PagingConfig` so
the paging system will jump to the saved status.
Remove the restoration code in `TimelineFragment`.
Fixes#53
Start building infrastructure to automatically build and deploy the
`orangeRelease` variant to Google Play.
The variant needs an automatically incrementing `versionCode`. That is
derived from the count of all commits.
Change the separator between the version and the build metadata in the
`versionName` from `-` to `+` to be consistent with semantic versioning.
This is still an experiment, so the workflow is triggered manually and
only uploads to the internal track
The previous code ran the API call in a `try/catch block`, and handled
errors in the `catch`. But `NetworkResult` already catches the exception
and transforms it to a failure, so the error case was not handled.
Replace with `NetworkResult.fold`.
Remove the rxjava3 `Single` type from the MastodonAPI definition,
replacing with `Response` or `NetworkResult` as appropriate.
Update callsites and tests as appropriate.
This removes the need for `com.squareup.retrofit2:adapter-rxjava3`
A previous change dropped the check to see if media was marked as
sensitive, and so all media was hidden when viewing a thread. Reinstate
the check so only sensitive media is hidden (if the user preferences are
set that way).
Previous code created `statusDisplayOptions` in full each time, risking
the creation of inconsistent states / defaults.
Refactor to use `StatusDisplayOptions.from()` so the user's settings
(and defaults) are always respected.
Previously the voting button was always enabled, even if the user hadn't
made a choice.
Disable the button by default, and listen for clicks on the options.
Enable the button whenever one or more options are selected.
Fixes#90
Previous code `include`'d `toolbar_basic` inside an `AppBarLayout`.
But `toolbar_basic` already contains an `AppBarLayout`, which
resulted in some rendering issues.
Remove the `include` and incorporate the `MaterialToolbar` directly.
Set the toolbar to scroll out of the way when the screen scrolls, so
the behaviour is consistent with the tabs in `MainActivity` and
`AccountActivity`.
Previously, in `MainActivity` and `AccountActivity` the status bar would
be `colorPrimaryDark`.
Adjust the layouts and code so that `colorSurface` is used to match
the toolbar colour.
Fixes#79
The previous code generally converted between a higher and a lower type
by putting the type conversion functions on the lower type.
This introduced cycles in the code dependency graph, and made it more
difficult to follow the code flow.
Refactor the code so that types generally have a `from(...)` static
factory method that can create an instance from a lower type, and if
appropriate a `to...()` method that can also create an instance of that
lower type.
Add `docs/code-style.md` which explains the rationale for this change
in more detail so that future contributors can write code in the same
style.
In `MediaUploader` the lint warning can be ignored, as the stream is
closed elsewhere.
In the other files `.use` is used to simplify the code and remove
the need for Closeable.closeQuietly (as `.use` catches exceptions that
are thrown when closing).
Use `assert` to note when a nullable value is known to be non-null.
Extract a method call to a variable where necessary to do this.
Update `CharSequence.unicodeWrap()` to handle a null `CharSequence`.
`ConversationViewHolder` calls `getDisplayName()`, which may return
null.
Replace with `getName()`, which is consistent with usage in other
classes. Mark `getDisplayName()` as deprecated to prevent future
usage.
The previous code used SwitchPreference to generate the switches, which
didn't apply the Material colours. This made it difficult to distinguish
between the on/off states, as the non-Material colours for those states
are very similar.
Fix by using SwitchPreferenceCompat which uses the correct Material
colours.
The previous code used "?attr/colorOnTertiary", which is the wrong
colour for the default background. Remove the override, so the correct
styled colour is used.
The previous code only attempted to restore the user's reading position
once, after any initial refresh.
Adjust this so the position is restored after any refresh (which may
have been triggered from a menu instead of a swipe), and use
`scrollToPositionWithOffset` to ensure it's visible.
Testing showed additional activities with toolbar flicking issues. Fix
as before, using `setLiftOnScrollTargetView` to specify the scrolling
view the toolbar should lift above.
The chips for adding a new hashtag to a tab specified the background
colour without setting the text colour, resulting in the colour being
too low-contrast against the background.
Use `?colorOnPrimary` to get the correct colour.
Switching to the Material 3 themes caused the previous list dividers to
disappear.
Replace `DividerItemDecoration` with `MaterialDividerItemDecoration` to
restore them.
The previous code would always fetch the latest statuses when the app
restarts, jumping the user to the top of the home timeline. This is
because state.anchorPosition was null in this case.
Fix this by passing the saved initialKey to CachedTimelineRemoteMediator
and using it to construct a page of statuses around the requested
status.
This restores the user's reading position, and ensures that if the
user is at the top of the list their reading position is not reset
to the second item in the list.
Fixes#41, #42
The previous code did not handle refreshing correctly; it retained some
of the cache, and tried merge new statuses in to the cache. This does
not work, and resulted in the app creating gaps in the timeline if more
than a page's worth of statuses had appeared since the user last
refreshed (e.g., overnight).
Fix this by treating the on-device cache as disposable, as the Paging3
library intends. On refresh the cached timeline is emptied and replaced
with a fresh page.
This causes a problem for state that is not stored on the server but is
part of a status' viewdata (has the user toggled viewing a piece of
media, expanded a CW, etc).
The previous code tried to work around that by pulling the state out of
the page cache and copying it in to the new statuses. That won't work
when the page cache is being destroyed.
So do it properly -- store the viewdata state in a separate (sparse)
table that contains only rows for statuses that have a non-default
state.
Save changes to the state when the user interacts with a status, and
use the state to ensure that the viewdata for a status in a thread
matches the viewdata for the same status if it is shown on the home
timeline (and vice-versa).
Fixes#16
The previous code didn't collect the uiState, so it was fixed at the
default value, ignoring any changes that happened over the life of
the viewmodel.
Fix that, so that the FAB will hide/show on scroll according to the
user's preferences.
While I'm here simplify the show/hide logic. The previous code would
ignore the user's preference if scrolling up. There doesn't seem to
be a good reason for that, and spelunking 6+ years back through the
history didn't find a justification for that behaviour in the original
commit.
Fixes#15
Scrolling a thread, set of search results, or viewing a thread would
cause the toolbar to flicker as items moved under it.
Fix this by configuring the toolbar to `liftOnScroll` in the relevant
layouts.
It needs to be configured with the view (or ID of the view) that it
will be scrolling. For views that are in the same layout this is done
with the `liftOnScrollTargetViewId` attribute.
For views that are in different layouts (e.g. the toolbar is in
the activity and the scrolling view is in a fragment) the app bar's
`setLiftOnScrollTargetView` method must be called.
Do this in `TimelineFragment` if the hosting activity is a new
interface `AppBarLayoutHost`. Implement this interface in
`StatusListActivity`.
Update the relevant layouts to use `MaterialToolbar`.
Fixes#21
First crash appeared to be caused by a failure to find the
`attr/colorBackgroundAccent` colour from the theme.
It wasn't clear why the attribute could not be found, so to fix it was
simpler to remove the color and attribute entirely, and replace it with
something more appropriate from the Material 3 tokens.
- Preview cards are stroked with `colorOutline`
- Poll options use `colorPrimary` (user's vote) or `colorSecondary`
(other choices) with appropriate text colours.
- Links in link preview cards use `android:attr/textColorLink`
- The placeholder icon in preview cards uses `?android/textColorLink`
- Remove it from `help_message_background`, and stroke with
`?colorOutline`
Doing this I discovered several places where a colour was being
specified unnecessarily, those have been removed.
To make it easier to understand the theme hierarchy that has been
collapsed and renamed to follow Android conventions.
- AppTheme -> Base.Theme.Pachli
- BaseTheme -> Theme.Pachli
- DefaultTheme has been removed as unnecessary
This unearthed a second crash, where `attr/actionBarSizeWithSubtitle`
was not found.
To fix that create an explicit style for toolbars that need it, and
apply the style (`Pachli.Widget.Toolbar`).
This also surfaced a third problem, where the `fragment_timeline*`
layouts had not been updated in `layout-sw640dp`, so those have been
updated to reflect the same views/IDs as the default `fragment_timeline`
layout.
These changes caused a small chain of "unused resource" lint errors,
which have been fixed by removing the unused colours.
The Android Material libraries were also being implicitly depended on
through other library imports instead of being explicit. So include
them as an explicit dependency.
Fixes#18
The previous code was operating on the wrong text, resulting in normal
URL spans (which have an underline) being applied, instead of the
correct spans (which don't).
Fix this by using the correct length.
Previous code created hashtag filters without the `#`, so muting the
tag `#something` would filter all posts that contained `something`,
with or without the `#`.
Fix this when creating filters, and only remove filters (when unmuting)
if the title and contents match.
Show a snackbar when hashtags are successfully muted/unmuted, so the
user is aware something happened.
While drafting the policy I noticed that the `READ_MEDIA_*` permissions
could be added (for newer devices), the `ACCESS_NETWORK_STATE`
permission was missing, and `VIBRATE` was unnecessary.
- Rename packages to app.pachli.*
- Switch to Pachli icons (blue / orange)
- Reset database schema version to 1
- Reset versionCode to 1 and versionName to "1.0"
- Update colour scheme, use colorPrimary etc through the app
- Use Material UI components for toolbars
- Use "Pachli" in strings (UI, constants, etc)
- Update copyright on code I contributed
- Update README
- Update fastlane metadata
Requiring trailing commas on multi-line lists of items (declarations
and call sites) reduces future repository churn when those lines are
changed, but introduces additional churn now.
Bite the bullet and make the change, as well as adjusting lines that
were too long / indented incorrectly.
The changes were performed automatically, using the `ktlintFormat` task.
Based on https://github.com/tuskyapp/Tusky/pull/3968 by
https://github.com/tinsukE
Filters that the user had set for the notifications timeline were not
being applied.
Fix this in NotificationsViewModel; fetch the user's filters and apply
them against the status in a notification. If the status should be
hidden it is removed, and if it should show a warning it does so.
The user can click through the warning to show the status.
Prior to this change the user had to repeatedly tap "Load more" when
scrolling. This is tedious for the user.
In addition, the previous code had bugs that meant that not all statuses
were being loaded. Users could leave the app for a while (overnight,
say), and when returning would discover far fewer statuses than had
actually been posted.
Fix this, following the architecture first introduced for notifications
(Fragment -> ViewModel -> Repository -> Source/Mediator).
- Load statuses for cached and non-cached timelines using Paging3
- Show Failures during a load, and the user can retry
- Delete the "Reading order" preference, it is no longer necessary
Android's choices for font customisation can be limited, depending on
the vendor. Allow users to choose from a small collection of embedded
fonts, chosen by asking users for recommendations.
The font choice is implemented as a preference. Provide a custom dialog
that shows the fonts (in that font) so the user can see what they're
choosing between.
Ensure the font's license information is displayed in the "About"
section.
The previous code did not credit all third party code used in the app,
or provide access to the licenses.
Fix this by adopting the "aboutlibraries" library, which processes
dependencies at build time and generates a list of dependencies,
versions, and license information to display to the user.
Use this to also ensure that the non-source dependencies (artwork,
emoji) are given appropriate credit.
- Remove the existing restriction on the number of tabs
- Allow the tabs to scroll to display more
- Update UI and text resources to remove obsolete content
- Implement the trending posts API
- Display trending statuses as a new Timeline kind
- Allow the user to add trending statuses to a dedicated tab
- Always show the "Trending" option in the navigation menu
- Implement the trending links API
- Provide a Fragment/ViewModel/Repository and Adapter/ViewHolder set
to display the content
- Show all trends (as a pageable fragment list) in TrendingActivity
- Allow the user to add trending links to a dedicated tab
- Always show the "Trending" option in the navigation menu
### Objective
* Prevent data loss when the user inadvertently hits back or wants to
leave the profile edition with unsaved changes.
### Description
* To limit the number of changes to the existing codebase, I merely
re-used the same method used by `save()` in the ViewModel to decide
whether to make a network request or simply return the profile as-is.
* ~A bit of code juggling around in the ViewModel and I was able to use
the logic for all the encoding of each profile field (Which is what the
ViewModel caches in memory).~ Thanks @Lakoja for improving this in the
VM.
* A couple of internal data classes used as helpers to move all the
fields around (now that they are no longer used in one single place)
were introduced.
### Potential Optimizations
* ~The profile encoding is done twice (once for checking, and then again
if the user has to actually save it). I'd say this is a negligible price
to pay, since the alternative would be to create a different set of
comparisons and/or keeping another profile in memory for the purpose of
comparison.~
### Visual Improvement
* I believe the Dialog is difficult to see, but it's being displayed
with Tusky's theme. Perhaps there's a better style to apply in this
case? (or maybe the edit profile activity shouldn't have the same
background color as dialogs?!)
### Issue
* #3486
Set the "System Design" as the default theme.
This ensures that the app's initial behaviour respect's the user's system-wide theme choice, while still allowing the user to adjust it later.
This is only done for new installs of Tusky. If the user is upgrading from a previous release and they did not have an explicit theme set then the dark theme is used, and the UX does not change.