Compare commits

..

189 Commits

Author SHA1 Message Date
sk
095f234bd5 bump version 2022-11-06 11:16:31 +01:00
sk
f10c0e06db Merge branch 'fork' of github.com:sk22/mastodon-android-fork into fork 2022-11-06 11:13:42 +01:00
sk
ebbd5d1fa3 Merge branch 'upstream' into fork 2022-11-06 11:13:26 +01:00
Grishka
c8abf26040 Fix #301 2022-11-06 05:43:17 +03:00
Grishka
bc733af147 Actually no, this makes more sense 2022-11-06 05:39:22 +03:00
Grishka
77a2fd2a60 Fix #298 2022-11-06 05:36:43 +03:00
Samuel Kaiser
5cfc5eb08a Update README.md 2022-11-05 11:27:02 +01:00
Samuel Kaiser
b832d2df26 Update README.md 2022-11-04 18:02:33 +01:00
Samuel Kaiser
db34dc40ba Merge branch 'mastodon:master' into fork 2022-11-04 14:32:13 +01:00
Gregory K
bfa48c2d3e Merge pull request #289 from sk22/fix-editing-cw
Fix spoiler not being published when editing
2022-11-04 07:02:40 +03:00
Gregory K
b5e229a84d Merge pull request #288 from sk22/fix-editing-visibility
Fix wrong visibility displayed when editing
2022-11-04 07:02:19 +03:00
sk
ef207f885b increase update check interval 2022-11-04 03:37:32 +01:00
sk
c4eee28335 Merge branch 'feature/check-for-update-button' into fork 2022-11-04 03:34:06 +01:00
sk
6fe466779e add toash message 2022-11-04 03:33:41 +01:00
sk
e71db1b883 custom app name in strings 2022-11-04 03:11:10 +01:00
sk
b6efafe99d Merge branch 'feature/check-for-update-button' into fork 2022-11-04 03:09:39 +01:00
sk
688d466f8e implement manual update check settings item 2022-11-04 03:09:19 +01:00
Samuel Kaiser
3c5797932e add download badge 2022-11-03 17:42:31 +01:00
sk
48a5e262ce bump version 2022-11-03 17:18:57 +01:00
sk
63b0365208 remove reverted default visibility 2022-11-03 17:18:42 +01:00
sk
972c05d60b Revert "set unlisted as default visibility"
This reverts commit d34653750e.
2022-11-03 17:13:10 +01:00
sk
acb5778e0b bump version, again 2022-11-03 16:36:31 +01:00
sk
7694c50358 Merge branch 'fix-editing-cw' into fork 2022-11-03 16:35:31 +01:00
sk
7ffa368d10 fix spoiler not being published when editing 2022-11-03 16:33:12 +01:00
Y32Gcnte8z
53f8f41d88 remain visibility when editing 2022-11-03 16:15:17 +01:00
sk
93d57d847e fix newly published posts appearing twice
see https://github.com/mastodon/mastodon-android/issues/283
2022-11-03 16:14:12 +01:00
sk
83a4f5eec2 re-add missing onStatusUpdate event listener 2022-11-03 16:11:44 +01:00
Samuel Kaiser
836c2dba8d Merge branch 'mastodon:master' into fork 2022-11-02 22:32:57 +01:00
Samuel Kaiser
13ae8d2ebe Update README.md 2022-11-02 22:30:16 +01:00
Gregory K
98dafb4e49 Merge pull request #280 from sk22/fix-notifications-crash
Check whether title status item is null to fix null pointer exception
2022-11-03 00:27:07 +03:00
sk
85b8bae42e bump version 2022-11-02 22:15:26 +01:00
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
db4dd436b7 more concise null check 2022-11-02 21:36:14 +01:00
sk
6906ac6c8f remove unnecessary import 2022-11-02 21:34:05 +01:00
sk
bc0b56a90e avoid null in notification display items
fixes bug where type = status, but status = null,
causing buildDisplayItems to return a null title status item and an account card
2022-11-02 21:31:44 +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
Grishka
50c844aa25 It was the wrong icon this whole time 2022-11-02 11:06:00 +03:00
Grishka
85b232330b Merge branch 'l10n_master' 2022-11-02 11:02:18 +03:00
Grishka
b4c305d094 Add privacy policy step to the signup flow 2022-11-02 11:01:52 +03:00
Grishka
be2ee3a029 Fix #277 2022-11-02 09:07:48 +03: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
Eugen Rochko
118c5d4b44 New translations strings.xml (French) 2022-11-01 21:31:10 +01:00
sk
a4cb05080a Merge branch 'upstream' into fork 2022-11-01 21:26:44 +01:00
Eugen Rochko
699ececc42 New translations strings.xml (French) 2022-11-01 20:17:30 +01:00
Gregory K
4f8f698911 Merge pull request #275 from davidmhewitt/fix-duplicate-emoji-crash
Fix crash on duplicate custom emojis
2022-11-01 21:51:24 +03:00
Eugen Rochko
30083837a5 New translations full_description.txt (Vietnamese) 2022-11-01 18:21:16 +01:00
Eugen Rochko
ea7aa6c52f New translations strings.xml (Vietnamese) 2022-11-01 18:21:15 +01:00
David Hewitt
244f2ed911 Fix crash on duplicate custom emojis
Fixes #274

Add a merge function to `Collectors.toMap` to discard any duplicate custom emojis that may be returned if a user uses the same custom emoji in both their name and profile.
2022-11-01 14:47:25 +00:00
Eugen Rochko
99a71c67e4 New translations full_description.txt (German) 2022-11-01 15:23:49 +01:00
Eugen Rochko
b3c0550a86 New translations strings.xml (German) 2022-11-01 15:23:47 +01:00
Eugen Rochko
39a0c6d08a New translations strings.xml (Vietnamese) 2022-11-01 11:03:52 +01:00
Gregory K
0ee494bcfc Merge pull request #273 from davidmhewitt/load-post-visibility-preference
Load post privacy preference
2022-11-01 00:30:01 +03:00
Eugen Rochko
4233c743e2 New translations strings.xml (Italian) 2022-10-31 22:06:56 +01:00
David Hewitt
eea00b0d53 Load post privacy preference
This queries the user's post visibility preference when opening the composer, and sets it on the composer.

In the case of composing a reply, the user's preference is only respected if it is "more private" than the privacy of the post being replied to, as this appears to be the behaviour in the web interface (and is what I'd expect)
2022-10-31 20:51:28 +00:00
Eugen Rochko
7cef4d282c New translations strings.xml (German) 2022-10-31 19:11:31 +01:00
Eugen Rochko
df6e085c97 New translations strings.xml (German) 2022-10-31 18:14:37 +01:00
Eugen Rochko
70cf48355e New translations strings.xml (German) 2022-10-31 10:54:54 +01:00
Eugen Rochko
da06c798d7 New translations strings.xml (German) 2022-10-31 09:23:34 +01:00
Eugen Rochko
9055c094e8 New translations strings.xml (Kabyle) 2022-10-31 07:46:18 +01:00
Eugen Rochko
5ab3ae3d59 New translations strings.xml (Chinese Traditional) 2022-10-31 07:46:17 +01:00
Eugen Rochko
92ebb2f339 New translations strings.xml (German) 2022-10-31 07:46:16 +01:00
Eugen Rochko
d8c034dfa3 New translations strings.xml (Arabic) 2022-10-31 07:46:15 +01:00
Eugen Rochko
247516849e New translations strings.xml (French) 2022-10-31 07:46:14 +01:00
Eugen Rochko
aad9996b6f New translations strings.xml (Spanish) 2022-10-31 07:46:13 +01:00
Eugen Rochko
76c1ad07a3 New translations strings.xml (Catalan) 2022-10-31 07:46:12 +01:00
Eugen Rochko
49599f48bb New translations strings.xml (Czech) 2022-10-31 07:46:11 +01:00
Eugen Rochko
158d128316 New translations strings.xml (Greek) 2022-10-31 07:46:09 +01:00
Eugen Rochko
00897e7388 New translations strings.xml (Basque) 2022-10-31 07:46:08 +01:00
Eugen Rochko
1867f294c8 New translations strings.xml (Finnish) 2022-10-31 07:46:07 +01:00
Eugen Rochko
eaa189d6b0 New translations strings.xml (Hebrew) 2022-10-31 07:46:06 +01:00
Eugen Rochko
87d11dcb0a New translations strings.xml (Armenian) 2022-10-31 07:46:05 +01:00
Eugen Rochko
ba99394890 New translations strings.xml (Italian) 2022-10-31 07:46:04 +01:00
Eugen Rochko
6b8236021e New translations strings.xml (Polish) 2022-10-31 07:46:03 +01:00
Eugen Rochko
68b12a579c New translations strings.xml (Japanese) 2022-10-31 07:46:01 +01:00
Eugen Rochko
3f0ae887b8 New translations strings.xml (Portuguese) 2022-10-31 07:46:00 +01:00
Eugen Rochko
187f9a2246 New translations strings.xml (Russian) 2022-10-31 07:45:59 +01:00
Eugen Rochko
e8566bc24b New translations strings.xml (Swedish) 2022-10-31 07:45:58 +01:00
Eugen Rochko
e375fc7d4d New translations strings.xml (Turkish) 2022-10-31 07:45:57 +01:00
Eugen Rochko
89157efce8 New translations strings.xml (Ukrainian) 2022-10-31 07:45:55 +01:00
Eugen Rochko
5e95291016 New translations strings.xml (Chinese Simplified) 2022-10-31 07:45:54 +01:00
Eugen Rochko
cbd6e668dc New translations strings.xml (Vietnamese) 2022-10-31 07:45:53 +01:00
Eugen Rochko
898e62490b New translations strings.xml (Galician) 2022-10-31 07:45:52 +01:00
Eugen Rochko
326ab4edf1 New translations strings.xml (Thai) 2022-10-31 07:45:51 +01:00
Eugen Rochko
f25a965478 New translations strings.xml (Croatian) 2022-10-31 07:45:50 +01:00
Eugen Rochko
f3f3bea7b3 New translations strings.xml (Bosnian) 2022-10-31 07:45:48 +01:00
Eugen Rochko
583b0788c6 New translations strings.xml (Occitan) 2022-10-31 07:45:47 +01:00
Eugen Rochko
ca29cee586 New translations strings.xml (Korean) 2022-10-31 07:45:46 +01:00
Eugen Rochko
ec905448b0 New translations strings.xml (Portuguese, Brazilian) 2022-10-31 07:45:45 +01:00
Gregory K
e8fa82d0de Merge pull request #269 from davidmhewitt/fix-image-keyboard
Fix receiving images from keyboards
2022-10-31 09:26:29 +03:00
Grishka
e381de812c Add self-updater for github builds 2022-10-31 09:26:17 +03:00
Eugen Rochko
1a9752d53b New translations strings.xml (German) 2022-10-30 23:05:59 +01:00
Eugen Rochko
09cc5c5fd2 New translations strings.xml (German) 2022-10-30 22:00:42 +01:00
Eugen Rochko
4e8d510d38 New translations strings.xml (German) 2022-10-30 20:57:57 +01:00
Eugen Rochko
7f7f6bae80 New translations strings.xml (German) 2022-10-30 17:30:49 +01:00
Eugen Rochko
358ff12fba New translations strings.xml (German) 2022-10-30 16:07:19 +01:00
David Hewitt
8ff3ecb4d4 Fix receiving images from keyboards
The call to `super.onCreateInputConnection` was overwriting the mimes in the `outAttrs`, so we can call that first and then modify the mimes.

