Compare commits

...

128 Commits

Author SHA1 Message Date
sk
d78e3a738d bump version 2022-11-12 03:06:40 +01:00
sk
3898ad0c1c update readme 2022-11-12 03:06:11 +01:00
sk
5632016220 Merge branch 'feature/follow-requests' into fork 2022-11-12 03:05:02 +01:00
sk
12fdd70ad0 implement accepting/rejecting follow requests
closes #13
2022-11-12 03:03:52 +01:00
sk
7fd6a6f83e Merge branch 'feature/follow-requests' into fork 2022-11-12 03:03:14 +01:00
sk
515592e8ea implement accepting/rejecting follow requests
closes #13
2022-11-12 03:01:59 +01:00
sk
5fa81e6c8a add accept/decline buttons 2022-11-12 01:18:07 +01:00
sk
c22a139a5d bump version 2022-11-11 21:09:31 +01:00
sk
bed95e54d2 update readme 2022-11-11 21:07:36 +01:00
sk
f568415d8c Merge branch 'feature/favs-list' into fork 2022-11-11 21:06:53 +01:00
sk
c1137cf7b7 implement favorited posts list
closes #4
2022-11-11 21:05:06 +01:00
sk
7f678945be add title for github pages, update readme 2022-11-11 20:44:14 +01:00
sk
bb72d43270 update readme layout 2022-11-11 20:29:01 +01:00
sk
67524f6e53 update readme 2022-11-11 20:23:11 +01:00
sk
b2a7b62902 Rename “Community“ to “Local” 2022-11-11 19:57:19 +01:00
sk
a356bdeee3 change github and website url 2022-11-11 19:33:26 +01:00
sk
1438e65c10 change gh pages jekyll theme 2022-11-11 19:31:51 +01:00
sk
76043e87ad Merge branch 'fix-edit-polls' into fork 2022-11-11 19:20:16 +01:00
sk
3b28bd6ce9 get poll duration from edited status
fixes mastodon#343
2022-11-11 19:10:45 +01:00
sk
1236b16a3a Merge branch 'feature/delete-redraft' into fork 2022-11-11 18:00:09 +01:00
sk
5ec4e5339b fix null pointer exception 2022-11-11 17:59:53 +01:00
sk
0643f72a0b Merge branch 'feature/delete-redraft' into fork 2022-11-11 17:57:27 +01:00
sk
443b985b06 navigate to re-drafted status when opened 2022-11-11 17:53:04 +01:00
sk
aecaba2a92 show reply icon as workaround for mastodon#341 2022-11-11 17:18:21 +01:00
sk
977f3d0635 fix re-drafting replies 2022-11-11 17:17:52 +01:00
sk
5742493185 re-implement redraft from upstream edit function 2022-11-11 16:12:26 +01:00
sk
8b2d06c548 Merge branch 'main' into feature/delete-redraft 2022-11-11 15:36:33 +01:00
sk
56b76080ec Revert "implement deleting and re-drafting"
This reverts commit 3a4d13b1c6.
2022-11-11 15:35:30 +01:00
sk
1a12659a23 Revert "preserve visibility when re-drafting"
This reverts commit e8b43c7179.
2022-11-11 15:35:26 +01:00
sk
d711ed986c set hiding interactions counts as default 2022-11-11 14:58:39 +01:00
sk
53cb809996 bump version, update readme 2022-11-11 13:26:48 +01:00
sk
ff0a77d6b5 Merge branch 'settings/hide-interaction-numbers' into fork 2022-11-11 13:25:35 +01:00
sk
596ec3230f add option to hide interaction counts 2022-11-11 13:23:53 +01:00
sk
fa75c8eb6f Merge branch 'compact-extended-footer' into fork 2022-11-11 13:03:51 +01:00
sk
6c7a17fb81 change edit history icon 2022-11-11 13:03:39 +01:00
sk
0ad8f926cc set reblogs and favs always in one line 2022-11-11 12:54:17 +01:00
sk
764ab60fea bump version 2022-11-11 03:15:52 +01:00
sk
9b13bdb06f update readme 2022-11-11 03:15:32 +01:00
sk
9283ca8878 Merge branch 'list-timeline-views' into fork 2022-11-11 03:06:55 +01:00
sk
679ede1124 update strings 2022-11-11 03:02:17 +01:00
sk
f4b1bde8c5 improve list item styling 2022-11-11 02:42:58 +01:00
sk
eff6cc3e17 Merge branch 'compact-extended-footer' into fork 2022-11-11 02:11:52 +01:00
sk
03044b86b1 add clickable app name button 2022-11-11 02:11:37 +01:00
sk
90ff88f02b Merge branch 'compact-extended-footer' into fork 2022-11-11 01:35:13 +01:00
sk
e1f378977a set fixed visibility icon size 2022-11-11 01:35:01 +01:00
sk
a0f3d2862c Merge branch 'compact-extended-footer' into fork 2022-11-11 01:27:46 +01:00
sk
0c64145368 implement new old extended footer design
fixes #34
2022-11-11 01:27:17 +01:00
sk
a1474fb461 bump version 2022-11-09 23:29:30 +01:00
sk
965ebc8669 Merge branch 'feature/post-notifications-toggle' into fork 2022-11-09 23:28:59 +01:00
sk
3514152439 fix issues with post notification button 2022-11-09 23:28:48 +01:00
sk
1b1dc7085e update readme 2022-11-09 18:42:49 +01:00
sk
94d004724f Merge branch 'feature/post-notifications-toggle' into fork 2022-11-09 18:41:14 +01:00
sk
e9f5d235cb Implement post notifications button
Closes #10, mastodon#81
2022-11-09 18:39:28 +01:00
sk
9692afb323 bump version 2022-11-09 18:38:58 +01:00
sk
3ae37a700b Merge branch 'feature/post-notifications-toggle' into fork 2022-11-09 18:38:20 +01:00
sk
b166ca705e Implement post notifications button 2022-11-09 18:38:10 +01:00
sk
e6a67543f4 Bump version, update README 2022-11-09 15:54:33 +01:00
sk
7d38f031f1 Merge branch 'feature/follow_hashtags' into fork
Fixes #31, mastodon#233
2022-11-09 15:49:29 +01:00
sk
cf864e6f49 Implement following hashtags 2022-11-09 15:48:01 +01:00
sk
f4b1629a1d Merge branch 'feature/filter-home-timeline' into fork 2022-11-09 10:34:47 +01:00
sk
1fbb97021e align code 2022-11-09 10:34:36 +01:00
sk
229c815a59 move option to home timeline section 2022-11-09 10:32:12 +01:00
sk
ee8d78637d Merge branch 'settings/load-new-posts' into fork 2022-11-09 10:31:30 +01:00
sk
4ede842171 add option to disable automatically loading new posts 2022-11-09 10:30:25 +01:00
sk
8306e78ce3 Merge branch 'feature/filter-home-timeline' into fork 2022-11-09 10:12:16 +01:00
sk
65d093ee9d fix new posts not filtering 2022-11-09 10:11:55 +01:00
sk
aa48233a75 Merge branch 'main' into fork 2022-11-09 09:48:03 +01:00
Grishka
ae50e618c0 Fix #316 2022-11-08 09:41:07 +03:00
Grishka
defa8b014e Fix #315 2022-11-08 09:38:26 +03:00
Grishka
159d91f390 Crash fixes 2022-11-08 09:36:56 +03:00
sk
430d4ec93b Merge branch 'spoiler-height-independent' into fork 2022-11-08 00:55:10 +01:00
sk
ae64784daf revert increased vertical padding
i changed my mind
2022-11-08 00:54:48 +01:00
sk
18df7c32a4 Merge branch 'feature/filter-home-timeline' into fork 2022-11-08 00:27:31 +01:00
sk
4615612a65 use home_timeline string 2022-11-08 00:27:19 +01:00
sk
6b226cdcad bump version 2022-11-08 00:21:33 +01:00
sk
1776709b38 update readme 2022-11-08 00:21:17 +01:00
sk
75a35cd680 Merge branch 'feature/filter-home-timeline' into fork 2022-11-08 00:18:19 +01:00
sk
18d4f2fa36 add timeline filters for replies/boosts
fixes #32, mastodon#210
2022-11-08 00:13:06 +01:00
sk
bca936f6a2 move contribute to the spicy zone
i've been planning to do this for a while :D
2022-11-07 23:37:10 +01:00
sk
0b59379c4e update readme 2022-11-07 23:35:31 +01:00
sk
58abdad62a bump version 2022-11-07 21:18:41 +01:00
sk
c693414cc3 update readme 2022-11-07 21:18:37 +01:00
sk
ee158e1aba Merge branch 'feature/mark-media-as-sensitive' into fork 2022-11-07 21:04:44 +01:00
sk
40d478aaec tweak styles 2022-11-07 21:04:28 +01:00
sk
e7fb96b3ff Merge branch 'feature/mark-media-as-sensitive' into fork 2022-11-07 20:47:15 +01:00
sk
ef75427b45 fix option not being there when editing 2022-11-07 20:46:49 +01:00
sk
2ff4f00774 Merge branch 'feature/mark-media-as-sensitive' into fork 2022-11-07 20:17:39 +01:00
sk
534fd8c119 implement "mark media as sensitive" button
fixes #21
2022-11-07 20:15:16 +01:00
sk
2d2cd89454 Merge branch 'spoiler-height-independent' into fork 2022-11-07 17:32:55 +01:00
sk
9f3f5ca7c1 make spoiler height independent of content
fixes #12, fixes mastodon/mastodon-android#166
2022-11-07 17:32:37 +01:00
Samuel Kaiser
c0e6f17c83 Merge pull request #28 from br4yd/patch-1
Add German translation for the federated timeline
2022-11-07 16:57:21 +01:00
sk
677f1cb42d Merge remote-tracking branch 'origin/upstream' into fork 2022-11-07 16:56:27 +01:00
Gregory K
1ac6a04a46 Merge pull request #309 from julroy67/perapp-language
Add Android 13 per-app language preferences
2022-11-07 06:49:58 +03:00
Julien Humbert
21927d2e25 Add missing line in Android Manifest 2022-11-07 12:32:20 +09:00
Julien Humbert
c35441f5f7 Add per-app language preferences 2022-11-07 12:27:26 +09:00
br4yd
3718fe6601 Add German translation for the federated timeline
I added the translation strings for the federation timeline tab to the German translation file and also translated the values.
2022-11-06 22:21:53 +01:00
sk
095f234bd5 bump version 2022-11-06 11:16:31 +01:00
sk
f10c0e06db Merge branch 'fork' of github.com:sk22/mastodon-android-fork into fork 2022-11-06 11:13:42 +01:00
sk
ebbd5d1fa3 Merge branch 'upstream' into fork 2022-11-06 11:13:26 +01:00
Grishka
c8abf26040 Fix #301 2022-11-06 05:43:17 +03:00
Grishka
bc733af147 Actually no, this makes more sense 2022-11-06 05:39:22 +03:00
Grishka
77a2fd2a60 Fix #298 2022-11-06 05:36:43 +03:00
Samuel Kaiser
5cfc5eb08a Update README.md 2022-11-05 11:27:02 +01:00
Samuel Kaiser
b832d2df26 Update README.md 2022-11-04 18:02:33 +01:00
Samuel Kaiser
db34dc40ba Merge branch 'mastodon:master' into fork 2022-11-04 14:32:13 +01:00
Gregory K
bfa48c2d3e Merge pull request #289 from sk22/fix-editing-cw
Fix spoiler not being published when editing
2022-11-04 07:02:40 +03:00
Gregory K
b5e229a84d Merge pull request #288 from sk22/fix-editing-visibility
Fix wrong visibility displayed when editing
2022-11-04 07:02:19 +03:00
sk
ef207f885b increase update check interval 2022-11-04 03:37:32 +01:00
sk
c4eee28335 Merge branch 'feature/check-for-update-button' into fork 2022-11-04 03:34:06 +01:00
sk
6fe466779e add toash message 2022-11-04 03:33:41 +01:00
sk
e71db1b883 custom app name in strings 2022-11-04 03:11:10 +01:00
sk
b6efafe99d Merge branch 'feature/check-for-update-button' into fork 2022-11-04 03:09:39 +01:00
sk
688d466f8e implement manual update check settings item 2022-11-04 03:09:19 +01:00
Samuel Kaiser
3c5797932e add download badge 2022-11-03 17:42:31 +01:00
sk
48a5e262ce bump version 2022-11-03 17:18:57 +01:00
sk
63b0365208 remove reverted default visibility 2022-11-03 17:18:42 +01:00
sk
972c05d60b Revert "set unlisted as default visibility"
This reverts commit d34653750e.
2022-11-03 17:13:10 +01:00
sk
acb5778e0b bump version, again 2022-11-03 16:36:31 +01:00
sk
7694c50358 Merge branch 'fix-editing-cw' into fork 2022-11-03 16:35:31 +01:00
sk
7ffa368d10 fix spoiler not being published when editing 2022-11-03 16:33:12 +01:00
Y32Gcnte8z
53f8f41d88 remain visibility when editing 2022-11-03 16:15:17 +01:00
sk
93d57d847e fix newly published posts appearing twice
see https://github.com/mastodon/mastodon-android/issues/283
2022-11-03 16:14:12 +01:00
sk
83a4f5eec2 re-add missing onStatusUpdate event listener 2022-11-03 16:11:44 +01:00
obstsalatschuessel
21a526dda9 Add list timelines view
* get accounts list timelines from API
* display available lists in discover view tab
* display list timeline
2022-11-03 09:03:34 +01:00
Samuel Kaiser
836c2dba8d Merge branch 'mastodon:master' into fork 2022-11-02 22:32:57 +01:00
Samuel Kaiser
13ae8d2ebe Update README.md 2022-11-02 22:30:16 +01:00
Gregory K
98dafb4e49 Merge pull request #280 from sk22/fix-notifications-crash
Check whether title status item is null to fix null pointer exception
2022-11-03 00:27:07 +03:00
sk
85b8bae42e bump version 2022-11-02 22:15:26 +01:00
79 changed files with 1507 additions and 397 deletions

103
README.md
View File

