Compare commits

...

187 Commits

Author SHA1 Message Date
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
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
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
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
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
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
sk
85b8bae42e bump version 2022-11-02 22:15:26 +01:00
Samuel Kaiser
64416ef9ee Merge pull request #26 from Y32Gcnte8z/fork
Should we also change the client name in the footer of status?
2022-11-02 21:55:20 +01:00
Samuel Kaiser
6274ab3384 Merge pull request #27 from Y32Gcnte8z/visibility
Retain visibility of status when editing
2022-11-02 21:53:34 +01:00
sk
4ea5d94dc6 Merge branch 'fix-notifications-crash' into fork 2022-11-02 21:41:30 +01:00
sk
349d95b8ee fix syntax error 2022-11-02 20:25:42 +01:00
sk
8c8eb395de Merge remote-tracking branch 'origin/upstream' into fork 2022-11-02 20:22:33 +01:00
sk
596799bf2f enable github update check 2022-11-01 23:28:55 +01:00
sk
10a405ef13 change self updater api url 2022-11-01 21:38:30 +01:00
sk
a4cb05080a Merge branch 'upstream' into fork 2022-11-01 21:26:44 +01:00
Y32Gcnte8z
96040e15fd remain visibility when editing 2022-09-15 22:38:44 +08:00
Y32Gcnte8z
12a5670441 change client name in footer of status 2022-09-15 21:21:50 +08:00
sk
8aeda56fc8 merge upstream changes 2022-09-08 14:56:43 +02:00
sk
f073eba538 Merge branch 'feature/pin-posts' into fork 2022-07-22 12:18:42 +02:00
sk
7f78431eff minor code style fix 2022-07-22 12:18:16 +02:00
sk
24c1ac042c Merge branch 'master' into feature/pin-posts 2022-07-22 11:57:02 +02:00
sk
105fe68438 update app name, close #15 2022-07-22 11:51:10 +02:00
sk
46057af093 update readme 2022-07-22 11:50:04 +02:00
sk
750fa4c112 Merge branch 'master' into fork 2022-07-22 11:30:17 +02:00
sk
c1e67c4f73 bump version 2022-06-08 21:44:39 +02:00
sk
e0e48f87eb Merge branch 'master' into fork 2022-06-08 21:42:42 +02:00
Samuel Kaiser
0ec14fe8fa Merge pull request #20 from Y32Gcnte8z/fork
fix simplified Chinese strings
2022-06-05 12:03:08 +02:00
Y32Gcnte8z
01a2f1d95c fix simplified Chinese strings 2022-06-04 13:20:56 +08:00
Samuel Kaiser
67b3e85837 Merge pull request #19 from Y32Gcnte8z/fork
New translations strings.xml (Chinese Simplified)
2022-06-03 11:31:19 +02:00
Y32Gcnte8z
9f4d330ab1 New translations strings.xml (Chinese Simplified) 2022-06-03 13:55:49 +08:00
sk
25092fbcfb add icon to readme 2022-05-31 17:04:58 +02:00
sk
705e98729d initial pink branding 2022-05-31 16:51:05 +02:00
sk
108d16a157 bump version 2022-05-26 22:44:31 +02:00
sk
e55ca6cc05 Merge branch 'feature/delete-redraft' into fork 2022-05-26 22:42:02 +02:00
sk
b8be1f184d hide redraft button when not applicable 2022-05-26 22:38:56 +02:00
sk
aa96ec54a3 Merge branch 'feature/delete-redraft' into fork 2022-05-26 22:09:39 +02:00
sk
e8b43c7179 preserve visibility when re-drafting 2022-05-26 22:09:02 +02:00
sk
b51b4a10ee Merge branch 'feature/delete-redraft' into fork 2022-05-26 21:44:07 +02:00
sk
f2b9ede27c Add proguard rules for parceler
according to http://parceler.org/
fixes issue where parceler can't find parcelable class
2022-05-26 21:43:39 +02:00
sk
a8c7d891f1 Merge branch 'feature/delete-redraft' into fork 2022-05-26 19:45:52 +02:00
sk
195c4d7b6d remove unused imports 2022-05-26 19:45:10 +02:00
sk
d280dc31e8 bump version 2022-05-26 19:35:36 +02:00
sk
eb0925c524 Merge branch 'feature/delete-redraft' into fork 2022-05-26 19:30:52 +02:00
sk
968de3664d fix german strings 2022-05-26 19:30:23 +02:00
sk
12f7336392 Merge branch 'feature/delete-redraft' into fork 2022-05-26 19:28:09 +02:00
sk
3a4d13b1c6 implement deleting and re-drafting 2022-05-26 19:19:42 +02:00
sk
273c841d9a Merge branch 'master' into feature/delete-redraft 2022-05-26 15:35:12 +02:00
sk
0186b7f8da Merge remote-tracking branch 'origin/fork' into fork 2022-05-22 02:14:06 +02:00
sk
d33654c793 bump version 2022-05-22 02:08:01 +02:00
Samuel Kaiser
86d2312615 Update README.md 2022-05-22 02:07:07 +02:00
sk
d1083c331b Merge branch 'feature/bookmarks' into fork 2022-05-22 02:04:08 +02:00
sk
ed7242217a add missing icon 2022-05-22 02:03:54 +02:00
sk
8fddaa8c82 implement bookmarks list!! 2022-05-22 02:03:29 +02:00
sk
00affe6e3e update readme 2022-05-21 23:38:56 +02:00
sk
f21b647ee0 bump version 2022-05-21 23:35:10 +02:00
sk
2a628a3791 Merge branch 'feature/back-returns-home' into fork 2022-05-21 23:34:18 +02:00
sk
ecd568503d make back button return home before exiting 2022-05-21 23:33:53 +02:00
sk
f9d0632a85 add missing files 2022-05-21 19:42:29 +02:00
sk
11905513b7 add missing icons 2022-05-21 19:40:49 +02:00
sk
9c89abf1c4 implement bookmark button 2022-05-21 19:27:44 +02:00
sk
4d950e43ac update readme 2022-05-21 18:59:13 +02:00
sk
99405f307d bump version 2022-05-21 18:49:59 +02:00
sk
f1bfe05263 Merge branch 'feature/always-preserve-cw' into fork 2022-05-21 18:49:07 +02:00
sk
0f223159c0 always preserve cw when replying 2022-05-21 18:48:48 +02:00
sk
ad9518e87c re-add deleted strings 2022-05-21 18:08:14 +02:00
sk
1c16cfb09e bump version 2022-05-21 18:05:01 +02:00
sk
d4a4b10017 Merge branch 'feature/compose-image-description-full-image' into fork 2022-05-21 18:03:29 +02:00
sk
74ae5bd04e change app name 2022-05-21 18:03:15 +02:00
sk
9638cf079f set image view height to wrap_content 2022-05-21 17:48:53 +02:00
sk
a6d161c1b4 minor code style change
for grishka's code style must prevail
2022-05-21 17:42:40 +02:00
sk
1136e40eb4 obey image max width 2022-05-21 17:39:28 +02:00
sk
98de3a2984 don't crop image when composing alt text 2022-05-21 17:27:31 +02:00
sk
b08415ca8f bump version 2022-05-15 20:35:10 +02:00
sk
3639c69d36 Merge branch 'master' into fork 2022-05-15 20:34:14 +02:00
sk
31e3a8592f bump version 2022-05-14 13:49:49 +02:00
sk
39655d5278 Merge branch 'master' into fork 2022-05-14 13:48:03 +02:00
sk
d844a77e65 add ui items for redraft 2022-05-11 17:25:00 +02:00
sk
bde2e398a8 bump version and change app name 2022-05-06 22:10:45 +02:00
sk
8d443b2051 Merge branch 'feature/pin-posts' into fork 2022-05-06 21:50:08 +02:00
sk
33d4b678ed update posts' pinned states 2022-05-06 21:49:33 +02:00
sk
3becad1468 fix created posts being added to pinned 2022-05-06 21:18:48 +02:00
sk
fad3ba3eae bump version 2022-05-06 19:49:36 +02:00
sk
cb16f95878 Merge branch 'feature/pin-posts' into fork 2022-05-06 19:45:33 +02:00
sk
4e833490ff fix about section not being displayed 2022-05-06 19:45:09 +02:00
Samuel Kaiser
04a973f7b0 Update README.md 2022-05-06 19:28:43 +02:00
sk
0318169b74 Merge branch 'feature/pin-posts' into fork 2022-05-06 19:24:58 +02:00
sk
972fb1e241 translate "pinned" strings to german 2022-05-06 19:21:39 +02:00
sk
9beb04b01d implement pinning and unpinning posts 2022-05-06 19:07:51 +02:00
sk
a3bea6ad24 add profile tab for pinned toots 2022-05-06 18:09:00 +02:00
sk
7996e4ee4a change client name, versioning 2022-05-06 15:39:57 +02:00
sk
69c4bf4213 Merge branch 'feature/display-alt-text' into fork 2022-05-06 01:00:34 +02:00
sk
7cd5ca77f5 Merge remote-tracking branch 'origin/feature/display-alt-text' into feature/display-alt-text 2022-05-06 01:00:01 +02:00
sk
7e736d3cd3 clean up code 2022-05-06 00:59:43 +02:00
sk
13c2adba56 update version and readme 2022-05-06 00:34:08 +02:00
sk
010095a50e Merge branch 'master' into fork 2022-05-06 00:30:23 +02:00
Samuel Kaiser
f0cef2103f Merge branch 'mastodon:master' into feature/display-alt-text 2022-05-06 00:29:05 +02:00
sk
8ed731a48b edit application id for fork 2022-05-06 00:27:59 +02:00
sk
8660d43cb1 bump version 2022-05-06 00:21:08 +02:00
sk
0f495f620a Merge branch 'feature/display-alt-text' into fork 2022-05-06 00:17:07 +02:00
sk
ac81f10ea8 make button disappear when no description 2022-05-06 00:16:25 +02:00
sk
9aa95413e6 make text selectable 2022-05-06 00:15:56 +02:00
sk
a0a28a0cb7 implement scroll-to-close 2022-05-05 23:39:24 +02:00
sk
11d88aed27 implement alt text as bottom sheet 2022-05-05 23:26:48 +02:00
sk
899c9cdf21 implement alt text as toast messages 2022-05-05 19:28:27 +02:00
sk
919d5cffb5 Merge branch 'master' into fork 2022-05-05 16:06:28 +02:00
sk
12599db0ff change dev versioning 2022-05-02 23:00:55 +02:00
sk
c751c85c1c add version number for upstream changes 2022-05-02 22:43:05 +02:00
sk
f1331a0f6d start fork versioning 2022-05-02 21:35:51 +02:00
sk
c75c9b60f9 add fork readme 2022-05-02 21:35:42 +02:00
sk
eb3adf1dfd Merge branch 'feature/add-federated-timeline' into fork 2022-05-02 21:35:14 +02:00
sk
6533163fd0 Merge branch 'master' into fork 2022-05-02 21:17:04 +02:00
sk
1becad6016 Merge branch 'feature/enable-unlisted-as-default' into fork 2022-05-02 19:32:26 +02:00
sk
d34653750e set unlisted as default visibility 2022-05-02 19:31:43 +02:00
sk
705592aefd set unlisted as default 2022-05-02 19:17:25 +02:00
sk
583325d6e8 add unlisted visibility option 2022-05-02 19:16:53 +02:00
sk
318d271127 add federation tab and change tab order 2022-05-02 18:31:29 +02:00
104 changed files with 1641 additions and 299 deletions