This fixes the ability to insert GIFs with the default GBoard GIF menu for me.
2022-10-30 14:56:47 +00:00
Eugen Rochko
47df35f0fd New translations full_description.txt (Chinese Traditional) 2022-10-30 01:23:19 +02:00
Eugen Rochko
e226851e03 New translations strings.xml (Arabic) 2022-10-29 14:15:24 +02:00
Eugen Rochko
7228907682 New translations strings.xml (Arabic) 2022-10-29 13:13:13 +02:00
Eugen Rochko
1dbabed716 New translations strings.xml (German) 2022-10-28 16:21:33 +02:00
Eugen Rochko
4224cd037d New translations strings.xml (German) 2022-10-28 15:11:58 +02:00
Eugen Rochko
9aa6d3f531 New translations strings.xml (Chinese Traditional) 2022-10-28 05:30:56 +02:00
Eugen Rochko
c9823ae9d0 New translations strings.xml (Chinese Traditional) 2022-10-28 04:30:39 +02:00
Grishka
1fa8a9e858 Always show domain for own account 2022-10-26 18:28:57 +03:00
Grishka
212e8893b9 Fix editing 2022-10-26 03:01:39 +03:00
Grishka
367057421b Declare and request notifications permission (should fix #262) 2022-10-26 02:46:23 +03:00
Eugen Rochko
c821480842 New translations strings.xml (Polish) 2022-10-24 00:07:29 +02:00
Eugen Rochko
3ef7c11e3b New translations strings.xml (Portuguese, Brazilian) 2022-10-17 16:18:05 +02:00
Eugen Rochko
1112756bc2 New translations strings.xml (Galician) 2022-10-15 17:57:46 +02:00
Eugen Rochko
878ed43135 New translations strings.xml (Thai) 2022-10-15 16:57:55 +02:00
Eugen Rochko
01b746d30a New translations strings.xml (Thai) 2022-10-15 13:55:51 +02:00
Eugen Rochko
a900351729 New translations strings.xml (Arabic) 2022-10-08 17:35:18 +02:00
Eugen Rochko
4114a6e5d8 New translations strings.xml (Turkish) 2022-10-07 18:41:40 +02:00
Eugen Rochko
04dd232e38 New translations strings.xml (Italian) 2022-10-05 21:07:13 +02:00
Eugen Rochko
bc26dfe856 New translations strings.xml (Chinese Simplified) 2022-10-05 07:12:25 +02:00
Eugen Rochko
0322f845af New translations strings.xml (Chinese Simplified) 2022-10-05 06:12:27 +02:00
Eugen Rochko
7daa2d63e6 New translations strings.xml (Vietnamese) 2022-10-04 18:08:26 +02:00
Eugen Rochko
3a8a41f568 New translations strings.xml (Kabyle) 2022-10-04 06:41:10 +02:00
Eugen Rochko
65cd7d076b New translations strings.xml (French) 2022-10-04 06:41:09 +02:00
Eugen Rochko
a1f71091fe New translations strings.xml (Spanish) 2022-10-04 06:41:08 +02:00
Eugen Rochko
131834af86 New translations strings.xml (Arabic) 2022-10-04 06:41:07 +02:00
Eugen Rochko
c0789cbdb9 New translations strings.xml (Catalan) 2022-10-04 06:41:06 +02:00
Eugen Rochko
7d12d866ab New translations strings.xml (Czech) 2022-10-04 06:41:05 +02:00
Eugen Rochko
7c2589c35b New translations strings.xml (German) 2022-10-04 06:41:04 +02:00
Eugen Rochko
fd2031ccf5 New translations strings.xml (Basque) 2022-10-04 06:41:02 +02:00
Eugen Rochko
6a165ec9f4 New translations strings.xml (Italian) 2022-10-04 06:40:59 +02:00
Eugen Rochko
fd8dad487a New translations strings.xml (Japanese) 2022-10-04 06:40:58 +02:00
Eugen Rochko
e9443b841a New translations strings.xml (Thai) 2022-10-04 06:40:57 +02:00
Eugen Rochko
89ec6acac9 New translations strings.xml (Korean) 2022-10-04 06:40:56 +02:00
Eugen Rochko
9b389b346f New translations strings.xml (Portuguese) 2022-10-04 06:40:55 +02:00
Eugen Rochko
4a522bffc0 New translations strings.xml (Russian) 2022-10-04 06:40:54 +02:00
Eugen Rochko
9b62f8b7a2 New translations strings.xml (Turkish) 2022-10-04 06:40:52 +02:00
Eugen Rochko
96342d67e3 New translations strings.xml (Chinese Traditional) 2022-10-04 06:40:50 +02:00
Eugen Rochko
95fc2bef9e New translations strings.xml (Vietnamese) 2022-10-04 06:40:49 +02:00
Eugen Rochko
4f36fbe3b9 New translations strings.xml (Galician) 2022-10-04 06:40:48 +02:00
Eugen Rochko
a501d8b995 New translations strings.xml (Portuguese, Brazilian) 2022-10-04 06:40:47 +02:00
Eugen Rochko
74691650b3 New translations strings.xml (Croatian) 2022-10-04 06:40:46 +02:00
Eugen Rochko
9325781cbc New translations strings.xml (Bosnian) 2022-10-04 06:40:45 +02:00
Eugen Rochko
b383dd6419 New translations strings.xml (Polish) 2022-10-04 06:40:43 +02:00
Eugen Rochko
fe98ecf0cc New translations strings.xml (Chinese Simplified) 2022-10-04 06:40:42 +02:00
Grishka
01970ab69b Compose media attachment redesign 2022-10-04 07:35:31 +03:00
Grishka
3aa252f681 Fix editing 2022-10-01 01:12:31 +03:00
Grishka
18633291e6 Add monochrome icon 2022-09-29 01:23:00 +03:00
Eugen Rochko
b79619b6e4 New translations strings.xml (Chinese Simplified) 2022-09-24 19:11:57 +02:00
Eugen Rochko
dbb03ee688 New translations strings.xml (Thai) 2022-09-23 21:06:49 +02:00
Eugen Rochko
00894b41d2 New translations strings.xml (Thai) 2022-09-23 20:09:36 +02:00
Eugen Rochko
8868ace90d New translations full_description.txt (German) 2022-09-19 19:52:12 +02:00
Eugen Rochko
a7586eeba8 New translations strings.xml (German) 2022-09-19 19:52:10 +02:00
Eugen Rochko
19b89c606a New translations strings.xml (German) 2022-09-19 18:43:32 +02:00
Eugen Rochko
b6c1e7d11e New translations strings.xml (Italian) 2022-09-18 02:43:45 +02:00
Eugen Rochko
ca90c89b2a New translations strings.xml (Italian) 2022-09-18 01:24:36 +02:00
Eugen Rochko
170a758f5f New translations strings.xml (Polish) 2022-09-16 12:51:10 +02:00
Eugen Rochko
43dcd6e7f4 New translations strings.xml (Polish) 2022-09-16 11:20:42 +02:00
Eugen Rochko
063542b2f5 New translations strings.xml (Spanish) 2022-09-16 09:46:29 +02:00
Eugen Rochko
70e4ae4fb4 New translations short_description.txt (Czech) 2022-09-15 21:09:48 +02:00
Eugen Rochko
8f1a4c60df New translations full_description.txt (Czech) 2022-09-15 21:09:47 +02:00
Eugen Rochko
07212dba96 New translations strings.xml (Czech) 2022-09-15 21:09:46 +02:00
Eugen Rochko
fb95caadbe New translations full_description.txt (Czech) 2022-09-15 20:01:47 +02:00
Eugen Rochko
51e86b686d New translations strings.xml (Czech) 2022-09-15 20:01:46 +02:00
Eugen Rochko
6ec335087d New translations strings.xml (Chinese Simplified) 2022-09-15 17:03:34 +02: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
Eugen Rochko
b756fe2cdb New translations strings.xml (Italian) 2022-09-12 22:55:33 +02:00
Eugen Rochko
3732a4c844 New translations full_description.txt (German) 2022-09-10 20:18:22 +02:00
Eugen Rochko
5bd59bd999 New translations strings.xml (German) 2022-09-10 20:18:21 +02:00
Eugen Rochko
90ba5551d2 New translations strings.xml (German) 2022-09-10 18:59:59 +02:00
Eugen Rochko
484b6891fd New translations strings.xml (Chinese Simplified) 2022-09-10 16:02:25 +02:00
Eugen Rochko
0435d5f0c7 New translations strings.xml (Spanish) 2022-09-10 12:46:24 +02:00
Eugen Rochko
864c8de8de New translations strings.xml (Spanish) 2022-09-10 11:45:40 +02:00
Eugen Rochko
8bcea5bfb8 New translations strings.xml (Spanish) 2022-09-10 10:49:12 +02:00
Eugen Rochko
beded04579 New translations strings.xml (Vietnamese) 2022-09-09 19:55:30 +02:00
Eugen Rochko
5dcd41170c New translations strings.xml (Vietnamese) 2022-09-09 16:55:37 +02:00
Eugen Rochko
3bb921a859 New translations strings.xml (Chinese Traditional) 2022-09-03 08:09:40 +02:00
Eugen Rochko
38edbde645 New translations strings.xml (Chinese Traditional) 2022-09-03 07:11:51 +02:00
Eugen Rochko
bcac7401ee New translations strings.xml (Chinese Simplified) 2022-08-28 23:21:49 +02:00
Eugen Rochko
c15f6519c0 New translations strings.xml (Vietnamese) 2022-08-01 08:15:42 +02:00
Eugen Rochko
2809b27be0 New translations strings.xml (Turkish) 2022-07-31 20:49:45 +02:00
Eugen Rochko
1b6e096bf9 New translations strings.xml (Kabyle) 2022-07-30 08:41:42 +02:00
Eugen Rochko
85db37f6a7 New translations full_description.txt (Czech) 2022-07-24 19:10:28 +02:00
Eugen Rochko
0cee490466 New translations strings.xml (Czech) 2022-07-24 19:10:27 +02:00
Eugen Rochko
a148c92da2 New translations full_description.txt (Czech) 2022-07-24 17:58:57 +02:00
Eugen Rochko
6272797834 New translations strings.xml (Czech) 2022-07-20 17:22:53 +02:00
Eugen Rochko
c048134ef2 New translations strings.xml (Czech) 2022-07-20 15:30:06 +02:00
Eugen Rochko
6f57cd9ffe New translations strings.xml (Czech) 2022-07-19 12:39:11 +02:00
Eugen Rochko
76815f8194 New translations strings.xml (Japanese) 2022-07-13 15:46:09 +02:00
102 changed files with 2821 additions and 393 deletions

View File

@@ -1,34 +1,38 @@
![Pink version of the Mastodon for Android launcher icon](mastodon/src/main/res/mipmap-xhdpi/ic_launcher_round.png)
# Mastodon for Android Fork
# Mastodos
> 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.
[![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
* [Enable "Unlisted" as a visibility option](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted)
([Pull request](https://github.com/mastodon/mastodon-android/pull/103)) and
[set as default](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted-as-default)
* [Add "Federation" tab and change Discover tab order](https://github.com/sk22/mastodon-android-fork/tree/feature/add-federated-timeline) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/8))
### Features
* [Add “Unlisted” as a post visibility option](https://github.com/sk22/mastodon-android-fork/tree/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/tree/feature/add-federated-timeline) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/8))
* [Add image description button and viewer](https://github.com/sk22/mastodon-android-fork/tree/feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
* [Implement pinning posts and displaying pinned posts](https://github.com/sk22/mastodon-android-fork/tree/feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
* [Display full image when adding image description](https://github.com/sk22/mastodon-android-fork/tree/feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
* [Always preserve content warnings when replying](https://github.com/sk22/mastodon-android-fork/tree/feature/always-preserve-cw) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/113))
* [Make back button return to the home tab before exiting the app](https://github.com/sk22/mastodon-android-fork/tree/feature/back-returns-home) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/118))
* [Implement a bookmark button and list](https://github.com/sk22/mastodon-android-fork/tree/feature/bookmarks) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/22))
### Behavior
* [Make back button return to the home tab before exiting the app](https://github.com/sk22/mastodon-android-fork/tree/feature/back-returns-home) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/118))
* [Always preserve content warnings when replying](https://github.com/sk22/mastodon-android-fork/tree/feature/always-preserve-cw) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/113))
* [Display full image when adding image description](https://github.com/sk22/mastodon-android-fork/tree/feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
* [Implement deleting and re-drafting](https://github.com/sk22/mastodon-android-fork/tree/feature/delete-redraft) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/21))
## Fork-specific changes
### Branding
* Custom app name
* App name “Mastodos”
* Pink primary color
* Custom icon: Modulate upstream icon using ImageMagick
```bash
mogrify -modulate 90,100,140 mastodon/src/main/res/mipmap-*/ic_launcher*.png
```
* Custom primary color: Hue of all `primary` colors in `colors.xml` is rotated, on basis of upstream Mastodon's [old branding](https://github.com/mastodon/mastodon-android/commit/74f03026cfcfcfd23237c38ff47d2b2a98a6f92a#diff-59134ec2a1cf3761f80b0ecccbbf8b9e433d9780d2f5c5d6ac3ac8cc254e808f)
by `109.8°` (equivalent of `161%`, done by hand using
[PineTools](https://pinetools.com/shift-hue-color))
## Building
As this app is using Java 17 features, you need JDK 17 or newer to build it. Other than that, everything is pretty standard. You can either import the project into Android Studio and build it from there, or run the following command in the project directory:

View File

@@ -1,16 +1,16 @@
Mastodon is the largest decentralized social network on the internet. Instead of a single website, its a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what youre into, you can meet passionate people posting about it on Mastodon!
Mastodon je největší decentralizovanou sociální sítí na internetu. Místo jednné webové stránky je to síť pro miliony uživatelů v nezávislých komunitách, kteří mohou všichni vzájemně a bezproblémově komunikovat. Bez ohledu na to, co vás baví, můžete se setkat s vášnivými lidmi, kteří o tom vysílají na Mastodon!
Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
Připojte se ke komunitě a vytvořte svůj profil. Najděte a sledujte fascinující lidi a přečtěte si jejich příspěvky v bezreklamní a chronologické časové linii. Vyjádřete se pomocí vlastních emojí, obrázků, GIFů, videí a zvuku v 500-znakových příspěvcích. Odpovězte na vlákna a reblogujte příspěvky od kohokoliv, abyste mohli sdílet skvělé věci. Najděte nové účty pro sledování a populární hashtagy pro rozšíření vaší sítě.
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
Mastodon je postaven se zaměřením na soukromí a bezpečnost. Rozhodněte, zda jsou vaše příspěvky sdíleny se svými sledujícími, jen s lidmi, které zmiňujete, nebo s celým světem. Upozornění na obsah vám umožní skrýt příspěvky obsahující citlivý nebo spouštěcí materiál, dokud se s nimi nezačnete zabývat. Každá komunita má vlastní pokyny a moderátory, aby udržela své členy v bezpečí, a robust blokování a hlášení nástrojů pomáhá předcházet zneužití.
More features:
Další funkce:
Dark Mode: Read posts in light, dark, or true black mode
Polls: Ask followers for their opinion and tally the votes
Explore: Trending hashtags and accounts are a tap away
Notifications: Get notified about new follows, replies, and reblogs
• Sharing: Post directly to Mastodon from any share sheet in any app
Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time
Tmavý režim: Čtěte příspěvky ve světlém, tmavém nebo zcela černém režimu
Ankety: Požádejte sledující o jejich názor a spojte se s jejich hlasováním
Průzkum: Trendové hashtagy a účty jsou pryč na jedno klepnutí
Upozornění: Dostávejte upozornění na nové sledování, odpovědi a reblogy
• Sdílení: Odesílání přímo do Mastodonu z libovolného seznamu sdílení v jakékoliv aplikaci
Roztomilost: Naším maskotem je roztomilý slon, kterého čas od času uvidíte
Mastodon is a registered nonprofit and development is supported directly by your donations. Theres no advertising, no monetization, and no venture capital, and we plan to keep it that way.
Mastodon je registrovaný neziskový projekt a vývojový program je podporován přímo vašimi dary. Neexistuje žádná reklama, žádná monetizace a žádný rizikový kapitál a my máme v plánu to udržet.

View File

@@ -1 +1 @@
Decentralized social network
Decentralizovaná sociální síť

View File

@@ -1,6 +1,6 @@
Mastodon ist das größte dezentralisierte soziale Netzwerk im Internet. Statt einer einzigen Website ist es ein Netzwerk von Millionen von Benutzer*innen in unabhängigen Gemeinschaften, die alle miteinander interagieren können. Egal was dich interessiert, auf Mastodon kannst du interessierte Leute treffen, die darüber schreiben!
Tritt einer Gemeinschaft bei und erstelle dein Profil. Finde und folge faszinierenden Leuten und lies ihre Beiträge in einer werbefreien, chronologischen Zeitachse. Drücke dich mit benutzerdefinierten Emojis, Bilderns, GIFs, Videos und Audio in 500-Zeichen-Beiträgen aus. Antworte auf Threads und teile Beiträge von anderen, um großartige Sachen zu verbreiten. Finde neue Accounts zum Folgen und angesagte Hashtags, um dein Netzwerk zu erweitern.
Tritt einer Gemeinschaft bei und erstelle dein Profil. Finde und folge faszinierenden Leuten, und lies ihre Beiträge in einer werbefreien, chronologischen Zeitachse. Drücke dich mit benutzerdefinierten Emojis, Bildern, GIFs, Videos und Audio in 500-Zeichen-Beiträgen aus. Antworte auf Threads und teile Beiträge von anderen, um großartige Sachen zu verbreiten. Finde neue Accounts zum Folgen und angesagte Hashtags, um dein Netzwerk zu erweitern.
Mastodon wurde mit einem Schwerpunkt auf Privatsphäre und Sicherheit gebaut. Entscheide, ob du deine Beiträge mit deinen Followern, nur mit den Menschen, die du erwähnst, oder mit der ganzen Welt teilen möchtest. Mit Inhaltswarnungen kannst du Beiträge mit sensiblem oder triggerndem Inhalt ausblenden, bis du bereit bist, dich damit auseinanderzusetzen. Jede Gemeinschaft hat ihre eigenen Regeln und Moderator*innen, um die Sicherheit ihrer Mitglieder zu gewährleisten, sowie robuste Sperr- und Meldewerkzeuge, um Missbrauch vorzubeugen.
@@ -8,9 +8,9 @@ Weitere Funktionen:
• Dunkler Modus: Beiträge im hellen, dunklen oder schwarzen Modus lesen
• Umfragen: Frage deine Follower nach ihrer Meinung und zähle die Stimmen
• Entdecken: Trending Hashtags und Accounts sind nur einen Fingertipp entfernt
• Entdecken: trendende Hashtags und Profile sind nur einen Fingertipp entfernt
• Benachrichtigungen: Erhalte Benachrichtigungen über neue Follower, Antworten und geteilte Beiträge
• Teilen: Veröffentliche auf Mastodon aus jeder beliebigen anderen App
• Niedlichkeit: Unser Maskottchen ist ein entzückender Elefant, und du wirst ihn von Zeit zu Zeit auftauchen sehen
Mastodon ist eine eingetragene gemeinnützige Organisation, und die Entwicklung wird direkt durch deine Spenden unterstützt. Es gibt keine Werbung, keine Monetisierung und kein Venture-Capital, und wir planen es so zu erhalten.
Mastodon ist eine eingetragene gemeinnützige Organisation, und die Entwicklung wird direkt durch deine Spenden unterstützt. Es gibt keine Werbung, keine Monetisierung und kein Venture-Capital, und wir planen, das auch so beizubehalten.

View File

@@ -1,6 +1,6 @@
Mastodon là mạng xã hội liên hợp lớn nhất trên internet. Thay vì một trang web duy nhất, nó là một mạng lưới hàng triệu người dùng trong các máy chủ độc lập, tất cả đều có thể tương tác với nhau một cách liền mạch. Bất kể bạn thích gì, bạn đều có thể gặp gỡ những người đăng tút về nó trên Mastodon!
Tham gia một máy chủ và tạo trang hồ sơ của bạn. Tìm, theo dõi những người thú vị và đọc tút của họ theo trình tự thời gian, không có quảng cáo. Thể hiện bản thân bằng emoji, hình ảnh, GIF, video và âm thanh trong tút tối đa 500 ký tự. Trả lời tút và đăng lại tút từ bất kỳ ai để chia sẻ những điều tuyệt vời. Tìm những người dùng mới để theo dõi và các hashtag xu hướng để mở rộng mạng lưới của bạn.
Tham gia một máy chủ và tạo trang hồ sơ của bạn. Tìm, theo dõi những người thú vị và đọc tút của họ theo trình tự thời gian, không có quảng cáo. Thể hiện bản thân bằng emoji, hình ảnh, GIF, video và âm thanh trong tút tối đa 500 ký tự. Trả lời tút và đăng lại tút từ bất kỳ ai để chia sẻ những điều tuyệt vời. Tìm những người dùng mới để theo dõi và các hashtag nổi bật để mở rộng mạng lưới của bạn.
Mastodon được xây dựng tập trung vào sự riêng tư và an toàn. Quyết định xem tút của bạn được chia sẻ với những người theo dõi, chỉ những người bạn nhắc đến hay cả thế giới. Nội dung ẩn cho phép bạn ẩn các tút chứa nội dung nhạy cảm hoặc chơi chữ cho đến khi bạn sẵn sàng tương tác với chúng. Mỗi máy chủ có các nguyên tắc riêng và kiểm duyệt viên riêng để giữ an toàn cho các thành viên, song song với các công cụ chặn và báo cáo mạnh mẽ giúp ngăn chặn hành vi bậy.
@@ -8,7 +8,7 @@ Tính năng khác:
• Chế độ Tối: Đọc tút ở chế độ sáng, tối hoặc đen
• Bình chọn: Hỏi cộng đồng về ý kiến của họ và đếm lượt bình chọn
• Khám phá: Xem hashtag xu hướng và tài khoản chỉ bằng một nhấn
• Khám phá: Xem hashtag nổi bật và tài khoản chỉ bằng một nhấn
• Thông báo: Nhận thông báo về người theo dõi, lượt trả lời và đăng lại mới
• Chia sẻ: Đăng trực tiếp lên Mastodon từ bất kỳ ứng dụng nào
• Đáng yêu: Linh vật của chúng tôi là một chú voi ma mút và bạn sẽ thấy anh ấy thỉnh thoảng xuất hiện

View File

@@ -1,16 +1,16 @@
Mastodon 是網際網路上最大的去中心化社交網路。 它是一個由能無縫互動的獨立社群中,數百萬使用者組成的網路,而非單一網站。 無論您對什麼事情感興趣,您都能在 Mastodon 上遇到充滿熱情的人們討論該話題。
加入社群並建立您的個人檔案。 尋找並追蹤迷人的夥伴,並在無廣告、按時間順序排列的時間軸上閱讀他們的文。 在 500 個字元的文中使用自訂表情符號、GIF、視訊與音訊來表達您自己。 回覆任何人的話題與轉發貼文以分享精彩內容。 尋找要追蹤的新帳號與熱門主題標籤來拓展您的網路。
加入社群並建立您的個人檔案。 尋找並跟隨迷人的朋友們,並在無廣告、按時間順序排列的時間軸上閱讀他們的文。 在 500 個字元的文中使用自訂表情符號、GIF、視訊與音訊來表達您自己。 回覆任何人的話題與轉發貼文以分享精彩內容。 尋找要跟隨的新帳號與熱門主題標籤來拓展您的網路。
Mastodon 以隱私與安全為要。 決定您的文要與您的追蹤者分享、只與您提及的人們分享,又或是與全世界分享。 內容警告可讓您隱藏包含敏感或可能觸發強烈情緒反應的文,直到您準備好與它們進行互動。 每個社群都有它們自己的指導方針與管理原來確保其成員安全,強大的封鎖與回報工具有助於防止濫用。
Mastodon 以隱私與安全為要。 決定您的文要與您的跟隨者分享、只與您提及的人們分享,又或是與全世界分享。 內容警告可讓您隱藏包含敏感或可能觸發強烈情緒反應的文,直到您準備好與它們進行互動。 每個社群都有它們自己的指導方針與管理原來確保其成員安全,強大的封鎖與回報工具有助於防止濫用。
更多功能:
• 深色模式:以淺色、深色或純黑色模式閱讀貼文
• 投票:詢問追蹤的意見並計票
• 投票:詢問跟隨者們的意見並計票
• 探索:僅需輕點一下,即可看到熱門主題標籤與帳號
• 通知:取得關於新追蹤、回覆與轉發的通知
• 分享:從任何應用程式中的分享表中直接發表文到 Mastodon 中
• 通知:取得關於新跟隨者們、回覆與轉發的通知
• 分享:從任何應用程式中的分享表中直接發表文到 Mastodon 中
• 可愛:我們的吉祥物是一隻可愛的大象,您會不時看到牠出現
Mastodon 是一家註冊的非營利組織,您的捐款會直接支援開發工作。 沒有廣告、沒有貨幣化、沒有風險投資,我們計畫維持這種狀態。

View File

@@ -4,13 +4,13 @@ plugins {
}
android {
compileSdk 31
compileSdk 33
defaultConfig {
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 31
versionCode 21
versionName '1.1.3+fork.21'
targetSdk 33
versionCode 29
versionName "1.1.4+fork.29"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -33,6 +33,9 @@ android {
initWith release
versionNameSuffix "-beta"
}
githubRelease{
initWith release
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
@@ -46,6 +49,12 @@ android {
appcenterPublicBeta{
setRoot "src/appcenter"
}
githubRelease{
setRoot "src/github"
}
debug {
setRoot "src/github"
}
}
lintOptions{
checkReleaseBuilds false

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.joinmastodon.android">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application>
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$InstallerStatusReceiver" android:exported="false"/>-->
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$AfterUpdateRestartReceiver" android:exported="true" android:enabled="false">-->
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>-->
<!-- </intent-filter>-->
<!-- </receiver>-->
<provider
android:authorities="${applicationId}.self_update_provider"
android:name=".updater.SelfUpdateContentProvider"
android:grantUriPermissions="true"
android:exported="false"/>
</application>
</manifest>

View File

@@ -0,0 +1,356 @@
package org.joinmastodon.android.updater;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageInstaller;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.E;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
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;
import androidx.annotation.Keep;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
@Keep
public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
private static final long CHECK_PERIOD=6*3600*1000L;
private static final String TAG="GithubSelfUpdater";
private UpdateState state=UpdateState.NO_UPDATE;
private UpdateInfo info;
private long downloadID;
private BroadcastReceiver downloadCompletionReceiver=new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent){
if(downloadID!=0 && intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)==downloadID){
MastodonApp.context.unregisterReceiver(this);
setState(UpdateState.DOWNLOADED);
}
}
};
public GithubSelfUpdaterImpl(){
SharedPreferences prefs=getPrefs();
int checkedByBuild=prefs.getInt("checkedByBuild", 0);
if(prefs.contains("version") && checkedByBuild==BuildConfig.VERSION_CODE){
info=new UpdateInfo();
info.version=prefs.getString("version", null);
info.size=prefs.getLong("apkSize", 0);
downloadID=prefs.getLong("downloadID", 0);
if(downloadID==0 || !getUpdateApkFile().exists()){
state=UpdateState.UPDATE_AVAILABLE;
}else{
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
state=dm.getUriForDownloadedFile(downloadID)==null ? UpdateState.DOWNLOADING : UpdateState.DOWNLOADED;
if(state==UpdateState.DOWNLOADING){
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
}
}else if(checkedByBuild!=BuildConfig.VERSION_CODE && checkedByBuild>0){
// We are in a new version, running for the first time after update. Gotta clean things up.
long id=getPrefs().getLong("downloadID", 0);
if(id!=0){
MastodonApp.context.getSystemService(DownloadManager.class).remove(id);
}
getUpdateApkFile().delete();
getPrefs().edit()
.remove("apkSize")
.remove("version")
.remove("apkURL")
.remove("checkedByBuild")
.remove("downloadID")
.apply();
}
}
private SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("githubUpdater", Context.MODE_PRIVATE);
}
@Override
public void maybeCheckForUpdates(){
if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
return;
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/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+)\\+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)),
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)),
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 || 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();
if("application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
long size=asset.get("size").getAsLong();
String url=asset.get("browser_download_url").getAsString();
UpdateInfo info=new UpdateInfo();
info.size=size;
info.version=version;
this.info=info;
getPrefs().edit()
.putLong("apkSize", size)
.putString("version", version)
.putString("apkURL", url)
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
.remove("downloadID")
.apply();
break;
}
}
}
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
}catch(Exception x){
Log.w(TAG, "actuallyCheckForUpdates", x);
}finally{
setState(info==null ? UpdateState.NO_UPDATE : UpdateState.UPDATE_AVAILABLE);
}
}
private void setState(UpdateState state){
this.state=state;
E.post(new SelfUpdateStateChangedEvent(state));
}
@Override
public UpdateState getState(){
return state;
}
@Override
public UpdateInfo getUpdateInfo(){
return info;
}
public File getUpdateApkFile(){
return new File(MastodonApp.context.getExternalCacheDir(), "update.apk");
}
@Override
public void downloadUpdate(){
if(state==UpdateState.DOWNLOADING)
throw new IllegalStateException();
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
downloadID=dm.enqueue(
new DownloadManager.Request(Uri.parse(getPrefs().getString("apkURL", null)))
.setDestinationUri(Uri.fromFile(getUpdateApkFile()))
);
getPrefs().edit().putLong("downloadID", downloadID).apply();
setState(UpdateState.DOWNLOADING);
}
@Override
public void installUpdate(Activity activity){
if(state!=UpdateState.DOWNLOADED)
throw new IllegalStateException();
Uri uri;
Intent intent=new Intent(Intent.ACTION_INSTALL_PACKAGE);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
uri=new Uri.Builder().scheme("content").authority(activity.getPackageName()+".self_update_provider").path("update.apk").build();
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else{
uri=Uri.fromFile(getUpdateApkFile());
}
intent.setDataAndType(uri, "application/vnd.android.package-archive");
activity.startActivity(intent);
// TODO figure out how to restart the app when updating via this new API
/*
PackageInstaller installer=activity.getPackageManager().getPackageInstaller();
try{
final int sid=installer.createSession(new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
installer.registerSessionCallback(new PackageInstaller.SessionCallback(){
@Override
public void onCreated(int i){
}
@Override
public void onBadgingChanged(int i){
}
@Override
public void onActiveChanged(int i, boolean b){
}
@Override
public void onProgressChanged(int id, float progress){
}
@Override
public void onFinished(int id, boolean success){
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}
});
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
PackageInstaller.Session session=installer.openSession(sid);
try(OutputStream out=session.openWrite("mastodon.apk", 0, info.size); InputStream in=new FileInputStream(getUpdateApkFile())){
byte[] buffer=new byte[16384];
int read;
while((read=in.read(buffer))>0){
out.write(buffer, 0, read);
}
}
// PendingIntent intent=PendingIntent.getBroadcast(activity, 1, new Intent(activity, InstallerStatusReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
PendingIntent intent=PendingIntent.getActivity(activity, 1, new Intent(activity, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(intent.getIntentSender());
}catch(IOException x){
Log.w(TAG, "installUpdate", x);
Toast.makeText(activity, x.getMessage(), Toast.LENGTH_SHORT).show();
}
*/
}
@Override
public float getDownloadProgress(){
if(state!=UpdateState.DOWNLOADING)
throw new IllegalStateException();
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
try(Cursor cursor=dm.query(new DownloadManager.Query().setFilterById(downloadID))){
if(cursor.moveToFirst()){
long loaded=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
long total=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
// Log.d(TAG, "getDownloadProgress: "+loaded+" of "+total);
return total>0 ? (float)loaded/total : 0f;
}
}
return 0;
}
@Override
public void cancelDownload(){
if(state!=UpdateState.DOWNLOADING)
throw new IllegalStateException();
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
dm.remove(downloadID);
downloadID=0;
getPrefs().edit().remove("downloadID").apply();
setState(UpdateState.UPDATE_AVAILABLE);
}
@Override
public void handleIntentFromInstaller(Intent intent, Activity activity){
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
activity.startActivity(confirmIntent);
}else if(status!=PackageInstaller.STATUS_SUCCESS){
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
Toast.makeText(activity, activity.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
}
}
/*public static class InstallerStatusReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent){
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
context.startActivity(confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}else if(status!=PackageInstaller.STATUS_SUCCESS){
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
Toast.makeText(context, context.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
}
}
}
public static class AfterUpdateRestartReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent){
if(Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())){
context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
Toast.makeText(context, R.string.update_installed, Toast.LENGTH_SHORT).show();
Intent restartIntent=new Intent(context, MainActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setPackage(context.getPackageName());
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.P){
context.startActivity(restartIntent);
}else{
// Bypass activity starting restrictions by starting it from a notification
NotificationManager nm=context.getSystemService(NotificationManager.class);
NotificationChannel chan=new NotificationChannel("selfUpdateRestart", context.getString(R.string.update_installed), NotificationManager.IMPORTANCE_HIGH);
nm.createNotificationChannel(chan);
Notification n=new Notification.Builder(context, "selfUpdateRestart")
.setContentTitle(context.getString(R.string.update_installed))
.setContentIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.setFullScreenIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE), true)
.setSmallIcon(R.drawable.ic_ntf_logo)
.build();
nm.notify(1, n);
}
}
}
}*/
}

View File

@@ -0,0 +1,62 @@
package org.joinmastodon.android.updater;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import java.io.FileNotFoundException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class SelfUpdateContentProvider extends ContentProvider{
@Override
public boolean onCreate(){
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder){
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri){
if(isCorrectUri(uri))
return "application/vnd.android.package-archive";
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values){
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs){
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs){
return 0;
}
@Nullable
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException{
if(isCorrectUri(uri)){
return ParcelFileDescriptor.open(((GithubSelfUpdaterImpl)GithubSelfUpdater.getInstance()).getUpdateApkFile(), ParcelFileDescriptor.MODE_READ_ONLY);
}
throw new FileNotFoundException();
}
private boolean isCorrectUri(Uri uri){
return "/update.apk".equals(uri.getPath());
}
}

View File

@@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>

View File

@@ -1,8 +1,12 @@
package org.joinmastodon.android;
import android.Manifest;
import android.app.Application;
import android.app.Fragment;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
@@ -17,6 +21,7 @@ import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.parceler.Parcels;
import java.lang.reflect.InvocationTargetException;
@@ -59,6 +64,8 @@ public class MainActivity extends FragmentStackActivity{
showFragmentForNotification(notification, session.getID());
}else if(intent.getBooleanExtra("compose", false)){
showCompose();
}else{
maybeRequestNotificationsPermission();
}
}
}
@@ -68,6 +75,8 @@ public class MainActivity extends FragmentStackActivity{
try{
Class.forName("org.joinmastodon.android.AppCenterWrapper").getMethod("init", Application.class).invoke(null, getApplication());
}catch(ClassNotFoundException|NoSuchMethodException|IllegalAccessException|InvocationTargetException ignore){}
}else if(GithubSelfUpdater.needSelfUpdating()){
GithubSelfUpdater.getInstance().maybeCheckForUpdates();
}
}
@@ -96,7 +105,9 @@ public class MainActivity extends FragmentStackActivity{
}
}else if(intent.getBooleanExtra("compose", false)){
showCompose();
}
}/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){
GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this);
}*/
}
private void showFragmentForNotification(Notification notification, String accountID){
@@ -131,4 +142,10 @@ public class MainActivity extends FragmentStackActivity{
compose.setArguments(composeArgs);
showFragment(compose);
}
private void maybeRequestNotificationsPermission(){
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)!=PackageManager.PERMISSION_GRANTED){
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
}
}
}

View File

@@ -102,7 +102,7 @@ public class CacheController{
.exec(accountID);
}catch(SQLiteException x){
Log.w(TAG, x);
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500)));
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500, x)));
}finally{
closeDelayed();
}
@@ -184,7 +184,7 @@ public class CacheController{
.exec(accountID);
}catch(SQLiteException x){
Log.w(TAG, x);
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500)));
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500, x)));
}finally{
closeDelayed();
}

View File

@@ -96,11 +96,11 @@ public class MastodonAPIController{
if(call.isCanceled())
return;
if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" failed: "+e);
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" failed", e);
synchronized(req){
req.okhttpCall=null;
}
req.onError(e.getLocalizedMessage(), 0);
req.onError(e.getLocalizedMessage(), 0, e);
}
@Override
@@ -133,7 +133,7 @@ public class MastodonAPIController{
}catch(JsonIOException|JsonSyntaxException x){
if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
req.onError(x.getLocalizedMessage(), response.code());
req.onError(x.getLocalizedMessage(), response.code(), x);
return;
}
@@ -142,7 +142,7 @@ public class MastodonAPIController{
}catch(IOException x){
if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error post-processing or validating response", x);
req.onError(x.getLocalizedMessage(), response.code());
req.onError(x.getLocalizedMessage(), response.code(), x);
return;
}
@@ -155,7 +155,7 @@ public class MastodonAPIController{
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error);
if(error.has("details")){
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code());
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code(), null);
HashMap<String, List<MastodonDetailedErrorResponse.FieldError>> details=new HashMap<>();
JsonObject errorDetails=error.getAsJsonObject("details");
for(String key:errorDetails.keySet()){
@@ -172,12 +172,12 @@ public class MastodonAPIController{
err.detailedErrors=details;
req.onError(err);
}else{
req.onError(error.get("error").getAsString(), response.code());
req.onError(error.get("error").getAsString(), response.code(), null);
}
}catch(JsonIOException|JsonSyntaxException x){
req.onError(response.code()+" "+response.message(), response.code());
req.onError(response.code()+" "+response.message(), response.code(), x);
}catch(Exception x){
req.onError("Error parsing an API error", response.code());
req.onError("Error parsing an API error", response.code(), x);
}
}
}catch(Exception x){
@@ -189,7 +189,7 @@ public class MastodonAPIController{
}catch(Exception x){
if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] error creating and sending http request", x);
req.onError(x.getLocalizedMessage(), 0);
req.onError(x.getLocalizedMessage(), 0, x);
}
}, 0);
}
@@ -197,4 +197,8 @@ public class MastodonAPIController{
public static void runInBackground(Runnable action){
thread.postRunnable(action, 0);
}
public static OkHttpClient getHttpClient(){
return httpClient;
}
}