@@ -1,34 +1,97 @@
![Pink version of the Mastodon for Android launcher icon](mastodon/src/main/res/mipmap-xhdpi/ic_launcher_round.png)
# Mastodon for Android Fork
# Mastodos
## Changes
> A fork of the [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly wont ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
* [Enable "Unlisted" as a visibility option](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted)
([Pull request](https://github.com/mastodon/mastodon-android/pull/103)) and
[set as default](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted-as-default)
* [Add "Federation" tab and change Discover tab order](https://github.com/sk22/mastodon-android-fork/tree/feature/add-federated-timeline) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/8))
* [Add image description button and viewer](https://github.com/sk22/mastodon-android-fork/tree/feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
* [Implement pinning posts and displaying pinned posts](https://github.com/sk22/mastodon-android-fork/tree/feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
* [Display full image when adding image description](https://github.com/sk22/mastodon-android-fork/tree/feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
* [Always preserve content warnings when replying](https://github.com/sk22/mastodon-android-fork/tree/feature/always-preserve-cw) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/113))
* [Make back button return to the home tab before exiting the app](https://github.com/sk22/mastodon-android-fork/tree/feature/back-returns-home) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/118))
* [Implement a bookmark button and list](https://github.com/sk22/mastodon-android-fork/tree/feature/bookmarks) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/22))
* [Implement deleting and re-drafting](https://github.com/sk22/mastodon-android-fork/tree/feature/delete-redraft) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/21))
[![Download latest release](https://img.shields.io/badge/dynamic/json?color=d92aad&label=download%20apk&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2Fsk22%2Fmastodon-android-fork%2Freleases%2Flatest&style=for-the-badge)](https://github.com/sk22/mastodos/releases/latest/download/mastodos.apk)
## Fork-specific changes
---
* Custom app name
## Key features
### **Unlisted posting**
**Allows you to post publicly without having your post show up in trends, hashtags or public timelines (i.e., in the tabs “Local”, “Community” and “Posts”).**
When posting with Unlisted visibility, your posts will be publicly accessible through your profile and shown in peoples Home timelines only if they follow you or someone they follow reposted/replied to your post.
The Mastodon documentation has some more information about [Unlisted posting](https://docs.joinmastodon.org/user/posting/#unlisted) and [Public timelines](https://docs.joinmastodon.org/user/network/#timelines).
### **Federated timeline**
**This allows you to chronologically see all Public posts from people on all other Fediverse instances your home instance is connected to.**
Despite being one of the main features of federated social media, the Federated timeline wasnt included in the official Mastodon app supposedly, because this conflicts with Googles safety requirements for apps on the Play Store.
Thats one of the reasons why choosing a small, **well-moderated instance is important**. Instance admins and moderators should always make sure to ban abusive users and stop federating with instances who platform them. On well-moderated instances, the Federated timeline can be a welcoming place to meet new people!
### **Image description viewer**
**Allows you to quickly check whether an image or video has an alternative text attached to it.**
This is important to **ensure the content youre sharing is as accessible as possible** to people who cant see the images and rely on software to read back the provided content descriptions. Thankfully, its quite common for people on the Fediverse to provide such alt texts, and hopefully things stay this way!
### **Pinning posts**
**This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in peoples profiles shows all the posts they pinned.**
On the Fediverse, its quite common for people to pin posts they want others to read before following them. You can pin/unpin posts yourself by clicking the `⋯` button in the top right corner of your posts.
### **Bookmarks**
**They allow for quickly saving posts and viewing them through the Bookmarks button on the top right of your profile.**
To bookmark a post, press the button between the Favorite and Share buttons on the bottom of the post. Bookmarks are saved privately, so the post authors wont know you saved their post the list of bookmarked posts is only visible to you.
## Installation
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Mastodos will automatically notify you about new updates inside the app.**
To install this app on your Android device, download the [latest release from GitHub](https://github.com/sk22/mastodos/releases/latest/download/mastodos.apk) and open it. You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/sk22/mastodos/releases) page.
Mastodos makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)s automatic update checker. Mastodos will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
---
## Detailed changes
### Features
* [Add “Unlisted” as a post visibility option](https://github.com/sk22/mastodos/commits/feature/enable-unlisted)
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
* [Add “Federation” tab and change Discover tab order](https://github.com/sk22/mastodos/commits/feature/add-federated-timeline) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/8))
* [Add image description button and viewer](https://github.com/sk22/mastodos/commits/feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
* [Implement pinning posts and displaying pinned posts](https://github.com/sk22/mastodos/commits/feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
* [Implement deleting and re-drafting](https://github.com/sk22/mastodos/commits/feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
* [Implement a bookmark button and list](https://github.com/sk22/mastodos/commits/feature/bookmarks) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/22))
* [Add “Check for update” button in addition to integrated update checker](https://github.com/sk22/mastodos/commits/feature/check-for-update-button)
* [Add “Mark media as sensitive” option](https://github.com/sk22/mastodos/commits/feature/mark-media-as-sensitive)
* [Add settings to hide replies and reposts from the timeline](https://github.com/sk22/mastodos/commits/feature/filter-home-timeline) ([Pull request](https://github.com/mastodon/mastodon-android/pull/317))
* [Follow and unfollow hashtags](https://github.com/sk22/mastodos/commit/7d38f031f197aa6cefaf53e39d929538689c1e4e) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/233))
* [Notification bell for posts](https://github.com/sk22/mastodos/commit/b166ca705eb9169025ef32bbe6315b42491b57ea) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/81))
* [Lists view (viewing only, for now)](https://github.com/sk22/mastodos/commits/list-timeline-views) based on [@obstsalatschuessel](https://github.com/obstsalatschuessel)'s [Pull request](https://github.com/mastodon/mastodon-android/pull/286)
* [List favorited posts](https://github.com/sk22/mastodos/commits/feature/favs-list)
* [Accept/reject follow requests](https://github.com/sk22/mastodos/commits/feature/follow-requests)
### Behavior
* [Make back button return to the home tab before exiting the app](https://github.com/sk22/mastodos/commits/feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
* [Always preserve content warnings when replying](https://github.com/sk22/mastodos/commits/feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
* [Display full image when adding image description](https://github.com/sk22/mastodos/commits/feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
* [Set spoiler height independently to content height](https://github.com/sk22/mastodos/commits/spoiler-height-independent) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/166))
* [Custom extended footer redesign](https://github.com/sk22/mastodos/commits/compact-extended-footer)
* [Option to hide interaction numbers](https://github.com/sk22/mastodos/commits/settings/hide-interaction-numbers)
### Branding
* App name “Mastodos”
* Pink primary color
* Custom icon: Modulate upstream icon using ImageMagick
```bash
mogrify -modulate 90,100,140 mastodon/src/main/res/mipmap-*/ic_launcher*.png
```
* Custom primary color: Hue of all `primary` colors in `colors.xml` is rotated, on basis of upstream Mastodon's [old branding](https://github.com/mastodon/mastodon-android/commit/74f03026cfcfcfd23237c38ff47d2b2a98a6f92a#diff-59134ec2a1cf3761f80b0ecccbbf8b9e433d9780d2f5c5d6ac3ac8cc254e808f)
by `109.8°` (equivalent of `161%`, done by hand using
[PineTools](https://pinetools.com/shift-hue-color))
## Building
As this app is using Java 17 features, you need JDK 17 or newer to build it. Other than that, everything is pretty standard. You can either import the project into Android Studio and build it from there, or run the following command in the project directory:

2
_config.yml Normal file
View File

@@ -0,0 +1,2 @@
title: Mastodos
theme: minima

View File

@@ -9,9 +9,13 @@ android {
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 33
versionCode 25
versionName "1.1.4+fork.25"
versionCode 38
versionName "1.1.4+fork.38"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resConfigs "en", "ar-rSA", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES",
"eu-rES", "fi-rFI", "fr-rFR", "gl-rES", "hr-rHR", "hy-rAM", "it-rIT", "iw-rIL",
"ja-rJP", "kab", "ko-rKR", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ru-rRU",
"sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
}
buildTypes {

View File

@@ -26,6 +26,8 @@ import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import java.io.File;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -36,7 +38,7 @@ import okhttp3.Response;
@Keep
public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
private static final long CHECK_PERIOD=24*3600*1000L;
private static final long CHECK_PERIOD=6*3600*1000L;
private static final String TAG="GithubSelfUpdater";
private UpdateState state=UpdateState.NO_UPDATE;
@@ -101,9 +103,15 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
}
}
@Override
public void checkForUpdates() {
setState(UpdateState.CHECKING);
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
}
private void actuallyCheckForUpdates(){
Request req=new Request.Builder()
.url("https://api.github.com/repos/sk22/mastodon-android-fork/releases/latest")
.url("https://api.github.com/repos/sk22/mastodos/releases/latest")
.build();
Call call=MastodonAPIController.getHttpClient().newCall(req);
try(Response resp=call.execute()){

View File

@@ -16,6 +16,7 @@
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:localeConfig="@xml/locales_config"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Mastodon.AutoLightDark"

View File

@@ -7,6 +7,10 @@ public class GlobalUserPreferences{
public static boolean playGifs;
public static boolean useCustomTabs;
public static boolean trueBlackTheme;
public static boolean showReplies;
public static boolean showBoosts;
public static boolean loadNewPosts;
public static boolean showInteractionCounts;
public static ThemePreference theme;
private static SharedPreferences getPrefs(){
@@ -18,6 +22,10 @@ public class GlobalUserPreferences{
playGifs=prefs.getBoolean("playGifs", true);
useCustomTabs=prefs.getBoolean("useCustomTabs", true);
trueBlackTheme=prefs.getBoolean("trueBlackTheme", false);
showReplies=prefs.getBoolean("showReplies", true);
showBoosts=prefs.getBoolean("showBoosts", true);
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
}
@@ -25,7 +33,11 @@ public class GlobalUserPreferences{
getPrefs().edit()
.putBoolean("playGifs", playGifs)
.putBoolean("useCustomTabs", useCustomTabs)
.putBoolean("showReplies", showReplies)
.putBoolean("showBoosts", showBoosts)
.putBoolean("loadNewPosts", loadNewPosts)
.putBoolean("trueBlackTheme", trueBlackTheme)
.putBoolean("showInteractionCounts", showInteractionCounts)
.putInt("theme", theme.ordinal())
.apply();
}

View File

@@ -12,6 +12,7 @@ import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -48,6 +49,8 @@ public class ResizedImageRequestBody extends CountingRequestBody{
}
contentType=MastodonApp.context.getContentResolver().getType(uri);
}
if(TextUtils.isEmpty(contentType))
contentType="image/jpeg";
if(needResize(opts.outWidth, opts.outHeight) || needCrop(opts.outWidth, opts.outHeight)){
Bitmap bitmap;
if(Build.VERSION.SDK_INT>=28){

View File

@@ -0,0 +1,49 @@
package org.joinmastodon.android.api.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
import java.io.IOException;
import java.util.List;
import okhttp3.Response;
public class GetFavourites extends MastodonAPIRequest<List<Status>>{
private String maxId;
public GetFavourites(String maxID, String minID, int limit){
super(HttpMethod.GET, "/favourites", new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(minID!=null)
addQueryParameter("min_id", minID);
if(limit>0)
addQueryParameter("limit", ""+limit);
}
@Override
public void validateAndPostprocessResponse(List<Status> respObj, Response httpResponse) throws IOException {
super.validateAndPostprocessResponse(respObj, httpResponse);
// <https://mastodon.social/api/v1/bookmarks?max_id=268962>; rel="next",
// <https://mastodon.social/api/v1/bookmarks?min_id=268981>; rel="prev"
String link=httpResponse.header("link");
// parsing link header by hand; using a library would be cleaner
// (also, the functionality should be part of the max id logics and implemented in MastodonAPIRequest)
if(link==null) return;
String maxIdEq="max_id=";
for(String s : link.split(",")) {
if(s.contains("rel=\"next\"")) {
int start=s.indexOf(maxIdEq)+maxIdEq.length();
int end=s.indexOf('>');
if(start<0 || start>end) return;
this.maxId=s.substring(start, end);
}
}
}
public String getMaxId() {
return maxId;
}
}

View File

@@ -4,10 +4,10 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;
public class SetAccountFollowed extends MastodonAPIRequest<Relationship>{
public SetAccountFollowed(String id, boolean followed, boolean showReblogs){
public SetAccountFollowed(String id, boolean followed, boolean showReblogs, boolean notify){
super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class);
if(followed)
setRequestBody(new Request(showReblogs, null));
setRequestBody(new Request(showReblogs, notify));
else
setRequestBody(new Object());
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.follow_requests;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;
public class AuthorizeFollowRequest extends MastodonAPIRequest<Relationship>{
public AuthorizeFollowRequest(String id){
super(HttpMethod.POST, "/follow_requests/"+id+"/authorize", Relationship.class);
setRequestBody(new Object());
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.follow_requests;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;
public class RejectFollowRequest extends MastodonAPIRequest<Relationship>{
public RejectFollowRequest(String id){
super(HttpMethod.POST, "/follow_requests/"+id+"/reject", Relationship.class);
setRequestBody(new Object());
}
}

View File

@@ -0,0 +1,14 @@
package org.joinmastodon.android.api.requests.lists;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
import java.util.List;
public class GetLists extends MastodonAPIRequest<List<ListTimeline>>{
public GetLists() {
super(HttpMethod.GET, "/lists", new TypeToken<>(){});
}
}

View File

@@ -14,6 +14,6 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
public String clientName="Mastodos";
public String redirectUris=AccountSessionManager.REDIRECT_URI;
public String scopes=AccountSessionManager.SCOPE;
public String website="https://github.com/sk22/mastodon-android-fork";
public String website="https://sk22.github.io/mastodos";
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.tags;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
public class GetHashtag extends MastodonAPIRequest<Hashtag> {
public GetHashtag(String name){
super(HttpMethod.GET, "/tags/"+name, Hashtag.class);
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.tags;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
public class SetHashtagFollowed extends MastodonAPIRequest<Hashtag>{
public SetHashtagFollowed(String name, boolean followed){
super(HttpMethod.POST, "/tags/"+name+"/"+(followed ? "follow" : "unfollow"), Hashtag.class);
setRequestBody(new Object());
}
}

View File

@@ -0,0 +1,22 @@
package org.joinmastodon.android.api.requests.timelines;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
import java.util.List;
public class GetListTimeline extends MastodonAPIRequest<List<Status>> {
public GetListTimeline(String listID, String maxID, String minID, int limit, String sinceID) {
super(HttpMethod.GET, "/timelines/list/"+listID, new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(minID!=null)
addQueryParameter("min_id", minID);
if(limit>0)
addQueryParameter("limit", ""+limit);
if(sinceID!=null)
addQueryParameter("since_id", sinceID);
}
}

View File

@@ -0,0 +1,9 @@
package org.joinmastodon.android.events;
public class NotificationDeletedEvent{
public final String id;
public NotificationDeletedEvent(String id){
this.id=id;
}
}

View File

@@ -4,7 +4,7 @@ import org.joinmastodon.android.model.Status;
public class StatusCountersUpdatedEvent{
public String id;
public int favorites, reblogs, replies;
public long favorites, reblogs, replies;
public boolean favorited, reblogged, pinned;
public StatusCountersUpdatedEvent(Status s){

View File

@@ -101,6 +101,8 @@ import org.parceler.Parcels;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -161,11 +163,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private Button publishButton;
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn;
private ImageView sensitiveIcon;
private ComposeMediaLayout attachmentsView;
private TextView replyText;
private ReorderableLinearLayout pollOptionsView;
private View pollWrap;
private View addPollOptionBtn;
private View sensitiveItem;
private TextView pollDurationView;
private ArrayList<DraftPollOption> pollOptions=new ArrayList<>();
@@ -181,11 +185,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private String pollDurationStr;
private EditText spoilerEdit;
private boolean hasSpoiler;
private boolean sensitive;
private ProgressBar sendProgress;
private ImageView sendError;
private View sendingOverlay;
private WindowManager wm;
private StatusPrivacy statusVisibility=StatusPrivacy.UNLISTED;
private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC;
private ComposeAutocompleteSpan currentAutocompleteSpan;
private FrameLayout mainEditTextWrap;
private ComposeAutocompleteViewController autocompleteViewController;
@@ -193,19 +198,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private boolean attachmentsErrorShowing;
private Status editingStatus;
private boolean redraftStatus;
private boolean pollChanged;
private boolean creatingView;
private boolean ignoreSelectionChanges=false;
private Runnable updateUploadEtaRunnable;
public static DraftMediaAttachment redraftAttachment(Attachment att) {
DraftMediaAttachment draft=new DraftMediaAttachment();
draft.serverAttachment=att;
draft.description=att.description;
draft.uri=Uri.parse(att.url);
return draft;
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -219,6 +217,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain);
if(getArguments().containsKey("editStatus")){
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
redraftStatus=getArguments().getBoolean("redraftStatus");
}
if(instance==null){
Nav.finish(this);
@@ -290,6 +289,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
emojiBtn=view.findViewById(R.id.btn_emoji);
spoilerBtn=view.findViewById(R.id.btn_spoiler);
visibilityBtn=view.findViewById(R.id.btn_visibility);
sensitiveIcon=view.findViewById(R.id.sensitive_icon);
sensitiveItem=view.findViewById(R.id.sensitive_item);
replyText=view.findViewById(R.id.reply_text);
mediaBtn.setOnClickListener(v->openFilePicker());
@@ -297,6 +298,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
spoilerBtn.setOnClickListener(v->toggleSpoiler());
visibilityBtn.setOnClickListener(this::onVisibilityClick);
sensitiveItem.setOnClickListener(v->toggleSensitive());
emojiKeyboard.setOnIconChangedListener(new PopupKeyboard.OnIconChangeListener(){
@Override
public void onIconChanged(int icon){
@@ -322,18 +324,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
pollDurationView.setOnClickListener(v->showPollDurationMenu());
pollOptions.clear();
ArrayList<String> restoredPollOptions=(savedInstanceState!=null ? savedInstanceState : getArguments())
.getStringArrayList("pollOptions");
if(restoredPollOptions!=null){
if(savedInstanceState==null){
// restoring from arguments
pollDuration=getArguments().getInt("pollDuration");
pollDurationStr=DateUtils.formatElapsedTime(pollDuration); // getResources().getQuantityString(R.plurals.x_hours, pollDuration/3600);
}
if(savedInstanceState!=null && savedInstanceState.containsKey("pollOptions")){
pollBtn.setSelected(true);
mediaBtn.setEnabled(false);
pollWrap.setVisibility(View.VISIBLE);
for(String oldText:restoredPollOptions){
for(String oldText:savedInstanceState.getStringArrayList("pollOptions")){
DraftPollOption opt=createDraftPollOption();
opt.edit.setText(oldText);
}
@@ -347,6 +342,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
DraftPollOption opt=createDraftPollOption();
opt.edit.setText(eopt.title);
}
pollDuration=(int)editingStatus.poll.expiresAt.minus(System.currentTimeMillis(), ChronoUnit.MILLIS).getEpochSecond();
pollDurationStr=UiUtils.formatTimeLeft(getActivity(), editingStatus.poll.expiresAt);
updatePollOptionHints();
pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr));
}else{
@@ -362,14 +359,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
spoilerEdit.setVisibility(View.VISIBLE);
spoilerBtn.setSelected(true);
}else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){
hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE);
spoilerEdit.setText(getArguments().getString("sourceSpoiler", editingStatus.spoilerText));
spoilerBtn.setSelected(true);
}
ArrayList<Parcelable> serializedAttachments=(savedInstanceState!=null ? savedInstanceState : getArguments())
.getParcelableArrayList("attachments");
if(serializedAttachments!=null){
if(savedInstanceState!=null && savedInstanceState.containsKey("attachments")){
ArrayList<Parcelable> serializedAttachments=savedInstanceState.getParcelableArrayList("attachments");
for(Parcelable a:serializedAttachments){
DraftMediaAttachment att=Parcels.unwrap(a);
attachmentsView.addView(createMediaAttachmentView(att));
@@ -386,6 +383,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(editingStatus!=null && editingStatus.visibility!=null) {
statusVisibility=editingStatus.visibility;
}
updateVisibilityIcon();
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
@@ -410,16 +408,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
outState.putStringArrayList("pollOptions", opts);
outState.putInt("pollDuration", pollDuration);
outState.putString("pollDurationStr", pollDurationStr);
outState.putBoolean("hasSpoiler", hasSpoiler);
if(!attachments.isEmpty()){
ArrayList<Parcelable> serializedAttachments=new ArrayList<>(attachments.size());
for(DraftMediaAttachment att:attachments){
serializedAttachments.add(Parcels.wrap(att));
}
outState.putParcelableArrayList("attachments", serializedAttachments);
}
outState.putSerializable("visibility", statusVisibility);
}
outState.putBoolean("hasSpoiler", hasSpoiler);
outState.putBoolean("sensitive", sensitive);
if(!attachments.isEmpty()){
ArrayList<Parcelable> serializedAttachments=new ArrayList<>(attachments.size());
for(DraftMediaAttachment att:attachments){
serializedAttachments.add(Parcels.wrap(att));
}
outState.putParcelableArrayList("attachments", serializedAttachments);
}
outState.putSerializable("visibility", statusVisibility);
}
@Override
@@ -527,7 +526,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
spoilerBtn.setSelected(true);
}
}
}else{
}else if (editingStatus==null || editingStatus.inReplyToId==null){
// TODO: remove workaround after https://github.com/mastodon/mastodon-android/issues/341 gets fixed
replyText.setVisibility(View.GONE);
}
if(savedInstanceState==null){
@@ -544,6 +544,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
da.serverAttachment=att;
da.description=att.description;
da.uri=Uri.parse(att.previewUrl);
da.state=AttachmentUploadState.DONE;
attachmentsView.addView(createMediaAttachmentView(da));
attachments.add(da);
}
@@ -567,15 +568,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
}
updateSensitive();
if(editingStatus!=null){
updateCharCounter();
visibilityBtn.setEnabled(false);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
publishButton=new Button(getActivity());
publishButton.setText(editingStatus==null ? R.string.publish : R.string.save);
publishButton.setText(editingStatus==null || redraftStatus ? R.string.publish : R.string.save);
publishButton.setOnClickListener(this::onPublishClick);
LinearLayout wrap=new LinearLayout(getActivity());
wrap.setOrientation(LinearLayout.HORIZONTAL);
@@ -676,11 +680,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
CreateStatus.Request req=new CreateStatus.Request();
req.status=text;
req.visibility=statusVisibility;
req.sensitive=sensitive;
if(!attachments.isEmpty()){
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
}
if(replyTo!=null){
req.inReplyToId=replyTo.id;
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
}
if(!pollOptions.isEmpty()){
req.poll=new CreateStatus.Request.Poll();
@@ -723,6 +728,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
E.post(new StatusUpdatedEvent(result));
}
Nav.finish(ComposeFragment.this);
if (getArguments().getBoolean("navigateToStatus", false)) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result));
if(replyTo!=null) args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo));
Nav.go(getActivity(), ThreadFragment.class, args);
}
}
@Override
@@ -736,7 +748,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
};
if(editingStatus!=null){
if(editingStatus!=null && !redraftStatus){
new EditStatus(req, editingStatus.id)
.setCallback(resCallback)
.exec(accountID);
@@ -882,6 +894,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
uploadNextQueuedAttachment();
}
updatePublishButtonState();
updateSensitive();
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS)
mediaBtn.setEnabled(false);
return true;
@@ -1056,6 +1069,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
updatePublishButtonState();
pollBtn.setEnabled(attachments.isEmpty());
mediaBtn.setEnabled(true);
updateSensitive();
}
private void onRetryOrCancelMediaUploadClick(View v){
@@ -1257,7 +1271,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
spoilerBtn.setSelected(false);
mainEditText.requestFocus();
updateCharCounter();
sensitiveIcon.setVisibility(getMediaAttachmentsCount() > 0 ? View.VISIBLE : View.GONE);
}
updateSensitive();
}
private void toggleSensitive() {
sensitive=!sensitive;
sensitiveIcon.setSelected(sensitive);
}
private void updateSensitive() {
sensitiveItem.setVisibility(View.GONE);
if (!attachments.isEmpty() && !hasSpoiler) sensitiveItem.setVisibility(View.VISIBLE);
if (attachments.isEmpty()) sensitive = false;
}
private int getMediaAttachmentsCount(){

View File

@@ -0,0 +1,46 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetFavourites;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Status;
import java.util.List;
import me.grishka.appkit.api.SimpleCallback;
public class FavoritesListFragment extends StatusListFragment{
private String accountID;
private String lastMaxId=null;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
setTitle(R.string.favorited_posts);
}
@Override
protected void onShown(){
super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
loadData();
}
@Override
protected void doLoadData(int offset, int count) {
GetFavourites b=new GetFavourites(offset>0 ? lastMaxId : null, null, count);
currentRequest=b.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
onDataLoaded(result, b.getMaxId()!=null);
lastMaxId=b.getMaxId();
}
})
.exec(accountID);
}
}

View File

@@ -2,23 +2,34 @@ package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.Toast;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.tags.GetHashtag;
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status;
import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class HashtagTimelineFragment extends StatusListFragment{
private String hashtag;
private boolean following;
private ImageButton fab;
private MenuItem followButton;
public HashtagTimelineFragment(){
setListLayoutId(R.layout.recycler_fragment_with_fab);
@@ -27,10 +38,61 @@ public class HashtagTimelineFragment extends StatusListFragment{
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
hashtag=getArguments().getString("hashtag");
updateTitle(getArguments().getString("hashtag"));
following=getArguments().getBoolean("following", false);
setHasOptionsMenu(true);
}
private void updateTitle(String hashtagName) {
hashtag = hashtagName;
setTitle('#'+hashtag);
}
private void updateFollowingState(boolean newFollowing) {
this.following = newFollowing;
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.hashtag_timeline, menu);
followButton = menu.findItem(R.id.follow_hashtag);
updateFollowingState(following);
followButton.setOnMenuItemClickListener(i -> {
updateFollowingState(!following);
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
@Override
public void onSuccess(Hashtag i) {
updateFollowingState(i.following);
Toast.makeText(getActivity(), getString(i.following ? R.string.followed_user : R.string.unfollowed_user, "#" + i.name), Toast.LENGTH_SHORT).show();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
updateFollowingState(!following);
}
}).exec(accountID);
return true;
});
new GetHashtag(hashtag).setCallback(new Callback<>() {
@Override
public void onSuccess(Hashtag hashtag) {
updateTitle(hashtag.name);
updateFollowingState(hashtag.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
}
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count)

View File

@@ -24,6 +24,7 @@ import android.widget.Toolbar;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -73,6 +74,13 @@ public class HomeTimelineFragment extends StatusListFragment{
loadData();
}
private List<Status> filterPosts(List<Status> items) {
return items.stream().filter(i ->
(GlobalUserPreferences.showReplies || i.inReplyToId == null) &&
(GlobalUserPreferences.showBoosts || i.reblog == null)
).collect(Collectors.toList());
}
@Override
protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance()
@@ -82,7 +90,8 @@ public class HomeTimelineFragment extends StatusListFragment{
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
if(getActivity()==null)
return;
onDataLoaded(result.items, !result.items.isEmpty());
List<Status> filteredItems = filterPosts(result.items);
onDataLoaded(filteredItems, !result.items.isEmpty());
maxID=result.maxID;
if(result.isFromCache())
loadNewPosts();
@@ -142,7 +151,6 @@ public class HomeTimelineFragment extends StatusListFragment{
}
}
@Subscribe
public void onStatusCreated(StatusCreatedEvent ev){
prependItems(Collections.singletonList(ev.status), true);
}
@@ -154,6 +162,7 @@ public class HomeTimelineFragment extends StatusListFragment{
}
private void loadNewPosts(){
if (!GlobalUserPreferences.loadNewPosts) return;
dataLoading=true;
// The idea here is that we request the timeline such that if there are fewer than `limit` posts,
// we'll get the currently topmost post as last in the response. This way we know there's no gap
@@ -165,6 +174,7 @@ public class HomeTimelineFragment extends StatusListFragment{
public void onSuccess(List<Status> result){
currentRequest=null;
dataLoading=false;
result = filterPosts(result);
if(result.isEmpty() || getActivity()==null)
return;
Status last=result.get(result.size()-1);

View File

@@ -0,0 +1,75 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.media.MediaRouter;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
import org.joinmastodon.android.model.Status;
import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class ListTimelineFragment extends StatusListFragment {
private String listID;
private String listTitle;
private ImageButton fab;
public ListTimelineFragment() {
setListLayoutId(R.layout.recycler_fragment_with_fab);
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
listID=getArguments().getString("listID");
listTitle=getArguments().getString("listTitle");
setTitle(listTitle);
}
@Override
protected void doLoadData(int offset, int count) {
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null)
.setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(List<Status> result) {
onDataLoaded(result, !result.isEmpty());
}
})
.exec(accountID);
}
@Override
protected void onShown() {
super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
}
private void onFabClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("prefilledText", listID+' ');
Nav.go(getActivity(), ComposeFragment.class, args);
}
@Override
protected void onSetFabBottomInset(int inset) {
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
}
}

View File

@@ -0,0 +1,96 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.List;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
private String accountId;
public ListTimelinesFragment() {
super(10);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
accountId=getArguments().getString("account");
}
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetLists()
.setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(List<ListTimeline> result) {
onDataLoaded(result, false);
}
})
.exec(accountId);
}
@Override
protected RecyclerView.Adapter getAdapter() {
return new ListsAdapter();
}
@Override
public void scrollToTop() {
smoothScrollRecyclerViewToTop(list);
}
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
@NonNull
@Override
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new ListViewHolder();
}
@Override
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
holder.bind(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
}
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
private final TextView title;
public ListViewHolder(){
super(getActivity(), R.layout.item_list_timeline, list);
title=findViewById(R.id.title);
}
@Override
public void onBind(ListTimeline item) {
title.setText(item.title);
}
@Override
public void onClick() {
UiUtils.openListTimeline(getActivity(), accountId, item);
}
}
}

View File

@@ -9,6 +9,7 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.NotificationDeletedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
@@ -78,7 +79,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
items.add(0, titleItem);
return items;
}else if(titleItem!=null){
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, n.account);
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, n.account, n);
return Arrays.asList(titleItem, card);
}else{
return Collections.emptyList();
@@ -180,4 +181,28 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
}
}
@Subscribe
public void onNotificationDeleted(NotificationDeletedEvent ev) {
Notification notification = getNotificationByID(ev.id);
if(notification==null)
return;
data.remove(notification);
int index=-1;
for(int i=0;i<displayItems.size();i++){
if(ev.id.equals(displayItems.get(i).parentID)){
index=i;
break;
}
}
if(index==-1)
return;
int lastIndex;
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
if(!displayItems.get(lastIndex).parentID.equals(ev.id))
break;
}
displayItems.subList(index, lastIndex).clear();
adapter.notifyItemRangeRemoved(index, lastIndex-index);
}
}

View File

@@ -98,7 +98,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private CoverImageView cover;
private View avatarBorder;
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel, postsCount, postsLabel;
private ProgressBarButton actionButton;
private ProgressBarButton actionButton, notifyButton;
private ViewPager2 pager;
private NestedRecyclerScrollView scrollView;
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
@@ -109,7 +109,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private float titleTransY;
private View postsBtn, followersBtn, followingBtn;
private EditText nameEdit, bioEdit;
private ProgressBar actionProgress;
private ProgressBar actionProgress, notifyProgress;
private FrameLayout[] tabViews;
private TabLayoutMediator tabLayoutMediator;
private TextView followsYouView;
@@ -181,6 +181,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
postsLabel=content.findViewById(R.id.posts_label);
postsBtn=content.findViewById(R.id.posts_btn);
actionButton=content.findViewById(R.id.profile_action_btn);
notifyButton=content.findViewById(R.id.notify_btn);
pager=content.findViewById(R.id.pager);
scrollView=content.findViewById(R.id.scroller);
tabbar=content.findViewById(R.id.tabbar);
@@ -188,6 +189,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
nameEdit=content.findViewById(R.id.name_edit);
bioEdit=content.findViewById(R.id.bio_edit);
actionProgress=content.findViewById(R.id.action_progress);
notifyProgress=content.findViewById(R.id.notify_progress);
fab=content.findViewById(R.id.fab);
followsYouView=content.findViewById(R.id.follows_you);
@@ -258,6 +260,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
});
actionButton.setOnClickListener(this::onActionButtonClick);
notifyButton.setOnClickListener(this::onNotifyButtonClick);
avatar.setOnClickListener(this::onAvatarClick);
cover.setOnClickListener(this::onCoverClick);
refreshLayout.setOnRefreshListener(this);
@@ -414,7 +417,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private void bindHeaderView(){
setTitle(account.displayName);
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, account.statusesCount, account.statusesCount));
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
@@ -451,13 +454,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, Math.min(999, account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, Math.min(999, account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, Math.min(999, account.statusesCount)));
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, account.statusesCount)));
UiUtils.loadCustomEmojiInTextView(name);
UiUtils.loadCustomEmojiInTextView(bio);
notifyButton.setVisibility(View.GONE);
if(AccountSessionManager.getInstance().isSelf(accountID, account)){
actionButton.setText(R.string.edit_profile);
}else{
@@ -532,6 +536,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
MenuItem item=menu.getItem(i);
item.setVisible(item.getItemId()==R.id.share || item.getItemId()==R.id.bookmarks);
}
menu.findItem(R.id.favorites_list).setVisible(true);
return;
}
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
@@ -560,6 +565,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(account));
Nav.go(getActivity(), BookmarksListFragment.class, args);
}else if(id==R.id.favorites_list) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(account));
Nav.go(getActivity(), FavoritesListFragment.class, args);
}else if(id==R.id.mute){
confirmToggleMuted();
}else if(id==R.id.block){
@@ -577,7 +587,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
updateRelationship();
});
}else if(id==R.id.hide_boosts){
new SetAccountFollowed(account.id, true, !relationship.showingReblogs)
new SetAccountFollowed(account.id, true, !relationship.showingReblogs, relationship.notifying)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
@@ -622,9 +632,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private void updateRelationship(){
invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
UiUtils.setRelationshipToActionButton(relationship, actionButton);
UiUtils.setRelationshipToActionButton(relationship, notifyButton, true);
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
notifyButton.setSelected(relationship.notifying);
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.user_post_notifications_on : R.string.user_post_notifications_off, '@'+account.username));
}
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
@@ -691,6 +706,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
actionButton.setClickable(!visible);
}
private void setNotifyProgressVisible(boolean visible){
notifyButton.setTextVisible(!visible);
notifyProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
notifyButton.setClickable(!visible);
}
private void loadAccountInfoAndEnterEditMode(){
if(editModeLoading)
return;
@@ -859,6 +880,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return Collections.singletonList(att);
}
private void onNotifyButtonClick(View v) {
UiUtils.performToggleAccountNotifications(getActivity(), account, accountID, relationship, actionButton, this::setNotifyProgressVisible, this::updateRelationship);
}
private void onAvatarClick(View v){
if(isInEditMode){
startImagePicker(AVATAR_RESULT);

View File

@@ -15,6 +15,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.ImageButton;
@@ -71,6 +72,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
private PushSubscription pushSubscription;
private ImageView themeTransitionWindowView;
private TextItem checkForUpdateItem;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -102,6 +104,24 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.useCustomTabs=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
GlobalUserPreferences.showInteractionCounts=i.checked;
GlobalUserPreferences.save();
}));
items.add(new HeaderItem(R.string.home_timeline));
items.add(new SwitchItem(R.string.settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
GlobalUserPreferences.showReplies=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
GlobalUserPreferences.showBoosts=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.settings_load_new_posts, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.loadNewPosts, i->{
GlobalUserPreferences.loadNewPosts=i.checked;
GlobalUserPreferences.save();
}));
items.add(new HeaderItem(R.string.settings_notifications));
items.add(notificationPolicyItem=new NotificationPolicyItem());
@@ -113,11 +133,15 @@ public class SettingsFragment extends MastodonToolbarFragment{
items.add(new HeaderItem(R.string.settings_boring));
items.add(new TextItem(R.string.settings_account, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit")));
items.add(new TextItem(R.string.settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/mastodon/mastodon-android")));
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
items.add(new RedHeaderItem(R.string.settings_spicy));
if (GithubSelfUpdater.needSelfUpdating()) {
checkForUpdateItem = new TextItem(R.string.check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
items.add(checkForUpdateItem);
}
items.add(new TextItem(R.string.settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/mastodon/mastodon-android")));
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
@@ -326,11 +350,25 @@ public class SettingsFragment extends MastodonToolbarFragment{
@Subscribe
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
if(items.get(0) instanceof UpdateItem item){
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(0);
if(holder instanceof UpdateViewHolder uvh){
uvh.bind(item);
}
checkForUpdateItem.loading = ev.state == GithubSelfUpdater.UpdateState.CHECKING;
if (list.findViewHolderForAdapterPosition(items.indexOf(checkForUpdateItem)) instanceof TextViewHolder tvh) tvh.rebind();
UpdateItem updateItem = null;
if(items.get(0) instanceof UpdateItem item0) {
updateItem = item0;
} else if (ev.state != GithubSelfUpdater.UpdateState.CHECKING
&& ev.state != GithubSelfUpdater.UpdateState.NO_UPDATE) {
updateItem = new UpdateItem();
items.add(0, updateItem);
list.setAdapter(new SettingsAdapter());
}
if(updateItem != null && list.findViewHolderForAdapterPosition(0) instanceof UpdateViewHolder uvh){
uvh.bind(updateItem);
}
if (ev.state == GithubSelfUpdater.UpdateState.NO_UPDATE) {
Toast.makeText(getActivity(), R.string.no_update_available, Toast.LENGTH_SHORT).show();
}
}
@@ -398,10 +436,16 @@ public class SettingsFragment extends MastodonToolbarFragment{
private class TextItem extends Item{
private String text;
private Runnable onClick;
private boolean loading;
public TextItem(@StringRes int text, Runnable onClick){
public TextItem(@StringRes int text, Runnable onClick) {
this(text, onClick, false);
}
public TextItem(@StringRes int text, Runnable onClick, boolean loading){
this.text=getString(text);
this.onClick=onClick;
this.loading=loading;
}
@Override
@@ -630,14 +674,18 @@ public class SettingsFragment extends MastodonToolbarFragment{
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
private final TextView text;
private final ProgressBar progress;
public TextViewHolder(){
super(getActivity(), R.layout.item_settings_text, list);
text=(TextView) itemView;
text = itemView.findViewById(R.id.text);
progress = itemView.findViewById(R.id.progress);
}
@Override
public void onBind(TextItem item){
text.setText(item.text);
progress.animate().alpha(item.loading ? 1 : 0);
}
@Override
@@ -692,8 +740,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
@Override
public void onBind(UpdateItem item){
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
GithubSelfUpdater.UpdateState state=updater.getState();
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
text.setText(getString(R.string.update_available, info.version));
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));

View File

@@ -10,7 +10,6 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
@@ -63,6 +62,59 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
protected void onStatusCreated(StatusCreatedEvent ev){}
protected void onStatusUpdated(StatusUpdatedEvent ev){
ArrayList<Status> statusesForDisplayItems=new ArrayList<>();
for(int i=0;i<data.size();i++){
Status s=data.get(i);
if(s.reblog!=null && s.reblog.id.equals(ev.status.id)){
s.reblog=ev.status;
statusesForDisplayItems.add(s);
}else if(s.id.equals(ev.status.id)){
data.set(i, ev.status);
statusesForDisplayItems.add(ev.status);
}
}
for(int i=0;i<preloadedData.size();i++){
Status s=preloadedData.get(i);
if(s.reblog!=null && s.reblog.id.equals(ev.status.id)){
s.reblog=ev.status;
}else if(s.id.equals(ev.status.id)){
preloadedData.set(i, ev.status);
}
}
if(statusesForDisplayItems.isEmpty())
return;
for(Status s:statusesForDisplayItems){
int i=0;
for(StatusDisplayItem item:displayItems){
if(item.parentID.equals(s.id)){
int start=i;
for(;i<displayItems.size();i++){
if(!displayItems.get(i).parentID.equals(s.id))
break;
}
List<StatusDisplayItem> postItems=displayItems.subList(start, i);
postItems.clear();
postItems.addAll(buildDisplayItems(s));
int oldSize=i-start, newSize=postItems.size();
if(oldSize==newSize){
adapter.notifyItemRangeChanged(start, newSize);
}else if(oldSize<newSize){
adapter.notifyItemRangeChanged(start, oldSize);
adapter.notifyItemRangeInserted(start+oldSize, newSize-oldSize);
}else{
adapter.notifyItemRangeChanged(start, newSize);
adapter.notifyItemRangeRemoved(start+newSize, oldSize-newSize);
}
break;
}
i++;
}
}
}
protected Status getContentStatusByID(String id){
Status s=getStatusByID(id);
return s==null ? null : s.getContentStatus();
@@ -138,6 +190,11 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
StatusListFragment.this.onStatusCreated(ev);
}
@Subscribe
public void onStatusUpdated(StatusUpdatedEvent ev){
StatusListFragment.this.onStatusUpdated(ev);
}
@Subscribe
public void onPollUpdated(PollUpdatedEvent ev){
if(!ev.accountID.equals(accountID))

View File

@@ -353,7 +353,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
bindRelationship();
});
}else if(id==R.id.hide_boosts){
new SetAccountFollowed(account.id, true, !relationship.showingReblogs)
new SetAccountFollowed(account.id, true, !relationship.showingReblogs, relationship.notifying)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){

View File

@@ -12,7 +12,7 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, account.followersCount, account.followersCount));
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, (int)(account.followersCount%1000), account.followersCount));
}
@Override

View File

@@ -12,7 +12,7 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setSubtitle(getResources().getQuantityString(R.plurals.x_following, account.followingCount, account.followingCount));
setSubtitle(getResources().getQuantityString(R.plurals.x_following, (int)(account.followingCount%1000), account.followingCount));
}
@Override

View File

@@ -11,7 +11,7 @@ public class StatusFavoritesListFragment extends StatusRelatedAccountListFragmen
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(getResources().getQuantityString(R.plurals.x_favorites, status.favouritesCount, status.favouritesCount));
setTitle(getResources().getQuantityString(R.plurals.x_favorites, (int)(status.favouritesCount%1000), status.favouritesCount));
}
@Override

View File

@@ -11,7 +11,7 @@ public class StatusReblogsListFragment extends StatusRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(getResources().getQuantityString(R.plurals.x_reblogs, status.reblogsCount, status.reblogsCount));
setTitle(getResources().getQuantityString(R.plurals.x_reblogs, (int)(status.reblogsCount%1000), status.reblogsCount));
}
@Override

View File

@@ -220,9 +220,9 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
followersCount.setText(UiUtils.abbreviateNumber(item.account.followersCount));
followingCount.setText(UiUtils.abbreviateNumber(item.account.followingCount));
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, Math.min(999, item.account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, Math.min(999, item.account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, Math.min(999, item.account.statusesCount)));
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
relationship=relationships.get(item.account.id);
if(relationship==null){
actionWrap.setVisibility(View.GONE);

View File

@@ -19,6 +19,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.fragments.ListTimelinesFragment;
import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
@@ -52,6 +53,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
private SearchFragment searchFragment;
private LocalTimelineFragment localTimelineFragment;
private FederatedTimelineFragment federatedTimelineFragment;
private ListTimelinesFragment listTimelinesFragment;
private String accountID;
private Runnable searchDebouncer=this::onSearchChangedDebounced;
@@ -73,7 +75,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
tabLayout=view.findViewById(R.id.tabbar);
pager=view.findViewById(R.id.pager);
tabViews=new FrameLayout[6];
tabViews=new FrameLayout[7];
for(int i=0;i<tabViews.length;i++){
FrameLayout tabView=new FrameLayout(getActivity());
tabView.setId(switch(i){
@@ -83,6 +85,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
case 3 -> R.id.discover_posts;
case 4 -> R.id.discover_news;
case 5 -> R.id.discover_users;
case 6 -> R.id.discover_lists;
default -> throw new IllegalStateException("Unexpected value: "+i);
});
tabView.setVisibility(View.GONE);
@@ -131,6 +134,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
federatedTimelineFragment=new FederatedTimelineFragment();
federatedTimelineFragment.setArguments(args);
listTimelinesFragment=new ListTimelinesFragment();
listTimelinesFragment.setArguments(args);
getChildFragmentManager().beginTransaction()
.add(R.id.discover_posts, postsFragment)
.add(R.id.discover_local_timeline, localTimelineFragment)
@@ -138,6 +144,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
.add(R.id.discover_hashtags, hashtagsFragment)
.add(R.id.discover_news, newsFragment)
.add(R.id.discover_users, accountsFragment)
.add(R.id.discover_lists, listTimelinesFragment)
.commit();
}
@@ -151,6 +158,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
case 3 -> R.string.posts;
case 4 -> R.string.news;
case 5 -> R.string.for_you;
case 6 -> R.string.list_timelines;
default -> throw new IllegalStateException("Unexpected value: "+position);
});
tab.view.textView.setAllCaps(true);
@@ -279,6 +287,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
case 3 -> postsFragment;
case 4 -> newsFragment;
case 5 -> accountsFragment;
case 6 -> listTimelinesFragment;
default -> throw new IllegalStateException("Unexpected value: "+page);
};
}

View File

@@ -91,6 +91,8 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
@Override
public void onItemClick(String id){
SearchResult res=getResultByID(id);
if(res==null)
return;
switch(res.type){
case ACCOUNT -> {
Bundle args=new Bundle();
@@ -98,7 +100,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
args.putParcelable("profileAccount", Parcels.wrap(res.account));
Nav.go(getActivity(), ProfileFragment.class, args);
}
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name);
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
case STATUS -> {
Status status=res.status.getContentStatus();
Bundle args=new Bundle();

View File

@@ -107,7 +107,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
@Override
public void onClick(){
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name);
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
}
}
}

View File

@@ -200,7 +200,6 @@ public class SignupFragment extends AppKitFragment{
@Override
public void onSuccess(Token result){
progressDialog.dismiss();
progressDialog=null;
Account fakeAccount=new Account();
fakeAccount.acct=fakeAccount.username=username;
fakeAccount.id="tmp"+System.currentTimeMillis();
@@ -238,7 +237,6 @@ public class SignupFragment extends AppKitFragment{
error.showToast(getActivity());
}
progressDialog.dismiss();
progressDialog=null;
}
})
.exec(instance.uri, apiToken);
@@ -255,9 +253,11 @@ public class SignupFragment extends AppKitFragment{
}
private void showProgressDialog(){
progressDialog=new ProgressDialog(getActivity());
progressDialog.setMessage(getString(R.string.loading));
progressDialog.setCancelable(false);
if(progressDialog==null){
progressDialog=new ProgressDialog(getActivity());
progressDialog.setMessage(getString(R.string.loading));
progressDialog.setCancelable(false);
}
progressDialog.show();
}
@@ -280,7 +280,6 @@ public class SignupFragment extends AppKitFragment{
if(submitAfterGettingToken){
submitAfterGettingToken=false;
progressDialog.dismiss();
progressDialog=null;
error.showToast(getActivity());
}
}
@@ -307,7 +306,6 @@ public class SignupFragment extends AppKitFragment{
if(submitAfterGettingToken){
submitAfterGettingToken=false;
progressDialog.dismiss();
progressDialog=null;
error.showToast(getActivity());
}
}

View File

@@ -125,7 +125,7 @@ public class ReportDoneFragment extends MastodonToolbarFragment{
}
private void onUnfollowClick(){
new SetAccountFollowed(reportAccount.id, false, false)
new SetAccountFollowed(reportAccount.id, false, false, false)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){

View File

@@ -96,15 +96,15 @@ public class Account extends BaseModel{
/**
* How many statuses are attached to this account.
*/
public int statusesCount;
public long statusesCount;
/**
* The reported followers of this profile.
*/
public int followersCount;
public long followersCount;
/**
* The reported follows of this profile.
*/
public int followingCount;
public long followingCount;
// Optional attributes

View File

@@ -11,6 +11,7 @@ public class Hashtag extends BaseModel{
public String name;
@RequiredField
public String url;
public boolean following;
public List<History> history;
@Override
@@ -18,6 +19,7 @@ public class Hashtag extends BaseModel{
return "Hashtag{"+
"name='"+name+'\''+
", url='"+url+'\''+
", following="+following+
", history="+history+
'}';
}

View File

@@ -0,0 +1,34 @@
package org.joinmastodon.android.model;
import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.api.RequiredField;
import org.parceler.Parcel;
@Parcel
public class ListTimeline extends BaseModel {
@RequiredField
public String id;
@RequiredField
public String title;
@RequiredField
public RepliesPolicy repliesPolicy;
@Override
public String toString() {
return "List{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", repliesPolicy=" + repliesPolicy +
'}';
}
public enum RepliesPolicy{
@SerializedName("followed")
FOLLOWED,
@SerializedName("list")
LIST,
@SerializedName("none")
NONE
}
}

View File

@@ -34,9 +34,9 @@ public class Status extends BaseModel implements DisplayItemsParent{
public List<Hashtag> tags;
@RequiredField
public List<Emoji> emojis;
public int reblogsCount;
public int favouritesCount;
public int repliesCount;
public long reblogsCount;
public long favouritesCount;
public long repliesCount;
public Instant editedAt;
public String url;

View File

@@ -13,9 +13,13 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.follow_requests.AuthorizeFollowRequest;
import org.joinmastodon.android.api.requests.follow_requests.RejectFollowRequest;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
@@ -24,6 +28,8 @@ import org.joinmastodon.android.ui.views.ProgressBarButton;
import java.util.Collections;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
@@ -31,13 +37,15 @@ import me.grishka.appkit.utils.V;
public class AccountCardStatusDisplayItem extends StatusDisplayItem{
private final Account account;
private final Notification notification;
public ImageLoaderRequest avaRequest, coverRequest;
public CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
public CharSequence parsedName, parsedBio;
public AccountCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account){
public AccountCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account, Notification notification){
super(parentID, parentFragment);
this.account=account;
this.notification=notification;
if(!TextUtils.isEmpty(account.avatar))
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.header))
@@ -73,9 +81,9 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<AccountCardStatusDisplayItem> implements ImageLoaderViewHolder{
private final ImageView cover, avatar;
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
private final ProgressBarButton actionButton;
private final ProgressBar actionProgress;
private final View actionWrap;
private final ProgressBarButton actionButton, acceptButton, rejectButton;
private final ProgressBar actionProgress, acceptProgress, rejectProgress;
private final View actionWrap, acceptWrap, rejectWrap;
private Relationship relationship;
@@ -96,6 +104,12 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
actionButton=findViewById(R.id.action_btn);
actionProgress=findViewById(R.id.action_progress);
actionWrap=findViewById(R.id.action_btn_wrap);
acceptButton=findViewById(R.id.accept_btn);
acceptProgress=findViewById(R.id.accept_progress);
acceptWrap=findViewById(R.id.accept_btn_wrap);
rejectButton=findViewById(R.id.reject_btn);
rejectProgress=findViewById(R.id.reject_progress);
rejectWrap=findViewById(R.id.reject_btn_wrap);
View card=findViewById(R.id.card);
card.setOutlineProvider(OutlineProviders.roundedRect(6));
@@ -105,6 +119,8 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
cover.setClipToOutline(true);
actionButton.setOnClickListener(this::onActionButtonClick);
acceptButton.setOnClickListener(this::onFollowRequestButtonClick);
rejectButton.setOnClickListener(this::onFollowRequestButtonClick);
}
@Override
@@ -115,18 +131,40 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
followersCount.setText(UiUtils.abbreviateNumber(item.account.followersCount));
followingCount.setText(UiUtils.abbreviateNumber(item.account.followingCount));
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, Math.min(999, item.account.followersCount)));
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, Math.min(999, item.account.followingCount)));
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, Math.min(999, item.account.statusesCount)));
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
relationship=item.parentFragment.getRelationship(item.account.id);
if(relationship==null){
if(item.notification.type == Notification.Type.FOLLOW_REQUEST && (relationship == null || !relationship.followedBy)){
actionWrap.setVisibility(View.GONE);
acceptWrap.setVisibility(View.VISIBLE);
rejectWrap.setVisibility(View.VISIBLE);
// i hate that i wasn't able to do this in xml
acceptButton.setCompoundDrawableTintList(acceptButton.getTextColors());
acceptProgress.setIndeterminateTintList(acceptButton.getTextColors());
rejectButton.setCompoundDrawableTintList(rejectButton.getTextColors());
rejectProgress.setIndeterminateTintList(rejectButton.getTextColors());
}else if(relationship==null){
actionWrap.setVisibility(View.GONE);
acceptWrap.setVisibility(View.GONE);
rejectWrap.setVisibility(View.GONE);
}else{
actionWrap.setVisibility(View.VISIBLE);
acceptWrap.setVisibility(View.GONE);
rejectWrap.setVisibility(View.GONE);
UiUtils.setRelationshipToActionButton(relationship, actionButton);
}
}
private void onFollowRequestButtonClick(View v) {
itemView.setHasTransientState(true);
UiUtils.handleFollowRequest((Activity) v.getContext(), item.account, item.parentFragment.getAccountID(), item.notification.id , v == acceptButton, relationship, rel -> {
itemView.setHasTransientState(false);
item.parentFragment.putRelationship(item.account.id, rel);
rebind();
});
}
private void onActionButtonClick(View v){
itemView.setHasTransientState(true);

View File

@@ -9,6 +9,8 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.TypefaceSpan;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.R;
@@ -45,18 +47,20 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
}
public static class Holder extends StatusDisplayItem.Holder<ExtendedFooterStatusDisplayItem>{
private final TextView time, favoritesCount, reblogsCount, lastEditTime;
private final View favorites, reblogs, editHistory;
private final TextView time;
private final Button favorites, reblogs, editHistory, applicationName;
private final ImageView visibility;
private final Context context;
public Holder(Context context, ViewGroup parent){
super(context, R.layout.display_item_extended_footer, parent);
this.context = context;
reblogs=findViewById(R.id.reblogs);
favorites=findViewById(R.id.favorites);
editHistory=findViewById(R.id.edit_history);
applicationName=findViewById(R.id.application_name);
visibility=findViewById(R.id.visibility);
time=findViewById(R.id.timestamp);
favoritesCount=findViewById(R.id.favorites_count);
reblogsCount=findViewById(R.id.reblogs_count);
lastEditTime=findViewById(R.id.last_edited);
reblogs.setOnClickListener(v->startAccountListFragment(StatusReblogsListFragment.class));
favorites.setOnClickListener(v->startAccountListFragment(StatusFavoritesListFragment.class));
@@ -67,20 +71,35 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(ExtendedFooterStatusDisplayItem item){
Status s=item.status;
favoritesCount.setText(String.format("%,d", s.favouritesCount));
reblogsCount.setText(String.format("%,d", s.reblogsCount));
favorites.setText(context.getResources().getQuantityString(R.plurals.x_favorites, (int)(s.favouritesCount%1000), s.favouritesCount));
reblogs.setText(context.getResources().getQuantityString(R.plurals.x_reblogs, (int)(s.reblogsCount%1000), s.reblogsCount));
if(s.editedAt!=null){
editHistory.setVisibility(View.VISIBLE);
lastEditTime.setText(item.parentFragment.getString(R.string.last_edit_at_x, UiUtils.formatRelativeTimestampAsMinutesAgo(itemView.getContext(), s.editedAt)));
editHistory.setText(UiUtils.formatRelativeTimestampAsMinutesAgo(itemView.getContext(), s.editedAt));
}else{
editHistory.setVisibility(View.GONE);
}
String timeStr=TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault()));
if(item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)){
time.setText(item.parentFragment.getString(R.string.timestamp_via_app, timeStr, item.status.application.name));
}else{
if (item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)) {
time.setText(item.parentFragment.getString(R.string.timestamp_via_app, timeStr, ""));
applicationName.setText(item.status.application.name);
if (item.status.application.website != null && item.status.application.website.toLowerCase().startsWith("https://")) {
applicationName.setOnClickListener(e -> UiUtils.openURL(context, null, item.status.application.website));
} else {
applicationName.setEnabled(false);
}
} else {
time.setText(timeStr);
applicationName.setVisibility(View.GONE);
}
visibility.setImageResource(switch (s.visibility) {
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
case UNLISTED -> R.drawable.ic_fluent_people_20_regular;
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
case DIRECT -> R.drawable.ic_at_symbol;
});
}
@Override

View File

@@ -11,6 +11,7 @@ import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
@@ -97,8 +98,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
}
private void bindButton(TextView btn, int count){
if(count>0 && !item.hideCounts){
private void bindButton(TextView btn, long count){
if(GlobalUserPreferences.showInteractionCounts && count>0 && !item.hideCounts){
btn.setText(DecimalFormat.getIntegerInstance().format(count));
btn.setCompoundDrawablePadding(V.dp(8));
}else{

View File

@@ -26,6 +26,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment;
@@ -137,10 +138,18 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
optionsMenu.setOnMenuItemClickListener(menuItem->{
Account account=item.user;
int id=menuItem.getItemId();
if(id==R.id.edit){
if(id==R.id.edit || id==R.id.delete_and_redraft) {
final Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID());
args.putParcelable("editStatus", Parcels.wrap(item.status));
if (id==R.id.delete_and_redraft) {
args.putBoolean("redraftStatus", true);
if (item.parentFragment instanceof ThreadFragment thread && !thread.isItemEnabled(item.status.id)) {
// ("enabled" = clickable; opened status is not clickable)
// request navigation to the re-drafted status if status is currently opened
args.putBoolean("navigateToStatus", true);
}
}
if(TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}else{
@@ -150,7 +159,13 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public void onSuccess(GetStatusSourceText.Response result){
args.putString("sourceText", result.text);
args.putString("sourceSpoiler", result.spoilerText);
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
if (id==R.id.delete_and_redraft) {
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}, true);
} else {
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}
}
@Override
@@ -163,8 +178,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}
}else if(id==R.id.delete){
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
}else if(id==R.id.delete_and_redraft) {
UiUtils.confirmDeleteAndRedraftPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
}else if(id==R.id.pin || id==R.id.unpin){
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
}else if(id==R.id.mute){

View File

@@ -158,7 +158,7 @@ public abstract class StatusDisplayItem{
}
public Holder(Context context, int layout, ViewGroup parent){
super(context, layout, parent);
super(context, layout, parent);
}
public String getItemID(){

View File

@@ -83,7 +83,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
itemView.setClickable(false);
}else{
spoilerOverlay.setVisibility(View.VISIBLE);
text.setVisibility(View.INVISIBLE);
text.setVisibility(View.GONE);
itemView.setClickable(true);
}
}else{

View File

@@ -34,7 +34,7 @@ public class LinkSpan extends CharacterStyle {
switch(getType()){
case URL -> UiUtils.openURL(context, accountID, link);
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link);
case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link, null);
}
}

View File

@@ -19,7 +19,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
import android.provider.OpenableColumns;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -40,26 +39,29 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
import org.joinmastodon.android.api.requests.follow_requests.AuthorizeFollowRequest;
import org.joinmastodon.android.api.requests.follow_requests.RejectFollowRequest;
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.NotificationDeletedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.text.SpacerSpan;
import org.parceler.Parcels;
@@ -70,8 +72,6 @@ import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -80,14 +80,11 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import androidx.annotation.AttrRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -193,6 +190,15 @@ public class UiUtils{
}
}
@SuppressLint("DefaultLocale")
public static String abbreviateNumber(long n){
if(n<1_000_000_000L)
return abbreviateNumber((int)n);
double a=n/1_000_000_000.0;
return a>99f ? String.format("%,dB", (int)Math.floor(a)) : String.format("%,.1fB", n/1_000_000_000.0);
}
/**
* Android 6.0 has a bug where start and end compound drawables don't get tinted.
* This works around it by setting the tint colors directly to the drawables.
@@ -300,13 +306,22 @@ public class UiUtils{
Nav.go((Activity)context, ProfileFragment.class, args);
}
public static void openHashtagTimeline(Context context, String accountID, String hashtag){
public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("hashtag", hashtag);
if (following != null) args.putBoolean("following", following);
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
}
public static void openListTimeline(Context context, String accountID, ListTimeline list){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("listID", list.id);
args.putString("listTitle", list.title);
Nav.go((Activity)context, ListTimelineFragment.class, args);
}
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){
showConfirmationAlert(context, context.getString(title), context.getString(message), context.getString(confirmButton), onConfirmed);
}
@@ -382,9 +397,12 @@ public class UiUtils{
.exec(accountID);
});
}
public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback){
showConfirmationAlert(activity, R.string.confirm_delete_title, R.string.confirm_delete, R.string.delete, ()->{
confirmDeletePost(activity, accountID, status, resultCallback, false);
}
public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback, boolean forRedraft){
showConfirmationAlert(activity, forRedraft ? R.string.confirm_delete_and_redraft_title : R.string.confirm_delete_title, forRedraft ? R.string.confirm_delete_and_redraft : R.string.confirm_delete, forRedraft ? R.string.delete_and_redraft : R.string.delete, ()->{
new DeleteStatus(status.id)
.setCallback(new Callback<>(){
@Override
@@ -431,62 +449,12 @@ public class UiUtils{
);
}
public static void confirmDeleteAndRedraftPost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback){
showConfirmationAlert(activity, R.string.confirm_delete_and_redraft_title, R.string.confirm_delete_and_redraft, R.string.delete_and_redraft, ()->{
new DeleteStatus(status.id)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Status result){
resultCallback.accept(result);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().deleteStatus(status.id);
E.post(new StatusDeletedEvent(status.id, accountID));
UiUtils.redraftStatus(status, accountID, activity);
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.deleting, false)
.exec(accountID);
});
}
public static void redraftStatus(Status status, String accountID, Activity activity) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putBoolean("hasDraft", true);
args.putString("prefilledText", HtmlParser.parse(status.content, status.emojis, status.mentions, status.tags, accountID).toString());
args.putString("spoilerText", status.spoilerText);
args.putSerializable("visibility", status.visibility);
if(status.poll!=null){
args.putInt("pollDuration", (int)status.poll.expiresAt.minus(status.createdAt.getEpochSecond(), ChronoUnit.SECONDS).getEpochSecond());
ArrayList<String> opts=status.poll.options.stream().map(o -> o.title).collect(Collectors.toCollection(ArrayList::new));
args.putStringArrayList("pollOptions", opts);
}
if(!status.mediaAttachments.isEmpty()){
ArrayList<Parcelable> serializedAttachments=status.mediaAttachments.stream()
.map(att -> Parcels.wrap(ComposeFragment.redraftAttachment(att)))
.collect(Collectors.toCollection(ArrayList::new));
args.putParcelableArrayList("attachments", serializedAttachments);
}
Callback<Status> cb=new Callback<>(){
@Override public void onError(ErrorResponse error) {
onSuccess(null);
error.showToast(activity);
}
@Override public void onSuccess(Status status) {
if (status!=null) args.putParcelable("replyTo", Parcels.wrap(status));
Nav.go(activity, ComposeFragment.class, args);
}
};
if(status.inReplyToId!=null) new GetStatusByID(status.inReplyToId).setCallback(cb).exec(accountID);
else cb.onSuccess(null);
}
public static void setRelationshipToActionButton(Relationship relationship, Button button){
setRelationshipToActionButton(relationship, button, false);
}
public static void setRelationshipToActionButton(Relationship relationship, Button button, boolean keepText){
CharSequence textBefore = keepText ? button.getText() : null;
boolean secondaryStyle;
if(relationship.blocking){
button.setText(R.string.button_blocked);
@@ -505,6 +473,8 @@ public class UiUtils{
secondaryStyle=true;
}
if (keepText) button.setText(textBefore);
button.setEnabled(!relationship.blockedBy);
int attr=secondaryStyle ? R.attr.secondaryButtonStyle : android.R.attr.buttonStyle;
TypedArray ta=button.getContext().obtainStyledAttributes(new int[]{attr});
@@ -521,6 +491,25 @@ public class UiUtils{
ta.recycle();
}
public static void performToggleAccountNotifications(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback) {
progressCallback.accept(true);
new SetAccountFollowed(account.id, true, relationship.showingReblogs, !relationship.notifying)
.setCallback(new Callback<>() {
@Override
public void onSuccess(Relationship result) {
resultCallback.accept(result);
progressCallback.accept(false);
Toast.makeText(activity, activity.getString(result.notifying ? R.string.user_post_notifications_on : R.string.user_post_notifications_off, '@'+account.username), Toast.LENGTH_SHORT).show();
}
@Override
public void onError(ErrorResponse error) {
progressCallback.accept(false);
error.showToast(activity);
}
}).exec(accountID);
}
public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback){
if(relationship.blocking){
confirmToggleBlockUser(activity, accountID, account, true, resultCallback);
@@ -528,7 +517,7 @@ public class UiUtils{
confirmToggleMuteUser(activity, accountID, account, true, resultCallback);
}else{
progressCallback.accept(true);
new SetAccountFollowed(account.id, !relationship.following && !relationship.requested, true)
new SetAccountFollowed(account.id, !relationship.following && !relationship.requested, true, false)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
@@ -546,6 +535,38 @@ public class UiUtils{
}
}
public static void handleFollowRequest(Activity activity, Account account, String accountID, String notificationID, boolean accepted, Relationship relationship, Consumer<Relationship> resultCallback) {
if (accepted) {
new AuthorizeFollowRequest(account.id).setCallback(new Callback<>() {
@Override
public void onSuccess(Relationship rel) {
resultCallback.accept(rel);
}
@Override
public void onError(ErrorResponse error) {
resultCallback.accept(relationship);
error.showToast(activity);
}
}).exec(accountID);
} else {
new RejectFollowRequest(account.id).setCallback(new Callback<>() {
@Override
public void onSuccess(Relationship rel) {
E.post(new NotificationDeletedEvent(notificationID));
resultCallback.accept(rel);
}
@Override
public void onError(ErrorResponse error) {
resultCallback.accept(relationship);
error.showToast(activity);
}
}).exec(accountID);
}
}
public static <T> void updateList(List<T> oldList, List<T> newList, RecyclerView list, RecyclerView.Adapter<?> adapter, BiPredicate<T, T> areItemsSame){
// Save topmost item position and offset because for some reason RecyclerView would scroll the list to weird places when you insert items at the top
int topItem, topItemOffset;
@@ -634,10 +655,10 @@ public class UiUtils{
return GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK;
}
public static void openURL(Context context, String accountID, String url){
public static void openURL(Context context, @Nullable String accountID, String url){
Uri uri=Uri.parse(url);
String accountDomain=AccountSessionManager.getInstance().getAccount(accountID).domain;
if("https".equals(uri.getScheme()) && accountDomain.equalsIgnoreCase(uri.getAuthority())){
String accountDomain=accountID != null ? AccountSessionManager.getInstance().getAccount(accountID).domain : null;
if(accountDomain!=null && "https".equals(uri.getScheme()) && accountDomain.equalsIgnoreCase(uri.getAuthority())){
List<String> path=uri.getPathSegments();
// Match URLs like https://mastodon.social/@Gargron/108132679274083591
if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$")){

View File

@@ -23,6 +23,8 @@ public abstract class GithubSelfUpdater{
return BuildConfig.BUILD_TYPE.equals("githubRelease") || BuildConfig.BUILD_TYPE.equals("debug");
}
public abstract void checkForUpdates();
public abstract void maybeCheckForUpdates();
public abstract GithubSelfUpdater.UpdateState getState();

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M9.042 19.003h5.916c-0.238 1.418-1.472 2.498-2.958 2.498-1.486 0-2.72-1.08-2.958-2.498zm2.958-17c4.142 0 7.5 3.359 7.5 7.5v4l1.418 3.16c0.055 0.122 0.084 0.254 0.084 0.389 0 0.524-0.426 0.95-0.95 0.95h-16.1c-0.134 0-0.266-0.029-0.388-0.083-0.479-0.215-0.693-0.777-0.479-1.256l1.415-3.16V9.49l0.005-0.25C4.644 5.211 7.955 2.004 12 2.004z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12 1.996c4.05 0 7.357 3.195 7.496 7.25l0.004 0.25v4.097l1.38 3.156c0.07 0.158 0.105 0.329 0.105 0.5 0 0.691-0.56 1.25-1.25 1.25L15 18.502c0 1.657-1.343 3-3 3-1.598 0-2.904-1.249-2.995-2.823L9 18.499H4.275c-0.171 0-0.34-0.034-0.498-0.103-0.633-0.275-0.924-1.01-0.649-1.644L4.5 13.594V9.496c0-4.155 3.352-7.5 7.5-7.5zM13.5 18.5l-3 0.002c0 0.829 0.672 1.5 1.5 1.5 0.78 0 1.42-0.595 1.493-1.355L13.5 18.5zM12 3.496c-3.32 0-6 2.674-6 6v4.41L4.656 17h14.697L18 13.907V9.509l-0.003-0.225C17.885 6.05 15.242 3.496 12 3.496z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--~ Copyright (c) 2022. ~ Microsoft Corporation. All rights reserved.-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_fluent_alert_24_filled" android:state_activated="true"/>
<item android:drawable="@drawable/ic_fluent_alert_24_filled" android:state_checked="true"/>
<item android:drawable="@drawable/ic_fluent_alert_24_filled" android:state_selected="true"/>
<item android:drawable="@drawable/ic_fluent_alert_24_regular"/>
</selector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M16.5 6.671c0.116 0 0.223 0.04 0.308 0.106l0.067 0.064 0.017 0.02C17.585 7.719 18 8.81 18 10c0 2.689-2.122 4.882-4.783 4.995L13 15H7c-0.102 0-0.203-0.003-0.303-0.009l1.657 1.655c0.173 0.174 0.192 0.443 0.057 0.638l-0.057 0.07C8.18 17.527 7.91 17.546 7.716 17.41l-0.07-0.057-2.5-2.5C4.973 14.68 4.954 14.41 5.09 14.216l0.057-0.07 2.5-2.5c0.196-0.195 0.512-0.195 0.708 0 0.173 0.174 0.192 0.443 0.057 0.638l-0.057 0.07-1.637 1.636 0.14 0.008L7 14h6c2.21 0 4-1.79 4-4 0-0.954-0.334-1.829-0.89-2.516C16.04 7.399 16 7.29 16 7.17c0-0.276 0.224-0.5 0.5-0.5zm-4.854-4.024c0.174-0.174 0.443-0.193 0.638-0.058l0.07 0.058 2.5 2.5 0.057 0.069c0.119 0.17 0.119 0.398 0 0.568l-0.057 0.07-2.5 2.5-0.07 0.057c-0.17 0.119-0.398 0.119-0.568 0l-0.07-0.057-0.057-0.07c-0.119-0.17-0.119-0.398 0-0.568l0.057-0.07 1.637-1.636-0.14-0.007L13 6H7c-2.21 0-4 1.79-4 4 0 0.956 0.336 1.834 0.895 2.522C3.96 12.606 4 12.714 4 12.832c0 0.275-0.224 0.5-0.5 0.5-0.167 0-0.315-0.083-0.406-0.208C2.41 12.268 2 11.182 2 10c0-2.689 2.122-4.882 4.783-4.995L7 5h6c0.102 0 0.203 0.003 0.303 0.009l-1.657-1.656-0.057-0.069c-0.135-0.195-0.116-0.464 0.057-0.637z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M4.209 10.733c-0.286 0.3-0.274 0.774 0.026 1.06 0.3 0.286 0.774 0.274 1.06-0.026l5.954-6.251V20.25c0 0.414 0.336 0.75 0.75 0.75 0.415 0 0.75-0.336 0.75-0.75V5.516l5.955 6.251c0.286 0.3 0.76 0.312 1.06 0.026 0.3-0.286 0.312-0.76 0.027-1.06l-7.067-7.42c-0.161-0.168-0.367-0.268-0.58-0.3C12.097 3.006 12.049 3 11.999 3c-0.05 0-0.098 0.005-0.145 0.014-0.213 0.031-0.418 0.131-0.578 0.3l-7.067 7.419z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M2 10c0-4.418 3.582-8 8-8 4.419 0 8 3.582 8 8 0 4.419-3.581 8-8 8-4.418 0-8-3.581-8-8zm8-7C9.915 3 9.83 3.002 9.745 3.005c0.118 0.222 0.253 0.504 0.373 0.823 0.28 0.746 0.527 1.817 0.085 2.758C9.799 7.446 9.106 7.67 8.57 7.808L8.474 7.833c-0.506 0.13-0.755 0.194-0.93 0.46-0.17 0.257-0.129 0.574 0.037 1.113L7.619 9.53c0.067 0.211 0.144 0.457 0.184 0.688 0.05 0.286 0.06 0.636-0.113 0.97C7.51 11.53 7.276 11.762 7 11.912c-0.26 0.142-0.533 0.197-0.747 0.235l-0.088 0.015c-0.407 0.072-0.645 0.113-0.867 0.351-0.177 0.19-0.279 0.508-0.336 0.941-0.024 0.178-0.038 0.355-0.053 0.534l-0.007 0.095c-0.017 0.199-0.037 0.419-0.079 0.605l-0.005 0.02C6.1 16.116 7.947 17 10 17c1.35 0 2.612-0.383 3.682-1.045-0.086-0.086-0.181-0.189-0.275-0.307-0.271-0.34-0.609-0.909-0.492-1.57 0.056-0.313 0.226-0.581 0.397-0.794 0.175-0.216 0.386-0.417 0.576-0.592l0.128-0.117c0.146-0.133 0.273-0.25 0.382-0.363 0.147-0.154 0.191-0.237 0.2-0.263 0.068-0.226-0.013-0.404-0.126-0.492-0.094-0.073-0.295-0.142-0.61 0.058-0.12 0.075-0.228 0.141-0.323 0.191-0.086 0.045-0.205 0.102-0.336 0.122-0.157 0.025-0.375 0.002-0.544-0.177-0.129-0.136-0.164-0.302-0.178-0.375-0.016-0.09-0.024-0.19-0.03-0.276l-0.005-0.066c-0.006-0.074-0.011-0.15-0.02-0.238-0.02-0.221-0.057-0.496-0.143-0.825-0.127-0.491-0.44-0.888-0.764-1.3L11.377 8.39c-0.16-0.206-0.363-0.478-0.436-0.77-0.042-0.163-0.049-0.353 0.024-0.547 0.072-0.19 0.203-0.336 0.352-0.448 0.428-0.32 1.128-1.013 1.743-1.652 0.303-0.314 0.576-0.607 0.775-0.822l0.005-0.006C12.738 3.422 11.418 3 10 3zm4.638 1.757L14.569 4.83c-0.201 0.218-0.48 0.516-0.788 0.836-0.602 0.626-1.352 1.373-1.855 1.753 0.03 0.066 0.1 0.176 0.242 0.359l0.124 0.157c0.316 0.398 0.774 0.973 0.959 1.684 0.103 0.395 0.147 0.725 0.171 0.984l0.001 0.01c0.588-0.33 1.21-0.296 1.66 0.053 0.459 0.354 0.653 0.971 0.472 1.572-0.081 0.268-0.273 0.495-0.434 0.664-0.135 0.141-0.296 0.289-0.446 0.425-0.037 0.035-0.074 0.068-0.11 0.1-0.188 0.174-0.35 0.332-0.474 0.485-0.127 0.157-0.178 0.268-0.191 0.342-0.04 0.227 0.072 0.497 0.29 0.772 0.101 0.128 0.209 0.234 0.291 0.31l0.025 0.021C16.03 14.074 17 12.151 17 10.001c0-2.088-0.913-3.962-2.362-5.244zm-5.84-1.402c-0.053-0.096-0.1-0.173-0.133-0.228C5.437 3.75 3 6.591 3 10c0 1.283 0.345 2.486 0.947 3.52l0.023-0.198c0.063-0.467 0.193-1.059 0.597-1.491 0.462-0.495 1.026-0.588 1.404-0.65l0.108-0.019c0.203-0.036 0.336-0.07 0.443-0.128 0.093-0.05 0.19-0.133 0.28-0.309 0.03-0.054 0.048-0.147 0.016-0.336-0.028-0.16-0.08-0.327-0.146-0.536L6.625 9.7C6.472 9.203 6.25 8.438 6.709 7.742c0.4-0.607 1.04-0.762 1.477-0.869L8.32 6.84c0.467-0.12 0.772-0.242 0.978-0.68 0.261-0.556 0.143-1.292-0.116-1.98-0.124-0.33-0.27-0.618-0.384-0.825z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M3 3.748c0-0.414 0.336-0.75 0.75-0.75h16.504c0.618 0 0.971 0.706 0.6 1.2L16.69 9.75l4.164 5.551c0.371 0.495 0.018 1.2-0.6 1.2H4.5v4.75c0 0.38-0.282 0.693-0.648 0.743L3.75 22c-0.38 0-0.693-0.282-0.743-0.648L3 21.25V3.748z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M3 3.748c0-0.414 0.336-0.75 0.75-0.75h16.504c0.618 0 0.971 0.706 0.6 1.2L16.69 9.75l4.164 5.551c0.371 0.495 0.018 1.2-0.6 1.2H4.5v4.75c0 0.38-0.282 0.693-0.648 0.743L3.75 22c-0.38 0-0.693-0.282-0.743-0.648L3 21.25V3.748zm15.754 0.75H4.5v10.503h14.254l-3.602-4.802c-0.2-0.266-0.2-0.633 0-0.9l3.602-4.8z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--~ Copyright (c) 2022. ~ Microsoft Corporation. All rights reserved.-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_fluent_flag_24_filled" android:state_activated="true"/>
<item android:drawable="@drawable/ic_fluent_flag_24_filled" android:state_checked="true"/>
<item android:drawable="@drawable/ic_fluent_flag_24_filled" android:state_selected="true"/>
<item android:drawable="@drawable/ic_fluent_flag_24_regular"/>
</selector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M3.5 1.997l0.09 0.008c0.204 0.037 0.365 0.198 0.402 0.402L4 2.497v2.211c1.164-1.323 2.748-2.242 4.542-2.574 4.345-0.805 8.519 2.064 9.324 6.408 0.805 4.344-2.064 8.519-6.408 9.324-4.344 0.805-8.519-2.064-9.324-6.408-0.087-0.468-0.158-1.32-0.13-1.98C2.016 9.204 2.25 8.989 2.524 9c0.277 0.012 0.49 0.245 0.48 0.52-0.025 0.58 0.04 1.358 0.113 1.756 0.705 3.8 4.357 6.311 8.159 5.607 3.8-0.705 6.311-4.357 5.607-8.159-0.705-3.8-4.357-6.311-8.159-5.607C6.885 3.458 5.3 4.502 4.258 5.995l2.74 0.002 0.09 0.008c0.233 0.042 0.41 0.246 0.41 0.492 0 0.245-0.177 0.45-0.41 0.492l-0.09 0.008H3.5L3.41 6.989C3.206 6.952 3.045 6.79 3.008 6.587L3 6.497v-4l0.008-0.09C3.045 2.203 3.206 2.042 3.41 2.005L3.5 1.997zm6 4l0.09 0.008c0.204 0.037 0.365 0.198 0.402 0.402L10 6.497V10h2l0.09 0.008c0.233 0.042 0.41 0.246 0.41 0.492 0 0.245-0.177 0.45-0.41 0.491L12 11H9.5l-0.09-0.008c-0.204-0.037-0.365-0.198-0.402-0.402L9 10.5V6.498l0.008-0.09C9.045 6.203 9.206 6.042 9.41 6.005L9.5 5.997z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M8.75 4c1.519 0 2.75 1.231 2.75 2.75v10.5c0 1.519-1.231 2.75-2.75 2.75h-4C3.231 20 2 18.769 2 17.25V6.75C2 5.231 3.231 4 4.75 4h4zm0 1.5h-4C4.06 5.5 3.5 6.06 3.5 6.75v10.5c0 0.69 0.56 1.25 1.25 1.25h4c0.69 0 1.25-0.56 1.25-1.25V6.75C10 6.06 9.44 5.5 8.75 5.5zM19.25 4C20.769 4 22 5.231 22 6.75v10.5c0 1.519-1.231 2.75-2.75 2.75h-4c-1.519 0-2.75-1.231-2.75-2.75V6.75C12.5 5.231 13.731 4 15.25 4h4zm0 1.5h-4C14.56 5.5 14 6.06 14 6.75v10.5c0 0.69 0.56 1.25 1.25 1.25h4c0.69 0 1.25-0.56 1.25-1.25V6.75c0-0.69-0.56-1.25-1.25-1.25zM6 9v6c0 0.414 0.336 0.75 0.75 0.75S7.5 15.414 7.5 15V9c0-0.414-0.336-0.75-0.75-0.75S6 8.586 6 9zm11.5 0.75v1.5h-1.25c-0.414 0-0.75 0.336-0.75 0.75v3c0 0.414 0.336 0.75 0.75 0.75h2c0.414 0 0.75-0.336 0.75-0.75s-0.336-0.75-0.75-0.75H17v-1.5h1.25c0.414 0 0.75-0.336 0.75-0.75V9c0-0.414-0.336-0.75-0.75-0.75h-2c-0.414 0-0.75 0.336-0.75 0.75s0.336 0.75 0.75 0.75h1.25z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M4.5 6.75c0-1.243 1.007-2.25 2.25-2.25S9 5.507 9 6.75 7.993 9 6.75 9 4.5 7.993 4.5 6.75zM6.75 3.5C4.955 3.5 3.5 4.955 3.5 6.75S4.955 10 6.75 10 10 8.545 10 6.75 8.545 3.5 6.75 3.5zm5.687 11.645c0.538 0.22 1.215 0.355 2.063 0.355 1.881 0 2.921-0.668 3.469-1.434 0.264-0.37 0.396-0.74 0.462-1.017 0.033-0.14 0.05-0.257 0.06-0.343l0.007-0.105 0.001-0.033V12.5c0-0.828-0.671-1.5-1.5-1.5h-4.628c0.24 0.29 0.42 0.629 0.525 1H17c0.276 0 0.5 0.224 0.5 0.5v0.054l-0.005 0.05c-0.005 0.049-0.015 0.122-0.037 0.213-0.043 0.182-0.13 0.425-0.303 0.667-0.327 0.459-1.037 1.016-2.656 1.016-0.731 0-1.277-0.114-1.686-0.281-0.082 0.28-0.201 0.596-0.376 0.926zM1.5 13c0-1.105 0.895-2 2-2H10c1.105 0 2 0.895 2 2v0.084c0 0.01 0 0.023-0.002 0.04-0.001 0.033-0.004 0.08-0.01 0.135-0.01 0.113-0.032 0.268-0.075 0.453-0.085 0.368-0.254 0.86-0.595 1.354C10.617 16.08 9.263 17 6.75 17c-2.513 0-3.867-0.92-4.568-1.934-0.34-0.494-0.51-0.986-0.595-1.354-0.042-0.185-0.064-0.34-0.075-0.453-0.006-0.056-0.009-0.102-0.01-0.135L1.5 13.084V13zm1 0.06v0.018l0.007 0.083c0.007 0.076 0.023 0.189 0.054 0.326 0.064 0.277 0.191 0.644 0.444 1.01C3.492 15.201 4.513 16 6.75 16s3.258-0.799 3.745-1.503c0.253-0.366 0.38-0.733 0.444-1.01 0.031-0.137 0.047-0.25 0.054-0.326C10.997 13.123 11 13.095 11 13.078V13c0-0.552-0.448-1-1-1H3.5c-0.552 0-1 0.448-1 1v0.06zM13 7.5C13 6.672 13.672 6 14.5 6S16 6.672 16 7.5 15.328 9 14.5 9 13 8.328 13 7.5zM14.5 5C13.12 5 12 6.12 12 7.5s1.12 2.5 2.5 2.5S17 8.88 17 7.5 15.88 5 14.5 5z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M4.5 5.75c0-1.243 1.007-2.25 2.25-2.25S9 4.507 9 5.75 7.993 8 6.75 8 4.5 6.993 4.5 5.75zM6.75 2.5C4.955 2.5 3.5 3.955 3.5 5.75S4.955 9 6.75 9 10 7.545 10 5.75 8.545 2.5 6.75 2.5zM1.5 12c0-1.105 0.895-2 2-2H10c0.361 0 0.7 0.096 0.993 0.263-0.277 0.23-0.531 0.486-0.758 0.765C10.159 11.01 10.08 11 10 11H3.5c-0.552 0-1 0.448-1 1v0.078l0.007 0.083c0.007 0.076 0.023 0.189 0.054 0.326 0.064 0.277 0.191 0.644 0.444 1.01C3.492 14.201 4.513 15 6.75 15c0.954 0 1.687-0.145 2.252-0.367 0.008 0.35 0.049 0.69 0.12 1.02C8.476 15.87 7.695 16 6.75 16c-2.513 0-3.867-0.92-4.568-1.934-0.34-0.494-0.51-0.986-0.595-1.354-0.042-0.185-0.064-0.34-0.075-0.453-0.006-0.056-0.009-0.102-0.01-0.135L1.5 12.084V12zM13 6.5C13 5.672 13.672 5 14.5 5S16 5.672 16 6.5 15.328 8 14.5 8 13 7.328 13 6.5zM14.5 4C13.12 4 12 5.12 12 6.5S13.12 9 14.5 9 17 7.88 17 6.5 15.88 4 14.5 4zM19 14.5c0 2.485-2.015 4.5-4.5 4.5S10 16.985 10 14.5s2.015-4.5 4.5-4.5 4.5 2.015 4.5 4.5zm-2.146-1.854c-0.196-0.195-0.512-0.195-0.708 0L13.5 15.293l-0.646-0.647c-0.196-0.195-0.512-0.195-0.708 0-0.195 0.196-0.195 0.512 0 0.708l1 1c0.196 0.195 0.512 0.195 0.708 0l3-3c0.195-0.196 0.195-0.512 0-0.708z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M17.5 12c3.037 0 5.5 2.463 5.5 5.5 0 3.038-2.463 5.5-5.5 5.5-3.038 0-5.5-2.462-5.5-5.5 0-3.037 2.462-5.5 5.5-5.5zm-5.478 2C11.375 15.01 11 16.21 11 17.5c0 1.644 0.61 3.146 1.617 4.29-0.802 0.142-1.675 0.211-2.617 0.211-2.89 0-5.128-0.656-6.691-2-0.829-0.712-1.306-1.75-1.306-2.844V16.25c0-1.242 1.008-2.25 2.25-2.25h7.77zm3.07 0.966l-0.068 0.058-0.058 0.07c-0.118 0.17-0.118 0.398 0 0.568l0.058 0.07 1.77 1.769-1.768 1.767-0.058 0.069c-0.118 0.17-0.118 0.398 0 0.569l0.058 0.069 0.07 0.058c0.17 0.118 0.398 0.118 0.568 0l0.07-0.058 1.766-1.767 1.77 1.77 0.069 0.057c0.17 0.118 0.398 0.118 0.568 0l0.07-0.058 0.057-0.07c0.118-0.17 0.118-0.397 0-0.568l-0.058-0.069-1.769-1.77 1.772-1.768 0.058-0.07c0.118-0.17 0.118-0.398 0-0.568l-0.058-0.07-0.07-0.057c-0.17-0.119-0.397-0.119-0.568 0l-0.069 0.057-1.772 1.77-1.77-1.77-0.069-0.057c-0.146-0.102-0.334-0.116-0.492-0.044l-0.076 0.043zM10 2.005c2.761 0 5 2.239 5 5s-2.239 5-5 5-5-2.239-5-5 2.239-5 5-5z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M9.104 2.9c0.367-0.744 1.427-0.744 1.794 0l1.93 3.91 4.317 0.628c0.82 0.12 1.148 1.127 0.554 1.706l-3.124 3.044 0.738 4.3c0.14 0.816-0.717 1.44-1.451 1.054l-3.86-2.03-3.862 2.03c-0.733 0.385-1.59-0.238-1.45-1.055l0.737-4.299-3.124-3.044C1.71 8.565 2.037 7.557 2.857 7.438l4.317-0.627 1.93-3.912zm0.897 0.442l-1.93 3.911C7.925 7.548 7.643 7.753 7.318 7.8L3 8.428l3.124 3.044c0.235 0.23 0.343 0.561 0.287 0.885l-0.737 4.3 3.86-2.03c0.292-0.153 0.64-0.153 0.931 0l3.861 2.03-0.737-4.3c-0.056-0.324 0.052-0.655 0.287-0.885L17 8.428 12.684 7.8c-0.325-0.047-0.607-0.252-0.752-0.547l-1.93-3.911z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -2,150 +2,113 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:background="?colorBackgroundLightest">
<RelativeLayout
android:id="@+id/reblogs"
<org.joinmastodon.android.ui.views.AutoOrientationLinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:background="?android:selectableItemBackground">
android:layout_height="wrap_content">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_fluent_arrow_repeat_all_24_regular"
android:tint="?android:textColorSecondary"
android:importantForAccessibility="no"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
<Button
android:id="@+id/reblogs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="8dp"
android:textSize="14sp"
android:minHeight="48dp"
android:textColor="?android:textColorSecondary"
android:background="@drawable/bg_text_button"
android:fontFamily="sans-serif"
android:drawableStart="@drawable/ic_fluent_arrow_repeat_all_20_regular"
android:drawablePadding="8dp"
android:drawableTint="?android:textColorSecondary"
tools:text="4 reblogs"/>
<Button
android:id="@+id/favorites"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="8dp"
android:textSize="14sp"
android:minHeight="48dp"
android:textColor="?android:textColorSecondary"
android:background="@drawable/bg_text_button"
android:fontFamily="sans-serif"
android:drawableStart="@drawable/ic_fluent_star_20_regular"
android:drawablePadding="8dp"
android:drawableTint="?android:textColorSecondary"
tools:text="12 favorites"/>
</LinearLayout>
<Button
android:id="@+id/edit_history"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_toEndOf="@id/icon"
android:minHeight="22dp"
android:singleLine="true"
android:text="@string/post_info_reblogs"
android:textAppearance="@style/m3_body_large" />
<TextView
android:id="@+id/reblogs_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignStart="@id/title"
android:singleLine="true"
android:textAppearance="@style/m3_body_medium"
android:gravity="center"
android:padding="8dp"
android:textSize="14sp"
android:minHeight="48dp"
android:textColor="?android:textColorSecondary"
tools:text="123 456"/>
android:background="@drawable/bg_text_button"
android:fontFamily="sans-serif"
android:drawableStart="@drawable/ic_fluent_history_20_regular"
android:drawablePadding="8dp"
android:drawableTint="?android:textColorSecondary"
tools:text="Dec 12, 2021, 12:42 PM"/>
</RelativeLayout>
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
<RelativeLayout
android:id="@+id/favorites"
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:background="?android:selectableItemBackground">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_fluent_star_24_regular"
android:tint="?android:textColorSecondary"
android:importantForAccessibility="no"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_toEndOf="@id/icon"
android:minHeight="22dp"
android:singleLine="true"
android:text="@string/post_info_favorites"
android:textAppearance="@style/m3_body_large" />
<TextView
android:id="@+id/favorites_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignStart="@id/title"
android:singleLine="true"
android:textAppearance="@style/m3_body_medium"
android:textColor="?android:textColorSecondary"
tools:text="123 456"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/edit_history"
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:background="?android:selectableItemBackground">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_fluent_edit_24_regular"
android:tint="?android:textColorSecondary"
android:importantForAccessibility="no"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_toEndOf="@id/icon"
android:minHeight="22dp"
android:singleLine="true"
android:text="@string/edit_history"
android:textAppearance="@style/m3_body_large" />
<TextView
android:id="@+id/last_edited"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignStart="@id/title"
android:singleLine="true"
android:textAppearance="@style/m3_body_medium"
android:textColor="?android:textColorSecondary"
tools:text="123 456"/>
</RelativeLayout>
<TextView
android:id="@+id/timestamp"
<LinearLayout
android:orientation="horizontal"
android:gravity="start|center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:minHeight="20dp"
android:gravity="center_vertical"
android:textSize="14sp"
android:textColor="?android:textColorSecondary"
tools:text="Dec 12, 2021, 12:42 PM via Mastodon for Android"/>
android:layout_marginHorizontal="8dp"
android:minHeight="48dp">
<ImageView
android:id="@+id/visibility"
android:layout_height="20dp"
android:layout_width="20dp"
android:src="@drawable/ic_fluent_earth_20_regular"
android:tint="?android:textColorSecondary" />
<TextView
android:id="@+id/timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:minHeight="20dp"
android:gravity="center_vertical"
android:textSize="14sp"
android:textColor="?android:textColorSecondary"
tools:text="Dec 12, 2021, 12:42 PM via "/>
<Button
android:id="@+id/application_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="0dp"
android:textSize="14sp"
android:minHeight="48dp"
android:textColor="?android:textColorSecondary"
android:background="@drawable/bg_text_button"
android:fontFamily="sans-serif"
android:singleLine="true"
android:ellipsize="end"
tools:text="Mastodon for Android"/>
</LinearLayout>
</LinearLayout>

View File

@@ -154,6 +154,38 @@
android:layout_gravity="center_horizontal"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/sensitive_item"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:layoutDirection="locale"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:background="?android:selectableItemBackground"
android:visibility="gone">
<ImageView
android:id="@+id/sensitive_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="32dp"
android:src="@drawable/ic_fluent_flag_24_selector"
android:tint="?android:textColorPrimary"
android:importantForAccessibility="no"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:singleLine="true"
android:text="@string/mark_media_as_sensitive" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -166,31 +166,66 @@
</LinearLayout>
</LinearLayout>
<FrameLayout
<LinearLayout
android:id="@+id/profile_action_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_below="@id/profile_counters"
android:padding="16dp"
android:clipToPadding="false">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/profile_action_btn"
android:layout_alignParentEnd="true">
<FrameLayout
android:clipToPadding="false"
android:paddingVertical="16dp"
android:paddingLeft="16dp"
android:paddingRight="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Edit Profile"/>
<ProgressBar
android:id="@+id/action_progress"
android:layout_height="wrap_content">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/notify_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="8dp"
android:drawableStart="@drawable/ic_fluent_alert_24_selector" />
<ProgressBar
android:id="@+id/notify_progress"
style="?android:progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:elevation="10dp"
android:indeterminate="true"
android:indeterminateTint="?colorButtonText"
android:outlineProvider="none"
android:visibility="gone" />
</FrameLayout>
<FrameLayout
android:clipToPadding="false"
android:paddingVertical="16dp"
android:paddingRight="16dp"
android:paddingLeft="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
style="?android:progressBarStyleSmall"
android:elevation="10dp"
android:outlineProvider="none"
android:indeterminateTint="?colorButtonText"
android:visibility="gone"/>
</FrameLayout>
android:layout_height="wrap_content">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/profile_action_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Edit Profile" />
<ProgressBar
android:id="@+id/action_progress"
style="?android:progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:elevation="10dp"
android:indeterminate="true"
android:indeterminateTint="?colorButtonText"
android:outlineProvider="none"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
<TextView
android:id="@+id/name"

View File

@@ -11,11 +11,11 @@
android:id="@+id/cover"
android:layout_width="match_parent"
android:layout_height="128dp"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="4dp"
android:layout_marginRight="4dp"
android:scaleType="centerCrop"
tools:src="#0f0"/>
tools:src="#0f0" />
<View
android:id="@+id/avatar_border"
@@ -81,6 +81,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/bio"
android:paddingRight="8dp"
android:orientation="horizontal">
<LinearLayout
@@ -111,76 +112,141 @@
android:id="@+id/followers_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
android:layout_marginTop="16dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/followers_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
tools:text="123" />
<TextView
android:id="@+id/followers_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
tools:text="following" />
</LinearLayout>
<LinearLayout
android:id="@+id/following_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:layout_marginStart="12dp"
android:orientation="vertical"
android:gravity="center_horizontal">
android:layout_marginTop="16dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/following_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_large"
tools:text="123"/>
tools:text="123" />
<TextView
android:id="@+id/following_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_small"
tools:text="following"/>
tools:text="following" />
</LinearLayout>
<Space
android:layout_width="0px"
android:layout_height="1px"
android:layout_weight="1"/>
android:layout_weight="1" />
<FrameLayout
android:id="@+id/reject_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
android:paddingHorizontal="4dp"
android:paddingVertical="8dp">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/reject_btn"
style="?secondaryButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/reject_follow_request"
android:drawableStart="@drawable/ic_fluent_dismiss_24_filled"
android:singleLine="true" />
<ProgressBar
android:id="@+id/reject_progress"
style="?android:progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:elevation="10dp"
android:indeterminate="true"
android:outlineProvider="none"
android:visibility="gone" />
</FrameLayout>
<FrameLayout
android:id="@+id/accept_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
android:paddingHorizontal="4dp"
android:paddingVertical="8dp">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/accept_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/accept_follow_request"
android:drawableStart="@drawable/ic_fluent_checkmark_24_filled"
android:singleLine="true" />
<ProgressBar
android:id="@+id/accept_progress"
style="?android:progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:elevation="10dp"
android:indeterminate="true"
android:outlineProvider="none"
android:visibility="gone" />
</FrameLayout>
<FrameLayout
android:id="@+id/action_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
android:padding="8dp"
android:layout_marginEnd="8dp"
android:clipToPadding="false">
android:visibility="gone">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/action_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
tools:text="@string/follow_back"/>
tools:text="@string/follow_back" />
<ProgressBar
android:id="@+id/action_progress"
style="?android:progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
style="?android:progressBarStyleSmall"
android:elevation="10dp"
android:outlineProvider="none"
android:indeterminate="true"
android:indeterminateTint="?colorButtonText"
android:visibility="gone"/>
android:outlineProvider="none"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="56dp"
android:paddingHorizontal="16dp"
android:orientation="horizontal"
android:gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:tint="?android:textColorSecondary"
android:src="@drawable/ic_fluent_people_community_24_regular"
android:importantForAccessibility="no"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_title_medium"
android:fontFamily="sans-serif"
android:singleLine="true"
android:ellipsize="end"
tools:text="List"/>
</LinearLayout>

View File

@@ -1,13 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:textSize="16sp"
android:textColor="?android:textColorPrimary"
android:singleLine="true"
android:ellipsize="end"
tools:text="daffdsa"/>
android:layoutDirection="locale">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="48dp"
android:paddingRight="16dp"
android:gravity="center_vertical"
android:textSize="16sp"
android:textColor="?android:textColorPrimary"
android:singleLine="true"
android:ellipsize="end"
tools:text="Account settings"/>
<ProgressBar
android:id="@+id/progress"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:alpha="0"
/>
</LinearLayout>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/follow_hashtag"
android:icon="@drawable/ic_fluent_person_add_24_regular"
android:showAsAction="always"
android:title="@string/button_follow"/>
</menu>

View File

@@ -7,6 +7,7 @@
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user"/>
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
<item android:id="@+id/favorites_list" android:title="@string/favorited_posts" android:visible="false"/>
<item
android:id="@+id/bookmarks"
android:showAsAction="always"

View File

@@ -252,8 +252,12 @@
<string name="theme_dark">Dunkel</string>
<string name="theme_true_black">AMOLED-Modus</string>
<string name="settings_behavior">App-Verhalten</string>
<string name="settings_show_replies">Antworten anzeigen</string>
<string name="settings_show_boosts">Geteilte Beiträge anzeigen</string>
<string name="settings_load_new_posts">Automatisch neue Beiträge laden</string>
<string name="settings_gif">Spiele animierte GIFs, Avatare und Emojis ab</string>
<string name="settings_custom_tabs">In-App-Browser verwenden</string>
<string name="settings_show_interaction_counts">Interaktions-Anzahlen anzeigen</string>
<string name="settings_notifications">Benachrichtigungen</string>
<string name="notify_me_when">Benachrichtige mich, wenn</string>
<string name="notify_anyone">irgendjemand</string>
@@ -271,7 +275,7 @@
<string name="settings_privacy_policy">Datenschutzbestimmungen</string>
<string name="settings_spicy">Gefährliches</string>
<string name="settings_clear_cache">Medien-Cache leeren</string>
<string name="settings_app_version">Mastodon für Android v%1$s (%2$d)</string>
<string name="settings_app_version">Mastodos v%1$s (%2$d)</string>
<string name="media_cache_cleared">Medien-Cache geleert</string>
<string name="confirm_log_out">Bist du dir sicher, dass du dich abmelden möchtest?</string>
<string name="sensitive_content">Sensibler Inhalt</string>
@@ -288,6 +292,7 @@
<string name="button_share">Teilen</string>
<string name="media_no_description">Medien ohne Beschreibung</string>
<string name="add_media">Medien hinzufügen</string>
<string name="mark_media_as_sensitive">Medien als NSFW markieren</string>
<string name="add_poll">Umfrage hinzufügen</string>
<string name="emoji">Emoji</string>
<string name="post_visibility">Sichtbarkeit des Beitrages</string>
@@ -300,6 +305,8 @@
<string name="open_in_browser">Beitrag im Browser öffnen</string>
<string name="hide_boosts_from_user">Verberge geteilte Beiträge von %s</string>
<string name="show_boosts_from_user">Zeige geteilte Beiträge von %s</string>
<string name="user_post_notifications_on">Benachrichtigungen über Beiträge von %s aktiviert</string>
<string name="user_post_notifications_off">Benachrichtigungen über Beiträge von %s deaktiviert</string>
<string name="signup_reason">Weshalb möchtest du beitreten?</string>
<string name="signup_reason_note">Dies wird uns dabei helfen, deine Anmeldungsanfrage besser zu verarbeiten.</string>
<string name="clear">Löschen</string>
@@ -314,11 +321,13 @@
<string name="file_saved">Datei gespeichert</string>
<string name="downloading">wird heruntergeladen </string>
<string name="no_app_to_handle_action">Es gibt keine App, um diese Aktion auszuführen</string>
<string name="local_timeline">Community</string>
<string name="local_timeline">Lokal</string>
<string name="federated_timeline">Föderation</string>
<string name="trending_posts_info_banner">Dies sind Beiträge, die auf deinem Mastodon-Server gerade angesagt sind.</string>
<string name="trending_hashtags_info_banner">Dies sind Hashtags, die auf deinem Mastodon-Server gerade angesagt sind.</string>
<string name="trending_links_info_banner">Dies sind journalistische Nachrichten, die auf deinem Mastodon-Server gerade am häufigsten geteilt werden.</string>
<string name="local_timeline_info_banner">Dies sind die neuesten Beiträge der Leute, die den gleichen Mastodon-Server verwenden wie du.</string>
<string name="federated_timeline_info_banner">Dies sind die neusten Beiträge der Leute, die in der Föderation deines Servers sind.</string>
<string name="dismiss">Verwerfen</string>
<string name="see_new_posts">Neue Beiträge anzeigen</string>
<string name="load_missing_posts">Weitere Beiträge laden</string>
@@ -387,10 +396,16 @@
<string name="upload_error_connection_lost">Dein Gerät hat gerade keinen Zugang zum Internet</string>
<string name="upload_processing">wird verarbeitet </string>
<!-- %s is version like 1.2.3 -->
<string name="update_available">Mastodon für Android %s kann nun heruntergeladen werden.</string>
<string name="update_available">Mastodos %s kann nun heruntergeladen werden.</string>
<!-- %s is version like 1.2.3 -->
<string name="update_ready">Mastodon für Android %s wurde heruntergeladen und kann nun installiert werden.</string>
<string name="update_ready">Mastodos %s wurde heruntergeladen und kann nun installiert werden.</string>
<!-- %s is file size -->
<string name="download_update">Download (%s)</string>
<string name="install_update">Installieren</string>
<string name="check_for_update">Auf Update prüfen</string>
<string name="no_update_available">Kein Update verfügbar</string>
<string name="list_timelines">Listen</string>
<string name="favorited_posts">Favorisierte Beiträge</string>
<string name="accept_follow_request">Folgeanfrage akzeptieren</string>
<string name="reject_follow_request">Folgeanfrage ablehnen</string>
</resources>

View File

@@ -14,6 +14,7 @@
<item name="discover_users" type="id"/>
<item name="discover_local_timeline" type="id"/>
<item name="discover_federated_timeline" type="id"/>
<item name="discover_lists" type="id"/>
<item name="notifications_all" type="id"/>
<item name="notifications_mentions" type="id"/>

View File

@@ -256,8 +256,12 @@
<string name="theme_dark">Dark</string>
<string name="theme_true_black">True black mode</string>
<string name="settings_behavior">Behavior</string>
<string name="settings_show_replies">Show replies</string>
<string name="settings_show_boosts">Show boosts</string>
<string name="settings_load_new_posts">Automatically load new posts</string>
<string name="settings_gif">Play animated avatars and emoji</string>
<string name="settings_custom_tabs">Use in-app browser</string>
<string name="settings_show_interaction_counts">Show interaction counts</string>
<string name="settings_notifications">Notifications</string>
<string name="notify_me_when">Notify me when</string>
<string name="notify_anyone">anyone</string>
@@ -275,7 +279,7 @@
<string name="settings_privacy_policy">Privacy policy</string>
<string name="settings_spicy">The spicy zone</string>
<string name="settings_clear_cache">Clear media cache</string>
<string name="settings_app_version">Mastodon for Android v%1$s (%2$d)</string>
<string name="settings_app_version">Mastodos v%1$s (%2$d)</string>
<string name="media_cache_cleared">Media cache cleared</string>
<string name="confirm_log_out">Are you sure you want to sign out?</string>
<string name="sensitive_content">Sensitive content</string>
@@ -294,6 +298,7 @@
<string name="bookmarks">Bookmarks</string>
<string name="media_no_description">Media without description</string>
<string name="add_media">Add media</string>
<string name="mark_media_as_sensitive">Mark media as sensitive</string>
<string name="add_poll">Add a poll</string>
<string name="emoji">Emoji</string>
<string name="post_visibility">Post visibility</string>
@@ -306,6 +311,8 @@
<string name="open_in_browser">Open in browser</string>
<string name="hide_boosts_from_user">Hide reblogs from %s</string>
<string name="show_boosts_from_user">Show reblogs from %s</string>
<string name="user_post_notifications_on">Turned on post notifications for %s</string>
<string name="user_post_notifications_off">Turned off post notifications for %s</string>
<string name="signup_reason">why do you want to join?</string>
<string name="signup_reason_note">This will help us review your application.</string>
<string name="clear">Clear</string>
@@ -320,7 +327,7 @@
<string name="file_saved">File saved</string>
<string name="downloading">Downloading…</string>
<string name="no_app_to_handle_action">There\'s no app to handle this action</string>
<string name="local_timeline">Community</string>
<string name="local_timeline">Local</string>
<string name="federated_timeline">Federation</string>
<string name="trending_posts_info_banner">These are the posts gaining traction in your corner of Mastodon.</string>
<string name="trending_hashtags_info_banner">These are the hashtags gaining traction in your corner of Mastodon.</string>
@@ -396,13 +403,19 @@
<string name="upload_error_connection_lost">Your device lost connection to the internet</string>
<string name="upload_processing">Processing…</string>
<!-- %s is version like 1.2.3 -->
<string name="update_available">Mastodon for Android %s is ready to download.</string>
<string name="update_available">Mastodos %s is ready to download.</string>
<!-- %s is version like 1.2.3 -->
<string name="update_ready">Mastodon for Android %s is downloaded and ready to install.</string>
<string name="update_ready">Mastodos %s is downloaded and ready to install.</string>
<!-- %s is file size -->
<string name="download_update">Download (%s)</string>
<string name="install_update">Install</string>
<string name="check_for_update">Check for update</string>
<string name="no_update_available">No update available</string>
<string name="privacy_policy_title">Mastodon and your privacy</string>
<string name="privacy_policy_subtitle">Although the Mastodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy.</string>
<string name="i_agree">I Agree</string>
<string name="list_timelines">Lists</string>
<string name="favorited_posts">Favorited posts</string>
<string name="accept_follow_request">Accept follow request</string>
<string name="reject_follow_request">Reject follow request</string>
</resources>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en" />
<locale android:name="ar" />
<locale android:name="bs" />
<locale android:name="ca" />
<locale android:name="cs" />
<locale android:name="de" />
<locale android:name="el" />
<locale android:name="es" />
<locale android:name="eu" />
<locale android:name="fi" />
<locale android:name="fr" />
<locale android:name="gl" />
<locale android:name="hr" />
<locale android:name="hy" />
<locale android:name="it" />
<locale android:name="iw" />
<locale android:name="ja" />
<locale android:name="kab" />
<locale android:name="ko" />
<locale android:name="oc" />
<locale android:name="pl" />
<locale android:name="pt-BR" />
<locale android:name="pt-PT" />
<locale android:name="ru" />
<locale android:name="sv" />
<locale android:name="th" />
<locale android:name="tr" />
<locale android:name="uk" />
<locale android:name="vi" />
<locale android:name="zh-Hans" />
<locale android:name="zh-Hant" />
</locale-config>