View File

@@ -1,11 +1,51 @@
# Mastodon for Android
[![Crowdin](https://badges.crowdin.net/mastodon-for-android/localized.svg)](https://crowdin.com/project/mastodon-for-android)
![Pink version of the Mastodon for Android launcher icon](mastodon/src/main/res/mipmap-xhdpi/ic_launcher_round.png)
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android"><img src="img/google-play-badge.png" height="50"></a>
# Mastodos
This is the repository for the official Android app for Mastodon.
> 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.
Learn more about this app in the [blog post](https://blog.joinmastodon.org/2022/02/official-mastodon-for-android-app-is-coming-soon/).
[![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/mastodon-android-fork/releases/latest/download/mastodos.apk)
## Changes
### Features
* [Add “Unlisted” as a post visibility option](https://github.com/sk22/mastodon-android-fork/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/mastodon-android-fork/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/mastodon-android-fork/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/mastodon-android-fork/commits/feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
* [Implement deleting and re-drafting](https://github.com/sk22/mastodon-android-fork/commits/feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
* [Implement a bookmark button and list](https://github.com/sk22/mastodon-android-fork/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/mastodon-android-fork/commits/feature/check-for-update-button)
* [Add “Mark media as sensitive” option](https://github.com/sk22/mastodon-android-fork/commits/feature/mark-media-as-sensitive)
* [Add settings to hide replies and reposts from the timeline](https://github.com/sk22/mastodon-android-fork/commits/feature/filter-home-timeline) ([Pull request](https://github.com/mastodon/mastodon-android/pull/317))
* [Follow and unfollow hashtags](https://github.com/sk22/mastodon-android-fork/commit/7d38f031f197aa6cefaf53e39d929538689c1e4e) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/233))
* [Notification bell for posts](https://github.com/sk22/mastodon-android-fork/commit/b166ca705eb9169025ef32bbe6315b42491b57ea) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/81))
* [Lists view (viewing only, for now)](https://github.com/sk22/mastodon-android-fork/commits/list-timeline-views) based on [@obstsalatschuessel](https://github.com/obstsalatschuessel)'s [Pull request](https://github.com/mastodon/mastodon-android/pull/286)
### Behavior
* [Make back button return to the home tab before exiting the app](https://github.com/sk22/mastodon-android-fork/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/mastodon-android-fork/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/mastodon-android-fork/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/mastodon-android-fork/commits/spoiler-height-independent) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/166))
* [Custom extended footer redesign](https://github.com/sk22/mastodon-android-fork/commits/compact-extended-footer)
### Installation
To install this app on your Android device, download the [latest release from GitHub](https://github.com/sk22/mastodon-android-fork/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/mastodon-android-fork/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!
### 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
```
## Building
@@ -17,4 +57,4 @@ As this app is using Java 17 features, you need JDK 17 or newer to build it. Oth
## License
This project is released under the [GPL-3 License](./LICENSE).
This project is released under the [GPL-3 License](./LICENSE).

View File

@@ -6,11 +6,11 @@ plugins {
android {
compileSdk 33
defaultConfig {
applicationId "org.joinmastodon.android"
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 33
versionCode 43
versionName "1.1.4"
versionCode 35
versionName "1.1.4+fork.35"
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",
@@ -56,6 +56,9 @@ android {
githubRelease{
setRoot "src/github"
}
debug {
setRoot "src/github"
}
}
lintOptions{
checkReleaseBuilds false

View File

@@ -46,4 +46,9 @@
-keep class org.joinmastodon.android.AppCenterWrapper { *; }
-keepattributes LineNumberTable
-keepattributes LineNumberTable
# Parceler library
-keep interface org.parceler.Parcel
-keep @org.parceler.Parcel class * { *; }
-keep class **$$Parcelable { *; }

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;
@@ -94,38 +96,51 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
public void maybeCheckForUpdates(){
if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
return;
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", 0);
if(timeSinceLastCheck>CHECK_PERIOD){
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", CHECK_PERIOD);
if(timeSinceLastCheck>=CHECK_PERIOD){
setState(UpdateState.CHECKING);
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
}
}
@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/mastodon/mastodon-android/releases/latest")
.url("https://api.github.com/repos/sk22/mastodon-android-fork/releases/latest")
.build();
Call call=MastodonAPIController.getHttpClient().newCall(req);
try(Response resp=call.execute()){
JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
String tag=obj.get("tag_name").getAsString();
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)");
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
Matcher matcher=pattern.matcher(tag);
if(!matcher.find()){
Log.w(TAG, "actuallyCheckForUpdates: release tag has wrong format: "+tag);
return;
}
int newMajor=Integer.parseInt(matcher.group(1)), newMinor=Integer.parseInt(matcher.group(2)), newRevision=Integer.parseInt(matcher.group(3));
int newMajor=Integer.parseInt(matcher.group(1)),
newMinor=Integer.parseInt(matcher.group(2)),
newRevision=Integer.parseInt(matcher.group(3)),
newForkNumber=Integer.parseInt(matcher.group(4));
matcher=pattern.matcher(BuildConfig.VERSION_NAME);
String[] currentParts=BuildConfig.VERSION_NAME.split("[.+]");
if(!matcher.find()){
Log.w(TAG, "actuallyCheckForUpdates: current version has wrong format: "+BuildConfig.VERSION_NAME);
return;
}
int curMajor=Integer.parseInt(matcher.group(1)), curMinor=Integer.parseInt(matcher.group(2)), curRevision=Integer.parseInt(matcher.group(3));
int curMajor=Integer.parseInt(matcher.group(1)),
curMinor=Integer.parseInt(matcher.group(2)),
curRevision=Integer.parseInt(matcher.group(3)),
curForkNumber=Integer.parseInt(matcher.group(4));
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
if(newVersion>curVersion || BuildConfig.DEBUG){
String version=newMajor+"."+newMinor+"."+newRevision;
if(newVersion>curVersion || newForkNumber>curForkNumber || BuildConfig.DEBUG){
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
for(JsonElement el:obj.getAsJsonArray("assets")){
JsonObject asset=el.getAsJsonObject();

View File

@@ -7,6 +7,9 @@ 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 ThemePreference theme;
private static SharedPreferences getPrefs(){
@@ -18,6 +21,9 @@ 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);
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
}
@@ -25,6 +31,9 @@ public class GlobalUserPreferences{
getPrefs().edit()
.putBoolean("playGifs", playGifs)
.putBoolean("useCustomTabs", useCustomTabs)
.putBoolean("showReplies", showReplies)
.putBoolean("showBoosts", showBoosts)
.putBoolean("loadNewPosts", loadNewPosts)
.putBoolean("trueBlackTheme", trueBlackTheme)
.putInt("theme", theme.ordinal())
.apply();

View File

@@ -4,6 +4,7 @@ import android.os.Looper;
import org.joinmastodon.android.E;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
@@ -18,6 +19,7 @@ public class StatusInteractionController{
private final String accountID;
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
public StatusInteractionController(String accountID){
this.accountID=accountID;
@@ -61,6 +63,36 @@ public class StatusInteractionController{
E.post(new StatusCountersUpdatedEvent(status));
}
public void setBookmarked(Status status, boolean bookmarked){
if(!Looper.getMainLooper().isCurrentThread())
throw new IllegalStateException("Can only be called from main thread");
SetStatusBookmarked current=runningBookmarkRequests.remove(status.id);
if(current!=null){
current.cancel();
}
SetStatusBookmarked req=(SetStatusBookmarked) new SetStatusBookmarked(status.id, bookmarked)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Status result){
runningBookmarkRequests.remove(status.id);
E.post(new StatusCountersUpdatedEvent(result));
}
@Override
public void onError(ErrorResponse error){
runningBookmarkRequests.remove(status.id);
error.showToast(MastodonApp.context);
status.bookmarked=!bookmarked;
E.post(new StatusCountersUpdatedEvent(status));
}
})
.exec(accountID);
runningBookmarkRequests.put(status.id, req);
status.bookmarked=bookmarked;
E.post(new StatusCountersUpdatedEvent(status));
}
public void setReblogged(Status status, boolean reblogged){
if(!Looper.getMainLooper().isCurrentThread())
throw new IllegalStateException("Can only be called from main thread");

View File

@@ -21,6 +21,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
switch(filter){
case DEFAULT -> addQueryParameter("exclude_replies", "true");
case INCLUDE_REPLIES -> {}
case PINNED -> addQueryParameter("pinned", "true");
case MEDIA -> addQueryParameter("only_media", "true");
case NO_REBLOGS -> {
addQueryParameter("exclude_replies", "true");
@@ -33,6 +34,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
public enum Filter{
DEFAULT,
INCLUDE_REPLIES,
PINNED,
MEDIA,
NO_REBLOGS,
OWN_POSTS_AND_REPLIES

View File

@@ -0,0 +1,52 @@
package org.joinmastodon.android.api.requests.accounts;
import androidx.annotation.NonNull;
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.Arrays;
import java.util.List;
import okhttp3.Response;
public class GetBookmarks extends MastodonAPIRequest<List<Status>>{
private String maxId;
public GetBookmarks(String maxID, String minID, int limit){
super(HttpMethod.GET, "/bookmarks", 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,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

@@ -11,9 +11,9 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
}
private static class Request{
public String clientName="Mastodon for Android";
public String clientName="Mastodos";
public String redirectUris=AccountSessionManager.REDIRECT_URI;
public String scopes=AccountSessionManager.SCOPE;
public String website="https://app.joinmastodon.org/android";
public String website="https://github.com/sk22/mastodon-android-fork";
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
public class SetStatusBookmarked extends MastodonAPIRequest<Status>{
public SetStatusBookmarked(String id, boolean bookmarked){
super(HttpMethod.POST, "/statuses/"+id+"/"+(bookmarked ? "bookmark" : "unbookmark"), Status.class);
setRequestBody(new Object());
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
public class SetStatusPinned extends MastodonAPIRequest<Status>{
public SetStatusPinned(String id, boolean pinned){
super(HttpMethod.POST, "/statuses/"+id+"/"+(pinned ? "pin" : "unpin"), Status.class);
setRequestBody(new Object());
}
}

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

@@ -5,7 +5,7 @@ import org.joinmastodon.android.model.Status;
public class StatusCountersUpdatedEvent{
public String id;
public long favorites, reblogs, replies;
public boolean favorited, reblogged;
public boolean favorited, reblogged, pinned;
public StatusCountersUpdatedEvent(Status s){
id=s.id;
@@ -14,5 +14,6 @@ public class StatusCountersUpdatedEvent{
replies=s.repliesCount;
favorited=s.favourited;
reblogged=s.reblogged;
pinned=s.pinned;
}
}

View File

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

View File

@@ -8,8 +8,10 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.parceler.Parcels;
import java.util.Collections;
@@ -76,6 +78,7 @@ public class AccountTimelineFragment extends StatusListFragment{
protected void onStatusCreated(StatusCreatedEvent ev){
if(!AccountSessionManager.getInstance().isSelf(accountID, ev.status.account))
return;
if(filter==GetAccountStatuses.Filter.PINNED) return;
if(filter==GetAccountStatuses.Filter.DEFAULT){
// Keep replies to self, discard all other replies
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
@@ -86,4 +89,24 @@ public class AccountTimelineFragment extends StatusListFragment{
}
prependItems(Collections.singletonList(ev.status), true);
}
protected void onStatusUnpinned(StatusUnpinnedEvent ev){
if(!ev.accountID.equals(accountID) || filter!=GetAccountStatuses.Filter.PINNED)
return;
Status status=getStatusByID(ev.id);
data.remove(status);
preloadedData.remove(status);
HeaderStatusDisplayItem item=findItemOfType(ev.id, HeaderStatusDisplayItem.class);
if(item==null)
return;
int index=displayItems.indexOf(item);
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

@@ -0,0 +1,53 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetBookmarks;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Status;
import java.util.List;
import me.grishka.appkit.api.SimpleCallback;
public class BookmarksListFragment extends StatusListFragment{
private String accountID;
private Account self;
private String lastMaxId=null;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
self=session.self;
setTitle(R.string.bookmarks);
}
@Override
protected void onShown(){
super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
loadData();
}
@Override
protected void doLoadData(int offset, int count) {
GetBookmarks b=new GetBookmarks(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

@@ -27,6 +27,7 @@ import android.text.Layout;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -160,11 +161,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<>();
@@ -180,6 +183,7 @@ 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;
@@ -197,6 +201,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
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);
@@ -281,6 +293,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());
@@ -288,6 +302,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){
@@ -313,11 +328,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
pollDurationView.setOnClickListener(v->showPollDurationMenu());
pollOptions.clear();
if(savedInstanceState!=null && savedInstanceState.containsKey("pollOptions")){
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);
}
pollBtn.setSelected(true);
mediaBtn.setEnabled(false);
pollWrap.setVisibility(View.VISIBLE);
for(String oldText:savedInstanceState.getStringArrayList("pollOptions")){
for(String oldText:restoredPollOptions){
DraftPollOption opt=createDraftPollOption();
opt.edit.setText(oldText);
}
@@ -352,8 +374,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
spoilerBtn.setSelected(true);
}
if(savedInstanceState!=null && savedInstanceState.containsKey("attachments")){
ArrayList<Parcelable> serializedAttachments=savedInstanceState.getParcelableArrayList("attachments");
sensitive = editingStatus != null ? editingStatus.sensitive
: (savedInstanceState != null && savedInstanceState.getBoolean("sensitive", false));
sensitiveIcon.setSelected(sensitive);
ArrayList<Parcelable> serializedAttachments=(savedInstanceState!=null ? savedInstanceState : getArguments())
.getParcelableArrayList("attachments");
if(serializedAttachments!=null){
for(Parcelable a:serializedAttachments){
DraftMediaAttachment att=Parcels.unwrap(a);
attachmentsView.addView(createMediaAttachmentView(att));
@@ -370,6 +397,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(editingStatus!=null && editingStatus.visibility!=null) {
statusVisibility=editingStatus.visibility;
}
updateVisibilityIcon();
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
@@ -396,6 +424,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
outState.putString("pollDurationStr", pollDurationStr);
}
outState.putBoolean("hasSpoiler", hasSpoiler);
outState.putBoolean("sensitive", sensitive);
if(!attachments.isEmpty()){
ArrayList<Parcelable> serializedAttachments=new ArrayList<>(attachments.size());
for(DraftMediaAttachment att:attachments){
@@ -504,7 +533,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
ignoreSelectionChanges=true;
mainEditText.setSelection(mainEditText.length());
ignoreSelectionChanges=false;
if(!TextUtils.isEmpty(replyTo.spoilerText) && AccountSessionManager.getInstance().isSelf(accountID, replyTo.account)){
if(!TextUtils.isEmpty(replyTo.spoilerText)){
hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE);
spoilerEdit.setText(replyTo.spoilerText);
@@ -552,6 +581,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
}
updateSensitive();
if(editingStatus!=null){
updateCharCounter();
visibilityBtn.setEnabled(false);
@@ -662,6 +693,7 @@ 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());
}
@@ -734,6 +766,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private boolean hasDraft(){
if(getArguments().getBoolean("hasDraft", false)) return true;
if(editingStatus!=null){
if(!mainEditText.getText().toString().equals(initialText))
return true;
@@ -867,6 +900,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
uploadNextQueuedAttachment();
}
updatePublishButtonState();
updateSensitive();
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS)
mediaBtn.setEnabled(false);
return true;
@@ -1041,6 +1075,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
updatePublishButtonState();
pollBtn.setEnabled(attachments.isEmpty());
mediaBtn.setEnabled(true);
updateSensitive();
}
private void onRetryOrCancelMediaUploadClick(View v){
@@ -1242,7 +1277,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(){
@@ -1256,7 +1304,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
UiUtils.enablePopupMenuIcons(getActivity(), menu);
m.setGroupCheckable(0, true, true);
m.findItem(switch(statusVisibility){
case PUBLIC, UNLISTED -> R.id.vis_public;
case PUBLIC -> R.id.vis_public;
case UNLISTED -> R.id.vis_unlisted;
case PRIVATE -> R.id.vis_followers;
case DIRECT -> R.id.vis_private;
}).setChecked(true);
@@ -1266,6 +1315,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
int id=item.getItemId();
if(id==R.id.vis_public){
statusVisibility=StatusPrivacy.PUBLIC;
}else if(id==R.id.vis_unlisted){
statusVisibility=StatusPrivacy.UNLISTED;
}else if(id==R.id.vis_followers){
statusVisibility=StatusPrivacy.PRIVATE;
}else if(id==R.id.vis_private){
@@ -1296,9 +1347,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public void onSuccess(Preferences result){
// Only override the reply visibility if our preference is more private
if (result.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) {
// Map unlisted from the API onto public, because we don't have unlisted in the UI
statusVisibility = switch (result.postingDefaultVisibility) {
case PUBLIC, UNLISTED -> StatusPrivacy.PUBLIC;
case PUBLIC -> StatusPrivacy.PUBLIC;
case UNLISTED -> StatusPrivacy.UNLISTED;
case PRIVATE -> StatusPrivacy.PRIVATE;
case DIRECT -> StatusPrivacy.DIRECT;
};

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

@@ -241,9 +241,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
@Override
public boolean onBackPressed(){
if(currentTab==R.id.tab_profile)
return profileFragment.onBackPressed();
if (profileFragment.onBackPressed()) return true;
if(currentTab==R.id.tab_search)
return searchFragment.onBackPressed();
if (searchFragment.onBackPressed()) return true;
if (currentTab!=R.id.tab_home) {
tabBar.selectTab(R.id.tab_home);
onTabSelected(R.id.tab_home);
return true;
}
return false;
}

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

@@ -98,10 +98,10 @@ 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, mediaFragment;
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
private ProfileAboutFragment aboutFragment;
private TabLayout tabbar;
private SwipeRefreshLayout refreshLayout;
@@ -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);
@@ -209,14 +211,15 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}
};
tabViews=new FrameLayout[4];
tabViews=new FrameLayout[5];
for(int i=0;i<tabViews.length;i++){
FrameLayout tabView=new FrameLayout(getActivity());
tabView.setId(switch(i){
case 0 -> R.id.profile_posts;
case 1 -> R.id.profile_posts_with_replies;
case 2 -> R.id.profile_media;
case 3 -> R.id.profile_about;
case 2 -> R.id.profile_pinned_posts;
case 3 -> R.id.profile_media;
case 4 -> R.id.profile_about;
default -> throw new IllegalStateException("Unexpected value: "+i);
});
tabView.setVisibility(View.GONE);
@@ -224,7 +227,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
tabViews[i]=tabView;
}
pager.setOffscreenPageLimit(4);
pager.setOffscreenPageLimit(5);
pager.setAdapter(new ProfilePagerAdapter());
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
@@ -240,8 +243,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
tab.setText(switch(position){
case 0 -> R.string.posts;
case 1 -> R.string.posts_and_replies;
case 2 -> R.string.media;
case 3 -> R.string.profile_about;
case 2 -> R.string.pinned_posts;
case 3 -> R.string.media;
case 4 -> R.string.profile_about;
default -> throw new IllegalStateException();
});
}
@@ -256,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);
@@ -298,6 +303,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
postsFragment.onRefresh();
if(postsWithRepliesFragment.loaded)
postsWithRepliesFragment.onRefresh();
if(pinnedPostsFragment.loaded)
pinnedPostsFragment.onRefresh();
if(mediaFragment.loaded)
mediaFragment.onRefresh();
}
@@ -322,6 +329,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(postsFragment==null){
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
pinnedPostsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
aboutFragment=new ProfileAboutFragment();
aboutFragment.setFields(fields);
@@ -402,6 +410,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(postsFragment!=null && postsFragment.isAdded() && childInsets!=null){
postsFragment.onApplyWindowInsets(childInsets);
postsWithRepliesFragment.onApplyWindowInsets(childInsets);
pinnedPostsFragment.onApplyWindowInsets(childInsets);
mediaFragment.onApplyWindowInsets(childInsets);
}
}
@@ -452,6 +461,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
UiUtils.loadCustomEmojiInTextView(name);
UiUtils.loadCustomEmojiInTextView(bio);
notifyButton.setVisibility(View.GONE);
if(AccountSessionManager.getInstance().isSelf(accountID, account)){
actionButton.setText(R.string.edit_profile);
}else{
@@ -524,7 +534,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(isOwnProfile){
for(int i=0;i<menu.size();i++){
MenuItem item=menu.getItem(i);
item.setVisible(item.getItemId()==R.id.share);
item.setVisible(item.getItemId()==R.id.share || item.getItemId()==R.id.bookmarks);
}
return;
}
@@ -544,11 +554,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override
public boolean onOptionsItemSelected(MenuItem item){
int id=item.getItemId();
if(id==R.id.share){
Intent intent=new Intent(Intent.ACTION_SEND);
if(id==R.id.share) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, account.url);
startActivity(Intent.createChooser(intent, item.getTitle()));
}else if(id==R.id.bookmarks) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(account));
Nav.go(getActivity(), BookmarksListFragment.class, args);
}else if(id==R.id.mute){
confirmToggleMuted();
}else if(id==R.id.block){
@@ -566,7 +581,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){
@@ -611,9 +626,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){
@@ -652,8 +672,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return switch(page){
case 0 -> postsFragment;
case 1 -> postsWithRepliesFragment;
case 2 -> mediaFragment;
case 3 -> aboutFragment;
case 2 -> pinnedPostsFragment;
case 3 -> mediaFragment;
case 4 -> aboutFragment;
default -> throw new IllegalStateException();
};
}
@@ -679,6 +700,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;
@@ -714,9 +741,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
invalidateOptionsMenu();
pager.setUserInputEnabled(false);
actionButton.setText(R.string.done);
pager.setCurrentItem(3);
pager.setCurrentItem(4);
ArrayList<Animator> animators=new ArrayList<>();
for(int i=0;i<3;i++){
for(int i=0;i<tabViews.length-1;i++){
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, .3f));
tabbar.getTabAt(i).view.setEnabled(false);
}
@@ -757,7 +784,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
invalidateOptionsMenu();
ArrayList<Animator> animators=new ArrayList<>();
actionButton.setText(R.string.edit_profile);
for(int i=0;i<3;i++){
for(int i=0;i<tabViews.length-1;i++){
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, 1f));
}
animators.add(ObjectAnimator.ofInt(avatar.getForeground(), "alpha", 0));
@@ -775,7 +802,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
for(int i=0;i<3;i++){
for(int i=0;i<tabViews.length-1;i++){
tabbar.getTabAt(i).view.setEnabled(true);
}
pager.setUserInputEnabled(true);
@@ -847,6 +874,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);
@@ -952,7 +983,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override
public int getItemCount(){
return loaded ? 4 : 0;
return loaded ? tabViews.length : 0;
}
@Override

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){
@@ -103,6 +105,20 @@ public class SettingsFragment extends MastodonToolbarFragment{
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());
PushSubscription pushSubscription=getPushSubscription();
@@ -113,11 +129,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 +346,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 +432,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 +670,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 +736,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

@@ -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

@@ -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;
@@ -51,6 +52,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
private DiscoverAccountsFragment accountsFragment;
private SearchFragment searchFragment;
private LocalTimelineFragment localTimelineFragment;
private FederatedTimelineFragment federatedTimelineFragment;
private ListTimelinesFragment listTimelinesFragment;
private String accountID;
private Runnable searchDebouncer=this::onSearchChangedDebounced;
@@ -72,15 +75,17 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
tabLayout=view.findViewById(R.id.tabbar);
pager=view.findViewById(R.id.pager);
tabViews=new FrameLayout[5];
tabViews=new FrameLayout[7];
for(int i=0;i<tabViews.length;i++){
FrameLayout tabView=new FrameLayout(getActivity());
tabView.setId(switch(i){
case 0 -> R.id.discover_posts;
case 1 -> R.id.discover_hashtags;
case 2 -> R.id.discover_news;
case 3 -> R.id.discover_local_timeline;
case 4 -> R.id.discover_users;
case 0 -> R.id.discover_local_timeline;
case 1 -> R.id.discover_federated_timeline;
case 2 -> R.id.discover_hashtags;
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);
@@ -106,7 +111,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}
});
if(postsFragment==null){
if(localTimelineFragment==null){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putBoolean("__is_tab", true);
@@ -126,12 +131,20 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
localTimelineFragment=new LocalTimelineFragment();
localTimelineFragment.setArguments(args);
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)
.add(R.id.discover_federated_timeline, federatedTimelineFragment)
.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();
}
@@ -139,11 +152,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
tab.setText(switch(position){
case 0 -> R.string.posts;
case 1 -> R.string.hashtags;
case 2 -> R.string.news;
case 3 -> R.string.local_timeline;
case 4 -> R.string.for_you;
case 0 -> R.string.local_timeline;
case 1 -> R.string.federated_timeline;
case 2 -> R.string.hashtags;
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);
@@ -229,8 +244,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}
public void loadData(){
if(postsFragment!=null && !postsFragment.loaded && !postsFragment.dataLoading)
postsFragment.loadData();
if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
localTimelineFragment.loadData();
}
private void onSearchEditFocusChanged(View v, boolean hasFocus){
@@ -266,11 +281,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
private Fragment getFragmentForPage(int page){
return switch(page){
case 0 -> postsFragment;
case 1 -> hashtagsFragment;
case 2 -> newsFragment;
case 3 -> localTimelineFragment;
case 4 -> accountsFragment;
case 0 -> localTimelineFragment;
case 1 -> federatedTimelineFragment;
case 2 -> hashtagsFragment;
case 3 -> postsFragment;
case 4 -> newsFragment;
case 5 -> accountsFragment;
case 6 -> listTimelinesFragment;
default -> throw new IllegalStateException("Unexpected value: "+page);
};
}

View File

@@ -0,0 +1,41 @@
package org.joinmastodon.android.fragments.discover;
import android.os.Bundle;
import android.view.View;
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback;
public class FederatedTimelineFragment extends StatusListFragment{
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
private String maxID;
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(!result.isEmpty())
maxID=result.get(result.size()-1).id;
onDataLoaded(result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList()), !result.isEmpty());
}
})
.exec(accountID);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
bannerHelper.maybeAddBanner(contentWrap);
}
}

View File

@@ -100,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

@@ -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

@@ -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

@@ -127,6 +127,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
repliesCount=ev.replies;
favourited=ev.favorited;
reblogged=ev.reblogged;
pinned=ev.pinned;
}
public Status getContentStatus(){

View File

@@ -0,0 +1,87 @@
package org.joinmastodon.android.ui;
import android.app.Activity;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.ui.utils.UiUtils;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.BottomSheet;
import me.grishka.appkit.views.UsableRecyclerView;
public class ImageDescriptionSheet extends BottomSheet{
private UsableRecyclerView list;
public ImageDescriptionSheet(@NonNull Activity activity, Attachment attachment){
super(activity);
View handleView=new View(activity);
handleView.setBackgroundResource(R.drawable.bg_bottom_sheet_handle);
ViewGroup handle=new FrameLayout(activity);
handle.addView(handleView);
handle.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(24)));
TextView textView = new TextView(activity);
if (attachment.description == null || attachment.description.isEmpty()) {
textView.setText(R.string.media_no_description);
textView.setTypeface(null, Typeface.ITALIC);
} else {
textView.setText(attachment.description);
textView.setTextIsSelectable(true);
}
TextView heading=new TextView(activity);
heading.setText(R.string.image_description);
heading.setAllCaps(true);
heading.setTypeface(null, Typeface.BOLD);
heading.setPadding(0, V.dp(24), 0, V.dp(8));
LinearLayout linearLayout = new LinearLayout(activity);
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setPadding(V.dp(24), 0, V.dp(24), 0);
linearLayout.addView(heading);
linearLayout.addView(textView);
FrameLayout layout=new FrameLayout(activity);
layout.addView(handle);
layout.addView(linearLayout);
list=new UsableRecyclerView(activity);
list.setLayoutManager(new LinearLayoutManager(activity));
list.setBackgroundResource(R.drawable.bg_bottom_sheet);
list.setAdapter(new SingleViewRecyclerAdapter(layout));
list.setClipToPadding(false);
setContentView(list);
setNavigationBarBackground(new ColorDrawable(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)), !UiUtils.isDarkTheme());
}
@Override
protected void onWindowInsetsUpdated(WindowInsets insets){
if(Build.VERSION.SDK_INT>=29){
int tappableBottom=insets.getTappableElementInsets().bottom;
int insetBottom=insets.getSystemWindowInsetBottom();
if(tappableBottom==0 && insetBottom>0){
list.setPadding(0, 0, 0, V.dp(48)-insetBottom);
}else{
list.setPadding(0, 0, 0, V.dp(24));
}
}else{
list.setPadding(0, 0, 0, V.dp(24));
}
}
}

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

@@ -43,7 +43,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
private final TextView reply, boost, favorite;
private final TextView reply, boost, favorite, bookmark;
private final ImageView share;
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
@@ -60,22 +60,27 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
reply=findViewById(R.id.reply);
boost=findViewById(R.id.boost);
favorite=findViewById(R.id.favorite);
bookmark=findViewById(R.id.bookmark);
share=findViewById(R.id.share);
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
UiUtils.fixCompoundDrawableTintOnAndroid6(reply);
UiUtils.fixCompoundDrawableTintOnAndroid6(boost);
UiUtils.fixCompoundDrawableTintOnAndroid6(favorite);
UiUtils.fixCompoundDrawableTintOnAndroid6(bookmark);
}
View reply=findViewById(R.id.reply_btn);
View boost=findViewById(R.id.boost_btn);
View favorite=findViewById(R.id.favorite_btn);
View share=findViewById(R.id.share_btn);
View bookmark=findViewById(R.id.bookmark_btn);
reply.setOnClickListener(this::onReplyClick);
reply.setAccessibilityDelegate(buttonAccessibilityDelegate);
boost.setOnClickListener(this::onBoostClick);
boost.setAccessibilityDelegate(buttonAccessibilityDelegate);
favorite.setOnClickListener(this::onFavoriteClick);
favorite.setAccessibilityDelegate(buttonAccessibilityDelegate);
bookmark.setOnClickListener(this::onBookmarkClick);
bookmark.setAccessibilityDelegate(buttonAccessibilityDelegate);
share.setOnClickListener(this::onShareClick);
share.setAccessibilityDelegate(buttonAccessibilityDelegate);
}
@@ -87,6 +92,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
bindButton(favorite, item.status.favouritesCount);
boost.setSelected(item.status.reblogged);
favorite.setSelected(item.status.favourited);
bookmark.setSelected(item.status.bookmarked);
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
}
@@ -120,6 +126,11 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
bindButton(favorite, item.status.favouritesCount);
}
private void onBookmarkClick(View v){
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
bookmark.setSelected(item.status.bookmarked);
}
private void onShareClick(View v){
Intent intent=new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
@@ -134,6 +145,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
return R.string.button_reblog;
if(id==R.id.favorite_btn)
return R.string.button_favorite;
if(id==R.id.bookmark_btn)
return R.string.button_bookmark;
if(id==R.id.share_btn)
return R.string.button_share;
return 0;

View File

@@ -163,6 +163,10 @@ 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){
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{});
}else if(id==R.id.block){
@@ -280,6 +284,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
menu.findItem(R.id.edit).setVisible(item.status!=null && isOwnPost);
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
menu.findItem(R.id.delete_and_redraft).setVisible(item.status!=null && isOwnPost);
menu.findItem(R.id.pin).setVisible(item.status!=null && isOwnPost && !item.status.pinned);
menu.findItem(R.id.unpin).setVisible(item.status!=null && isOwnPost && item.status.pinned);
menu.findItem(R.id.open_in_browser).setVisible(item.status!=null);
MenuItem blockDomain=menu.findItem(R.id.block_domain);
MenuItem mute=menu.findItem(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

@@ -19,6 +19,7 @@ import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.opengl.Visibility;
import android.os.Build;
import android.os.Environment;
import android.os.SystemClock;
@@ -48,6 +49,7 @@ import android.widget.Toolbar;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.ui.ImageDescriptionSheet;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import java.io.File;
@@ -97,6 +99,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
private TextView videoTimeView;
private ImageButton videoPlayPauseButton;
private View videoControls;
private MenuItem imageDescriptionButton;
private boolean uiVisible=true;
private AudioManager.OnAudioFocusChangeListener audioFocusListener=this::onAudioFocusChanged;
private Runnable uiAutoHider=()->{
@@ -174,11 +177,24 @@ public class PhotoViewer implements ZoomPanView.Listener{
toolbarWrap=uiOverlay.findViewById(R.id.toolbar_wrap);
toolbar=uiOverlay.findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(v->onStartSwipeToDismissTransition(0));
toolbar.getMenu().add(R.string.download).setIcon(R.drawable.ic_fluent_arrow_download_24_regular).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
toolbar.setOnMenuItemClickListener(item->{
saveCurrentFile();
return true;
});
imageDescriptionButton = toolbar.getMenu()
.add(R.string.image_description)
.setIcon(R.drawable.ic_fluent_image_alt_text_24_regular)
.setVisible(attachments.get(pager.getCurrentItem()).description != null
&& !attachments.get(pager.getCurrentItem()).description.isEmpty())
.setOnMenuItemClickListener(item -> {
new ImageDescriptionSheet(activity,attachments.get(pager.getCurrentItem())).show();
return true;
});
imageDescriptionButton.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
toolbar.getMenu()
.add(R.string.download)
.setIcon(R.drawable.ic_fluent_arrow_download_24_regular)
.setOnMenuItemClickListener(item -> {
saveCurrentFile();
return true;
})
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
uiOverlay.setAlpha(0f);
videoControls=uiOverlay.findViewById(R.id.video_player_controls);
videoSeekBar=uiOverlay.findViewById(R.id.seekbar);
@@ -374,6 +390,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
private void onPageChanged(int index){
currentIndex=index;
Attachment att=attachments.get(index);
imageDescriptionButton.setVisible(att.description != null && !att.description.isEmpty());
toolbar.invalidate();
V.setVisibilityAnimated(videoControls, att.type==Attachment.Type.VIDEO ? View.VISIBLE : View.GONE);
if(att.type==Attachment.Type.VIDEO){
videoSeekBar.setSecondaryProgress(0);

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

@@ -36,6 +36,7 @@ public class DiscoverInfoBannerHelper{
case TRENDING_HASHTAGS -> R.string.trending_hashtags_info_banner;
case TRENDING_LINKS -> R.string.trending_links_info_banner;
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
case FEDERATED_TIMELINE -> R.string.federated_timeline_info_banner;
});
}
}
@@ -59,6 +60,7 @@ public class DiscoverInfoBannerHelper{
TRENDING_HASHTAGS,
TRENDING_LINKS,
LOCAL_TIMELINE,
FEDERATED_TIMELINE,
// ACCOUNTS
}
}

View File

@@ -11,7 +11,6 @@ import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
@@ -20,6 +19,7 @@ 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;
@@ -42,17 +42,26 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
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.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;
@@ -63,6 +72,8 @@ 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;
@@ -71,10 +82,15 @@ 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;
@@ -296,13 +312,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);
}
@@ -400,7 +425,94 @@ public class UiUtils{
});
}
public static void confirmPinPost(Activity activity, String accountID, Status status, boolean pinned, Consumer<Status> resultCallback){
showConfirmationAlert(activity,
pinned ? R.string.confirm_pin_post_title : R.string.confirm_unpin_post_title,
pinned ? R.string.confirm_pin_post : R.string.confirm_unpin_post,
pinned ? R.string.pin_post : R.string.unpin_post,
()->{
new SetStatusPinned(status.id, pinned)
.setCallback(new Callback<>() {
@Override
public void onSuccess(Status result) {
resultCallback.accept(result);
E.post(new StatusCountersUpdatedEvent(result));
if (!result.pinned)
E.post(new StatusUnpinnedEvent(status.id, accountID));
}
@Override
public void onError(ErrorResponse error) {
error.showToast(activity);
}
})
.wrapProgress(activity, pinned ? R.string.pinning : R.string.unpinning, false)
.exec(accountID);
}
);
}
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);
@@ -419,6 +531,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});
@@ -435,6 +549,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);
@@ -442,7 +575,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){
@@ -548,10 +681,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

@@ -20,9 +20,11 @@ public abstract class GithubSelfUpdater{
}
public static boolean needSelfUpdating(){
return BuildConfig.BUILD_TYPE.equals("githubRelease");
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,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/bookmark_selected" android:state_selected="true"/>
<item android:color="?android:textColorSecondary"/>
</selector>

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="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M6.19 21.855c-0.495 0.357-1.187 0.002-1.187-0.61V6.25C5.003 4.455 6.458 3 8.253 3h7.498c1.795 0 3.25 1.455 3.25 3.25v14.996c0 0.611-0.692 0.966-1.188 0.609l-5.81-4.181-5.812 4.18z" 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="M6.19 21.855c-0.495 0.357-1.187 0.002-1.187-0.61V6.25C5.003 4.455 6.458 3 8.253 3h7.498c1.795 0 3.25 1.455 3.25 3.25v14.996c0 0.611-0.692 0.966-1.188 0.609l-5.81-4.181-5.812 4.18zM17.502 6.25c0-0.966-0.783-1.75-1.75-1.75H8.253c-0.967 0-1.75 0.784-1.75 1.75v13.532l5.061-3.641c0.262-0.188 0.614-0.188 0.876 0l5.061 3.641V6.25z" 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_bookmark_24_filled" android:state_activated="true"/>
<item android:drawable="@drawable/ic_fluent_bookmark_24_filled" android:state_checked="true"/>
<item android:drawable="@drawable/ic_fluent_bookmark_24_filled" android:state_selected="true"/>
<item android:drawable="@drawable/ic_fluent_bookmark_24_regular"/>
</selector>

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 6.748c0-1.243 1.007-2.25 2.25-2.25h9c1.243 0 2.25 1.007 2.25 2.25V21.25c0 0.268-0.143 0.517-0.376 0.65-0.233 0.134-0.52 0.133-0.751-0.002l-5.623-3.28-5.622 3.28c-0.232 0.135-0.519 0.136-0.752 0.002C4.144 21.767 4 21.52 4 21.25V6.748zM15.25 2C17.873 2 20 4.127 20 6.75v11.873c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75V6.751c0-1.796-1.455-3.25-3.25-3.25H6.637S6.75 2.942 7.434 2.42C8 2 8.602 2 8.602 2h6.648z" 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="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M17.181 2.926c-1.152-1.212-3.076-1.236-4.259-0.054l-9.375 9.375c-0.327 0.328-0.555 0.742-0.655 1.195l-0.878 3.95c-0.037 0.167 0.014 0.34 0.134 0.462 0.121 0.12 0.296 0.171 0.462 0.134l3.927-0.873c0.467-0.104 0.895-0.339 1.234-0.678l9.358-9.358c1.141-1.14 1.164-2.983 0.052-4.153zM13.63 3.58c0.785-0.785 2.063-0.77 2.828 0.035 0.738 0.777 0.722 2-0.035 2.757L15.75 7.043l-2.793-2.792 0.671-0.671zm-1.378 1.378l2.793 2.793-7.98 7.98c-0.204 0.204-0.462 0.345-0.744 0.408L3.16 16.84l0.708-3.182c0.059-0.267 0.193-0.512 0.387-0.705l7.996-7.996z" 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="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M1 3c0-1.105 0.895-2 2-2h7c1.105 0 2 0.895 2 2v6c0 1.105-0.895 2-2 2H3c-1.105 0-2-0.895-2-2V3zm2.5 1C3.224 4 3 4.224 3 4.5S3.224 5 3.5 5h6C9.776 5 10 4.776 10 4.5S9.776 4 9.5 4h-6zm0 3C3.224 7 3 7.224 3 7.5S3.224 8 3.5 8h6C9.776 8 10 7.776 10 7.5S9.776 7 9.5 7h-6zM4 12h1.5v6.75c0 0.208 0.036 0.408 0.103 0.594l5.823-5.701c0.833-0.816 2.142-0.854 3.02-0.116l0.128 0.116 5.822 5.702c0.067-0.186 0.104-0.386 0.104-0.595V7.25c0-0.966-0.784-1.75-1.75-1.75H13V4h5.75C20.545 4 22 5.455 22 7.25v11.5c0 1.795-1.455 3.25-3.25 3.25H7.25C5.455 22 4 20.545 4 18.75V12zm15.33 8.401l-5.805-5.686c-0.265-0.26-0.675-0.283-0.966-0.071l-0.084 0.07-5.807 5.687C6.85 20.465 7.046 20.5 7.25 20.5h11.5c0.203 0 0.399-0.035 0.58-0.099zM16.253 7.5c1.244 0 2.252 1.008 2.252 2.252 0 1.244-1.008 2.252-2.252 2.252-1.244 0-2.252-1.008-2.252-2.252C14 8.508 15.008 7.5 16.252 7.5zm0 1.5C15.837 9 15.5 9.337 15.5 9.752s0.337 0.752 0.752 0.752c0.416 0 0.752-0.336 0.752-0.752C17.004 9.337 16.667 9 16.252 9z" 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,107 @@
<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"/>
<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: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_arrow_repeat_all_20_regular"
android:drawablePadding="8dp"
android:drawableTint="?android:textColorSecondary"
tools:text="4 reblogs"/>
</RelativeLayout>
<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"
<Button
android:id="@+id/favorites"
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_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: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_star_20_regular"
android:drawablePadding="8dp"
android:drawableTint="?android:textColorSecondary"
tools:text="12 favorites"/>
</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"
<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/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: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_edit_20_regular"
android:drawablePadding="8dp"
android:drawableTint="?android:textColorSecondary"
tools:text="edited"/>
</RelativeLayout>
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
<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

@@ -73,6 +73,28 @@
tools:text="123"/>
</FrameLayout>
<Space
android:layout_width="0px"
android:layout_height="1px"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/bookmark_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="56dp">
<TextView
android:id="@+id/bookmark"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center"
android:drawableStart="@drawable/ic_fluent_bookmark_24_selector"
android:drawablePadding="8dp"
android:drawableTint="@color/bookmark_icon"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_large" />
</FrameLayout>
<Space
android:layout_width="0px"
android:layout_height="1px"

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

@@ -9,20 +9,21 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<org.joinmastodon.android.ui.views.ComposeMediaLayout
android:layout_width="wrap_content"
<org.joinmastodon.android.ui.views.MaxWidthFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
android:layout_gravity="center"
android:maxWidth="400dp">
<ImageView
android:id="@+id/photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:importantForAccessibility="no"
tools:src="#0f0"/>
</org.joinmastodon.android.ui.views.ComposeMediaLayout>
</org.joinmastodon.android.ui.views.MaxWidthFrameLayout>
<TextView
android:id="@+id/title"

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

@@ -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

@@ -3,6 +3,9 @@
<item android:id="@+id/vis_public"
android:icon="@drawable/ic_fluent_earth_24_regular"
android:title="@string/visibility_public"/>
<item android:id="@+id/vis_unlisted"
android:icon="@drawable/ic_fluent_people_community_24_regular"
android:title="@string/visibility_unlisted"/>
<item android:id="@+id/vis_followers"
android:icon="@drawable/ic_fluent_people_checkmark_24_regular"
android:title="@string/visibility_followers_only"/>

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

@@ -2,6 +2,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/edit" android:title="@string/edit"/>
<item android:id="@+id/delete" android:title="@string/delete"/>
<item android:id="@+id/delete_and_redraft" android:title="@string/delete_and_redraft"/>
<item android:id="@+id/pin" android:title="@string/pin_post"/>
<item android:id="@+id/unpin" android:title="@string/unpin_post"/>
<item android:id="@+id/mute" android:title="@string/mute_user"/>
<item android:id="@+id/block" android:title="@string/block_user"/>
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>

View File

@@ -7,4 +7,10 @@
<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/bookmarks"
android:showAsAction="always"
android:visible="false"
android:icon="@drawable/ic_fluent_bookmark_multiple_24_filled"
android:title="@string/bookmarks"/>
</menu>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 B

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 952 B

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -41,6 +41,7 @@
</plurals>
<string name="posts">Beiträge</string>
<string name="posts_and_replies">Beiträge &amp; Antworten</string>
<string name="pinned_posts">Angeheftet</string>
<string name="media">Medien</string>
<string name="profile_about">Über</string>
<string name="button_follow">Folgen</string>
@@ -120,9 +121,20 @@
<string name="action_vote">Abstimmen</string>
<string name="tap_to_reveal">Zum Anzeigen tippen</string>
<string name="delete">Löschen</string>
<string name="delete_and_redraft">Löschen und neu erstellen</string>
<string name="confirm_delete_title">Beitrag löschen</string>
<string name="confirm_delete_and_redraft_title">Beitrag löschen und neu erstellen</string>
<string name="confirm_delete">Bist du dir sicher, dass du den Beitrag löschen möchtest?</string>
<string name="deleting">wird gelöscht …</string>
<string name="confirm_delete_and_redraft">Bist du dir sicher, dass du den Beitrag löschen und neu erstellen möchtest?</string>
<string name="deleting">Wird gelöscht…</string>
<string name="pin_post">An Profil anheften</string>
<string name="confirm_pin_post_title">Beitrag an Profil anheften</string>
<string name="confirm_pin_post">Möchtest du den Beitrag an dein Profil anheften?</string>
<string name="pinning">Wird angeheftet…</string>
<string name="unpin_post">Von Profil lösen</string>
<string name="confirm_unpin_post_title">Angehefteten Beitrag von Profil lösen</string>
<string name="confirm_unpin_post">Bist du dir sicher, dass du den angehefteten Beitrag von deinem Profil lösen möchtest?</string>
<string name="unpinning">Wird vom Profil gelöst…</string>
<string name="notification_channel_audio_player">Audiowiedergabe</string>
<string name="play">Abspielen</string>
<string name="pause">Pausieren</string>
@@ -201,15 +213,19 @@
<string name="open_email_app">Öffne meine E-Mail-App</string>
<string name="resent_email">Bestätigungs-E-Mail gesendet</string>
<string name="compose_hint">Was gibt\'s Neues?</string>
<string name="content_warning">Inhaltwarnung</string>
<string name="add_image_description">Bildbeschreibung hinzufügen </string>
<string name="content_warning">Inhaltswarnung</string>
<string name="add_image_description">Bildbeschreibung hinzufügen…</string>
<string name="retry_upload">Hochladen erneut versuchen</string>
<string name="image_description">Bildbeschreibung</string>
<string name="image_upload_failed">Fehler beim Hochladen des Bildes</string>
<string name="video_upload_failed">Fehler beim Hochladen des Videos</string>
<string name="edit_image">Bild bearbeiten</string>
<string name="save">Speichern</string>
<string name="add_alt_text">Bildbeschreibung hinzufügen</string>
<string name="alt_text_subtitle">Die Bildbeschreibung („Alt-Text“) ist eine wichtige Unterstützung für blinde und sehbehinderte Menschen. Beschränke dich bei der Formulierung auf das nötigste, interpretiere nicht und beschreibe nur, was zu sehen ist, damit der Kontext verständlich ist und alle Menschen daran teilhaben können.</string>
<string name="alt_text_hint">z. B. „Eine Giraffe auf einem Dreirad, während sie eine Banane isst.“</string>
<string name="visibility_public">Öffentlich</string>
<string name="visibility_unlisted">Nicht gelistet</string>
<string name="visibility_followers_only">Nur Follower</string>
<string name="visibility_private">Nur erwähnte Profile</string>
<string name="search_all">Alle</string>
@@ -236,6 +252,9 @@
<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_notifications">Benachrichtigungen</string>
@@ -255,7 +274,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>
@@ -272,6 +291,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>
@@ -284,6 +304,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>
@@ -299,10 +321,12 @@
<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="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>
@@ -371,10 +395,13 @@
<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>
</resources>

Some files were not shown because too many files have changed in this diff Show More