View File

@@ -82,7 +82,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
account.getApiController().submitRequest(this);
}catch(Exception x){
Log.e(TAG, "exec: this shouldn't happen, but it still did", x);
invokeErrorCallback(new MastodonErrorResponse(x.getLocalizedMessage(), -1));
invokeErrorCallback(new MastodonErrorResponse(x.getLocalizedMessage(), -1, x));
}
return this;
}
@@ -194,8 +194,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
invokeErrorCallback(err);
}
void onError(String msg, int httpStatus){
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus));
void onError(String msg, int httpStatus, Throwable exception){
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus, exception));
}
void onSuccess(T resp){

View File

@@ -7,8 +7,8 @@ import java.util.Map;
public class MastodonDetailedErrorResponse extends MastodonErrorResponse{
public Map<String, List<FieldError>> detailedErrors;
public MastodonDetailedErrorResponse(String error, int httpStatus){
super(error, httpStatus);
public MastodonDetailedErrorResponse(String error, int httpStatus, Throwable exception){
super(error, httpStatus, exception);
}
public static class FieldError{

View File

@@ -12,10 +12,12 @@ import me.grishka.appkit.api.ErrorResponse;
public class MastodonErrorResponse extends ErrorResponse{
public final String error;
public final int httpStatus;
public final Throwable underlyingException;
public MastodonErrorResponse(String error, int httpStatus){
public MastodonErrorResponse(String error, int httpStatus, Throwable exception){
this.error=error;
this.httpStatus=httpStatus;
this.underlyingException=exception;
}
@Override

View File

@@ -0,0 +1,10 @@
package org.joinmastodon.android.api.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Preferences;
public class GetPreferences extends MastodonAPIRequest<Preferences> {
public GetPreferences(){
super(HttpMethod.GET, "/preferences", Preferences.class);
}
}

View File

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

View File

@@ -0,0 +1,21 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Attachment;
import java.io.IOException;
import okhttp3.Response;
public class GetAttachmentByID extends MastodonAPIRequest<Attachment>{
public GetAttachmentByID(String id){
super(HttpMethod.GET, "/media/"+id, Attachment.class);
}
@Override
public void validateAndPostprocessResponse(Attachment respObj, Response httpResponse) throws IOException{
if(httpResponse.code()==206)
respObj.url="";
super.validateAndPostprocessResponse(respObj, httpResponse);
}
}

View File

@@ -17,6 +17,7 @@ import java.io.IOException;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.Response;
public class UploadAttachment extends MastodonAPIRequest<Attachment>{
private Uri uri;
@@ -40,6 +41,18 @@ public class UploadAttachment extends MastodonAPIRequest<Attachment>{
return this;
}
@Override
protected String getPathPrefix(){
return "/api/v2";
}
@Override
public void validateAndPostprocessResponse(Attachment respObj, Response httpResponse) throws IOException{
if(respObj.url==null)
respObj.url="";
super.validateAndPostprocessResponse(respObj, httpResponse);
}
@Override
public RequestBody getRequestBody() throws IOException{
MultipartBody.Builder builder=new MultipartBody.Builder()

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.updater.GithubSelfUpdater;
public class SelfUpdateStateChangedEvent{
public final GithubSelfUpdater.UpdateState state;
public SelfUpdateStateChangedEvent(GithubSelfUpdater.UpdateState state){
this.state=state;
}
}

View File

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

View File

@@ -4,13 +4,18 @@ import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.RenderEffect;
import android.graphics.Shader;
import android.graphics.drawable.LayerDrawable;
import android.icu.text.BreakIterator;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -52,9 +57,13 @@ import com.twitter.twittertext.TwitterTextEmojiRegex;
import org.joinmastodon.android.E;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.ProgressListener;
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
import org.joinmastodon.android.api.requests.statuses.EditStatus;
import org.joinmastodon.android.api.requests.statuses.GetAttachmentByID;
import org.joinmastodon.android.api.requests.statuses.UploadAttachment;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -68,6 +77,7 @@ import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Mention;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.ComposeAutocompleteViewController;
@@ -79,6 +89,7 @@ import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.TransferSpeedTracker;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ComposeEditText;
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
@@ -87,6 +98,9 @@ import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
import org.parceler.Parcel;
import org.parceler.Parcels;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -108,6 +122,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private static final int MEDIA_RESULT=717;
private static final int IMAGE_DESCRIPTION_RESULT=363;
private static final int MAX_ATTACHMENTS=4;
private static final String TAG="ComposeFragment";
private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
@@ -155,8 +170,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private ArrayList<DraftPollOption> pollOptions=new ArrayList<>();
private ArrayList<DraftMediaAttachment> queuedAttachments=new ArrayList<>(), failedAttachments=new ArrayList<>(), attachments=new ArrayList<>(), allAttachments=new ArrayList<>();
private DraftMediaAttachment uploadingAttachment;
private ArrayList<DraftMediaAttachment> attachments=new ArrayList<>();
private List<EmojiCategory> customEmojis;
private CustomEmojiPopupKeyboard emojiKeyboard;
@@ -171,7 +185,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private ImageView sendError;
private View sendingOverlay;
private WindowManager wm;
private StatusPrivacy statusVisibility=StatusPrivacy.UNLISTED;
private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC;
private ComposeAutocompleteSpan currentAutocompleteSpan;
private FrameLayout mainEditTextWrap;
private ComposeAutocompleteViewController autocompleteViewController;
@@ -181,6 +195,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private Status editingStatus;
private boolean pollChanged;
private boolean creatingView;
private boolean ignoreSelectionChanges=false;
private Runnable updateUploadEtaRunnable;
public static DraftMediaAttachment redraftAttachment(Attachment att) {
DraftMediaAttachment draft=new DraftMediaAttachment();
@@ -219,25 +235,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
else
charLimit=500;
if(getArguments().containsKey("replyTo")){
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
statusVisibility=replyTo.visibility;
}
if(getArguments().containsKey("visibility")){
statusVisibility=(StatusPrivacy) getArguments().getSerializable("visibility");
}
if(savedInstanceState!=null){
statusVisibility=(StatusPrivacy) savedInstanceState.getSerializable("visibility");
}
loadDefaultStatusVisibility(savedInstanceState);
}
@Override
public void onDestroy(){
super.onDestroy();
if(uploadingAttachment!=null && uploadingAttachment.uploadRequest!=null)
uploadingAttachment.uploadRequest.cancel();
for(DraftMediaAttachment att:attachments){
if(att.isUploadingOrProcessing())
att.cancelUpload();
}
if(updateUploadEtaRunnable!=null){
UiUtils.removeCallbacks(updateUploadEtaRunnable);
updateUploadEtaRunnable=null;
}
}
@Override
@@ -351,6 +362,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
spoilerEdit.setVisibility(View.VISIBLE);
spoilerBtn.setSelected(true);
}else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){
hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE);
spoilerEdit.setText(getArguments().getString("sourceSpoiler", editingStatus.spoilerText));
spoilerBtn.setSelected(true);
@@ -365,12 +377,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
attachments.add(att);
}
attachmentsView.setVisibility(View.VISIBLE);
}else if(!allAttachments.isEmpty()){
}else if(!attachments.isEmpty()){
attachmentsView.setVisibility(View.VISIBLE);
for(DraftMediaAttachment att:allAttachments){
for(DraftMediaAttachment att:attachments){
attachmentsView.addView(createMediaAttachmentView(att));
}
}
if(editingStatus!=null && editingStatus.visibility!=null) {
statusVisibility=editingStatus.visibility;
}
updateVisibilityIcon();
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
@@ -502,7 +518,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
initialText=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
if(savedInstanceState==null){
mainEditText.setText(initialText);
ignoreSelectionChanges=true;
mainEditText.setSelection(mainEditText.length());
ignoreSelectionChanges=false;
if(!TextUtils.isEmpty(replyTo.spoilerText)){
hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE);
@@ -517,7 +535,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(editingStatus!=null){
initialText=getArguments().getString("sourceText", "");
mainEditText.setText(initialText);
ignoreSelectionChanges=true;
mainEditText.setSelection(mainEditText.length());
ignoreSelectionChanges=false;
if(!editingStatus.mediaAttachments.isEmpty()){
attachmentsView.setVisibility(View.VISIBLE);
for(Attachment att:editingStatus.mediaAttachments){
@@ -534,7 +554,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
String prefilledText=getArguments().getString("prefilledText");
if(!TextUtils.isEmpty(prefilledText)){
mainEditText.setText(prefilledText);
ignoreSelectionChanges=true;
mainEditText.setSelection(mainEditText.length());
ignoreSelectionChanges=false;
initialText=prefilledText;
}
ArrayList<Uri> mediaUris=getArguments().getParcelableArrayList("mediaAttachments");
@@ -548,6 +570,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(editingStatus!=null){
updateCharCounter();
visibilityBtn.setEnabled(false);
}
}
@@ -624,8 +647,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(opt.edit.length()>0)
nonEmptyPollOptionsCount++;
}
publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && uploadingAttachment==null && failedAttachments.isEmpty() && queuedAttachments.isEmpty()
&& (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
if(publishButton==null)
return;
int nonDoneAttachmentCount=0;
for(DraftMediaAttachment att:attachments){
if(att.state!=AttachmentUploadState.DONE)
nonDoneAttachmentCount++;
}
publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
}
private void onCustomEmojiClick(Emoji emoji){
@@ -721,6 +750,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;
@@ -732,10 +762,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
boolean pollFieldsHaveContent=false;
for(DraftPollOption opt:pollOptions)
pollFieldsHaveContent|=opt.edit.length()>0;
return getArguments().getBoolean("hasDraft", false)
|| (mainEditText.length()>0 && !mainEditText.getText().toString().equals(initialText))
|| !attachments.isEmpty() || uploadingAttachment!=null || !queuedAttachments.isEmpty()
|| !failedAttachments.isEmpty() || pollFieldsHaveContent;
return (mainEditText.length()>0 && !mainEditText.getText().toString().equals(initialText)) || !attachments.isEmpty() || pollFieldsHaveContent;
}
@Override
@@ -836,7 +863,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
if(size>sizeLimit){
float mb=sizeLimit/(float) (1024*1024);
String sMb=String.format(Locale.getDefault(), mb%1f==0f ? "%f" : "%.2f", mb);
String sMb=String.format(Locale.getDefault(), mb%1f==0f ? "%.0f" : "%.2f", mb);
showMediaAttachmentError(getString(R.string.media_attachment_too_big, UiUtils.getFileName(uri), sMb));
return false;
}
@@ -845,18 +872,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
pollBtn.setEnabled(false);
DraftMediaAttachment draft=new DraftMediaAttachment();
draft.uri=uri;
draft.mimeType=type;
draft.description=description;
attachmentsView.addView(createMediaAttachmentView(draft));
allAttachments.add(draft);
attachments.add(draft);
attachmentsView.setVisibility(View.VISIBLE);
draft.overlay.setVisibility(View.VISIBLE);
draft.infoBar.setVisibility(View.GONE);
draft.setOverlayVisible(true, false);
if(uploadingAttachment==null){
uploadMediaAttachment(draft);
}else{
queuedAttachments.add(draft);
if(!areThereAnyUploadingAttachments()){
uploadNextQueuedAttachment();
}
updatePublishButtonState();
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS)
@@ -875,25 +900,35 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private View createMediaAttachmentView(DraftMediaAttachment draft){
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
ImageView img=thumb.findViewById(R.id.thumb);
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
if(draft.serverAttachment!=null){
ViewImageLoader.load(img, draft.serverAttachment.blurhashPlaceholder, new UrlImageLoaderRequest(draft.serverAttachment.previewUrl, V.dp(250), V.dp(250)));
}else{
if(draft.mimeType.startsWith("image/")){
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
}else if(draft.mimeType.startsWith("video/")){
loadVideoThumbIntoView(img, draft.uri);
}
}
TextView fileName=thumb.findViewById(R.id.file_name);
fileName.setText(UiUtils.getFileName(draft.uri));
fileName.setText(UiUtils.getFileName(draft.serverAttachment!=null ? Uri.parse(draft.serverAttachment.url) : draft.uri));
draft.view=thumb;
draft.imageView=img;
draft.progressBar=thumb.findViewById(R.id.progress);
draft.infoBar=thumb.findViewById(R.id.info_bar);
draft.overlay=thumb.findViewById(R.id.overlay);
draft.descriptionView=thumb.findViewById(R.id.description);
draft.uploadStateTitle=thumb.findViewById(R.id.state_title);
draft.uploadStateText=thumb.findViewById(R.id.state_text);
ImageButton btn=thumb.findViewById(R.id.remove_btn);
btn.setTag(draft);
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
btn=thumb.findViewById(R.id.remove_btn2);
btn.setTag(draft);
btn.setOnClickListener(this::onRemoveMediaAttachmentClick);
Button retry=thumb.findViewById(R.id.retry_upload);
ImageButton retry=thumb.findViewById(R.id.retry_or_cancel_upload);
retry.setTag(draft);
retry.setOnClickListener(this::onRetryMediaUploadClick);
retry.setVisibility(View.GONE);
retry.setOnClickListener(this::onRetryOrCancelMediaUploadClick);
draft.retryButton=retry;
draft.infoBar.setTag(draft);
draft.infoBar.setOnClickListener(this::onEditMediaDescriptionClick);
@@ -901,12 +936,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(!TextUtils.isEmpty(draft.description))
draft.descriptionView.setText(draft.description);
if(uploadingAttachment!=draft && !queuedAttachments.contains(draft)){
draft.progressBar.setVisibility(View.GONE);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S){
draft.overlay.setBackgroundColor(0xA6000000);
}
if(failedAttachments.contains(draft)){
draft.infoBar.setVisibility(View.GONE);
draft.overlay.setVisibility(View.VISIBLE);
if(draft.state==AttachmentUploadState.UPLOADING || draft.state==AttachmentUploadState.PROCESSING || draft.state==AttachmentUploadState.QUEUED){
draft.progressBar.setVisibility(View.GONE);
}else if(draft.state==AttachmentUploadState.ERROR){
draft.setOverlayVisible(true, false);
}
return thumb;
@@ -918,67 +955,92 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
draft.uri=uri;
draft.description=description;
attachmentsView.addView(createMediaAttachmentView(draft));
allAttachments.add(draft);
attachments.add(draft);
attachmentsView.setVisibility(View.VISIBLE);
}
private void uploadMediaAttachment(DraftMediaAttachment attachment){
if(uploadingAttachment!=null)
throw new IllegalStateException("there is already an attachment being uploaded");
uploadingAttachment=attachment;
if(areThereAnyUploadingAttachments()){
throw new IllegalStateException("there is already an attachment being uploaded");
}
attachment.state=AttachmentUploadState.UPLOADING;
attachment.progressBar.setVisibility(View.VISIBLE);
ObjectAnimator rotationAnimator=ObjectAnimator.ofFloat(attachment.progressBar, View.ROTATION, 0f, 360f);
rotationAnimator.setInterpolator(new LinearInterpolator());
rotationAnimator.setDuration(1500);
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
rotationAnimator.start();
attachment.progressBarAnimator=rotationAnimator;
int maxSize=0;
String contentType=getActivity().getContentResolver().getType(attachment.uri);
if(contentType!=null && contentType.startsWith("image/")){
maxSize=2_073_600; // TODO get this from instance configuration when it gets added there
}
attachment.uploadStateTitle.setText("");
attachment.uploadStateText.setText("");
attachment.progressBar.setProgress(0);
attachment.speedTracker.reset();
attachment.speedTracker.addSample(0);
attachment.uploadRequest=(UploadAttachment) new UploadAttachment(attachment.uri, maxSize, attachment.description)
.setProgressListener(new ProgressListener(){
@Override
public void onProgress(long transferred, long total){
if(updateUploadEtaRunnable==null){
UiUtils.runOnUiThread(updateUploadEtaRunnable=ComposeFragment.this::updateUploadETAs, 100);
}
int progress=Math.round(transferred/(float)total*attachment.progressBar.getMax());
if(Build.VERSION.SDK_INT>=24)
attachment.progressBar.setProgress(progress, true);
else
attachment.progressBar.setProgress(progress);
attachment.speedTracker.setTotalBytes(total);
attachment.uploadStateTitle.setText(getString(R.string.file_upload_progress, UiUtils.formatFileSize(getActivity(), transferred, true), UiUtils.formatFileSize(getActivity(), total, true)));
attachment.speedTracker.addSample(transferred);
}
})
.setCallback(new Callback<>(){
@Override
public void onSuccess(Attachment result){
attachment.serverAttachment=result;
attachment.uploadRequest=null;
uploadingAttachment=null;
attachments.add(attachment);
attachment.progressBar.setVisibility(View.GONE);
if(!queuedAttachments.isEmpty())
uploadMediaAttachment(queuedAttachments.remove(0));
updatePublishButtonState();
rotationAnimator.cancel();
V.setVisibilityAnimated(attachment.overlay, View.GONE);
V.setVisibilityAnimated(attachment.infoBar, View.VISIBLE);
if(TextUtils.isEmpty(result.url)){
attachment.state=AttachmentUploadState.PROCESSING;
attachment.processingPollingRunnable=()->pollForMediaAttachmentProcessing(attachment);
if(getActivity()==null)
return;
attachment.uploadStateTitle.setText(R.string.upload_processing);
attachment.uploadStateText.setText("");
UiUtils.runOnUiThread(attachment.processingPollingRunnable, 1000);
if(!areThereAnyUploadingAttachments())
uploadNextQueuedAttachment();
}else{
finishMediaAttachmentUpload(attachment);
}
}
@Override
public void onError(ErrorResponse error){
attachment.uploadRequest=null;
uploadingAttachment=null;
failedAttachments.add(attachment);
// error.showToast(getActivity());
Toast.makeText(getActivity(), R.string.image_upload_failed, Toast.LENGTH_SHORT).show();
attachment.progressBarAnimator=null;
attachment.state=AttachmentUploadState.ERROR;
attachment.uploadStateTitle.setText(R.string.upload_failed);
if(error instanceof MastodonErrorResponse er){
if(er.underlyingException instanceof SocketException || er.underlyingException instanceof UnknownHostException || er.underlyingException instanceof InterruptedIOException)
attachment.uploadStateText.setText(R.string.upload_error_connection_lost);
else
attachment.uploadStateText.setText(er.error);
}else{
attachment.uploadStateText.setText("");
}
attachment.retryButton.setImageResource(R.drawable.ic_fluent_arrow_clockwise_24_filled);
attachment.retryButton.setContentDescription(getString(R.string.retry_upload));
rotationAnimator.cancel();
V.setVisibilityAnimated(attachment.retryButton, View.VISIBLE);
V.setVisibilityAnimated(attachment.progressBar, View.GONE);
if(!queuedAttachments.isEmpty())
uploadMediaAttachment(queuedAttachments.remove(0));
if(!areThereAnyUploadingAttachments())
uploadNextQueuedAttachment();
}
})
.exec(accountID);
@@ -986,37 +1048,109 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private void onRemoveMediaAttachmentClick(View v){
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
if(att==uploadingAttachment){
att.uploadRequest.cancel();
uploadingAttachment=null;
if(!queuedAttachments.isEmpty())
uploadMediaAttachment(queuedAttachments.remove(0));
}else{
attachments.remove(att);
queuedAttachments.remove(att);
failedAttachments.remove(att);
}
allAttachments.remove(att);
if(att.isUploadingOrProcessing())
att.cancelUpload();
attachments.remove(att);
uploadNextQueuedAttachment();
attachmentsView.removeView(att.view);
if(getMediaAttachmentsCount()==0)
attachmentsView.setVisibility(View.GONE);
updatePublishButtonState();
pollBtn.setEnabled(attachments.isEmpty() && queuedAttachments.isEmpty() && failedAttachments.isEmpty() && uploadingAttachment==null);
pollBtn.setEnabled(attachments.isEmpty());
mediaBtn.setEnabled(true);
}
private void onRetryMediaUploadClick(View v){
private void onRetryOrCancelMediaUploadClick(View v){
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
if(failedAttachments.remove(att)){
V.setVisibilityAnimated(att.retryButton, View.GONE);
if(att.state==AttachmentUploadState.ERROR){
att.retryButton.setImageResource(R.drawable.ic_fluent_dismiss_24_filled);
att.retryButton.setContentDescription(getString(R.string.cancel));
V.setVisibilityAnimated(att.progressBar, View.VISIBLE);
if(uploadingAttachment==null)
uploadMediaAttachment(att);
else
queuedAttachments.add(att);
att.state=AttachmentUploadState.QUEUED;
if(!areThereAnyUploadingAttachments()){
uploadNextQueuedAttachment();
}
}else{
onRemoveMediaAttachmentClick(v);
}
}
private void pollForMediaAttachmentProcessing(DraftMediaAttachment attachment){
attachment.processingPollingRequest=(GetAttachmentByID) new GetAttachmentByID(attachment.serverAttachment.id)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Attachment result){
attachment.processingPollingRequest=null;
if(!TextUtils.isEmpty(result.url)){
attachment.processingPollingRunnable=null;
attachment.serverAttachment=result;
finishMediaAttachmentUpload(attachment);
}else if(getActivity()!=null){
UiUtils.runOnUiThread(attachment.processingPollingRunnable, 1000);
}
}
@Override
public void onError(ErrorResponse error){
attachment.processingPollingRequest=null;
if(getActivity()!=null)
UiUtils.runOnUiThread(attachment.processingPollingRunnable, 1000);
}
})
.exec(accountID);
}
private void finishMediaAttachmentUpload(DraftMediaAttachment attachment){
if(attachment.state!=AttachmentUploadState.PROCESSING && attachment.state!=AttachmentUploadState.UPLOADING)
throw new IllegalStateException("Unexpected state "+attachment.state);
attachment.uploadRequest=null;
attachment.state=AttachmentUploadState.DONE;
attachment.progressBar.setVisibility(View.GONE);
if(!areThereAnyUploadingAttachments())
uploadNextQueuedAttachment();
updatePublishButtonState();
if(attachment.progressBarAnimator!=null){
attachment.progressBarAnimator.cancel();
attachment.progressBarAnimator=null;
}
attachment.setOverlayVisible(false, true);
}
private void uploadNextQueuedAttachment(){
for(DraftMediaAttachment att:attachments){
if(att.state==AttachmentUploadState.QUEUED){
uploadMediaAttachment(att);
return;
}
}
}
private boolean areThereAnyUploadingAttachments(){
for(DraftMediaAttachment att:attachments){
if(att.state==AttachmentUploadState.UPLOADING)
return true;
}
return false;
}
private void updateUploadETAs(){
if(!areThereAnyUploadingAttachments()){
UiUtils.removeCallbacks(updateUploadEtaRunnable);
updateUploadEtaRunnable=null;
return;
}
for(DraftMediaAttachment att:attachments){
if(att.state==AttachmentUploadState.UPLOADING){
long eta=att.speedTracker.updateAndGetETA();
// Log.i(TAG, "onProgress: transfer speed "+UiUtils.formatFileSize(getActivity(), Math.round(att.speedTracker.getLastSpeed()), false)+" average "+UiUtils.formatFileSize(getActivity(), Math.round(att.speedTracker.getAverageSpeed()), false)+" eta "+eta);
String time=String.format("%d:%02d", eta/60, eta%60);
att.uploadStateText.setText(getString(R.string.file_upload_time_remaining, time));
}
}
UiUtils.runOnUiThread(updateUploadEtaRunnable, 100);
}
private void onEditMediaDescriptionClick(View v){
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
if(att.serverAttachment==null)
@@ -1129,7 +1263,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private int getMediaAttachmentsCount(){
return allAttachments.size();
return attachments.size();
}
private void onVisibilityClick(View v){
@@ -1165,12 +1299,53 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
menu.show();
}
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
if(getArguments().containsKey("replyTo")){
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
statusVisibility = replyTo.visibility;
}
// A saved privacy setting from a previous compose session wins over the reply visibility
if(savedInstanceState !=null){
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
}
new GetPreferences()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Preferences result){
// Only override the reply visibility if our preference is more private
if (result.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) {
statusVisibility = switch (result.postingDefaultVisibility) {
case PUBLIC -> StatusPrivacy.PUBLIC;
case UNLISTED -> StatusPrivacy.UNLISTED;
case PRIVATE -> StatusPrivacy.PRIVATE;
case DIRECT -> StatusPrivacy.DIRECT;
};
}
// A saved privacy setting from a previous compose session wins over all
if(savedInstanceState !=null){
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
}
updateVisibilityIcon ();
}
@Override
public void onError(ErrorResponse error){
Log.w(TAG, "Unable to get user preferences to set default post privacy");
}
})
.exec(accountID);
}
private void updateVisibilityIcon(){
if(statusVisibility==null){ // TODO find out why this happens
statusVisibility=StatusPrivacy.PUBLIC;
}
visibilityBtn.setImageResource(switch(statusVisibility){
case PUBLIC -> R.drawable.ic_fluent_earth_24_filled;
case PUBLIC -> R.drawable.ic_fluent_earth_24_regular;
case UNLISTED -> R.drawable.ic_fluent_people_community_24_regular;
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_24_regular;
case DIRECT -> R.drawable.ic_at_symbol;
@@ -1179,6 +1354,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override
public void onSelectionChanged(int start, int end){
if(ignoreSelectionChanges)
return;
if(start==end && mainEditText.length()>0){
ComposeAutocompleteSpan[] spans=mainEditText.getText().getSpans(start, end, ComposeAutocompleteSpan.class);
if(spans.length>0){
@@ -1249,6 +1426,30 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
finishAutocomplete();
}
private void loadVideoThumbIntoView(ImageView target, Uri uri){
MastodonAPIController.runInBackground(()->{
Context context=getActivity();
if(context==null)
return;
try{
MediaMetadataRetriever mmr=new MediaMetadataRetriever();
mmr.setDataSource(context, uri);
Bitmap frame=mmr.getFrameAtTime(3_000_000);
mmr.release();
int size=Math.max(frame.getWidth(), frame.getHeight());
int maxSize=V.dp(250);
if(size>maxSize){
float factor=maxSize/(float)size;
frame=Bitmap.createScaledBitmap(frame, Math.round(frame.getWidth()*factor), Math.round(frame.getHeight()*factor), true);
}
Bitmap finalFrame=frame;
target.post(()->target.setImageBitmap(finalFrame));
}catch(Exception x){
Log.w(TAG, "loadVideoThumbIntoView: error getting video frame", x);
}
});
}
@Override
public CharSequence getTitle(){
return getString(R.string.new_post);
@@ -1269,14 +1470,75 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public Attachment serverAttachment;
public Uri uri;
public transient UploadAttachment uploadRequest;
public transient GetAttachmentByID processingPollingRequest;
public String description;
public String mimeType;
public AttachmentUploadState state=AttachmentUploadState.QUEUED;
public transient View view;
public transient ProgressBar progressBar;
public transient TextView descriptionView;
public transient View overlay;
public transient View infoBar;
public transient Button retryButton;
public transient ImageButton retryButton;
public transient ObjectAnimator progressBarAnimator;
public transient Runnable processingPollingRunnable;
public transient ImageView imageView;
public transient TextView uploadStateTitle, uploadStateText;
public transient TransferSpeedTracker speedTracker=new TransferSpeedTracker();
public void cancelUpload(){
switch(state){
case UPLOADING -> {
if(uploadRequest!=null){
uploadRequest.cancel();
uploadRequest=null;
}
}
case PROCESSING -> {
if(processingPollingRunnable!=null){
UiUtils.removeCallbacks(processingPollingRunnable);
processingPollingRunnable=null;
}
if(processingPollingRequest!=null){
processingPollingRequest.cancel();
processingPollingRequest=null;
}
}
default -> throw new IllegalStateException("Unexpected state "+state);
}
}
public boolean isUploadingOrProcessing(){
return state==AttachmentUploadState.UPLOADING || state==AttachmentUploadState.PROCESSING;
}
public void setOverlayVisible(boolean visible, boolean animated){
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S){
if(visible){
imageView.setRenderEffect(RenderEffect.createBlurEffect(V.dp(16), V.dp(16), Shader.TileMode.REPEAT));
}else{
imageView.setRenderEffect(null);
}
}
int infoBarVis=visible ? View.GONE : View.VISIBLE;
int overlayVis=visible ? View.VISIBLE : View.GONE;
if(animated){
V.setVisibilityAnimated(infoBar, infoBarVis);
V.setVisibilityAnimated(overlay, overlayVis);
}else{
infoBar.setVisibility(infoBarVis);
overlay.setVisibility(overlayVis);
}
}
}
enum AttachmentUploadState{
QUEUED,
UPLOADING,
PROCESSING,
ERROR,
DONE
}
private static class DraftPollOption{

View File

@@ -2,13 +2,9 @@ package org.joinmastodon.android.fragments;
import android.app.Fragment;
import android.app.NotificationManager;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Outline;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -18,20 +14,14 @@ import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.PushNotificationReceiver;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
import org.joinmastodon.android.fragments.discover.SearchFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TabBar;
import org.parceler.Parcels;
@@ -41,15 +31,12 @@ import java.util.ArrayList;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.BottomSheet;
import me.grishka.appkit.views.FragmentRootLinearLayout;
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
@@ -141,7 +128,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
}
});
}
}else{
}
return content;

View File

@@ -23,9 +23,11 @@ import android.widget.Toolbar;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Filter;
@@ -33,6 +35,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.Collections;
@@ -101,6 +104,11 @@ public class HomeTimelineFragment extends StatusListFragment{
}
}
});
if(GithubSelfUpdater.needSelfUpdating()){
E.register(this);
updateUpdateState(GithubSelfUpdater.getInstance().getState());
}
}
@Override
@@ -134,7 +142,6 @@ public class HomeTimelineFragment extends StatusListFragment{
}
}
@Subscribe
public void onStatusCreated(StatusCreatedEvent ev){
prependItems(Collections.singletonList(ev.status), true);
}
@@ -397,4 +404,22 @@ public class HomeTimelineFragment extends StatusListFragment{
scrollToTop();
}
}
@Override
public void onDestroyView(){
super.onDestroyView();
if(GithubSelfUpdater.needSelfUpdating()){
E.unregister(this);
}
}
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING)
getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_24_badged);
}
@Subscribe
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
updateUpdateState(ev.state);
}
}

View File

@@ -22,6 +22,7 @@ import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -76,9 +77,11 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
if(titleItem!=null)
items.add(0, titleItem);
return items;
}else{
}else if(titleItem!=null){
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, n.account);
return Arrays.asList(titleItem, card);
}else{
return Collections.emptyList();
}
}

View File

@@ -414,16 +414,23 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private void bindHeaderView(){
setTitle(account.displayName);
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, account.statusesCount, account.statusesCount));
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
HtmlParser.parseCustomEmoji(ssb, account.emojis);
name.setText(ssb);
setTitle(ssb);
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
if(account.locked){
ssb=new SpannableStringBuilder("@");
ssb.append(account.acct);
if(isSelf){
ssb.append('@');
ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
}
ssb.append(" ");
Drawable lock=username.getResources().getDrawable(R.drawable.ic_fluent_lock_closed_20_filled, getActivity().getTheme()).mutate();
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
@@ -431,7 +438,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BOTTOM), 0);
username.setText(ssb);
}else{
username.setText('@'+account.acct);
// noinspection SetTextI18n
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
}
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(TextUtils.isEmpty(parsedBio)){
@@ -443,9 +451,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, Math.min(999, account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, Math.min(999, account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, Math.min(999, account.statusesCount)));
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, account.statusesCount)));
UiUtils.loadCustomEmojiInTextView(name);
UiUtils.loadCustomEmojiInTextView(bio);

View File

@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
@@ -14,15 +15,22 @@ 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;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RadioButton;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.MastodonApp;
@@ -31,11 +39,13 @@ import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -47,7 +57,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.ToolbarFragment;
import me.grishka.appkit.imageloader.ImageCache;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
@@ -63,6 +72,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
private PushSubscription pushSubscription;
private ImageView themeTransitionWindowView;
private TextItem checkForUpdateItem;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -73,6 +83,14 @@ public class SettingsFragment extends MastodonToolbarFragment{
accountID=getArguments().getString("account");
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
if(GithubSelfUpdater.needSelfUpdating()){
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
GithubSelfUpdater.UpdateState state=updater.getState();
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING){
items.add(new UpdateItem());
}
}
items.add(new HeaderItem(R.string.settings_theme));
items.add(themeItem=new ThemeItem());
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
@@ -102,6 +120,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
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_clear_cache, this::clearImageCache));
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
@@ -131,7 +153,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
// Add 32dp gaps between sections
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if((holder instanceof HeaderViewHolder || holder instanceof FooterViewHolder) && holder.getAbsoluteAdapterPosition()>0)
if((holder instanceof HeaderViewHolder || holder instanceof FooterViewHolder) && holder.getAbsoluteAdapterPosition()>1)
outRect.top=V.dp(32);
}
});
@@ -155,6 +177,20 @@ public class SettingsFragment extends MastodonToolbarFragment{
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
if(GithubSelfUpdater.needSelfUpdating())
E.register(this);
}
@Override
public void onDestroyView(){
super.onDestroyView();
if(GithubSelfUpdater.needSelfUpdating())
E.unregister(this);
}
private void onThemePreferenceClick(GlobalUserPreferences.ThemePreference theme){
GlobalUserPreferences.theme=theme;
GlobalUserPreferences.save();
@@ -294,6 +330,30 @@ public class SettingsFragment extends MastodonToolbarFragment{
});
}
@Subscribe
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
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();
}
}
private static abstract class Item{
public abstract int getViewType();
}
@@ -358,10 +418,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
@@ -395,6 +461,14 @@ public class SettingsFragment extends MastodonToolbarFragment{
}
}
private class UpdateItem extends Item{
@Override
public int getViewType(){
return 7;
}
}
private class SettingsAdapter extends RecyclerView.Adapter<BindableViewHolder<Item>>{
@NonNull
@Override
@@ -408,6 +482,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
case 4 -> new TextViewHolder();
case 5 -> new HeaderViewHolder(true);
case 6 -> new FooterViewHolder();
case 7 -> new UpdateViewHolder();
default -> throw new IllegalStateException("Unexpected value: "+viewType);
};
}
@@ -581,14 +656,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
@@ -609,4 +688,75 @@ public class SettingsFragment extends MastodonToolbarFragment{
text.setText(item.text);
}
}
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
private final TextView text;
private final Button button;
private final ImageButton cancelBtn;
private final ProgressBar progress;
private ObjectAnimator rotationAnimator;
private Runnable progressUpdater=this::updateProgress;
public UpdateViewHolder(){
super(getActivity(), R.layout.item_settings_update, list);
text=findViewById(R.id.text);
button=findViewById(R.id.button);
cancelBtn=findViewById(R.id.cancel_btn);
progress=findViewById(R.id.progress);
button.setOnClickListener(v->{
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
switch(updater.getState()){
case UPDATE_AVAILABLE -> updater.downloadUpdate();
case DOWNLOADED -> updater.installUpdate(getActivity());
}
});
cancelBtn.setOnClickListener(v->GithubSelfUpdater.getInstance().cancelDownload());
rotationAnimator=ObjectAnimator.ofFloat(progress, View.ROTATION, 0f, 360f);
rotationAnimator.setInterpolator(new LinearInterpolator());
rotationAnimator.setDuration(1500);
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
}
@Override
public void onBind(UpdateItem item){
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
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)));
}else{
text.setText(getString(R.string.update_ready, info.version));
button.setText(R.string.install_update);
}
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
rotationAnimator.start();
button.setVisibility(View.INVISIBLE);
cancelBtn.setVisibility(View.VISIBLE);
progress.setVisibility(View.VISIBLE);
updateProgress();
}else{
rotationAnimator.cancel();
button.setVisibility(View.VISIBLE);
cancelBtn.setVisibility(View.GONE);
progress.setVisibility(View.GONE);
progress.removeCallbacks(progressUpdater);
}
}
private void updateProgress(){
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
if(updater.getState()!=GithubSelfUpdater.UpdateState.DOWNLOADING)
return;
int value=Math.round(progress.getMax()*updater.getDownloadProgress());
if(Build.VERSION.SDK_INT>=24)
progress.setProgress(value, true);
else
progress.setProgress(value);
progress.postDelayed(progressUpdater, 1000);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,231 @@
package org.joinmastodon.android.fragments.onboarding;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.parceler.Parcels;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class GoogleMadeMeAddThisFragment extends AppKitFragment{
private UsableRecyclerView list;
private MergeRecyclerAdapter adapter;
private Button btn;
private View buttonBar;
private Instance instance;
private ArrayList<Item> items=new ArrayList<>();
private Call currentRequest;
private ItemsAdapter itemsAdapter;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
items.add(new Item("Mastodon for Android Privacy Policy", "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png"));
loadServerPrivacyPolicy();
}
@Override
public void onDestroy(){
super.onDestroy();
if(currentRequest!=null){
currentRequest.cancel();
currentRequest=null;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
list=view.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
TextView title=headerView.findViewById(R.id.title);
TextView subtitle=headerView.findViewById(R.id.subtitle);
headerView.findViewById(R.id.step_counter).setVisibility(View.GONE);
title.setText(R.string.privacy_policy_title);
subtitle.setText(R.string.privacy_policy_subtitle);
adapter=new MergeRecyclerAdapter();
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
list.setAdapter(adapter);
list.setSelector(null);
list.addItemDecoration(new RecyclerView.ItemDecoration(){
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
if(parent.getChildViewHolder(view) instanceof ItemViewHolder){
outRect.left=outRect.right=V.dp(18.5f);
outRect.top=V.dp(16);
}
}
});
btn=view.findViewById(R.id.btn_next);
btn.setOnClickListener(v->onButtonClick());
buttonBar=view.findViewById(R.id.button_bar);
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
protected void onButtonClick(){
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
Nav.go(getActivity(), SignupFragment.class, args);
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(Build.VERSION.SDK_INT>=27){
int inset=insets.getSystemWindowInsetBottom();
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
}else{
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
}
private void loadServerPrivacyPolicy(){
Request req=new Request.Builder()
.url("https://"+instance.uri+"/terms")
.addHeader("Accept-Language", Locale.getDefault().toLanguageTag())
.build();
currentRequest=MastodonAPIController.getHttpClient().newCall(req);
currentRequest.enqueue(new Callback(){
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e){
currentRequest=null;
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{
currentRequest=null;
try(ResponseBody body=response.body()){
if(!response.isSuccessful())
return;
Document doc=Jsoup.parse(Objects.requireNonNull(body).byteStream(), Objects.requireNonNull(body.contentType()).charset(StandardCharsets.UTF_8).name(), req.url().toString());
final Item item=new Item(doc.title(), instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico");
Activity activity=getActivity();
if(activity!=null){
activity.runOnUiThread(()->{
items.add(item);
itemsAdapter.notifyItemInserted(items.size()-1);
});
}
}
}
});
}
private class ItemsAdapter extends RecyclerView.Adapter<ItemViewHolder>{
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new ItemViewHolder();
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position){
holder.bind(items.get(position));
}
@Override
public int getItemCount(){
return items.size();
}
}
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
private final TextView domain, title;
private final ImageView favicon;
public ItemViewHolder(){
super(getActivity(), R.layout.item_privacy_policy_link, list);
domain=findViewById(R.id.domain);
title=findViewById(R.id.title);
favicon=findViewById(R.id.favicon);
itemView.setOutlineProvider(OutlineProviders.roundedRect(10));
itemView.setClipToOutline(true);
}
@Override
public void onBind(Item item){
domain.setText(item.domain);
title.setText(item.title);
ViewImageLoader.load(favicon, null, new UrlImageLoaderRequest(item.faviconUrl));
}
@Override
public void onClick(){
UiUtils.launchWebBrowser(getActivity(), item.url);
}
}
private static class Item{
public String title, domain, url, faviconUrl;
public Item(String title, String domain, String url, String faviconUrl){
this.title=title;
this.domain=domain;
this.url=url;
this.faviconUrl=faviconUrl;
}
}
}

View File

@@ -84,7 +84,7 @@ public class InstanceRulesFragment extends AppKitFragment{
protected void onButtonClick(){
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
Nav.go(getActivity(), SignupFragment.class, args);
Nav.go(getActivity(), GoogleMadeMeAddThisFragment.class, args);
}
@Override

View File

@@ -102,6 +102,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
else
selectedIDs.add(id);
list.invalidate();
btn.setEnabled(!selectedIDs.isEmpty());
}
@Override

View File

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

View File

@@ -0,0 +1,12 @@
package org.joinmastodon.android.model;
import com.google.gson.annotations.SerializedName;
public enum ExpandMedia {
@SerializedName("default")
DEFAULT,
@SerializedName("show_all")
SHOW_ALL,
@SerializedName("hide_all")
HIDE_ALL;
}

View File

@@ -0,0 +1,38 @@
package org.joinmastodon.android.model;
import com.google.gson.annotations.SerializedName;
/**
* Preferred common behaviors to be shared across clients.
*/
public class Preferences extends BaseModel {
/**
* Default visibility for new posts
*/
@SerializedName("posting:default:visibility")
public StatusPrivacy postingDefaultVisibility;
/**
* Default sensitivity flag for new posts
*/
@SerializedName("posting:default:sensitive")
public boolean postingDefaultSensitive;
/**
* Default language for new posts
*/
@SerializedName("posting:default:language")
public String postingDefaultLanguage;
/**
* Whether media attachments should be automatically displayed or blurred/hidden.
*/
@SerializedName("reading:expand:media")
public ExpandMedia readingExpandMedia;
/**
* Whether CWs should be expanded by default.
*/
@SerializedName("reading:expand:spoilers")
public boolean readingExpandSpoilers;
}

View File

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

View File

@@ -4,11 +4,25 @@ import com.google.gson.annotations.SerializedName;
public enum StatusPrivacy{
@SerializedName("public")
PUBLIC,
PUBLIC(0),
@SerializedName("unlisted")
UNLISTED,
UNLISTED(1),
@SerializedName("private")
PRIVATE,
PRIVATE(2),
@SerializedName("direct")
DIRECT;
DIRECT(3);
private int privacy;
StatusPrivacy(int privacy) {
this.privacy = privacy;
}
public boolean isLessVisibleThan(StatusPrivacy other) {
return privacy > other.getPrivacy();
}
public int getPrivacy() {
return privacy;
}
}

View File

@@ -115,9 +115,9 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
followersCount.setText(UiUtils.abbreviateNumber(item.account.followersCount));
followingCount.setText(UiUtils.abbreviateNumber(item.account.followingCount));
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, Math.min(999, item.account.followersCount)));
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, Math.min(999, item.account.followingCount)));
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, Math.min(999, item.account.statusesCount)));
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
relationship=item.parentFragment.getRelationship(item.account.id);
if(relationship==null){
actionWrap.setVisibility(View.GONE);

View File

@@ -97,7 +97,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
}
private void bindButton(TextView btn, int count){
private void bindButton(TextView btn, long count){
if(count>0 && !item.hideCounts){
btn.setText(DecimalFormat.getIntegerInstance().format(count));
btn.setCompoundDrawablePadding(V.dp(8));

View File

@@ -129,7 +129,16 @@ public class HtmlParser{
}
public static void parseCustomEmoji(SpannableStringBuilder ssb, List<Emoji> emojis){
Map<String, Emoji> emojiByCode=emojis.stream().collect(Collectors.toMap(e->e.shortcode, Function.identity()));
Map<String, Emoji> emojiByCode =
emojis.stream()
.collect(
Collectors.toMap(e->e.shortcode, Function.identity(), (emoji1, emoji2) -> {
// Ignore duplicate shortcodes and just take the first, it will be
// the same emoji anyway
return emoji1;
})
);
Matcher matcher=EMOJI_CODE_PATTERN.matcher(ssb);
int spanCount=0;
CustomEmojiSpan lastSpan=null;

View File

@@ -0,0 +1,51 @@
package org.joinmastodon.android.ui.utils;
import android.os.SystemClock;
public class TransferSpeedTracker{
private final double SMOOTHING_FACTOR=0.05;
private long lastKnownPos;
private long lastKnownPosTime;
private double lastSpeed;
private double averageSpeed;
private long totalBytes;
public void addSample(long position){
if(lastKnownPosTime==0){
lastKnownPosTime=SystemClock.uptimeMillis();
lastKnownPos=position;
}else{
long time=SystemClock.uptimeMillis();
lastSpeed=(position-lastKnownPos)/((double)(time-lastKnownPosTime)/1000.0);
lastKnownPos=position;
lastKnownPosTime=time;
}
}
public double getLastSpeed(){
return lastSpeed;
}
public double getAverageSpeed(){
return averageSpeed;
}
public long updateAndGetETA(){ // must be called at a constant interval
if(averageSpeed==0.0)
averageSpeed=lastSpeed;
else
averageSpeed=SMOOTHING_FACTOR*lastSpeed+(1.0-SMOOTHING_FACTOR)*averageSpeed;
return Math.round((totalBytes-lastKnownPos)/averageSpeed);
}
public void setTotalBytes(long totalBytes){
this.totalBytes=totalBytes;
}
public void reset(){
lastKnownPos=lastKnownPosTime=0;
lastSpeed=averageSpeed=0.0;
totalBytes=0;
}
}

View File

@@ -193,6 +193,15 @@ public class UiUtils{
}
}
@SuppressLint("DefaultLocale")
public static String abbreviateNumber(long n){
if(n<1_000_000_000L)
return abbreviateNumber((int)n);
double a=n/1_000_000_000.0;
return a>99f ? String.format("%,dB", (int)Math.floor(a)) : String.format("%,.1fB", n/1_000_000_000.0);
}
/**
* Android 6.0 has a bug where start and end compound drawables don't get tinted.
* This works around it by setting the tint colors directly to the drawables.
@@ -214,6 +223,14 @@ public class UiUtils{
mainHandler.post(runnable);
}
public static void runOnUiThread(Runnable runnable, long delay){
mainHandler.postDelayed(runnable, delay);
}
public static void removeCallbacks(Runnable runnable){
mainHandler.removeCallbacks(runnable);
}
/** Linear interpolation between {@code startValue} and {@code endValue} by {@code fraction}. */
public static int lerp(int startValue, int endValue, float fraction) {
return startValue + Math.round(fraction * (endValue - startValue));
@@ -231,6 +248,18 @@ public class UiUtils{
return uri.getLastPathSegment();
}
public static String formatFileSize(Context context, long size, boolean atLeastKB){
if(size<1024 && !atLeastKB){
return context.getString(R.string.file_size_bytes, size);
}else if(size<1024*1024){
return context.getString(R.string.file_size_kb, size/1024.0);
}else if(size<1024*1024*1024){
return context.getString(R.string.file_size_mb, size/(1024.0*1024.0));
}else{
return context.getString(R.string.file_size_gb, size/(1024.0*1024.0*1024.0));
}
}
public static MediaType getFileMediaType(File file){
String name=file.getName();
return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.')+1)));

View File

@@ -54,12 +54,13 @@ public class ComposeEditText extends EditText{
// Support receiving images from keyboards
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs){
final var ic = super.onCreateInputConnection(outAttrs);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N_MR1){
outAttrs.contentMimeTypes=selectionListener.onGetAllowedMediaMimeTypes();
inputConnectionWrapper.setTarget(super.onCreateInputConnection(outAttrs));
inputConnectionWrapper.setTarget(ic);
return inputConnectionWrapper;
}
return super.onCreateInputConnection(outAttrs);
return ic;
}
// Support pasting images

View File

@@ -0,0 +1,56 @@
package org.joinmastodon.android.updater;
import android.app.Activity;
import android.content.Intent;
import org.joinmastodon.android.BuildConfig;
public abstract class GithubSelfUpdater{
private static GithubSelfUpdater instance;
public static GithubSelfUpdater getInstance(){
if(instance==null){
try{
Class<?> c=Class.forName("org.joinmastodon.android.updater.GithubSelfUpdaterImpl");
instance=(GithubSelfUpdater) c.newInstance();
}catch(IllegalAccessException|InstantiationException|ClassNotFoundException ignored){
}
}
return instance;
}
public static boolean needSelfUpdating(){
return BuildConfig.BUILD_TYPE.equals("githubRelease") || BuildConfig.BUILD_TYPE.equals("debug");
}
public abstract void checkForUpdates();
public abstract void maybeCheckForUpdates();
public abstract GithubSelfUpdater.UpdateState getState();
public abstract GithubSelfUpdater.UpdateInfo getUpdateInfo();
public abstract void downloadUpdate();
public abstract void installUpdate(Activity activity);
public abstract float getDownloadProgress();
public abstract void cancelDownload();
public abstract void handleIntentFromInstaller(Intent intent, Activity activity);
public enum UpdateState{
NO_UPDATE,
CHECKING,
UPDATE_AVAILABLE,
DOWNLOADING,
DOWNLOADED
}
public static class UpdateInfo{
public String version;
public long size;
}
}

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16.7096,17.7682C19.4819,17.4391 21.8955,15.7408 22.199,14.1888C22.6769,11.7442 22.6376,8.2231 22.6376,8.2231C22.6376,3.4504 19.4929,2.0516 19.4929,2.0516C17.9073,1.3274 15.1846,1.023 12.356,1H12.2865C9.4579,1.023 6.7369,1.3274 5.1513,2.0516C5.1513,2.0516 2.0066,3.4504 2.0066,8.2231C2.0066,8.5125 2.0051,8.8169 2.0035,9.1339C1.9991,10.0135 1.9943,10.9896 2.0199,12.0083C2.1341,16.6755 2.8805,21.2752 7.2202,22.4175C9.2213,22.944 10.9392,23.0542 12.323,22.9785C14.832,22.8403 16.2406,22.0883 16.2406,22.0883L16.1577,20.2779C16.1577,20.2779 14.3648,20.8402 12.3511,20.7717C10.356,20.7037 8.2496,20.5577 7.9269,18.1221C7.8972,17.9082 7.8823,17.6794 7.8823,17.4391C7.8823,17.4391 9.8408,17.9152 12.323,18.0283C13.8407,18.0974 15.2639,17.9399 16.7096,17.7682ZM18.8747,14.3719V8.5932C18.8747,7.4121 18.5723,6.4736 17.9648,5.7792C17.3382,5.0849 16.518,4.729 15.4997,4.729C14.3212,4.729 13.4291,5.1792 12.8392,6.0799L12.2657,7.0359L11.692,6.0799C11.1023,5.1792 10.21,4.729 9.0316,4.729C8.0134,4.729 7.193,5.0849 6.5664,5.7792C5.9589,6.4736 5.6565,7.4121 5.6565,8.5932V14.3719H7.959V8.763C7.959,7.5805 8.4594,6.9806 9.4602,6.9806C10.5665,6.9806 11.1211,7.6925 11.1211,9.1001V12.1701H13.4101V9.1001C13.4101,7.6925 13.9647,6.9806 15.071,6.9806C16.0718,6.9806 16.5722,7.5805 16.5722,8.763V14.3719H18.8747Z"
android:fillColor="#fff"
android:fillType="evenOdd"/>
</vector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?colorPollVoted"/>
<corners android:radius="4dp"/>
</shape>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorForeground">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="#18000000"/>
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="?android:colorBackground"/>
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/highlight_over_dark">
<item>
<shape android:shape="oval">
<solid android:color="@color/gray_600"/>
</shape>
</item>
</ripple>

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 4.75c-4.004 0-7.25 3.246-7.25 7.25s3.246 7.25 7.25 7.25 7.25-3.246 7.25-7.25c0-0.286-0.017-0.567-0.049-0.844C19.133 10.568 19.56 10 20.151 10c0.515 0 0.968 0.358 1.03 0.87 0.046 0.37 0.069 0.747 0.069 1.13 0 5.109-4.141 9.25-9.25 9.25S2.75 17.109 2.75 12 6.891 2.75 12 2.75c2.173 0 4.171 0.75 5.75 2.004V4.25c0-0.552 0.448-1 1-1s1 0.448 1 1v2.698L19.784 7H19.75v0.25c0 0.552-0.448 1-1 1h-3c-0.552 0-1-0.448-1-1s0.448-1 1-1h0.666c-1.222-0.94-2.754-1.5-4.416-1.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="16dp" android:height="16dp" android:viewportWidth="16" android:viewportHeight="16">
<path android:pathData="M2.397 2.554L2.47 2.47c0.266-0.267 0.683-0.29 0.976-0.073L3.53 2.47 8 6.939l4.47-4.47c0.293-0.292 0.767-0.292 1.06 0 0.293 0.294 0.293 0.768 0 1.061L9.061 8l4.47 4.47c0.266 0.266 0.29 0.683 0.072 0.976L13.53 13.53c-0.266 0.267-0.683 0.29-0.976 0.073L12.47 13.53 8 9.061l-4.47 4.47c-0.293 0.292-0.767 0.292-1.06 0-0.293-0.294-0.293-0.768 0-1.061L6.939 8l-4.47-4.47C2.204 3.264 2.18 2.847 2.398 2.554L2.47 2.47 2.397 2.554z" 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.21 4.387l0.083-0.094c0.36-0.36 0.928-0.388 1.32-0.083l0.094 0.083L12 10.585l6.293-6.292c0.39-0.39 1.024-0.39 1.414 0 0.39 0.39 0.39 1.024 0 1.414L13.415 12l6.292 6.293c0.36 0.36 0.388 0.928 0.083 1.32l-0.083 0.094c-0.36 0.36-0.928 0.388-1.32 0.083l-0.094-0.083L12 13.415l-6.293 6.292c-0.39 0.39-1.024 0.39-1.414 0-0.39-0.39-0.39-1.024 0-1.414L10.585 12 4.293 5.707c-0.36-0.36-0.388-0.928-0.083-1.32l0.083-0.094L4.21 4.387z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,3 +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="M10.946 2.047l0.005 0.007C11.296 2.02 11.646 2 12 2c5.522 0 10 4.477 10 10s-4.478 10-10 10c-3.21 0-6.066-1.512-7.896-3.862H4.102v-0.003C2.786 16.441 2 14.312 2 12c0-5.162 3.911-9.41 8.932-9.944l0.014-0.009zM12 3.5c-0.053 0-0.106 0-0.16 0.002 0.123 0.244 0.255 0.532 0.374 0.85 0.347 0.921 0.666 2.28 0.1 3.486-0.522 1.113-1.424 1.4-2.09 1.573L10.14 9.432c-0.657 0.17-0.91 0.235-1.093 0.514-0.17 0.257-0.144 0.582 0.061 1.25l0.046 0.148c0.082 0.258 0.18 0.57 0.23 0.863 0.064 0.364 0.082 0.827-0.152 1.275-0.231 0.444-0.538 0.747-0.9 0.945-0.341 0.185-0.694 0.256-0.958 0.302l-0.093 0.017c-0.515 0.09-0.761 0.134-1 0.39-0.187 0.2-0.307 0.553-0.377 1.079-0.029 0.214-0.046 0.427-0.064 0.646l-0.01 0.117c-0.02 0.242-0.044 0.521-0.099 0.76v0.002c1.554 1.696 3.787 2.76 6.27 2.76 1.576 0 3.053-0.43 4.319-1.178-0.099-0.1-0.205-0.218-0.31-0.35-0.34-0.428-0.786-1.164-0.631-2.033 0.074-0.418 0.298-0.768 0.515-1.036 0.22-0.274 0.486-0.526 0.72-0.74l0.158-0.146c0.179-0.163 0.33-0.301 0.46-0.437 0.172-0.18 0.21-0.262 0.212-0.267 0.068-0.224-0.015-0.384-0.106-0.454-0.046-0.035-0.107-0.06-0.19-0.061-0.084 0-0.22 0.024-0.401 0.14-0.21 0.132-0.515 0.214-0.836 0.085-0.267-0.108-0.415-0.314-0.486-0.432-0.144-0.237-0.225-0.546-0.278-0.772-0.04-0.174-0.08-0.372-0.115-0.553l-0.04-0.206c-0.05-0.25-0.094-0.428-0.134-0.54l-0.02-0.037c-0.014-0.027-0.035-0.062-0.064-0.105-0.058-0.089-0.133-0.192-0.227-0.317l-0.11-0.143c-0.16-0.212-0.353-0.463-0.516-0.712-0.196-0.298-0.417-0.688-0.487-1.104-0.037-0.22-0.036-0.475 0.055-0.734 0.094-0.264 0.265-0.482 0.487-0.649 0.483-0.362 1.193-1.172 1.823-1.959 0.288-0.359 0.544-0.695 0.736-0.95C15.222 3.98 13.667 3.5 12 3.5z" android:fillColor="@color/fluent_default_icon_tint"/>
<path android:pathData="M10.946 2.047l0.005 0.007C11.296 2.02 11.646 2 12 2c5.522 0 10 4.477 10 10s-4.478 10-10 10c-3.21 0-6.066-1.512-7.896-3.862H4.102v-0.003C2.786 16.441 2 14.312 2 12c0-5.162 3.911-9.41 8.932-9.944l0.014-0.009zM12 3.5c-0.053 0-0.106 0-0.16 0.002 0.123 0.244 0.255 0.532 0.374 0.85 0.347 0.921 0.666 2.28 0.1 3.486-0.522 1.113-1.424 1.4-2.09 1.573L10.14 9.432c-0.657 0.17-0.91 0.235-1.093 0.514-0.17 0.257-0.144 0.582 0.061 1.25l0.046 0.148c0.082 0.258 0.18 0.57 0.23 0.863 0.064 0.364 0.082 0.827-0.152 1.275-0.231 0.444-0.538 0.747-0.9 0.945-0.341 0.185-0.694 0.256-0.958 0.302l-0.093 0.017c-0.515 0.09-0.761 0.134-1 0.39-0.187 0.2-0.307 0.553-0.377 1.079-0.029 0.214-0.046 0.427-0.064 0.646l-0.01 0.117c-0.02 0.242-0.044 0.521-0.099 0.76v0.002c1.554 1.696 3.787 2.76 6.27 2.76 1.576 0 3.053-0.43 4.319-1.178-0.099-0.1-0.205-0.218-0.31-0.35-0.34-0.428-0.786-1.164-0.631-2.033 0.074-0.418 0.298-0.768 0.515-1.036 0.22-0.274 0.486-0.526 0.72-0.74l0.158-0.146c0.179-0.163 0.33-0.301 0.46-0.437 0.172-0.18 0.21-0.262 0.212-0.267 0.068-0.224-0.015-0.384-0.106-0.454-0.046-0.035-0.107-0.06-0.19-0.061-0.084 0-0.22 0.024-0.401 0.14-0.21 0.132-0.515 0.214-0.836 0.085-0.267-0.108-0.415-0.314-0.486-0.432-0.144-0.237-0.225-0.546-0.278-0.772-0.04-0.174-0.08-0.372-0.115-0.553l-0.04-0.206c-0.05-0.25-0.094-0.428-0.134-0.54l-0.02-0.037c-0.014-0.027-0.035-0.062-0.064-0.105-0.058-0.089-0.133-0.192-0.227-0.317l-0.11-0.143c-0.16-0.212-0.353-0.463-0.516-0.712-0.196-0.298-0.417-0.688-0.487-1.104-0.037-0.22-0.036-0.475 0.055-0.734 0.094-0.264 0.265-0.482 0.487-0.649 0.483-0.362 1.193-1.172 1.823-1.959 0.288-0.359 0.544-0.695 0.736-0.95C15.222 3.98 13.667 3.5 12 3.5zm5.727 2.22c-0.197 0.263-0.461 0.608-0.757 0.978-0.602 0.751-1.4 1.685-2.05 2.187 0.026 0.1 0.1 0.262 0.255 0.498 0.131 0.2 0.281 0.397 0.44 0.604l0.129 0.17c0.172 0.229 0.411 0.548 0.52 0.844 0.087 0.234 0.149 0.519 0.198 0.762l0.049 0.246c0.025 0.13 0.049 0.253 0.075 0.37 0.601-0.172 1.201-0.068 1.67 0.294 0.608 0.47 0.862 1.286 0.624 2.074-0.11 0.362-0.364 0.66-0.563 0.869-0.17 0.177-0.372 0.362-0.556 0.53l-0.132 0.12c-0.23 0.212-0.423 0.4-0.568 0.579-0.148 0.184-0.195 0.299-0.205 0.356-0.04 0.219 0.067 0.51 0.328 0.838 0.118 0.148 0.244 0.274 0.341 0.362l0.033 0.03C19.36 16.872 20.5 14.569 20.5 12c0-2.488-1.069-4.726-2.773-6.28zM3.5 12c0 1.398 0.338 2.718 0.936 3.881 0.085-0.557 0.262-1.248 0.748-1.768 0.6-0.642 1.335-0.763 1.798-0.839l0.13-0.021c0.248-0.044 0.391-0.083 0.502-0.143 0.088-0.049 0.188-0.128 0.288-0.321 0.015-0.028 0.042-0.107 0.004-0.325-0.032-0.187-0.093-0.381-0.172-0.636-0.02-0.06-0.04-0.125-0.06-0.192-0.185-0.604-0.48-1.602 0.12-2.515 0.522-0.792 1.36-0.994 1.893-1.123l0.162-0.04c0.563-0.145 0.883-0.28 1.108-0.758 0.295-0.629 0.168-1.485-0.146-2.32-0.15-0.396-0.324-0.744-0.463-0.994-0.043-0.078-0.083-0.146-0.116-0.202C6.386 4.498 3.5 7.912 3.5 12z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_fluent_settings_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
<shape android:shape="oval">
<stroke android:color="?android:colorPrimary" android:width="2dp"/>
<solid android:color="@color/primary_600"/>
</shape>
</item>
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/progress">
<shape
android:innerRadius="16dp"
android:shape="ring"
android:thickness="4dp"
android:useLevel="true">
<solid android:color="?colorSearchHint"/>
</shape>
</item>
</layer-list>

View File

@@ -6,7 +6,7 @@
android:shape="ring"
android:thickness="4dp"
android:useLevel="true">
<solid android:color="?android:colorAccent"/>
<solid android:color="@color/gray_100"/>
</shape>
</item>
</layer-list>

View File

@@ -65,30 +65,68 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#cc000000"
android:backgroundTint="?colorWindowBackground"
android:padding="8dp"
android:clipToPadding="false"
tools:visibility="visible"
android:visibility="gone">
<ProgressBar
android:id="@+id/progress"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_gravity="center"
android:progressDrawable="@drawable/upload_progress"
android:max="1000"
android:padding="0dp"
android:indeterminateOnly="false"
android:indeterminate="false"/>
<Button
android:id="@+id/retry_upload"
android:layout_width="wrap_content"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
style="?secondaryButtonStyle"
android:text="@string/retry_upload"/>
android:layout_gravity="center_vertical">
<ImageButton
android:id="@+id/retry_or_cancel_upload"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
android:src="@drawable/ic_fluent_dismiss_24_filled"
android:contentDescription="@string/cancel"
android:tint="@color/gray_100"
android:background="@drawable/bg_upload_progress"/>
<ProgressBar
android:id="@+id/progress"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
android:progressDrawable="@drawable/upload_progress"
android:max="1000"
android:padding="0dp"
android:indeterminateOnly="false"
android:indeterminate="false"/>
<TextView
android:id="@+id/state_title"
android:layout_width="match_parent"
android:layout_height="16dp"
android:layout_below="@id/retry_or_cancel_upload"
android:layout_marginTop="16dp"
android:textColor="@color/gray_200"
android:textSize="14dp"
android:gravity="center_horizontal"
android:singleLine="true"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:includeFontPadding="false"
tools:text="Upload failed"/>
<TextView
android:id="@+id/state_text"
android:layout_width="match_parent"
android:layout_height="32dp"
android:layout_below="@id/state_title"
android:includeFontPadding="false"
android:textColor="@color/gray_200"
android:gravity="center_horizontal|top"
android:lines="2"
android:maxLines="2"
android:ellipsize="end"
tools:text="Your device lost connection to the internet"/>
</RelativeLayout>
<ImageButton
android:id="@+id/remove_btn2"
@@ -98,6 +136,7 @@
android:layout_gravity="end|bottom"
android:background="?android:selectableItemBackgroundBorderless"
android:tint="#D92C2C"
android:contentDescription="@string/delete"
android:src="@drawable/ic_fluent_delete_20_regular"/>
</FrameLayout>

View File

@@ -227,7 +227,7 @@
android:tint="@color/compose_button"
android:tintMode="src_in"
android:contentDescription="@string/post_visibility"
android:src="@drawable/ic_fluent_earth_24_filled"/>
android:src="@drawable/ic_fluent_earth_24_regular"/>
<Space
android:layout_width="0px"

View File

@@ -41,7 +41,7 @@
android:layout_margin="16dp"
android:minWidth="145dp"
style="?primaryLargeButtonStyle"
android:text="@string/next" />
android:text="@string/i_agree" />
</LinearLayout>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:elevation="3dp"
android:background="?colorBackgroundLightest"
android:foreground="?android:selectableItemBackground">
<ImageView
android:id="@+id/favicon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="2dp"
android:importantForAccessibility="no"
tools:src="#0f0"/>
<TextView
android:id="@+id/domain"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_toEndOf="@id/favicon"
android:layout_marginStart="4dp"
android:singleLine="true"
android:ellipsize="end"
android:textAppearance="@style/m3_title_small"
android:textColor="?android:textColorPrimary"
android:gravity="center_vertical"
tools:text="joinmastodon.org"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="24dp"
android:layout_below="@id/domain"
android:layout_marginTop="6dp"
android:textAppearance="@style/m3_title_medium"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center_vertical"
tools:text="Mastodon for Android privacy policy"/>
</RelativeLayout>

View File

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

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="64dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:background="@drawable/bg_settings_update"
android:orientation="horizontal">
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:textAppearance="@style/m3_body_medium"
tools:text="@string/update_available"/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:background="?android:selectableItemBackground"
android:textColor="?colorAccentLight"
android:textAllCaps="true"
android:textSize="14dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:stateListAnimator="@null"
tools:text="@string/install_update"/>
<ImageButton
android:id="@+id/cancel_btn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_update_download_progress"
android:tint="?colorSearchHint"
android:contentDescription="@string/cancel"
android:visibility="gone"
android:src="@drawable/ic_fluent_dismiss_16_filled"/>
<ProgressBar
android:id="@+id/progress"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="16dp"
android:progressDrawable="@drawable/update_progress"
android:max="1000"
android:padding="0dp"
android:visibility="gone"
android:indeterminateOnly="false"
android:indeterminate="false"/>
</FrameLayout>
</LinearLayout>
</FrameLayout>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/vis_public"
android:icon="@drawable/ic_fluent_earth_24_filled"
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"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/notify_anyone" android:title="@string/notify_anyone" android:icon="@drawable/ic_fluent_earth_24_filled"/>
<item android:id="@+id/notify_anyone" android:title="@string/notify_anyone" android:icon="@drawable/ic_fluent_earth_24_regular"/>
<item android:id="@+id/notify_follower" android:title="@string/notify_follower" android:icon="@drawable/ic_fluent_people_checkmark_24_regular"/>
<item android:id="@+id/notify_followed" android:title="@string/notify_followed" android:icon="@drawable/ic_fluent_people_checkmark_24_regular"/>
<item android:id="@+id/notify_none" android:title="@string/notify_none" android:icon="@drawable/ic_fluent_prohibited_24_regular"/>

View File

@@ -2,4 +2,9 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome>
<layer-list>
<item android:drawable="@drawable/ic_launcher_monochrome" android:gravity="center"/>
</layer-list>
</monochrome>
</adaptive-icon>

View File

@@ -2,4 +2,9 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome>
<layer-list>
<item android:drawable="@drawable/ic_launcher_monochrome" android:gravity="center"/>
</layer-list>
</monochrome>
</adaptive-icon>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="get_started">الخطوات الأولى</string>
<string name="log_in">تسجيل الدخول</string>
<string name="log_in">تسجيلُ الدخول</string>
<string name="next">التالي</string>
<string name="loading_instance">يَجري الحُصُول على معلومات المَثيل…</string>
<string name="error">خطأ</string>
@@ -16,7 +16,7 @@
<string name="user_sent_follow_request">أرسَلَ طَلَبًا لِمُتابَعَتِك</string>
<string name="user_favorited">فَضَّلَ مَنشُورَك</string>
<string name="notification_boosted">أعادَ تَدوينَ مَنشُورَك</string>
<string name="poll_ended">انتهى استطلاع الرأي</string>
<string name="poll_ended">انتهى استطلاعُ الرأي</string>
<string name="time_seconds">%d ثا</string>
<string name="time_minutes">%d د</string>
<string name="time_hours">%d سا</string>
@@ -54,7 +54,7 @@
<string name="posts">منشورات</string>
<string name="posts_and_replies">مَنشُوراتٌ وَرُدُود</string>
<string name="media">وسائط</string>
<string name="profile_about">عن</string>
<string name="profile_about">حَول</string>
<string name="button_follow">تابِع</string>
<string name="button_following">يُتابِع</string>
<string name="edit_profile">حرّر الملف الشخصي</string>
@@ -260,8 +260,6 @@
<string name="content_warning">تحذير من المحتوى</string>
<string name="add_image_description">أضف وصفًا للصورة…</string>
<string name="retry_upload">حاول الرفع مجددًا</string>
<string name="image_upload_failed">فشل رفع الصورة</string>
<string name="video_upload_failed">فشل رفع الفيديو</string>
<string name="edit_image">حرّر الصورة</string>
<string name="save">احفظ</string>
<string name="add_alt_text">أضف نصًا بديلًا</string>
@@ -409,4 +407,32 @@
</plurals>
<string name="timestamp_via_app">%1$s عبر %2$s</string>
<string name="time_now">الآن</string>
<string name="post_info_reblogs">إعادات التدوين</string>
<string name="post_info_favorites">المفضلة</string>
<string name="last_edit_at_x">آخر تعديل %s</string>
<string name="time_just_now">للتوّ</string>
<string name="edited_timestamp">عُدّل في %s</string>
<string name="edit_original_post">المنشور الأصلي</string>
<string name="edit_text_edited">تم تعديل النص</string>
<string name="edit_spoiler_added">تم إضافة تحذير المحتوى</string>
<string name="edit_spoiler_edited">تم تعديل تحذير المحتوى</string>
<string name="edit_spoiler_removed">تم حذف تحذير المحتوى</string>
<string name="edit_poll_added">تمت إضافة استطلاع للرأي</string>
<string name="edit_poll_edited">تم تعديل الاستطلاع</string>
<string name="edit_poll_removed">تمت إزالة الاستطلاع</string>
<string name="edit_media_added">تمت إضافة الوسائط</string>
<string name="edit_media_removed">تمت إزالة الوسائط</string>
<string name="edit_media_reordered">تمت إعادة ترتيب الوسائط</string>
<string name="edit_marked_sensitive">مُعَين كحساس</string>
<string name="edit_marked_not_sensitive">مُعَين كمنشور غير حساس</string>
<string name="edit_multiple_changed">عُدّل المنشور</string>
<string name="edit">تعديل</string>
<string name="discard_changes">تجاهل التغييرات؟</string>
<string name="upload_failed">فشلت عملية التحميل</string>
<string name="file_upload_time_remaining">%s متبقية</string>
<string name="upload_error_connection_lost">فقد جهازك الاتصال بالإنترنت</string>
<string name="upload_processing">قيد المعالجة…</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -162,8 +162,6 @@
<string name="content_warning">Pizanje sadržaja</string>
<string name="add_image_description">Dodaj opis slike…</string>
<string name="retry_upload">Pokušaj ponovo</string>
<string name="image_upload_failed">Slika se nije mogla sačuvati</string>
<string name="video_upload_failed">Video se nije mogao sačuvati</string>
<string name="edit_image">Uredi sliku</string>
<string name="save">Sačuvaj</string>
<string name="add_alt_text">Dodaj alt tekst</string>
@@ -256,4 +254,7 @@
<string name="file_saved">Datoteka sačuvana</string>
<string name="downloading">Downloading…</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -199,8 +199,6 @@
<string name="content_warning">Avís de contingut</string>
<string name="add_image_description">Afegir una descripció a la imatge…</string>
<string name="retry_upload">Torna a provar de pujar</string>
<string name="image_upload_failed">La imatge no s\'ha pogut penjar</string>
<string name="video_upload_failed">No s\'ha pogut penjar el vídeo</string>
<string name="edit_image">Editar imatge</string>
<string name="save">Desar</string>
<string name="add_alt_text">Afegir text alternatiu</string>
@@ -319,4 +317,7 @@
</plurals>
<string name="timestamp_via_app">%1$s través de %2$s</string>
<string name="time_now">ara</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="get_started">Začínáme</string>
<string name="log_in">Přihlásit</string>
<string name="get_started">Začít</string>
<string name="log_in">Přihlásit se</string>
<string name="next">Další</string>
<string name="loading_instance">Získávání informací o instanci…</string>
<string name="error">Chyba</string>
@@ -9,13 +9,13 @@
<string name="ok">OK</string>
<string name="preparing_auth">Příprava na ověřování…</string>
<string name="finishing_auth">Dokončení ověřování…</string>
<string name="user_boosted">%s sdíleno</string>
<string name="user_boosted">%s boostnul</string>
<string name="in_reply_to">V odpovědi na %s</string>
<string name="notifications">Upozornění</string>
<string name="user_followed_you">vás sleduje</string>
<string name="user_sent_follow_request">vám poslal žádost o sledování</string>
<string name="user_favorited">si oblíbil váš příspěvek</string>
<string name="notification_boosted">sdílel váš příspěvek</string>
<string name="notification_boosted">boostnul váš příspěvek</string>
<string name="poll_ended">anketa skončila</string>
<string name="time_seconds">%d s</string>
<string name="time_minutes">%d m</string>
@@ -27,6 +27,24 @@
<string name="discard_draft">Zahodit koncept?</string>
<string name="discard">Zahodit</string>
<string name="cancel">Zrušit</string>
<plurals name="followers">
<item quantity="one">sledující</item>
<item quantity="few">sledující</item>
<item quantity="many">sledujících</item>
<item quantity="other">sledujících</item>
</plurals>
<plurals name="following">
<item quantity="one">sledovaní</item>
<item quantity="few">sledování</item>
<item quantity="many">sledování</item>
<item quantity="other">sledování</item>
</plurals>
<plurals name="posts">
<item quantity="one">příspěvek</item>
<item quantity="few">příspěvky</item>
<item quantity="many">příspěvků</item>
<item quantity="other">příspěvků</item>
</plurals>
<string name="posts">Příspěvky</string>
<string name="posts_and_replies">Příspěvky a odpovědi</string>
<string name="media">Média</string>
@@ -43,6 +61,12 @@
<string name="report_user">Nahlásit %s</string>
<string name="block_domain">Blokovat %s</string>
<string name="unblock_domain">Odblokovat %s</string>
<plurals name="x_posts">
<item quantity="one">%,d příspěvek</item>
<item quantity="few">%,d příspěvky</item>
<item quantity="many">%,d příspěvků</item>
<item quantity="other">%,d příspěvků</item>
</plurals>
<string name="profile_joined">Účet vytvořen</string>
<string name="done">Hotovo</string>
<string name="loading">Načítání…</string>
@@ -169,7 +193,7 @@
<string name="report_sent_subtitle">Zatímco to posuzujeme, můžete podniknout kroky proti %s.</string>
<string name="unfollow_user">Přestat sledovat %s</string>
<string name="unfollow">Přestat sledovat</string>
<string name="mute_user_explain">Neuvidíte jejich příspěvky nebo sdílení v domovském kanálu. Nebudou vědět, že jsou skrytí.</string>
<string name="mute_user_explain">Neuvidíte jejich příspěvky nebo boostnutí v domovském kanálu. Nebudou vědět, že jsou skrytí.</string>
<string name="block_user_explain">Už nebudou moci sledovat nebo vidět vaše příspěvky, ale mohou vidět, že byli blokováni.</string>
<string name="report_personal_title">Nechcete tohle vidět?</string>
<string name="report_personal_subtitle">Když uvidíte něco, co se vám nelíbí na Mastodonu, můžete odstranit tuto osobu ze svého zážitku.</string>
@@ -204,15 +228,182 @@
<string name="resend">Poslat znovu</string>
<string name="open_email_app">Otevřít e-mailovou aplikaci</string>
<string name="resent_email">Potvrzující e-mail odeslán</string>
<string name="compose_hint">Napište nebo vložte, co máte na mysli</string>
<string name="content_warning">Varování o obsahu</string>
<string name="add_image_description">Přidat popis obrázku…</string>
<string name="retry_upload">Opakovat nahrání</string>
<string name="edit_image">Upravit obrázek</string>
<string name="save">Uložit</string>
<string name="add_alt_text">Přidat alt text</string>
<string name="alt_text_subtitle">Alt text popisuje obrázky pro lidi se špatným nebo žádným zrakem. Pokuste se zahrnout jen tolik obsahu, kolik je potřeba pro pochopení kontextu.</string>
<string name="alt_text_hint">Např. podezřívavě rozhlížející se pes se zúženýma očima namířenýma na kameru.</string>
<string name="visibility_public">Veřejný</string>
<string name="visibility_followers_only">Pouze sledující</string>
<string name="visibility_private">Pouze lidé, které zmíním</string>
<string name="search_all">Vše</string>
<string name="search_people">Lidé</string>
<string name="recent_searches">Nedávná hledání</string>
<string name="step_x_of_n">Krok %1$d z %2$d</string>
<string name="skip">Přeskočit</string>
<string name="notification_type_follow">Noví sledující</string>
<string name="notification_type_favorite">Oblíbené</string>
<string name="notification_type_reblog">Boostnutí</string>
<string name="notification_type_mention">Zmínky</string>
<string name="notification_type_poll">Ankety</string>
<string name="choose_account">Vybrat účet</string>
<string name="err_not_logged_in">Nejprve se přihlaste do Mastodonu</string>
<plurals name="cant_add_more_than_x_attachments">
<item quantity="one">Nelze přidat více než %d multimediálních příloh</item>
<item quantity="few">Nelze přidat více než %d multimediální přílohy</item>
<item quantity="many">Nelze přidat více než %d multimediálních příloh</item>
<item quantity="other">Nelze přidat více než %d multimediálních příloh</item>
</plurals>
<string name="media_attachment_unsupported_type">Soubor %s nepatří mezi podporované typy</string>
<string name="media_attachment_too_big">Soubor %1$s překračuje limit velikosti %2$s MB</string>
<string name="settings_theme">Vizuální podoba</string>
<string name="theme_auto">Automatická</string>
<string name="theme_light">Světlá</string>
<string name="theme_dark">Tmavá</string>
<string name="theme_true_black">Režim skutečně černé</string>
<string name="settings_behavior">Chování</string>
<string name="settings_gif">Přehrávat animované avatary a emoji</string>
<string name="settings_custom_tabs">Používat interní prohlížeč</string>
<string name="settings_notifications">Upozornění</string>
<string name="notify_me_when">Upozornit mě, když</string>
<string name="notify_anyone">kdokoliv</string>
<string name="notify_follower">sledující</string>
<string name="notify_followed">někdo, koho sleduji</string>
<string name="notify_none">nikoho</string>
<string name="notify_favorites">Oblíbil si můj příspěvek</string>
<string name="notify_follow">Sleduje mě</string>
<string name="notify_reblog">Sdílel můj příspěvek</string>
<string name="notify_reblog">Boostnul můj příspěvek</string>
<string name="notify_mention">Zmiňuje mě</string>
<string name="settings_boring">Nudná část</string>
<string name="settings_account">Nastavení účtu</string>
<string name="settings_contribute">Přispějte do Mastodonu</string>
<string name="settings_tos">Podmínky používání</string>
<string name="settings_privacy_policy">Zásady ochrany osobních údajů</string>
<string name="settings_spicy">Ostrá část</string>
<string name="settings_clear_cache">Vymazat mezipaměť médií</string>
<string name="settings_app_version">Mastodon pro Android v%1$s (%2$d)</string>
<string name="media_cache_cleared">Mezipaměť médií vymazána</string>
<string name="confirm_log_out">Opravdu se chcete odhlásit?</string>
<string name="sensitive_content">Citlivý obsah</string>
<string name="sensitive_content_explain">Autor označil toto médium za citlivé. Klepnutím zobrazíte.</string>
<string name="media_hidden">Klepnutím zobrazit</string>
<string name="avatar_description">Jít na profil %s</string>
<string name="more_options">Více možností</string>
<string name="reveal_content">Zobrazit obsah</string>
<string name="hide_content">Skrýt obsah</string>
<string name="new_post">Nový příspěvek</string>
<string name="button_reply">Odpovědět</string>
<string name="button_reblog">Boostnout</string>
<string name="button_favorite">Oblíbit</string>
<string name="button_share">Sdílet</string>
<string name="media_no_description">Média bez popisu</string>
<string name="add_media">Přidat média</string>
<string name="add_poll">Přidat anketu</string>
<string name="emoji">Emoji</string>
<string name="post_visibility">Viditelnost příspěvku</string>
<string name="home_timeline">Domovská časová osa</string>
<string name="my_profile">Můj profil</string>
<string name="media_viewer">Prohlížeč médií</string>
<string name="follow_user">Sledovat %s</string>
<string name="unfollowed_user">Sledování %s ukončeno</string>
<string name="followed_user">Nyní sledujete %s</string>
<string name="open_in_browser">Otevřít v prohlížeči</string>
<string name="hide_boosts_from_user">Skrýt boosty od %s</string>
<string name="show_boosts_from_user">Zobrazit boosty od %s</string>
<string name="signup_reason">proč se chcete připojit?</string>
<string name="signup_reason_note">Toto nám pomůže posoudit vaši žádost.</string>
<string name="clear">Vyčistit</string>
<string name="profile_header">Obrázek v záhlaví</string>
<string name="profile_picture">Profilová fotografie</string>
<string name="reorder">Změnit pořadí</string>
<string name="download">Stáhnout</string>
<string name="permission_required">Vyžadováno oprávnění</string>
<string name="storage_permission_to_download">Aplikace potřebuje přístup k vašemu úložišti, aby mohla uložit tento soubor.</string>
<string name="open_settings">Otevřít nastavení</string>
<string name="error_saving_file">Chyba při ukládání souboru</string>
<string name="file_saved">Soubor uložen</string>
<string name="downloading">Stahování…</string>
<string name="no_app_to_handle_action">Nebyly nalezeny žádné aplikace pro tuto úlohu</string>
<string name="local_timeline">Komunita</string>
<string name="trending_posts_info_banner">Toto jsou příspěvky, které získávají pozornost ve vašem koutu Mastodonu.</string>
<string name="trending_hashtags_info_banner">Toto jsou hashtagy, které získávají pozornost ve vašem koutu Mastodonu.</string>
<string name="trending_links_info_banner">Toto jsou zprávy, které jsou nejvíce sdíleny ve vašem koutu Mastodonu.</string>
<string name="local_timeline_info_banner">Toto jsou nejnovější příspěvky od lidí, kteří používají stejný server Mastodonu jako vy.</string>
<string name="dismiss">Zavřít</string>
<string name="see_new_posts">Zobrazit nové příspěvky</string>
<string name="load_missing_posts">Načíst chybějící příspěvky</string>
<string name="follow_back">Sledovat zpátky</string>
<string name="button_follow_pending">Čekající</string>
<string name="follows_you">Sleduje vás</string>
<string name="manually_approves_followers">Ručně schvaluje sledující</string>
<string name="current_account">Současný účet</string>
<string name="log_out_account">Odhlásit %s</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<plurals name="x_followers">
<item quantity="one">%,d sledující</item>
<item quantity="few">%,d sledující</item>
<item quantity="many">%,d sledujících</item>
<item quantity="other">%,d sledujících</item>
</plurals>
<plurals name="x_following">
<item quantity="one">%,d sledující</item>
<item quantity="few">%,d sledování</item>
<item quantity="many">%,d sledování</item>
<item quantity="other">%,d sledování</item>
</plurals>
<plurals name="x_favorites">
<item quantity="one">%,d oblíbený</item>
<item quantity="few">%,d oblíbené</item>
<item quantity="many">%,d oblíbených</item>
<item quantity="other">%,d oblíbených</item>
</plurals>
<plurals name="x_reblogs">
<item quantity="one">%,d boostnul</item>
<item quantity="few">%,d boostnuli</item>
<item quantity="many">%,d boostnulo</item>
<item quantity="other">%,d boostnulo</item>
</plurals>
<string name="timestamp_via_app">%1$s přes %2$s</string>
<string name="time_now">teď</string>
<string name="post_info_reblogs">Reblogy</string>
<string name="post_info_favorites">Oblíbené</string>
<string name="edit_history">Historie změn</string>
<string name="last_edit_at_x">Poslední úprava %s</string>
<string name="time_just_now">právě teď</string>
<plurals name="x_seconds_ago">
<item quantity="one">Před 1 vteřinou</item>
<item quantity="few">Před %d vteřinami</item>
<item quantity="many">Před %d vteřinami</item>
<item quantity="other">Před %d vteřinami</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="one">před %d minutou</item>
<item quantity="few">před %d minutami</item>
<item quantity="many">před %d minutami</item>
<item quantity="other">před %d minutami</item>
</plurals>
<string name="edited_timestamp">upraveno %s</string>
<string name="edit_original_post">Původní příspěvek</string>
<string name="edit_text_edited">Text upraven</string>
<string name="edit_spoiler_added">Upozornění na obsah bylo přidáno</string>
<string name="edit_spoiler_edited">Upozornění na obsah upraveno</string>
<string name="edit_spoiler_removed">Upozornění na obsah odstraněno</string>
<string name="edit_poll_added">Anketa přidána</string>
<string name="edit_poll_edited">Anketa upravena</string>
<string name="edit_poll_removed">Anketa odstraněna</string>
<string name="edit_media_added">Média přidána</string>
<string name="edit_media_removed">Média odstraněna</string>
<string name="edit_media_reordered">Média přeřazena</string>
<string name="edit_marked_sensitive">Označeno jako citlivé</string>
<string name="edit_marked_not_sensitive">Označeno, že není citlivé</string>
<string name="edit_multiple_changed">Příspěvek upraven</string>
<string name="edit">Upravit</string>
<string name="discard_changes">Zrušit změny?</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -3,27 +3,27 @@
<string name="get_started">Loslegen</string>
<string name="log_in">Anmelden</string>
<string name="next">Weiter</string>
<string name="loading_instance">Lade Serverinformationen…</string>
<string name="loading_instance">Instanzinformationen werden geladen </string>
<string name="error">Fehler</string>
<string name="not_a_mastodon_instance">%s scheint keine Mastodon-Instanz zu sein.</string>
<string name="ok">OK</string>
<string name="preparing_auth">Bereite Authentifizierung vor</string>
<string name="finishing_auth">Authentifizierung erfolgt</string>
<string name="user_boosted">%s teilte</string>
<string name="in_reply_to">Antwort auf %s</string>
<string name="preparing_auth">Authentifizierung wird vorbereitet </string>
<string name="finishing_auth">Authentifizierung abschließen </string>
<string name="user_boosted">%s hat diesen Beitrag geteilt</string>
<string name="in_reply_to">Antwort auf den Beitrag von %s</string>
<string name="notifications">Benachrichtigungen</string>
<string name="user_followed_you">ist dir gefolgt</string>
<string name="user_followed_you">folgt dir jetzt</string>
<string name="user_sent_follow_request">hat dir eine Folgeanfrage gesendet</string>
<string name="user_favorited">hat deinen Beitrag favorisiert</string>
<string name="notification_boosted">hat deinen Beitrag geteilt</string>
<string name="poll_ended">Abstimmung beendet</string>
<string name="time_seconds">%dSek.</string>
<string name="time_minutes">%dMin.</string>
<string name="time_hours">%d Std.</string>
<string name="time_days">%d T</string>
<string name="time_seconds">vor %d Sekunden</string>
<string name="time_minutes">vor %d Minuten</string>
<string name="time_hours">vor %d Stunden</string>
<string name="time_days">vor %d Tagen</string>
<string name="share_toot_title">Teilen</string>
<string name="settings">Einstellungen</string>
<string name="publish">Tröt</string>
<string name="publish">Veröffentlichen</string>
<string name="discard_draft">Entwurf verwerfen?</string>
<string name="discard">Verwerfen</string>
<string name="cancel">Abbrechen</string>
@@ -32,15 +32,15 @@
<item quantity="other">Follower</item>
</plurals>
<plurals name="following">
<item quantity="one">Folgender</item>
<item quantity="other">Folgende</item>
<item quantity="one">Folge ich</item>
<item quantity="other">Folge ich</item>
</plurals>
<plurals name="posts">
<item quantity="one">Beitrag</item>
<item quantity="other">Beiträge</item>
</plurals>
<string name="posts">Beiträge</string>
<string name="posts_and_replies">Beiträge und Antworten</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>
@@ -48,26 +48,26 @@
<string name="button_following">Folge ich</string>
<string name="edit_profile">Profil bearbeiten</string>
<string name="mention_user">%s erwähen</string>
<string name="share_user">Teile das Profil von %s</string>
<string name="share_user">%s teilen</string>
<string name="mute_user">%s stummschalten</string>
<string name="unmute_user">%s nicht mehr stummschalten</string>
<string name="block_user">%s blockieren</string>
<string name="unblock_user">%s nicht mehr blockieren</string>
<string name="report_user">%s melden</string>
<string name="block_domain">%s blockieren</string>
<string name="unblock_domain">%s nicht mehr blockieren</string>
<string name="block_domain">Domain %s blockieren</string>
<string name="unblock_domain">Domain %s nicht mehr blockieren</string>
<plurals name="x_posts">
<item quantity="one">%,d Beitrag</item>
<item quantity="other">%,d Beiträge</item>
</plurals>
<string name="profile_joined">Beigetreten</string>
<string name="done">Fertig</string>
<string name="loading">Lädt</string>
<string name="loading">wird geladen </string>
<string name="field_label">Beschriftung</string>
<string name="field_content">Inhalt</string>
<string name="saving">Speichern…</string>
<string name="saving">Speichern </string>
<string name="post_from_user">Beitrag von %s</string>
<string name="poll_option_hint">Auswahlmöglichkeit %d</string>
<string name="poll_option_hint">%d. Auswahl</string>
<plurals name="x_minutes">
<item quantity="one">%d Minute</item>
<item quantity="other">%d Minuten</item>
@@ -98,23 +98,23 @@
<item quantity="other">%d Tage verbleibend</item>
</plurals>
<plurals name="x_voters">
<item quantity="one">%,d hat gewählt</item>
<item quantity="other">%,d haben gewählt</item>
<item quantity="one">%,d Stimme</item>
<item quantity="other">%,d Stimmen</item>
</plurals>
<string name="poll_closed">Geschlossen</string>
<string name="poll_closed">Beendet</string>
<string name="confirm_mute_title">Konto stummschalten</string>
<string name="confirm_mute">Bestätigen um %s stummzuschalten</string>
<string name="confirm_mute">Bestätigen, um %s stummzuschalten</string>
<string name="do_mute">Stummschalten</string>
<string name="confirm_unmute_title">Konto nicht mehr stummschalten</string>
<string name="confirm_unmute">Bestätigen, um %s nicht mehr stummzuschalten</string>
<string name="do_unmute">Nicht mehr stummschalten</string>
<string name="confirm_block_title">Konto blockieren</string>
<string name="confirm_block_title">Konto sperren</string>
<string name="confirm_block_domain_title">Domain blockieren</string>
<string name="confirm_block">Bestätigen um %s zu blockieren</string>
<string name="do_block">Blockieren</string>
<string name="confirm_unblock_title">Konto nicht mehr blockieren</string>
<string name="confirm_block">Bestätigen, um %s zu blockieren</string>
<string name="do_block">Ja, blockieren</string>
<string name="confirm_unblock_title">Konto nicht mehr sperren</string>
<string name="confirm_unblock_domain_title">Domain nicht mehr blockieren</string>
<string name="confirm_unblock">Bestätigen um %s nicht mehr zu blockieren</string>
<string name="confirm_unblock">Bestätigen, um %s nicht mehr zu blockieren</string>
<string name="do_unblock">Nicht mehr blockieren</string>
<string name="button_muted">Stummgeschaltet</string>
<string name="button_blocked">Blockiert</string>
@@ -143,7 +143,7 @@
<string name="search_hint">Suchen</string>
<string name="hashtags">Hashtags</string>
<string name="news">Nachrichten</string>
<string name="for_you">Für dich</string>
<string name="for_you">Vorschläge</string>
<string name="all_notifications">Alle</string>
<string name="mentions">Erwähnungen</string>
<plurals name="x_people_talking">
@@ -151,15 +151,15 @@
<item quantity="other">%d Personen reden darüber</item>
</plurals>
<plurals name="discussed_x_times">
<item quantity="one">%d mal diskutiert</item>
<item quantity="other">%d mal diskutiert</item>
<item quantity="one">%d Mal diskutiert</item>
<item quantity="other">%d × auf dieser Mastodon-Instanz geteilt</item>
</plurals>
<string name="report_title">%s melden</string>
<string name="report_choose_reason">Sag uns, was mit diesem Beitrag vor sich geht?</string>
<string name="report_choose_reason">Was stimmt mit diesem Beitrag nicht?</string>
<string name="report_choose_reason_account">Was ist los mit %s?</string>
<string name="report_choose_reason_subtitle">Wähle die beste Zugehörigkeit</string>
<string name="report_reason_personal">Das gefällt mir nicht</string>
<string name="report_reason_personal_subtitle">Das ist nicht etwas, was ihr nicht sehen wollt</string>
<string name="report_choose_reason_subtitle">Wähle die bestmögliche Option aus</string>
<string name="report_reason_personal">Der Beitrag gefällt mir nicht</string>
<string name="report_reason_personal_subtitle">Den Inhalt kann man nicht allen zumuten</string>
<string name="report_reason_spam">Das ist Spam</string>
<string name="report_reason_spam_subtitle">Bösartige Links, gefälschtes Engagement oder wiederholte Antworten</string>
<string name="report_reason_violation">Es verstößt gegen Serverregeln</string>
@@ -172,27 +172,27 @@
<string name="report_choose_posts_subtitle">Alles Zutreffende auswählen</string>
<string name="report_comment_title">Gibt es etwas anderes, was wir wissen sollten?</string>
<string name="report_comment_hint">Zusätzliche Kommentare</string>
<string name="sending_report">Sende Meldung</string>
<string name="report_sent_title">Vielen Dank für die Berichterstattung, wir werden uns damit befassen.</string>
<string name="sending_report">Bericht wird gesendet </string>
<string name="report_sent_title">Vielen Dank für die Meldung, wir werden uns damit befassen.</string>
<string name="report_sent_subtitle">Während wir dies überprüfen, kannst du gegen %s vorgehen.</string>
<string name="unfollow_user">%s entfolgen</string>
<string name="unfollow">Entfolgen</string>
<string name="mute_user_explain">Du wirst die Beiträge vom Konto nicht mehr sehen. Das Konto kann dir immer noch folgen, und die Person hinter dem Konto wird deine Beiträge sehen können und nicht wissen, dass du sie stummgeschaltet hast.</string>
<string name="mute_user_explain">Du wirst die eigenen und geteilten Beiträge des Kontos nicht mehr sehen können. Dass du das Profil stummgeschaltet hast, erfährt die Person hinter dem Account aber nicht.</string>
<string name="block_user_explain">Du wirst die Beiträge von diesem Konto nicht sehen. Das Konto wird nicht in der Lage sein, deine Beiträge zu sehen oder dir zu folgen. Die Person hinter dem Konto wird wissen, dass du das Konto blockiert hast.</string>
<string name="report_personal_title">Du willst das nicht mehr sehen?</string>
<string name="report_personal_subtitle">Wenn du etwas auf Mastodon nicht sehen willst, kannst du den Nutzer aus deiner Erfahrung streichen.</string>
<string name="back">Zurück</string>
<string name="instance_catalog_title">Mastodon besteht aus Benutzern auf verschiedenen Servern.</string>
<string name="instance_catalog_subtitle">Wähle einen Server basierend auf deinen Interessen, deiner Region oder einen Allgemeinen. Du kannst trotzdem mit jedem Interagieren, egal auf welchem Server.</string>
<string name="search_communities">Nach Server suchen oder URL eingeben</string>
<string name="instance_rules_title">Hier ein paar Regeln</string>
<string name="instance_rules_subtitle">Nimm dir eine Minute Zeit und gehe kurz durch alle Regeln durch, die %s machen.</string>
<string name="instance_catalog_subtitle">Wähle einen Server basierend auf deinen Interessen oder deiner Region oder einfach einen allgemeinen. Du kannst trotzdem mit jedem interagieren, egal auf welchem Server.</string>
<string name="search_communities">Server suchen oder Adresse eingeben</string>
<string name="instance_rules_title">Einige Grundregeln</string>
<string name="instance_rules_subtitle">Nimm dir eine Minute Zeit, und gehe kurz alle Regeln von %s durch.</string>
<string name="signup_title">Okay, lass uns mit %s anfangen</string>
<string name="edit_photo">bearbeiten</string>
<string name="display_name">anzeigename</string>
<string name="username">nutzername</string>
<string name="display_name">Anzeigename</string>
<string name="username">Nutzername</string>
<string name="email">E-Mail</string>
<string name="password">passwort</string>
<string name="password">Passwort</string>
<string name="password_note">Verwende Großbuchstaben, Sonderzeichen und Zahlen, um deine Passwortstärke zu erhöhen.</string>
<string name="category_academia">Bildung</string>
<string name="category_activism">Aktivismus</string>
@@ -214,76 +214,76 @@
<string name="resent_email">Bestätigungs-E-Mail gesendet</string>
<string name="compose_hint">Was gibt\'s Neues?</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="add_image_description">Füge eine Bildbeschreibung hinzu…</string>
<string name="retry_upload">Upload erneut versuchen</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">Alternativtext hinzufügen</string>
<string name="alt_text_subtitle">Alternativtext erscheint für blinde Menschen. Versuche, nur so viele Details einzubeziehen, um den Kontext zu verstehen.</string>
<string name="alt_text_hint">z.B. Eine Giraffe auf einem Dreirad während sie eine Banane isst</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 Folgende</string>
<string name="visibility_private">Nur Leute, die ich erwähne</string>
<string name="visibility_followers_only">Nur Follower</string>
<string name="visibility_private">Nur erwähnte Profile</string>
<string name="search_all">Alle</string>
<string name="search_people">Personen</string>
<string name="recent_searches">Letzte Suchanfragen</string>
<string name="step_x_of_n">Schritt %1$d von %2$d</string>
<string name="skip">Überspringen</string>
<string name="notification_type_follow">Neue Follower</string>
<string name="notification_type_follow">Neue Folgende</string>
<string name="notification_type_favorite">Favoriten</string>
<string name="notification_type_reblog">Teilungen</string>
<string name="notification_type_reblog">Geteilte Beiträge</string>
<string name="notification_type_mention">Erwähnungen</string>
<string name="notification_type_poll">Umfragen</string>
<string name="choose_account">Konto auswählen</string>
<string name="err_not_logged_in">Melde dich zuerst mit Mastodon an</string>
<string name="err_not_logged_in">Bitte zuerst in Mastodon anmelden</string>
<plurals name="cant_add_more_than_x_attachments">
<item quantity="one">Du kannst nicht mehr als %d Medienanhang hinzufügen</item>
<item quantity="other">Du kannst nicht mehr als %d Medienanhänge hinzufügen</item>
<item quantity="one">Du kannst nicht mehr als %d Medien-Datei anhängen</item>
<item quantity="other">Du kannst nicht mehr als %d Medien-Dateien anhängen</item>
</plurals>
<string name="media_attachment_unsupported_type">Datei %s wird nicht unterstützt</string>
<string name="media_attachment_too_big">Die Dateigröße %1$s übersteigt das Uploadlimit von %2$s MB</string>
<string name="settings_theme">Visuelle Darstellung</string>
<string name="theme_auto">Automatisch</string>
<string name="settings_theme">Design</string>
<string name="theme_auto">Systembedingt</string>
<string name="theme_light">Hell</string>
<string name="theme_dark">Dunkel</string>
<string name="theme_true_black">AMOLED-Modus</string>
<string name="settings_behavior">Verhalten</string>
<string name="settings_gif">Spiele animierte Avatare und Emojis ab</string>
<string name="settings_custom_tabs">Verwende In-App-Browser</string>
<string name="settings_behavior">App-Verhalten</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>
<string name="notify_me_when">Benachrichtige mich, wenn</string>
<string name="notify_anyone">jeder</string>
<string name="notify_follower">ein Follower</string>
<string name="notify_followed">jemand, dem ich folge</string>
<string name="notify_anyone">irgendjemand</string>
<string name="notify_follower">ein Follower von mir</string>
<string name="notify_followed">jemand, dem ich folge,</string>
<string name="notify_none">niemand</string>
<string name="notify_favorites">Meinen Beitrag favorisiert</string>
<string name="notify_follow">Mir folgt</string>
<string name="notify_reblog">Meinen Beitrag teilt</string>
<string name="notify_mention">Mich erwähnt</string>
<string name="settings_boring">Die Langeweile-Zone</string>
<string name="notify_favorites">meinen Beitrag favorisiert</string>
<string name="notify_follow">mir folgt</string>
<string name="notify_reblog">einen meiner Beiträge geteilt hat</string>
<string name="notify_mention">mich erwähnt</string>
<string name="settings_boring">Langweiliges</string>
<string name="settings_account">Kontoeinstellungen</string>
<string name="settings_contribute">Zu Mastodon beitragen</string>
<string name="settings_tos">AGBs</string>
<string name="settings_privacy_policy">Datenschutzerklärung</string>
<string name="settings_spicy">Die gefährliche Zone</string>
<string name="settings_contribute">Bei Mastodon unterstützen und mitmachen</string>
<string name="settings_tos">Nutzungsbedingungen</string>
<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">NSFW-Inhalt</string>
<string name="sensitive_content_explain">Der Autor hat den Inhalt als NSFW markiert. Tippen zum Öffnen.</string>
<string name="sensitive_content">Sensibler Inhalt</string>
<string name="sensitive_content_explain">Der Autor hat den Inhalt als sensiblen Inhalt markiert. Tippen zum Anzeigen.</string>
<string name="media_hidden">Tippen zum Öffnen</string>
<string name="avatar_description">Gehe zu %s\'s Profil</string>
<string name="avatar_description">Das Profil von %s öffnen</string>
<string name="more_options">Mehr Optionen</string>
<string name="reveal_content">Inhalt anzeigen</string>
<string name="hide_content">Inhalt verstecken</string>
<string name="new_post">Neuer Beitrag</string>
<string name="button_reply">Antworten</string>
<string name="button_reblog">Erneut teilen</string>
<string name="button_reblog">Teilen</string>
<string name="button_favorite">Favorisieren</string>
<string name="button_share">Teilen</string>
<string name="media_no_description">Medien ohne Beschreibung</string>
@@ -297,54 +297,102 @@
<string name="follow_user">%s folgen</string>
<string name="unfollowed_user">%s entfolgt</string>
<string name="followed_user">Du folgst nun %s</string>
<string name="open_in_browser">Im Browser öffnen</string>
<string name="hide_boosts_from_user">Verstecke Teilungen von %s</string>
<string name="show_boosts_from_user">Zeige Teilungen von %s</string>
<string name="signup_reason">Warum möchtest du beitreten?</string>
<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="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>
<string name="profile_header">Header-Bild</string>
<string name="profile_header">Kopfbild</string>
<string name="profile_picture">Profilbild</string>
<string name="reorder">Neu sortieren</string>
<string name="download">Herunterladen</string>
<string name="permission_required">Berechtigung erforderlich</string>
<string name="storage_permission_to_download">Die App benötigt Zugriff auf Ihren Speicher, um diese Datei zu speichern.</string>
<string name="storage_permission_to_download">Die App benötigt Zugriff auf den Speicher deines Geräts, um diese Datei zu speichern.</string>
<string name="open_settings">Einstellungen öffnen</string>
<string name="error_saving_file">Fehler beim Speichern der Datei</string>
<string name="file_saved">Datei gespeichert</string>
<string name="downloading">Herunterladen…</string>
<string name="downloading">wird heruntergeladen </string>
<string name="no_app_to_handle_action">Es gibt keine App, um diese Aktion auszuführen</string>
<string name="local_timeline">Community</string>
<string name="trending_posts_info_banner">Dies sind die Beiträge, die in deiner Mastodon-Bubble beliebter werden.</string>
<string name="trending_hashtags_info_banner">Dies sind die Hashtags in deiner Mastodon-Bubble.</string>
<string name="trending_links_info_banner">Dies sind die Nachrichten, die am meisten in deiner Mastodon-Bubble geteilt werden.</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="dismiss">Verwerfen</string>
<string name="see_new_posts">Neue Beiträge anzeigen</string>
<string name="load_missing_posts">Fehlende Beiträge laden</string>
<string name="load_missing_posts">Weitere Beiträge laden</string>
<string name="follow_back">Zurück folgen</string>
<string name="button_follow_pending">Ausstehend</string>
<string name="follows_you">Folgt dir</string>
<string name="manually_approves_followers">Genehmigt Folgende manuell</string>
<string name="current_account">Aktuelles Konto</string>
<string name="log_out_account">Ausloggen</string>
<string name="log_out_account">%s abmelden</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<plurals name="x_followers">
<item quantity="one">%,d Follower</item>
<item quantity="other">%,d Follower</item>
</plurals>
<plurals name="x_following">
<item quantity="one">%,d Gefolgt</item>
<item quantity="other">%,d Gefolgt</item>
<item quantity="one">%,d folge ich</item>
<item quantity="other">%,d folge ich</item>
</plurals>
<plurals name="x_favorites">
<item quantity="one">%,d Favorit</item>
<item quantity="one">%,d × favorisiert</item>
<item quantity="other">%,d Favoriten</item>
</plurals>
<plurals name="x_reblogs">
<item quantity="one">%,d Reblog</item>
<item quantity="other">%,d Reblogs</item>
<item quantity="one">%,d × geteilt</item>
<item quantity="other">%,d × geteilt</item>
</plurals>
<string name="timestamp_via_app">%1$s via %2$s</string>
<string name="time_now">jetzt</string>
<string name="post_info_reblogs">Geteilte Beiträge</string>
<string name="post_info_favorites">Favoriten</string>
<string name="edit_history">Verlauf bearbeiten</string>
<string name="last_edit_at_x">Letzte Bearbeitung: %s</string>
<string name="time_just_now">gerade eben</string>
<plurals name="x_seconds_ago">
<item quantity="one">vor %d Sekunde</item>
<item quantity="other">vor %d Sekunden</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="one">vor %d Minute</item>
<item quantity="other">vor %d Minuten</item>
</plurals>
<string name="edited_timestamp">bearbeitet %s</string>
<string name="edit_original_post">Ursprünglicher Beitrag</string>
<string name="edit_text_edited">Text bearbeitet</string>
<string name="edit_spoiler_added">Inhaltwarnung hinzugefügt</string>
<string name="edit_spoiler_edited">Inhaltwarnung bearbeitet</string>
<string name="edit_spoiler_removed">Inhaltwarnung entfernt</string>
<string name="edit_poll_added">Umfrage hinzugefügt</string>
<string name="edit_poll_edited">Umfrage bearbeitet</string>
<string name="edit_poll_removed">Umfrage entfernt</string>
<string name="edit_media_added">Medien hinzugefügt</string>
<string name="edit_media_removed">Medien entfernt</string>
<string name="edit_media_reordered">Medien umsortiert</string>
<string name="edit_marked_sensitive">Als NSFW markiert</string>
<string name="edit_marked_not_sensitive">Als nicht NSFW markiert</string>
<string name="edit_multiple_changed">Beitrag bearbeitet</string>
<string name="edit">Bearbeiten</string>
<string name="discard_changes">Änderungen verwerfen?</string>
<string name="upload_failed">Upload fehlgeschlagen</string>
<string name="file_size_bytes">%d bytes</string>
<string name="file_size_kb">%.2f KB</string>
<string name="file_size_mb">%.2f MB</string>
<string name="file_size_gb">%.2f GB</string>
<string name="file_upload_progress">%1$s von %2$s</string>
<string name="file_upload_time_remaining">%s verbleibend</string>
<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">Mastodos %s kann nun heruntergeladen werden.</string>
<!-- %s is version like 1.2.3 -->
<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>
</resources>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -204,8 +204,6 @@
<string name="content_warning">Advertencia de contenido</string>
<string name="add_image_description">Añadir descripción de la imagen…</string>
<string name="retry_upload">Reintentar subida</string>
<string name="image_upload_failed">Error al subir la imagen</string>
<string name="video_upload_failed">Error al subir el vídeo</string>
<string name="edit_image">Editar imagen</string>
<string name="save">Guardar</string>
<string name="add_alt_text">Añadir texto alternativo</string>
@@ -333,4 +331,37 @@
</plurals>
<string name="timestamp_via_app">%1$s a través de %2$s</string>
<string name="time_now">ahora</string>
<string name="post_info_reblogs">Reblogueos</string>
<string name="post_info_favorites">Favoritos</string>
<string name="edit_history">Editar historial</string>
<string name="last_edit_at_x">Última edición: %s</string>
<string name="time_just_now">justo ahora</string>
<plurals name="x_seconds_ago">
<item quantity="one">Hace %d segundo</item>
<item quantity="other">Hace %d segundos</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="one">Hace %d minuto</item>
<item quantity="other">Hace %d minutos</item>
</plurals>
<string name="edited_timestamp">editado %s</string>
<string name="edit_original_post">Publicación original</string>
<string name="edit_text_edited">Texto editado</string>
<string name="edit_spoiler_added">Advertencia de contenido añadida</string>
<string name="edit_spoiler_edited">Advertencia de contenido editada</string>
<string name="edit_spoiler_removed">Advertencia de contenido eliminada</string>
<string name="edit_poll_added">Encuesta añadida</string>
<string name="edit_poll_edited">Encuesta editada</string>
<string name="edit_poll_removed">Encuesta eliminada</string>
<string name="edit_media_added">Contenido multimedia añadido</string>
<string name="edit_media_removed">Contenido multimedia eliminado</string>
<string name="edit_media_reordered">Contenido multimedia reordenado</string>
<string name="edit_marked_sensitive">Marcado como sensible</string>
<string name="edit_marked_not_sensitive">Marcado como no sensible</string>
<string name="edit_multiple_changed">Publicación editada</string>
<string name="edit">Editar</string>
<string name="discard_changes">¿Descartar cambios?</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -184,8 +184,6 @@
<string name="content_warning">Edukiaren abisua</string>
<string name="add_image_description">Gehitu irudiaren deskribapena…</string>
<string name="retry_upload">Saiatu berriro igotzen</string>
<string name="image_upload_failed">Irudia ezin izan da kargatu</string>
<string name="video_upload_failed">Bideoa ezin izan da kargatu</string>
<string name="edit_image">Editatu irudia</string>
<string name="save">Gorde</string>
<string name="add_alt_text">Gehitu ordezko testua</string>
@@ -258,4 +256,7 @@
<string name="file_saved">Fitxategia gorde da</string>
<string name="downloading">Jeisten…</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -49,4 +49,7 @@
<string name="downloading">Ladataan…</string>
<string name="follow_back">Seuraa takaisin</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -204,8 +204,6 @@
<string name="content_warning">Avertissement sur le contenu</string>
<string name="add_image_description">Ajouter une description de limage…</string>
<string name="retry_upload">Réessayer le téléversement</string>
<string name="image_upload_failed">Échec du téléversement de limage</string>
<string name="video_upload_failed">Échec du téléversement de la vidéo</string>
<string name="edit_image">Modifier limage</string>
<string name="save">Enregistrer</string>
<string name="add_alt_text">Ajouter un texte alternatif</string>
@@ -333,4 +331,50 @@
</plurals>
<string name="timestamp_via_app">%1$s via %2$s</string>
<string name="time_now">à linstant</string>
<string name="post_info_reblogs">Partages</string>
<string name="post_info_favorites">Favoris</string>
<string name="edit_history">Historique des éditions</string>
<string name="last_edit_at_x">Dernière édition %s</string>
<string name="time_just_now">à linstant</string>
<plurals name="x_seconds_ago">
<item quantity="one">Il y a %d seconde</item>
<item quantity="other">Il y a %d secondes</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="one">Il y a %d minute</item>
<item quantity="other">Il y a %d minutes</item>
</plurals>
<string name="edited_timestamp">édité %s</string>
<string name="edit_original_post">Message originel</string>
<string name="edit_text_edited">Texte modifié</string>
<string name="edit_spoiler_added">Avertissement de contenu ajouté</string>
<string name="edit_spoiler_edited">Avertissement de contenu modifié</string>
<string name="edit_spoiler_removed">Avertissement de contenu supprimé</string>
<string name="edit_poll_added">Sondage ajouté</string>
<string name="edit_poll_edited">Sondage édité</string>
<string name="edit_poll_removed">Sondage supprimé</string>
<string name="edit_media_added">Média ajouté</string>
<string name="edit_media_removed">Média supprimé</string>
<string name="edit_media_reordered">Média réordonné</string>
<string name="edit_marked_sensitive">Marqué comme sensible</string>
<string name="edit_marked_not_sensitive">Marqué comme non-sensible</string>
<string name="edit_multiple_changed">Message édité</string>
<string name="edit">Éditer</string>
<string name="discard_changes">Ignorer les modifications ?</string>
<string name="upload_failed">Échec de lenvoi</string>
<string name="file_size_bytes">%d octets</string>
<string name="file_size_kb">%.2f Ko</string>
<string name="file_size_mb">%.2f Mo</string>
<string name="file_size_gb">%.2f Go</string>
<string name="file_upload_progress">%1$s sur %2$s</string>
<string name="file_upload_time_remaining">%s restant</string>
<string name="upload_error_connection_lost">Votre appareil a perdu la connexion à internet</string>
<string name="upload_processing">Traitement en cours…</string>
<!-- %s is version like 1.2.3 -->
<string name="update_available">Mastodon pour Android %s est prêt à être téléchargé.</string>
<!-- %s is version like 1.2.3 -->
<string name="update_ready">Mastodon pour Android %s est téléchargé et prêt à être installé.</string>
<!-- %s is file size -->
<string name="download_update">Téléchargement (%s)</string>
<string name="install_update">Installer</string>
</resources>

View File

@@ -204,8 +204,6 @@
<string name="content_warning">Aviso sobre o contido</string>
<string name="add_image_description">Engadir descrición da imaxe…</string>
<string name="retry_upload">Reintentar a suba</string>
<string name="image_upload_failed">Non se puido subir a imaxe</string>
<string name="video_upload_failed">Non se puido subir o vídeo</string>
<string name="edit_image">Editar imaxe</string>
<string name="save">Gardar</string>
<string name="add_alt_text">Engadir texto descritivo</string>
@@ -333,4 +331,46 @@
</plurals>
<string name="timestamp_via_app">%1$s vía %2$s</string>
<string name="time_now">agora</string>
<string name="post_info_reblogs">Promocións</string>
<string name="post_info_favorites">Favoritos</string>
<string name="edit_history">Editar historial</string>
<string name="last_edit_at_x">Última edición %s</string>
<string name="time_just_now">xusto agora</string>
<plurals name="x_seconds_ago">
<item quantity="one">fai %d segundo</item>
<item quantity="other">fai %d segundos</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="one">fai %d minuto</item>
<item quantity="other">fai %d minutos</item>
</plurals>
<string name="edited_timestamp">editado %s</string>
<string name="edit_original_post">Publicación orixinal</string>
<string name="edit_text_edited">Texto editado</string>
<string name="edit_spoiler_added">Engadido aviso sobre o contido</string>
<string name="edit_spoiler_edited">Editado o aviso sobre o contido</string>
<string name="edit_spoiler_removed">Eliminado o aviso sobre o contido</string>
<string name="edit_poll_added">Enquisa engadida</string>
<string name="edit_poll_edited">Enquisa editada</string>
<string name="edit_poll_removed">Enquisa eliminada</string>
<string name="edit_media_added">Engadido multimedia</string>
<string name="edit_media_removed">Engadido eliminado</string>
<string name="edit_media_reordered">Multimedia reordenado</string>
<string name="edit_marked_sensitive">Marcado como sensible</string>
<string name="edit_marked_not_sensitive">Marcado como non sensible</string>
<string name="edit_multiple_changed">Publicación editada</string>
<string name="edit">Editar</string>
<string name="discard_changes">Descartar cambios?</string>
<string name="upload_failed">Fallou a carga</string>
<string name="file_size_bytes">%d bytes</string>
<string name="file_size_kb">%.2f KB</string>
<string name="file_size_mb">%.2f MB</string>
<string name="file_size_gb">%.2f GB</string>
<string name="file_upload_progress">%1$s de %2$s</string>
<string name="file_upload_time_remaining">%s restante</string>
<string name="upload_error_connection_lost">O dispositivo perdeu a conexión a internet</string>
<string name="upload_processing">Procesando…</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -212,8 +212,6 @@
<string name="content_warning">Upozorenje za sadržaj</string>
<string name="add_image_description">Dodaj opis slike…</string>
<string name="retry_upload">Ponovi prijenos</string>
<string name="image_upload_failed">Prijenos slike nije uspio</string>
<string name="video_upload_failed">Prijenos videa nije uspio</string>
<string name="edit_image">Uredi sliku</string>
<string name="save">Spremi</string>
<string name="add_alt_text">Dodaj altenativni tekst</string>
@@ -307,4 +305,7 @@
<string name="downloading">Preuzimanje…</string>
<string name="no_app_to_handle_action">Ne postoji aplikacija za rukovanje ovom radnjom</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -51,4 +51,7 @@
<string name="unblock_user">Հանել արգելափակումից %s</string>
<string name="report_user">Բողոքել %s</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -204,8 +204,6 @@
<string name="content_warning">Avviso sul contenuto</string>
<string name="add_image_description">Aggiungi descrizione immagine…</string>
<string name="retry_upload">Riprova caricamento</string>
<string name="image_upload_failed">Impossibile caricare il file</string>
<string name="video_upload_failed">Impossibile caricare il video</string>
<string name="edit_image">Modifica immagine</string>
<string name="save">Salva</string>
<string name="add_alt_text">Aggiungi testo alt</string>
@@ -333,4 +331,50 @@
</plurals>
<string name="timestamp_via_app">%1$s tramite %2$s</string>
<string name="time_now">ora</string>
<string name="post_info_reblogs">Condivisioni</string>
<string name="post_info_favorites">Preferiti</string>
<string name="edit_history">Modifica cronologia</string>
<string name="last_edit_at_x">Ultima modifica: %s</string>
<string name="time_just_now">proprio ora</string>
<plurals name="x_seconds_ago">
<item quantity="one">%d secondo fa</item>
<item quantity="other">%d secondi fa</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="one">%d minuto fa</item>
<item quantity="other">%d minuti fa</item>
</plurals>
<string name="edited_timestamp">modificato %s</string>
<string name="edit_original_post">Post originale</string>
<string name="edit_text_edited">Testo modificato</string>
<string name="edit_spoiler_added">Avviso sul contenuto aggiunto</string>
<string name="edit_spoiler_edited">Avviso sul contenuto modificato</string>
<string name="edit_spoiler_removed">Avviso sul contenuto rimosso</string>
<string name="edit_poll_added">Sondaggio aggiunto</string>
<string name="edit_poll_edited">Sondaggio modificato</string>
<string name="edit_poll_removed">Sondaggio rimosso</string>
<string name="edit_media_added">Media aggiunto</string>
<string name="edit_media_removed">Media rimosso</string>
<string name="edit_media_reordered">Media riordinati</string>
<string name="edit_marked_sensitive">Contrassegnato come sensibile</string>
<string name="edit_marked_not_sensitive">Contrassegnato come non sensibile</string>
<string name="edit_multiple_changed">Post modificato</string>
<string name="edit">Modifica</string>
<string name="discard_changes">Annullare le modifiche?</string>
<string name="upload_failed">Caricamento fallito</string>
<string name="file_size_bytes">%d bytes</string>
<string name="file_size_kb">%.2f KB</string>
<string name="file_size_mb">%.2f MB</string>
<string name="file_size_gb">%.2f GB</string>
<string name="file_upload_progress">%1$s di %2$s</string>
<string name="file_upload_time_remaining">%s rimanenti</string>
<string name="upload_error_connection_lost">Il tuo dispositivo ha perso la connessione a internet</string>
<string name="upload_processing">In elaborazione…</string>
<!-- %s is version like 1.2.3 -->
<string name="update_available">Mastodon per Android %s è pronto per il download.</string>
<!-- %s is version like 1.2.3 -->
<string name="update_ready">Mastodon per Android %s è scaricato e pronto per l\'installazione.</string>
<!-- %s is file size -->
<string name="download_update">Scarica (%s)</string>
<string name="install_update">Installa</string>
</resources>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -25,7 +25,7 @@
<string name="settings">設定</string>
<string name="publish">公開</string>
<string name="discard_draft">下書きを削除しますか?</string>
<string name="discard">下書き</string>
<string name="discard">破棄</string>
<string name="cancel">キャンセル</string>
<plurals name="followers">
<item quantity="other">フォロワー</item>
@@ -190,8 +190,6 @@
<string name="content_warning">コンテンツ警告</string>
<string name="add_image_description">画像の説明を追加</string>
<string name="retry_upload">アップロードを再試行</string>
<string name="image_upload_failed">画像のアップロードに失敗しました</string>
<string name="video_upload_failed">ビデオのアップロードに失敗しました。</string>
<string name="edit_image">画像を編集</string>
<string name="save">保存</string>
<string name="add_alt_text">代替テキストを追加</string>
@@ -314,4 +312,7 @@
</plurals>
<string name="timestamp_via_app">%1$sに%2$s経由</string>
<string name="time_now"></string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -199,8 +199,6 @@
<string name="content_warning">Alɣu ɣef ugbur</string>
<string name="add_image_description">Rnu aglam n tugna…</string>
<string name="retry_upload">Ɛreḍ asali i tikkelt-nniḍen</string>
<string name="image_upload_failed">Asali n tugna yecceḍ</string>
<string name="video_upload_failed">Asali n tvidyut yecceḍ</string>
<string name="edit_image">Ẓreg tugna</string>
<string name="save">Sekles</string>
<string name="add_alt_text">Rnu ɣer uḍris</string>
@@ -300,9 +298,15 @@
<string name="dismiss">Ffer</string>
<string name="see_new_posts">Ẓer tissufaɣ timaynutin</string>
<string name="load_missing_posts">Sali tisuffaɣ i iruḥen</string>
<string name="follow_back">Ḍfeṛ</string>
<string name="button_follow_pending">Yettraǧu</string>
<string name="follows_you">Yeṭṭafaṛ-ik·em-id</string>
<string name="current_account">Amiḍan amiran</string>
<string name="log_out_account">Ffeɣ seg %s</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<string name="timestamp_via_app">%1$s s %2$s</string>
<string name="time_now">tura</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -190,8 +190,6 @@
<string name="content_warning">열람 주의</string>
<string name="add_image_description">이미지 설명 추가…</string>
<string name="retry_upload">업로드 재시도</string>
<string name="image_upload_failed">이미지 업로드 실패</string>
<string name="video_upload_failed">동영상 업로드 실패</string>
<string name="edit_image">이미지 편집</string>
<string name="save">저장</string>
<string name="add_alt_text">대체 텍스트 추가</string>
@@ -314,4 +312,7 @@
</plurals>
<string name="timestamp_via_app">%1$s에 %2$s에서</string>
<string name="time_now">방금</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -83,4 +83,7 @@
<string name="file_saved">Fichièr salvagardat</string>
<string name="downloading">Telecargament…</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -232,8 +232,6 @@
<string name="content_warning">Ostrzeżenie o zawartości</string>
<string name="add_image_description">Dodaj opis obrazu…</string>
<string name="retry_upload">Ponów wysyłanie</string>
<string name="image_upload_failed">Nie udało się wysłać obrazu</string>
<string name="video_upload_failed">Nie udało się wysłać filmu</string>
<string name="edit_image">Edytuj obraz</string>
<string name="save">Zapisz</string>
<string name="add_alt_text">Dodaj tekst alternatywny</string>
@@ -371,4 +369,50 @@
</plurals>
<string name="timestamp_via_app">%1$s przez %2$s</string>
<string name="time_now">przed chwilą</string>
<string name="post_info_reblogs">Podbicia</string>
<string name="post_info_favorites">Ulubione</string>
<string name="edit_history">Historia edycji</string>
<string name="last_edit_at_x">Ostatnia zmiana: %s</string>
<string name="time_just_now">przed chwilą</string>
<plurals name="x_seconds_ago">
<item quantity="one">%d sekundę temu</item>
<item quantity="few">%d sekund temu</item>
<item quantity="many">%d sekund temu</item>
<item quantity="other">%d sekund temu</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="one">%d minutę temu</item>
<item quantity="few">%d minuty temu</item>
<item quantity="many">%d minut temu</item>
<item quantity="other">%d min temu</item>
</plurals>
<string name="edited_timestamp">edytowano %s</string>
<string name="edit_original_post">Oryginalny post</string>
<string name="edit_text_edited">Tekst edytowany</string>
<string name="edit_spoiler_added">Ostrzeżenie o zawartości</string>
<string name="edit_spoiler_edited">Ostrzeżenie o zawartości edytowane</string>
<string name="edit_spoiler_removed">Ostrzeżenie o treści usunięte</string>
<string name="edit_poll_added">Dodano ankietę</string>
<string name="edit_poll_edited">Edytowano ankietę</string>
<string name="edit_poll_removed">Ankieta usunięta</string>
<string name="edit_media_added">Dodano media</string>
<string name="edit_media_removed">Media usunięte</string>
<string name="edit_media_reordered">Kolejność mediów</string>
<string name="edit_marked_sensitive">Oznaczono jako wrażliwe</string>
<string name="edit_marked_not_sensitive">Oznaczono jako wrażliwe</string>
<string name="edit_multiple_changed">Edytowano post</string>
<string name="edit">Edytuj</string>
<string name="discard_changes">Odrzucić zmiany?</string>
<string name="upload_failed">Przesyłanie nie powiodło się</string>
<string name="file_size_bytes">%d bajtów</string>
<string name="file_size_kb">%.2f KB</string>
<string name="file_size_mb">%.2f MB</string>
<string name="file_size_gb">%.2f GB</string>
<string name="file_upload_progress">%1$s z %2$s</string>
<string name="file_upload_time_remaining">Pozostało %s</string>
<string name="upload_error_connection_lost">Twoje urządzenie utraciło połączenie z Internetem</string>
<string name="upload_processing">Przetwarzanie…</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -9,11 +9,13 @@
<string name="ok">OK</string>
<string name="preparing_auth">Preparando para autenticação…</string>
<string name="finishing_auth">Finalizando autenticação…</string>
<string name="user_boosted">%s reblogado</string>
<string name="in_reply_to">Em resposta à %s</string>
<string name="notifications">Notificações</string>
<string name="user_followed_you">seguiu você</string>
<string name="user_sent_follow_request">enviou uma solicitação para você seguir</string>
<string name="user_favorited">favoritou seu post</string>
<string name="notification_boosted">reblogado sua postagem</string>
<string name="poll_ended">enquete encerrada</string>
<string name="time_seconds">%ds</string>
<string name="time_minutes">%dm</string>
@@ -159,6 +161,7 @@
<string name="report_sent_subtitle">Enquanto revisamos isso, você pode tomar medidas contra @%s.</string>
<string name="unfollow_user">Deixar de seguir @%s</string>
<string name="unfollow">Deixar de seguir</string>
<string name="mute_user_explain">Você não verá suas postagens ou reblogs em seu feed inicial. Eles não saberão que foram silenciados.</string>
<string name="block_user_explain">Eles não poderão mais seguir ou ver seus post, mas poderão ver se foram bloqueados.</string>
<string name="report_personal_title">Não quer ver isto?</string>
<string name="report_personal_subtitle">Quando você vê algo que não gosta no Mastodon, pode remover a pessoa da sua experiência.</string>
@@ -197,8 +200,6 @@
<string name="content_warning">Aviso de conteúdo</string>
<string name="add_image_description">Adicionar descrição da imagem…</string>
<string name="retry_upload">Tentar enviar novamente</string>
<string name="image_upload_failed">Falha ao carregar imagem</string>
<string name="video_upload_failed">Falha no envio do vídeo</string>
<string name="edit_image">Editar imagem</string>
<string name="save">Salvar</string>
<string name="add_alt_text">Adicionar texto alternativo</string>
@@ -214,6 +215,7 @@
<string name="skip">Pular</string>
<string name="notification_type_follow">Novos seguidores</string>
<string name="notification_type_favorite">Favoritos</string>
<string name="notification_type_reblog">Reblogues</string>
<string name="notification_type_mention">Menções</string>
<string name="notification_type_poll">Enquetes</string>
<string name="choose_account">Escolher conta</string>
@@ -240,6 +242,7 @@
<string name="notify_none">ninguém</string>
<string name="notify_favorites">Favoritaram minha publicação</string>
<string name="notify_follow">Sigam-me</string>
<string name="notify_reblog">Rebloga meu post</string>
<string name="notify_mention">Mencione-me</string>
<string name="settings_boring">A zona entediante</string>
<string name="settings_account">Configurações da conta</string>
@@ -260,6 +263,7 @@
<string name="hide_content">Ocultar conteúdo</string>
<string name="new_post">Novo post</string>
<string name="button_reply">Responder</string>
<string name="button_reblog">Reblogar</string>
<string name="button_favorite">Favoritar</string>
<string name="button_share">Compartilhar</string>
<string name="media_no_description">Mídia sem descrição</string>
@@ -274,6 +278,8 @@
<string name="unfollowed_user">Deixar de seguir @%s</string>
<string name="followed_user">Você agora está seguindo %s</string>
<string name="open_in_browser">Abrir no navegador</string>
<string name="hide_boosts_from_user">Ocultar reblogues de %s</string>
<string name="show_boosts_from_user">Mostrar reblogues de %s</string>
<string name="signup_reason">por que você quer entrar?</string>
<string name="signup_reason_note">Isso vai nos ajudar a revisar sua aplicação.</string>
<string name="clear">Limpar</string>
@@ -317,4 +323,30 @@
</plurals>
<string name="timestamp_via_app">%1$s via %2$s</string>
<string name="time_now">agora</string>
<string name="post_info_reblogs">Reblogues</string>
<string name="post_info_favorites">Favoritos</string>
<string name="edit_history">Editar histórico</string>
<string name="last_edit_at_x">Última edição %s</string>
<string name="time_just_now">agora mesmo</string>
<plurals name="x_seconds_ago">
<item quantity="one">%d segundo atrás</item>
<item quantity="other">%d segundos atrás</item>
</plurals>
<string name="edited_timestamp">editado %s</string>
<string name="edit_original_post">Postagem original</string>
<string name="edit_text_edited">Texto editado</string>
<string name="edit_spoiler_added">Aviso de conteúdo adicionado</string>
<string name="edit_spoiler_edited">Aviso de conteúdo editado</string>
<string name="edit_spoiler_removed">Aviso de conteúdo removido</string>
<string name="edit_marked_sensitive">Marcadas como sensíveis</string>
<string name="edit_marked_not_sensitive">Marcado como não sensível</string>
<string name="edit_multiple_changed">Publicação editada</string>
<string name="edit">Editar</string>
<string name="discard_changes">Descartar alterações?</string>
<string name="upload_failed">Falha no upload</string>
<string name="upload_error_connection_lost">Seu dispositivo perdeu a conexão com a internet</string>
<string name="upload_processing">Processando…</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -178,8 +178,6 @@
<string name="content_warning">Aviso de conteúdo</string>
<string name="add_image_description">Adicionar descrição da imagem…</string>
<string name="retry_upload">Tentar enviar novamente</string>
<string name="image_upload_failed">Falha ao carregar imagem</string>
<string name="video_upload_failed">Falha ao carregar vídeo</string>
<string name="edit_image">Editar imagem</string>
<string name="save">Salvar</string>
<string name="add_alt_text">Adicionar texto alternativo</string>
@@ -194,4 +192,7 @@
<string name="step_x_of_n">Passo %1$d de %2$d</string>
<string name="skip">Ignorar</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -232,8 +232,6 @@
<string name="content_warning">Предупреждение о содержании</string>
<string name="add_image_description">Добавить описание…</string>
<string name="retry_upload">Повторить попытку</string>
<string name="image_upload_failed">Не удалось загрузить изображение</string>
<string name="video_upload_failed">Не удалось загрузить видео</string>
<string name="edit_image">Редактировать изображение</string>
<string name="save">Сохранить</string>
<string name="add_alt_text">Добавить альтернативный текст</string>
@@ -359,4 +357,7 @@
</plurals>
<string name="timestamp_via_app">%1$s через %2$s</string>
<string name="time_now">только что</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -29,4 +29,7 @@
<string name="emoji">Emoji</string>
<string name="open_in_browser">Öppna i webbläsare</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -190,8 +190,6 @@
<string name="content_warning">คำเตือนเนื้อหา</string>
<string name="add_image_description">เพิ่มคำอธิบายภาพ…</string>
<string name="retry_upload">ลองอัปโหลดใหม่</string>
<string name="image_upload_failed">ไม่สามารถอัปโหลดภาพ</string>
<string name="video_upload_failed">ไม่สามารถอัปโหลดวิดีโอ</string>
<string name="edit_image">แก้ไขภาพ</string>
<string name="save">บันทึก</string>
<string name="add_alt_text">เพิ่มข้อความแสดงแทน</string>
@@ -314,4 +312,44 @@
</plurals>
<string name="timestamp_via_app">%1$s ผ่าน %2$s</string>
<string name="time_now">ตอนนี้</string>
<string name="post_info_reblogs">การดัน</string>
<string name="post_info_favorites">รายการโปรด</string>
<string name="edit_history">ประวัติการแก้ไข</string>
<string name="last_edit_at_x">การแก้ไขล่าสุดเมื่อ %s</string>
<string name="time_just_now">เมื่อกี้นี้</string>
<plurals name="x_seconds_ago">
<item quantity="other">%d วินาทีที่แล้ว</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="other">%d นาทีที่แล้ว</item>
</plurals>
<string name="edited_timestamp">แก้ไขเมื่อ %s</string>
<string name="edit_original_post">โพสต์ดั้งเดิม</string>
<string name="edit_text_edited">แก้ไขข้อความแล้ว</string>
<string name="edit_spoiler_added">เพิ่มคำเตือนเนื้อหาแล้ว</string>
<string name="edit_spoiler_edited">แก้ไขคำเตือนเนื้อหาแล้ว</string>
<string name="edit_spoiler_removed">เอาคำเตือนเนื้อหาออกแล้ว</string>
<string name="edit_poll_added">เพิ่มการสำรวจความคิดเห็นแล้ว</string>
<string name="edit_poll_edited">แก้ไขการสำรวจความคิดเห็นแล้ว</string>
<string name="edit_poll_removed">เอาการสำรวจความคิดเห็นออกแล้ว</string>
<string name="edit_media_added">เพิ่มสื่อแล้ว</string>
<string name="edit_media_removed">เอาสื่อออกแล้ว</string>
<string name="edit_media_reordered">เรียงลำดับสื่อใหม่แล้ว</string>
<string name="edit_marked_sensitive">ทำเครื่องหมายว่าละเอียดอ่อนแล้ว</string>
<string name="edit_marked_not_sensitive">ทำเครื่องหมายว่าไม่ละเอียดอ่อนแล้ว</string>
<string name="edit_multiple_changed">แก้ไขโพสต์แล้ว</string>
<string name="edit">แก้ไข</string>
<string name="discard_changes">ละทิ้งการเปลี่ยนแปลง?</string>
<string name="upload_failed">การอัปโหลดล้มเหลว</string>
<string name="file_size_bytes">%d ไบต์</string>
<string name="file_size_kb">%.2f กิโลไบต์</string>
<string name="file_size_mb">%.2f เมกะไบต์</string>
<string name="file_size_gb">%.2f กิกะไบต์</string>
<string name="file_upload_progress">%1$s จาก %2$s</string>
<string name="file_upload_time_remaining">เหลืออีก %s</string>
<string name="upload_error_connection_lost">อุปกรณ์ของคุณขาดการเชื่อมต่อกับอินเทอร์เน็ต</string>
<string name="upload_processing">กำลังประมวลผล…</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -9,7 +9,7 @@
<string name="ok">Tamam</string>
<string name="preparing_auth">Kimlik doğrulama için hazırlanıyor…</string>
<string name="finishing_auth">Kimlik doğrulama tamamlanıyor…</string>
<string name="user_boosted">%s gönderinizi yeniden paylaştı</string>
<string name="user_boosted">%s yeniden paylaştı</string>
<string name="in_reply_to">%s için yanıt</string>
<string name="notifications">Bildirimler</string>
<string name="user_followed_you">seni takip etti</string>
@@ -29,11 +29,11 @@
<string name="cancel">İptal</string>
<plurals name="followers">
<item quantity="one">Takipçi</item>
<item quantity="other">Takipçiler</item>
<item quantity="other">Takipçi</item>
</plurals>
<plurals name="following">
<item quantity="one">Takip edilen</item>
<item quantity="other">Takip edilenler</item>
<item quantity="one">Takip</item>
<item quantity="other">Takip</item>
</plurals>
<plurals name="posts">
<item quantity="one">gönderi</item>
@@ -44,7 +44,7 @@
<string name="media">Medya</string>
<string name="profile_about">Hakkında</string>
<string name="button_follow">Takip et</string>
<string name="button_following">Takip ediyor</string>
<string name="button_following">Takip ediliyor</string>
<string name="edit_profile">Profili düzenle</string>
<string name="mention_user">Bahset %s</string>
<string name="share_user">Paylaş %s</string>
@@ -204,8 +204,6 @@
<string name="content_warning">İçerik Uyarısı</string>
<string name="add_image_description">Resim açıklaması ekle…</string>
<string name="retry_upload">Yüklemeyi yeniden dene</string>
<string name="image_upload_failed">Resim yüklenemedi</string>
<string name="video_upload_failed">Video yüklenemedi</string>
<string name="edit_image">Resmi Düzenle</string>
<string name="save">Kaydet</string>
<string name="add_alt_text">Alternatif metin ekle</string>
@@ -282,7 +280,7 @@
<string name="media_viewer">Medya görüntüleyici</string>
<string name="follow_user">%s\'yi takip et</string>
<string name="unfollowed_user">%s takip edilmedi</string>
<string name="followed_user">%s\'i takip ediyorsunuz</string>
<string name="followed_user">%s kişisini takip ediyorsunuz</string>
<string name="open_in_browser">Tarayıcıda aç</string>
<string name="hide_boosts_from_user">%s kişisinin yeniden paylaşımlarını gizle</string>
<string name="show_boosts_from_user">%s kişisinin yeniden paylaşımlarını göster</string>
@@ -320,8 +318,8 @@
<item quantity="other">%,d takipçi</item>
</plurals>
<plurals name="x_following">
<item quantity="one">%,d takip edilen</item>
<item quantity="other">%,d takip edilen</item>
<item quantity="one">%,d takip</item>
<item quantity="other">%,d takip</item>
</plurals>
<plurals name="x_favorites">
<item quantity="one">%,d favori</item>
@@ -332,4 +330,10 @@
<item quantity="other">%,d yeniden paylaşım</item>
</plurals>
<string name="timestamp_via_app">%1$s tarihinde %2$s uygulamasıyla</string>
<string name="time_now">şimdi</string>
<string name="post_info_reblogs">Yeniden Paylaşımlar</string>
<string name="post_info_favorites">Favoriler</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -28,4 +28,7 @@
<string name="media">Медіа</string>
<string name="button_follow">Підписатися</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

View File

@@ -163,7 +163,7 @@
<string name="instance_rules_subtitle">Hãy đọc kỹ quy tắc máy chủ của %s và cân nhắc.</string>
<string name="signup_title">Hãy để tôi đăng ký trên %s</string>
<string name="edit_photo">sửa</string>
<string name="display_name">tên hiển thị</string>
<string name="display_name">biệt danh</string>
<string name="username">tên người dùng</string>
<string name="email">email</string>
<string name="password">mật khẩu</string>
@@ -190,8 +190,6 @@
<string name="content_warning">Nội dung ẩn</string>
<string name="add_image_description">Thêm mô tả ảnh…</string>
<string name="retry_upload">Thử tải lên lại</string>
<string name="image_upload_failed">Không thể tải ảnh lên</string>
<string name="video_upload_failed">Không thể tải video lên</string>
<string name="edit_image">Sửa ảnh</string>
<string name="save">Lưu</string>
<string name="add_alt_text">Thêm văn bản thay thế</string>
@@ -246,7 +244,7 @@
<string name="media_cache_cleared">Đã xóa bộ nhớ đệm</string>
<string name="confirm_log_out">Bạn có chắc muốn đăng xuất không?</string>
<string name="sensitive_content">Nội dung nhạy cảm</string>
<string name="sensitive_content_explain">Người đăng đã đánh dấu media này nhạy cảm. Nhấn để xem.</string>
<string name="sensitive_content_explain">Media này có thể nhạy cảm. Nhấn để xem.</string>
<string name="media_hidden">Nhấn để xem</string>
<string name="avatar_description">Xem hồ sơ %s</string>
<string name="more_options">Nhiều tùy chọn hơn</string>
@@ -314,4 +312,48 @@
</plurals>
<string name="timestamp_via_app">%1$s qua %2$s</string>
<string name="time_now">vừa xong</string>
<string name="post_info_reblogs">Lượt đăng lại</string>
<string name="post_info_favorites">Lượt thích</string>
<string name="edit_history">Lịch sử chỉnh sửa</string>
<string name="last_edit_at_x">Sửa lần cuối %s</string>
<string name="time_just_now">vừa xong</string>
<plurals name="x_seconds_ago">
<item quantity="other">%d giây trước</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="other">%d phút trước</item>
</plurals>
<string name="edited_timestamp">đã sửa %s</string>
<string name="edit_original_post">Tút gốc</string>
<string name="edit_text_edited">Nội dung chỉnh sửa</string>
<string name="edit_spoiler_added">Thêm cảnh báo nội dung</string>
<string name="edit_spoiler_edited">Sửa cảnh báo nội dung</string>
<string name="edit_spoiler_removed">Xóa cảnh báo nội dung</string>
<string name="edit_poll_added">Thêm bình chọn</string>
<string name="edit_poll_edited">Sửa bình chọn</string>
<string name="edit_poll_removed">Xóa bình chọn</string>
<string name="edit_media_added">Thêm media</string>
<string name="edit_media_removed">Xóa media</string>
<string name="edit_media_reordered">Xếp lại media</string>
<string name="edit_marked_sensitive">Đánh dấu nhạy cảm</string>
<string name="edit_marked_not_sensitive">Đánh dấu là bình thường</string>
<string name="edit_multiple_changed">Tút đã sửa</string>
<string name="edit">Sửa</string>
<string name="discard_changes">Hủy bỏ các thay đổi?</string>
<string name="upload_failed">Tải lên không thành công</string>
<string name="file_size_bytes">%d bytes</string>
<string name="file_size_kb">%.2f KB</string>
<string name="file_size_mb">%.2f MB</string>
<string name="file_size_gb">%.2f GB</string>
<string name="file_upload_progress">%1$s trong %2$s</string>
<string name="file_upload_time_remaining">%s còn lại</string>
<string name="upload_error_connection_lost">Thiết bị của bạn bị rớt mạng</string>
<string name="upload_processing">Đang tải lên…</string>
<!-- %s is version like 1.2.3 -->
<string name="update_available">Mastodon Android %s đã sẵn sàng tải về.</string>
<!-- %s is version like 1.2.3 -->
<string name="update_ready">Mastodon Android %s đã được tải về và sẵn sàng cài đặt.</string>
<!-- %s is file size -->
<string name="download_update">Tải về (%s)</string>
<string name="install_update">Cài đặt</string>
</resources>

View File

@@ -203,8 +203,6 @@
<string name="image_description">图片描述</string>
<string name="add_image_description">添加图片描述…</string>
<string name="retry_upload">重新上传</string>
<string name="image_upload_failed">图片上传失败</string>
<string name="video_upload_failed">视频上传失败</string>
<string name="edit_image">编辑图片</string>
<string name="save">保存</string>
<string name="add_alt_text">添加备注</string>
@@ -250,7 +248,7 @@
<string name="notify_reblog">转发我的嘟文</string>
<string name="notify_mention">提及我</string>
<string name="settings_boring">更多</string>
<string name="settings_account">户设置</string>
<string name="settings_account">户设置</string>
<string name="settings_contribute">贡献给Mastodon</string>
<string name="settings_tos">使用条款</string>
<string name="settings_privacy_policy">隐私政策</string>
@@ -332,4 +330,44 @@
</plurals>
<string name="timestamp_via_app">%1$s 来自 %2$s</string>
<string name="time_now">刚刚</string>
<string name="post_info_reblogs">转发</string>
<string name="post_info_favorites">喜欢</string>
<string name="edit_history">编辑记录</string>
<string name="last_edit_at_x">编辑于 %s</string>
<string name="time_just_now">不久前</string>
<plurals name="x_seconds_ago">
<item quantity="other">%d 秒前</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="other">%d 分钟前</item>
</plurals>
<string name="edited_timestamp">已编辑 %s</string>
<string name="edit_original_post">初始嘟文</string>
<string name="edit_text_edited">修改了文本</string>
<string name="edit_spoiler_added">添加了内容警告</string>
<string name="edit_spoiler_edited">修改了内容警告</string>
<string name="edit_spoiler_removed">移除了内容警告</string>
<string name="edit_poll_added">添加了投票</string>
<string name="edit_poll_edited">修改了投票</string>
<string name="edit_poll_removed">移除了投票</string>
<string name="edit_media_added">添加了媒体</string>
<string name="edit_media_removed">移除了媒体</string>
<string name="edit_media_reordered">调整了媒体顺序</string>
<string name="edit_marked_sensitive">标记为敏感内容</string>
<string name="edit_marked_not_sensitive">取消了敏感内容标记</string>
<string name="edit_multiple_changed">编辑了嘟文</string>
<string name="edit">编辑</string>
<string name="discard_changes">放弃更改?</string>
<string name="upload_failed">上传失败</string>
<string name="file_size_bytes">%d 字节</string>
<string name="file_size_kb">%.2f KB</string>
<string name="file_size_mb">%.2f MB</string>
<string name="file_size_gb">%.2f GB</string>
<string name="file_upload_progress">已上传 %1$s共 %2$s</string>
<string name="file_upload_time_remaining">剩余 %s</string>
<string name="upload_error_connection_lost">网络连接已断开</string>
<string name="upload_processing">正在处理…</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
</resources>

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