Compare commits

...

112 Commits

Author SHA1 Message Date
SomeTr
d96b9558b4 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (417 of 417 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-11-15 17:48:41 +00:00
sk
1ed3f00f82 bump version 2023-11-15 18:48:12 +01:00
sk
327aef0271 fix crash for non-github/debug builds 2023-11-15 18:47:07 +01:00
sk22
66f8cc5d18 Translated using Weblate (German)
Currently translated at 100.0% (20 of 20 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/de/
2023-11-15 16:00:40 +00:00
sk
36699e0ab1 change change log 2023-11-15 16:58:49 +01:00
sk22
1a382606d7 Translated using Weblate (German)
Currently translated at 100.0% (417 of 417 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-11-15 15:50:50 +00:00
sk22
2522c9c428 Translated using Weblate (German)
Currently translated at 97.6% (407 of 417 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-11-15 15:46:34 +00:00
gallegonovato
d1ee12f248 Translated using Weblate (Spanish)
Currently translated at 100.0% (412 of 412 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-11-15 15:46:34 +00:00
SomeTr
d5f9c19fd3 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (19 of 19 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-11-15 15:46:34 +00:00
SomeTr
dc700d9e37 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (412 of 412 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-11-15 15:46:34 +00:00
qbane
6eb87149e2 Translated using Weblate (Chinese (Traditional))
Currently translated at 24.0% (96 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hant/
2023-11-15 15:46:34 +00:00
qbane
79a0f8a891 Translated using Weblate (Chinese (Traditional))
Currently translated at 17.7% (71 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hant/
2023-11-15 15:46:34 +00:00
alextecplayz
26d41b006f Translated using Weblate (Romanian)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-11-15 15:46:34 +00:00
EndermanCo
60d9dc22d2 Translated using Weblate (Persian)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-11-15 15:46:34 +00:00
SomeTr
c0bf78fa7c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-11-15 15:46:34 +00:00
ling0412
f0c2bd0fe6 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/zh_Hans/
2023-11-15 15:46:34 +00:00
Arkxv
235cfd1e80 Translated using Weblate (Japanese)
Currently translated at 93.9% (375 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ja/
2023-11-15 15:46:34 +00:00
SomeTr
c96931ba72 Translated using Weblate (Croatian)
Currently translated at 64.4% (257 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/hr/
2023-11-15 15:46:34 +00:00
butterflyoffire
2c654a28d6 Translated using Weblate (French)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-11-15 15:46:33 +00:00
Andrewblasco
72d3fc84f5 Translated using Weblate (Spanish)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-11-15 15:46:33 +00:00
ling0412
3b77604ae6 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (398 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-11-15 15:46:33 +00:00
poesty
4e5f49b37d Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (398 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-11-15 15:46:33 +00:00
sk22
a89c075082 Added translation using Weblate (Esperanto) 2023-11-15 15:46:33 +00:00
sk
99e881bb95 change string name 2023-11-15 16:46:24 +01:00
sk
4c585fc5d0 change confirm string 2023-11-15 16:45:29 +01:00
sk
ae934b5167 add changelog 2023-11-15 16:42:22 +01:00
Jacoco
6b234209c6 Previewing posts on Akkoma (#933)
* Previewing posts on Akkoma

* move preview property into status

* clean up code

* revert imm-related changes

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-15 15:17:29 +01:00
Jacoco
786ce78b08 Translations for Akkoma (#934)
* Translations for Akkoma

* simplify akkoma translation code

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-15 14:32:56 +01:00
sk
cde332684e bump version 2023-11-15 14:07:00 +01:00
sk
d51e06b61f save latest crash log
closes sk22#932
closes sk22#419
2023-11-15 12:06:54 +01:00
sk
0c376d57e7 fix timeline editor messing with non-hashtag tls 2023-11-13 21:31:54 +01:00
sk
ee82772fee weather icons :) 2023-11-13 21:30:06 +01:00
sk
d5b6750abe fix hashtag options showing up for all timelines 2023-11-13 21:17:58 +01:00
sk
70328217c6 add bookmarks and favorites as timelines
closes sk22#908
2023-11-13 21:10:33 +01:00
sk
733fb3f53a re-add pre-releases setting
closes sk22#906
2023-11-13 21:02:06 +01:00
sk
f82eb96a35 increase translate timeout to 1 minute
closes sk22#910
2023-11-13 20:50:43 +01:00
sk
148952b96c update cache on poll vote
closes sk22#924
2023-11-13 20:48:16 +01:00
sk
52928e1577 increase default read timeout
closes sk22#923
2023-11-13 20:43:34 +01:00
sk
cd13777c06 fix report view, allow opening posts in list
closes sk22#928
closes sk22#441
2023-11-13 20:39:27 +01:00
sk
b21e2acdb6 fix metadata items being cut off
closes sk22#921
closes sk22#648
2023-11-13 19:11:04 +01:00
sk
2b65aeb8b4 fix edit note item visible in context menu 2023-11-13 18:48:24 +01:00
Jacoco
a8dcb11094 feature: Display post that's being quoted on Akkoma (#927)
* Displaying Akkoma quote status

* Dummy display items for quote posts

* Only remove quote-inline with RE:

* fix null reference (reply-to instead of quote status)

* fix text bottom padding in quote

* Postprocess status quote

* fix rounded bottom for quoted media

closes sk22#929

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-13 18:46:15 +01:00
sk
aa42873274 use verified/error text colors for diff 2023-11-12 23:01:46 +01:00
FineFindus
8a5b36db96 feat(profile): add note (#918)
* feat(profile): add note

* simplify note code

* adjust spacing

* size and hitbox adjustments, progress

* profile menu item to add note

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-12 22:37:31 +01:00
FineFindus
c85af5502d feat: translate media attachments and poll options (#916)
* feat(status): translate media attachments

* feat(status): translate poll options

* fix(status/translation): do not require all fields

* feat(status/translation): support translating spoiler
2023-11-10 20:44:06 +01:00
FineFindus
06698d3c52 feat: display edit history diff (#922)
* build: add google's diff-match-patch

Copied from 62f2e689f4/java/src/name/fraser/neil/plaintext/diff_match_patch.java

* feat(status/edit-history): display diff for text

Closes https://github.com/sk22/megalodon/issues/789

* fix(status/edit-history): add fake poll id

* code style adjustments

* don't diff if only formatting changed

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-10 20:40:34 +01:00
S1m
2818672cda Fix NullPointerException when receiving push when killed (#914) 2023-11-10 20:15:45 +01:00
S1m
24794f28aa Fix: registering multi account for UnifiedPush (#907)
* Register all account when enabling UnifiedPush
* Register new account to UnifiedPush if enabled
2023-11-10 20:12:27 +01:00
FineFindus
50d1523210 feat: re-add 12 hour option to polls (#917) 2023-11-10 20:10:50 +01:00
sk
613cd2a1ea remove unused playRelease build type 2023-11-10 20:06:18 +01:00
sk
295cb74287 don't show translate for empty decoding 2023-11-10 16:30:46 +01:00
sk
95dd3ff068 fix translation language name 2023-11-10 16:13:43 +01:00
sk
d6aeb753fc allow exclamation/question marks in pronouns 2023-11-10 16:13:14 +01:00
sk
a085744038 fix missing parens on start of pronouns 2023-11-10 15:37:31 +01:00
sk
26391a6f14 hopefully fix string index out of bounds
https://paste.crdroid.net/3FNRe7
2023-11-10 15:37:03 +01:00
sk
4cf734ce9a avoid IllegalArgumentException 2023-11-10 15:23:42 +01:00
sk
c8122aa65b fuck it, production release 2023-10-28 15:09:00 +02:00
sk
ef1584de55 merge upstream strings 2023-10-28 14:50:56 +02:00
sk
3075030b1c add todo 2023-10-27 17:38:54 +02:00
sk
42c56401db fix wrong check for gap item
re sk22#898
2023-10-27 17:34:47 +02:00
sk
a18a4383e5 update gap in database when deleting status
closes sk22#898
2023-10-27 17:19:59 +02:00
sk
375b5b3133 fix gaps not showing time
closes sk22#889
2023-10-27 16:14:16 +02:00
sk
4cab916957 move status list fragment check out of predicate 2023-10-27 16:02:14 +02:00
sk
7902691093 avoid index out of bounds
closes sk22#904
2023-10-27 01:28:27 +02:00
sk
d5d9e20a06 update strings 2023-10-26 18:53:40 +02:00
Codeberg Translate
1eb55428db Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/
2023-10-26 16:39:24 +00:00
reindex
9b82d704f6 Translated using Weblate (Japanese)
Currently translated at 11.1% (2 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ja/
2023-10-26 16:39:24 +00:00
reindex
dbf3d2776e Translated using Weblate (Japanese)
Currently translated at 86.4% (345 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ja/
2023-10-26 16:39:24 +00:00
Espasant3
c077b9d47c Translated using Weblate (Galician)
Currently translated at 98.4% (393 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-10-26 16:39:24 +00:00
kallekn
34157805a1 Translated using Weblate (Finnish)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fi/
2023-10-26 16:39:24 +00:00
AiOO
576da2a3eb Translated using Weblate (Korean)
Currently translated at 99.2% (396 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ko/
2023-10-26 16:39:24 +00:00
SomeTr
e99e5b2836 Translated using Weblate (Ukrainian)
Currently translated at 99.7% (398 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-26 16:39:24 +00:00
Oliebol
fe25974958 Translated using Weblate (Dutch)
Currently translated at 91.7% (366 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nl/
2023-10-26 16:39:24 +00:00
Linerly
1a95b4e361 Translated using Weblate (Indonesian)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-10-26 16:39:24 +00:00
Choukajohn
35e3d5afc4 Translated using Weblate (French)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-26 16:39:24 +00:00
gallegonovato
cc1e13d1bd Translated using Weblate (Spanish)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-10-26 16:39:24 +00:00
poesty
0b622f8f2a Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (398 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-26 16:39:24 +00:00
gallegonovato
9cf7da6419 Translated using Weblate (Spanish)
Currently translated at 100.0% (398 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-10-26 16:39:24 +00:00
sk
812ab7198c revert, i said!
actually, the cached posts limit can stay
2023-10-26 18:38:06 +02:00
sk
e8c9253a76 don't remove gap when removing status
re: sk22#898
2023-10-26 18:32:46 +02:00
sk
c8f633ae3b don't hide gap behind warning
closes sk22#899
2023-10-26 18:23:56 +02:00
sk
af339a833f fix gap time being wrong when next status is own
closes sk22#902
2023-10-26 18:11:54 +02:00
sk
708142e1b8 Revert "refresh data from cache"
This reverts commit 60a998be89.
2023-10-26 17:53:36 +02:00
sk
55f32671c5 Revert "fix timeline breaking when max_id is null"
This reverts commit 289db09770.
2023-10-26 17:52:43 +02:00
sk
289db09770 fix timeline breaking when max_id is null 2023-10-25 14:42:35 +02:00
sk
89d7dfd694 don't globally remove status on refresh
closes sk22#896
2023-10-23 23:55:04 +02:00
sk
60a998be89 refresh data from cache 2023-10-23 01:00:26 +02:00
sk
128e75bc24 fix animatable avatars not playing in notification headers
closes sk22#882
2023-10-22 23:35:09 +02:00
sk
69c60d484c load custom emoji in reply header
closes sk22#886
2023-10-22 23:30:33 +02:00
sk
c70750a508 fix strings 2023-10-22 23:21:47 +02:00
sk
5553ca25a1 Merge remote-tracking branch 'upstream/l10n_master' 2023-10-22 23:10:56 +02:00
sk
ff87829e72 Merge remote-tracking branch 'weblate/main' 2023-10-22 23:09:45 +02:00
sk
02983c76b9 add mutuals follow button state
closes sk22#890
2023-10-22 23:09:35 +02:00
sk
3e28eb2ccf fix polls not updating when behind spoiler
closes sk22#892
2023-10-22 22:39:10 +02:00
sk
3cf23474e3 fix issue with too-wide context menu icons
closes sk22#893
2023-10-22 22:10:25 +02:00
sk
9f0ff2dcd4 fix editing scheduled/drafted polls
closes sk22#894
2023-10-22 21:44:02 +02:00
Eugen Rochko
8ed9fb6276 New translations strings.xml (Chinese Traditional) 2023-10-22 08:53:38 +02:00
SomeTr
df7a0ee490 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-10-21 20:53:12 +00:00
Espasant3
2e360dc275 Translated using Weblate (Galician)
Currently translated at 98.7% (393 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-10-21 20:53:12 +00:00
poesty
8dc88d2fd7 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (397 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-21 20:53:12 +00:00
Eugen Rochko
901c70efc3 New translations strings.xml (Icelandic) 2023-10-21 19:24:31 +02:00
Eugen Rochko
3d44e5d2cc New translations strings.xml (Icelandic) 2023-10-21 18:27:37 +02:00
Eugen Rochko
33ea3da84d New translations strings.xml (Basque) 2023-10-21 11:37:37 +02:00
Eugen Rochko
e97203a6e3 New translations strings.xml (Thai) 2023-10-21 09:36:50 +02:00
Eugen Rochko
b3f2987b14 New translations strings.xml (Vietnamese) 2023-10-21 03:54:29 +02:00
Eugen Rochko
c7426453a5 New translations strings.xml (German) 2023-10-21 02:52:53 +02:00
sk
b60c1a5db3 add back old see_new_posts strings 2023-10-21 01:43:03 +02:00
Eugen Rochko
664d5cc4c3 New translations strings.xml (German) 2023-10-21 01:04:51 +02:00
sk
47d1b182ac fix see new posts button design 2023-10-21 01:00:51 +02:00
sk
c33c3d9112 fix compatibility issue 2023-10-21 00:54:38 +02:00
sk
ff99e1023a Revert "use tonal background for see new posts button"
This reverts commit 8009dc0a75.
2023-10-20 16:23:02 +02:00
sk
8009dc0a75 use tonal background for see new posts button 2023-10-20 15:31:06 +02:00
148 changed files with 5304 additions and 1260 deletions

View File

@@ -15,8 +15,8 @@ android {
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 33
versionCode 107
versionName "2.1.6+fork.107"
versionCode 110
versionName "2.1.6+fork.110"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
}
@@ -33,7 +33,6 @@ android {
applicationIdSuffix '.debug'
}
githubRelease { initWith release }
playRelease { initWith release }
fdroidRelease { initWith release }
}
compileOptions {

View File

@@ -257,5 +257,9 @@ public class UiUtilsTest {
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "-- * (asterisk) --")
)).orElseThrow());
assertEquals("they/(she?)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "they/(she?)...")
)).orElseThrow());
}
}

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

File diff suppressed because it is too large Load Diff

View File

@@ -31,18 +31,44 @@ import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Instant;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
private static final String TAG="MainActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState){
AccountSession session=getCurrentSession();
UiUtils.setUserPreferredTheme(this, session);
super.onCreate(savedInstanceState);
Thread.UncaughtExceptionHandler defaultHandler=Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e)->{
File file=new File(MastodonApp.context.getFilesDir(), "crash.log");
try(FileOutputStream out=new FileOutputStream(file)){
PrintWriter writer=new PrintWriter(out);
writer.println(BuildConfig.VERSION_NAME+" ("+BuildConfig.VERSION_CODE+")");
writer.println(Instant.now().toString());
writer.println();
e.printStackTrace(writer);
writer.flush();
}catch(IOException x){
Log.e(TAG, "Error writing crash.log", x);
}finally{
defaultHandler.uncaughtException(t, e);
}
});
if(savedInstanceState==null){
restartHomeFragment();
}

View File

@@ -121,7 +121,7 @@ public class CacheController{
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
if(!clear)
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"100"});
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"1000"});
});
}
@@ -273,6 +273,28 @@ public class CacheController{
public void deleteStatus(String id){
runOnDbThread((db)->{
String gapId=null;
int gapFlags=0;
// select to-be-removed and newer row
try(Cursor cursor=db.query("home_timeline", new String[]{"id", "flags"}, "`time`>=(SELECT `time` FROM `home_timeline` WHERE `id`=?)", new String[]{id}, null, null, "`time` ASC", "2")){
boolean hadGapAfter=false;
// always either one or two iterations (only one if there's no newer post)
while(cursor.moveToNext()){
String currentId=cursor.getString(0);
int currentFlags=cursor.getInt(1);
if(currentId.equals(id)){
hadGapAfter=((currentFlags & POST_FLAG_GAP_AFTER)!=0);
}else if(hadGapAfter){
gapFlags=currentFlags|POST_FLAG_GAP_AFTER;
gapId=currentId;
}
}
}
if(gapId!=null){
ContentValues values=new ContentValues();
values.put("flags", gapFlags);
db.update("home_timeline", values, "`id`=?", new String[]{gapId});
}
db.delete("home_timeline", "`id`=?", new String[]{id});
});
}

View File

@@ -53,7 +53,9 @@ public class MastodonAPIController{
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
.create();
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
private static OkHttpClient httpClient=new OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.build();
private AccountSession session;
private static List<String> badDomains = new ArrayList<>();

View File

@@ -0,0 +1,19 @@
package org.joinmastodon.android.api.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;
public class SetPrivateNote extends MastodonAPIRequest<Relationship>{
public SetPrivateNote(String id, String comment){
super(MastodonAPIRequest.HttpMethod.POST, "/accounts/"+id+"/note", Relationship.class);
Request req = new Request(comment);
setRequestBody(req);
}
private static class Request{
public String comment;
public Request(String comment){
this.comment=comment;
}
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.AkkomaTranslation;
public class AkkomaTranslateStatus extends MastodonAPIRequest<AkkomaTranslation>{
public AkkomaTranslateStatus(String id, String lang){
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
}
}

View File

@@ -48,6 +48,8 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public String quoteId;
public ContentType contentType;
public boolean preview;
public static class Poll{
public ArrayList<String> options=new ArrayList<>();
public int expiresIn;

View File

@@ -26,6 +26,8 @@ public class GetStatusEditHistory extends MastodonAPIRequest<List<Status>>{
s.visibility=StatusPrivacy.PUBLIC;
s.mentions=Collections.emptyList();
s.tags=Collections.emptyList();
if (s.poll != null)
s.poll.id="fakeID"+i;
i++;
}
super.validateAndPostprocessResponse(respObj, httpResponse);

View File

@@ -260,11 +260,13 @@ public class AccountSession{
}
private boolean isFilteredType(Status s){
AccountLocalPreferences localPreferences = getLocalPreferences();
return (!localPreferences.showReplies && s.inReplyToId != null)
|| (!localPreferences.showBoosts && s.reblog != null);
}
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
AccountLocalPreferences localPreferences = getLocalPreferences();
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
Status s=extractor.apply(obj);
if(s!=null && s.filtered!=null){
@@ -307,7 +309,7 @@ public class AccountSession{
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
return true;
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
if(localPreferences.serverSideFiltersSupported){
if(getLocalPreferences().serverSideFiltersSupported){
for(FilterResult filter : s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true;

View File

@@ -1,5 +1,7 @@
package org.joinmastodon.android.api.session;
import static org.unifiedpush.android.connector.UnifiedPush.getDistributor;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.ComponentName;
@@ -34,6 +36,7 @@ import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Token;
import org.unifiedpush.android.connector.UnifiedPush;
import java.io.File;
import java.io.FileInputStream;
@@ -101,6 +104,7 @@ public class AccountSessionManager{
}
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
Context context = MastodonApp.context;
instances.put(instance.uri, instance);
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
sessions.put(session.getID(), session);
@@ -113,7 +117,14 @@ public class AccountSessionManager{
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
updateMoreInstanceInfo(instance, instance.uri);
if(PushSubscriptionManager.arePushNotificationsAvailable()){
if (!UnifiedPush.getDistributor(context).isEmpty()) {
UnifiedPush.registerApp(
context,
session.getID(),
new ArrayList<>(),
context.getPackageName()
);
} else if(PushSubscriptionManager.arePushNotificationsAvailable()){
session.getPushSubscriptionManager().registerAccountForPush(null);
}
maybeUpdateShortcuts();

View File

@@ -19,12 +19,15 @@ import android.widget.Toolbar;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
import org.joinmastodon.android.api.requests.statuses.AkkomaTranslateStatus;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AkkomaTranslation;
import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship;
@@ -47,6 +50,7 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent;
@@ -59,6 +63,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
@@ -358,12 +363,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
});
list.addItemDecoration(new StatusListItemDecoration());
list.addItemDecoration(new InsetStatusItemDecoration(this));
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
private Rect tmpRect=new Rect();
@Override
public void getSelectorBounds(View view, Rect outRect){
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
int lastIndex = -1, firstIndex = -1;
if(list!=view.getParent()) return;
boolean hasDescendant=false, hasAncestor=false, isWarning=false;
int lastIndex=-1, firstIndex=-1;
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
list.getDecoratedBoundsWithMargins(view, outRect);
}else{
@@ -468,10 +475,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected void updatePoll(String itemID, Status status, Poll poll){
status.poll=poll;
int firstOptionIndex=-1, footerIndex=-1;
int spoilerFirstOptionIndex=-1, spoilerFooterIndex=-1;
SpoilerStatusDisplayItem spoilerItem=null;
int i=0;
for(StatusDisplayItem item:displayItems){
if(item.parentID.equals(itemID)){
if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
if(item instanceof SpoilerStatusDisplayItem){
spoilerItem=(SpoilerStatusDisplayItem) item;
}else if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
firstOptionIndex=i;
}else if(item instanceof PollFooterStatusDisplayItem){
footerIndex=i;
@@ -484,8 +495,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
throw new IllegalStateException("Can't find all poll items in displayItems");
List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1);
int prevSize=pollItems.size();
if(spoilerItem!=null){
spoilerFirstOptionIndex=spoilerItem.contentItems.indexOf(pollItems.get(0));
spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1));
}
pollItems.clear();
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems);
StatusDisplayItem.buildPollItems(itemID, this, poll, status, pollItems);
if(spoilerItem!=null){
spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear();
spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems);
}
if(prevSize!=pollItems.size()){
adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize);
adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size());
@@ -552,7 +571,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
Status status=holder.getItem().status;
toggleSpoiler(status, holder.getItemID());
boolean isForQuote=holder.getItem().isForQuote;
toggleSpoiler(status, isForQuote, holder.getItemID());
}
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
@@ -574,15 +594,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
}
protected void toggleSpoiler(Status status, String itemID){
protected void toggleSpoiler(Status status, boolean isForQuote, String itemID){
status.spoilerRevealed=!status.spoilerRevealed;
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
status.sensitiveRevealed = false;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
List<SpoilerStatusDisplayItem.Holder> spoilers=findAllHoldersOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
SpoilerStatusDisplayItem.Holder spoiler=spoilers.size() > 1 && isForQuote ? spoilers.get(1) : spoilers.get(0);
if(spoiler!=null) spoiler.rebind();
else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class);
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(spoiler.getItem());
int index=displayItems.indexOf(spoilerItem);
if(status.spoilerRevealed){
@@ -838,45 +859,53 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
status.translationState=Status.TranslationState.SHOWN;
}else{
status.translationState=Status.TranslationState.LOADING;
new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage())
.setCallback(new Callback<>(){
Consumer<Translation> successCallback=(result)->{
status.translation=result;
status.translationState=Status.TranslationState.SHOWN;
updateTranslation(itemID);
};
MastodonAPIRequest<?> req=isInstanceAkkoma()
? new AkkomaTranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
@Override
public void onSuccess(AkkomaTranslation result){
if(getActivity()!=null) successCallback.accept(result.toTranslation());
}
@Override
public void onError(ErrorResponse error){
if(getActivity()!=null) translationCallbackError(status, itemID);
}
})
: new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
@Override
public void onSuccess(Translation result){
if(getActivity()==null)
return;
status.translation=result;
status.translationState=Status.TranslationState.SHOWN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
if(getActivity()!=null) successCallback.accept(result);
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
status.translationState=Status.TranslationState.HIDDEN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.translation_failed)
.setPositiveButton(R.string.ok, null)
.show();
if(getActivity()!=null) translationCallbackError(status, itemID);
}
})
.exec(accountID);
});
// 1 minute
req.setTimeout(60000).exec(accountID);
}
}
}
updateTranslation(itemID);
}
private void translationCallbackError(Status status, String itemID) {
status.translationState=Status.TranslationState.HIDDEN;
updateTranslation(itemID);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.translation_failed)
.setPositiveButton(R.string.ok, null)
.show();
}
private void updateTranslation(String itemID) {
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
@@ -884,6 +913,25 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
if(isInstanceAkkoma())
return;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null){
spoiler.rebind();
}
MediaGridStatusDisplayItem.Holder media=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
if (media!=null) {
media.rebind();
}
for(int i=0;i<list.getChildCount();i++){
if(list.getChildViewHolder(list.getChildAt(i)) instanceof PollOptionStatusDisplayItem.Holder item){
item.rebind();
}
}
}
public void rebuildAllDisplayItems(){

View File

@@ -200,7 +200,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public Instance instance;
public Status editingStatus;
private ScheduledStatus scheduledStatus;
public ScheduledStatus scheduledStatus;
private boolean redraftStatus;
private ContentType contentType;
@@ -214,7 +214,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private BackgroundColorSpan overLimitBG;
private ForegroundColorSpan overLimitFG;
public ComposeFragment(){
super(R.layout.toolbar_fragment_with_progressbar);
}
@@ -699,7 +699,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
}
replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.getDisplayName()));
replyText.setText(HtmlParser.parseCustomEmoji(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.getDisplayName()), status.account.emojis));
UiUtils.loadCustomEmojiInTextView(replyText);
int visibilityNameRes = switch (status.visibility) {
case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted;
@@ -735,7 +736,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
&& !status.spoilerText.startsWith("re: ") ? "re: " : "";
spoilerEdit.setText(prefix + replyTo.spoilerText);
spoilerEdit.setText(prefix + status.spoilerText);
spoilerBtn.setSelected(true);
}
if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language);
@@ -806,25 +807,28 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
actionItem.setActionView(wrap);
actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
draftsBtn = wrap.findViewById(R.id.drafts_btn);
draftOptionsPopup = new PopupMenu(getContext(), draftsBtn);
draftsBtn=wrap.findViewById(R.id.drafts_btn);
draftOptionsPopup=new PopupMenu(getContext(), draftsBtn);
draftOptionsPopup.inflate(R.menu.compose_more);
draftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.draft);
undraftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.undraft);
scheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.schedule);
unscheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.unschedule);
Menu draftOptionsMenu=draftOptionsPopup.getMenu();
draftMenuItem=draftOptionsMenu.findItem(R.id.draft);
undraftMenuItem=draftOptionsMenu.findItem(R.id.undraft);
scheduleMenuItem=draftOptionsMenu.findItem(R.id.schedule);
unscheduleMenuItem=draftOptionsMenu.findItem(R.id.unschedule);
draftOptionsMenu.findItem(R.id.preview).setVisible(isInstanceAkkoma());
draftOptionsPopup.setOnMenuItemClickListener(i->{
int id = i.getItemId();
if (id == R.id.draft) updateScheduledAt(getDraftInstant());
else if (id == R.id.schedule) pickScheduledDateTime();
else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null);
else navigateToUnsentPosts();
int id=i.getItemId();
if(id==R.id.draft) updateScheduledAt(getDraftInstant());
else if(id==R.id.schedule) pickScheduledDateTime();
else if(id==R.id.unschedule || id==R.id.undraft) updateScheduledAt(null);
else if(id==R.id.drafts) navigateToUnsentPosts();
else if(id==R.id.preview) publish(true);
return true;
});
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
publishButton = wrap.findViewById(R.id.publish_btn);
languageButton = wrap.findViewById(R.id.language_btn);
publishButton=wrap.findViewById(R.id.publish_btn);
languageButton=wrap.findViewById(R.id.language_btn);
languageButton.setOnClickListener(v->showLanguageAlert());
languageButton.setOnLongClickListener(v->{
if(!getLocalPrefs().bottomEncoding){
@@ -1050,6 +1054,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void publish(){
publish(false);
}
private void publish(boolean preview){
sendingOverlay=new View(getActivity());
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@@ -1063,10 +1071,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
publishButton.setEnabled(false);
V.setVisibilityAnimated(sendProgress, View.VISIBLE);
mediaViewController.saveAltTextsBeforePublishing(this::actuallyPublish, this::handlePublishError);
mediaViewController.saveAltTextsBeforePublishing(
()->actuallyPublish(preview),
this::handlePublishError);
}
private void actuallyPublish(){
private void actuallyPublish(boolean preview){
String text=mainEditText.getText().toString();
CreateStatus.Request req=new CreateStatus.Request();
if("bottom".equals(postLang.encoding)){
@@ -1084,6 +1094,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.sensitive=sensitive;
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
req.scheduledAt=scheduledAt;
req.preview=preview;
if(!mediaViewController.isEmpty()){
req.mediaIds=mediaViewController.getAttachmentIDs();
if(editingStatus != null){
@@ -1111,7 +1122,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Callback<Status> resCallback=new Callback<>(){
@Override
public void onSuccess(Status result){
maybeDeleteScheduledPost(() -> {
if(preview){
openPreview(result);
return;
}
maybeDeleteScheduledPost(()->{
wm.removeView(sendingOverlay);
sendingOverlay=null;
if(editingStatus==null || redraftStatus){
@@ -1133,10 +1149,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
E.post(new StatusUpdatedEvent(editedStatus));
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) {
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()){
Nav.finish(ComposeFragment.this);
}
if (getArguments().getBoolean("navigateToStatus", false)) {
if(getArguments().getBoolean("navigateToStatus", false)){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result));
@@ -1152,11 +1168,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
};
if(editingStatus!=null && !redraftStatus){
if(editingStatus!=null && !redraftStatus && !preview){
new EditStatus(req, editingStatus.id)
.setCallback(resCallback)
.exec(accountID);
}else if(req.scheduledAt == null){
}else if(req.scheduledAt == null || preview){
new CreateStatus(req, uuid)
.setCallback(resCallback)
.exec(accountID);
@@ -1209,6 +1225,25 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
}
private void openPreview(Status result){
result.preview=true;
wm.removeView(sendingOverlay);
sendingOverlay=null;
publishButton.setEnabled(true);
V.setVisibilityAnimated(sendProgress, View.GONE);
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(contentView.getWindowToken(), 0);
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result));
if(replyTo!=null){
args.putParcelable("inReplyTo", Parcels.wrap(replyTo));
args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo.account));
}
Nav.go(getActivity(), ThreadFragment.class, args);
}
private void updateRecentLanguages() {
if (postLang == null || postLang.language == null) return;
String language = postLang.language.getLanguage();
@@ -1527,6 +1562,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
contentTypePopup.setOnMenuItemClickListener(i->{
uuid=null;
int index=i.getItemId();
contentType=ContentType.values()[index];
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());

View File

@@ -60,191 +60,191 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
private String accountID;
private TimelinesAdapter adapter;
private final ItemTouchHelper itemTouchHelper;
private Menu optionsMenu;
private boolean updated;
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>();
private final List<ListTimeline> listTimelines = new ArrayList<>();
private final List<Hashtag> hashtags = new ArrayList<>();
private MenuItem addHashtagItem;
public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop{
private String accountID;
private TimelinesAdapter adapter;
private final ItemTouchHelper itemTouchHelper;
private Menu optionsMenu;
private boolean updated;
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem=new HashMap<>();
private final List<ListTimeline> listTimelines=new ArrayList<>();
private final List<Hashtag> hashtags=new ArrayList<>();
private MenuItem addHashtagItem;
public EditTimelinesFragment() {
super(10);
ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelperCallback() ;
itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
}
public EditTimelinesFragment(){
super(10);
ItemTouchHelper.SimpleCallback itemTouchCallback=new ItemTouchHelperCallback();
itemTouchHelper=new ItemTouchHelper(itemTouchCallback);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
setTitle(R.string.sk_timelines);
accountID = getArguments().getString("account");
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
setTitle(R.string.sk_timelines);
accountID=getArguments().getString("account");
new GetLists().setCallback(new Callback<>() {
@Override
public void onSuccess(List<ListTimeline> result) {
listTimelines.addAll(result);
updateOptionsMenu();
}
new GetLists().setCallback(new Callback<>(){
@Override
public void onSuccess(List<ListTimeline> result){
listTimelines.addAll(result);
updateOptionsMenu();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountID);
@Override
public void onError(ErrorResponse error){
error.showToast(getContext());
}
}).exec(accountID);
new GetFollowedHashtags().setCallback(new Callback<>() {
@Override
public void onSuccess(HeaderPaginationList<Hashtag> result) {
hashtags.addAll(result);
updateOptionsMenu();
}
new GetFollowedHashtags().setCallback(new Callback<>(){
@Override
public void onSuccess(HeaderPaginationList<Hashtag> result){
hashtags.addAll(result);
updateOptionsMenu();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountID);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getContext());
}
}).exec(accountID);
}
@Override
protected void onShown(){
super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData();
}
@Override
protected void onShown(){
super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
itemTouchHelper.attachToRecyclerView(list);
refreshLayout.setEnabled(false);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16));
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
itemTouchHelper.attachToRecyclerView(list);
refreshLayout.setEnabled(false);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
this.optionsMenu = menu;
updateOptionsMenu();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
this.optionsMenu=menu;
updateOptionsMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_back) {
updateOptionsMenu();
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
return true;
}
TimelineDefinition tl = timelineByMenuItem.get(item);
if (tl != null) {
addTimeline(tl);
} else if (item == addHashtagItem) {
makeTimelineEditor(null, (hashtag) -> {
if (hashtag != null) addTimeline(hashtag);
}, null);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
if(item.getItemId()==R.id.menu_back){
updateOptionsMenu();
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
return true;
}
TimelineDefinition tl=timelineByMenuItem.get(item);
if(tl!=null){
addTimeline(tl);
}else if(item==addHashtagItem){
makeTimelineEditor(null, (hashtag)->{
if(hashtag!=null) addTimeline(hashtag);
}, null);
}
return true;
}
private void addTimeline(TimelineDefinition tl) {
data.add(tl.copy());
adapter.notifyItemInserted(data.size());
saveTimelines();
updateOptionsMenu();
}
private void addTimeline(TimelineDefinition tl){
data.add(tl.copy());
adapter.notifyItemInserted(data.size());
saveTimelines();
updateOptionsMenu();
}
private void addTimelineToOptions(TimelineDefinition tl, Menu menu) {
if (data.contains(tl)) return;
MenuItem item = addOptionsItem(menu, tl.getTitle(getContext()), tl.getIcon().iconRes);
timelineByMenuItem.put(item, tl);
}
private void addTimelineToOptions(TimelineDefinition tl, Menu menu){
if(data.contains(tl)) return;
MenuItem item=addOptionsItem(menu, tl.getTitle(getContext()), tl.getIcon().iconRes);
timelineByMenuItem.put(item, tl);
}
private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon) {
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, name);
item.setIcon(icon);
return item;
}
private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon){
MenuItem item=menu.add(0, View.generateViewId(), Menu.NONE, name);
item.setIcon(icon);
return item;
}
private void updateOptionsMenu() {
if(getActivity()==null) return;
optionsMenu.clear();
timelineByMenuItem.clear();
private void updateOptionsMenu(){
if(getActivity()==null) return;
optionsMenu.clear();
timelineByMenuItem.clear();
SubMenu menu = optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
SubMenu menu=optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
SubMenu timelinesMenu = menu.addSubMenu(R.string.sk_timeline);
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
SubMenu listsMenu = menu.addSubMenu(R.string.sk_list);
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
SubMenu hashtagsMenu = menu.addSubMenu(R.string.sk_hashtag);
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
SubMenu timelinesMenu=menu.addSubMenu(R.string.sk_timeline);
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
SubMenu listsMenu=menu.addSubMenu(R.string.sk_list);
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
SubMenu hashtagsMenu=menu.addSubMenu(R.string.sk_hashtag);
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
makeBackItem(timelinesMenu);
makeBackItem(listsMenu);
makeBackItem(hashtagsMenu);
makeBackItem(timelinesMenu);
makeBackItem(listsMenu);
makeBackItem(hashtagsMenu);
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
addHashtagItem = addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl->addTimelineToOptions(tl, timelinesMenu));
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl->addTimelineToOptions(tl, listsMenu));
addHashtagItem=addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl->addTimelineToOptions(tl, hashtagsMenu));
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0);
listsMenu.getItem().setVisible(listsMenu.size() > 0);
hashtagsMenu.getItem().setVisible(hashtagsMenu.size() > 0);
timelinesMenu.getItem().setVisible(timelinesMenu.size()>0);
listsMenu.getItem().setVisible(listsMenu.size()>0);
hashtagsMenu.getItem().setVisible(hashtagsMenu.size()>0);
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
}
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
}
private void saveTimelines() {
updated=true;
private void saveTimelines(){
updated=true;
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE);
prefs.timelines=data;
prefs.save();
}
private void removeTimeline(int position) {
data.remove(position);
adapter.notifyItemRemoved(position);
saveTimelines();
updateOptionsMenu();
}
private void removeTimeline(int position){
data.remove(position);
adapter.notifyItemRemoved(position);
saveTimelines();
updateOptionsMenu();
}
@Override
protected void doLoadData(int offset, int count){
onDataLoaded(AccountSessionManager.get(accountID).getLocalPreferences().timelines);
updateOptionsMenu();
}
@Override
protected void doLoadData(int offset, int count){
onDataLoaded(AccountSessionManager.get(accountID).getLocalPreferences().timelines);
updateOptionsMenu();
}
@Override
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter() {
return adapter = new TimelinesAdapter();
}
@Override
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter(){
return adapter=new TimelinesAdapter();
}
@Override
public void scrollToTop() {
smoothScrollRecyclerViewToTop(list);
}
@Override
public void scrollToTop(){
smoothScrollRecyclerViewToTop(list);
}
@Override
public void onDestroy() {
super.onDestroy();
if (updated) UiUtils.restartApp();
}
@Override
public void onDestroy(){
super.onDestroy();
if(updated) UiUtils.restartApp();
}
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
if (tags == null || tags.isEmpty()) return false;
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags){
if(tags==null || tags.isEmpty()) return false;
editText.setText(tags);
editText.chipifyAllUnterminatedTokens();
return true;
}
return true;
}
private NachoTextView prepareChipTextView(NachoTextView nacho) {
private NachoTextView prepareChipTextView(NachoTextView nacho){
//Ill Be Back
nacho.setChipTerminators(
Map.of(
@@ -254,223 +254,228 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
';', BEHAVIOR_CHIPIFY_ALL
)
);
nacho.enableEditChipOnTouch(true, true);
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
return nacho;
}
nacho.enableEditChipOnTouch(true, true);
nacho.setOnFocusChangeListener((v, hasFocus)->nacho.chipifyAllUnterminatedTokens());
return nacho;
}
@SuppressLint("ClickableViewAccessibility")
protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove) {
Context ctx = getContext();
View view = getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
@SuppressLint("ClickableViewAccessibility")
protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove){
Context ctx=getContext();
View view=getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
View divider = view.findViewById(R.id.divider);
Button advancedBtn = view.findViewById(R.id.advanced);
EditText editText = view.findViewById(R.id.input);
if (item != null) editText.setText(item.getCustomTitle());
editText.setHint(item != null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
View divider=view.findViewById(R.id.divider);
Button advancedBtn=view.findViewById(R.id.advanced);
EditText editText=view.findViewById(R.id.input);
if(item!=null) editText.setText(item.getCustomTitle());
editText.setHint(item!=null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
LinearLayout tagWrap = view.findViewById(R.id.tag_wrap);
boolean advancedOptionsAvailable = item == null || item.getType() == TimelineDefinition.TimelineType.HASHTAG;
advancedBtn.setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE);
advancedBtn.setOnClickListener(l -> {
advancedBtn.setSelected(!advancedBtn.isSelected());
LinearLayout tagWrap=view.findViewById(R.id.tag_wrap);
boolean hashtagOptionsAvailable=item==null || item.getType()==TimelineDefinition.TimelineType.HASHTAG;
advancedBtn.setVisibility(hashtagOptionsAvailable ? View.VISIBLE : View.GONE);
advancedBtn.setOnClickListener(l->{
advancedBtn.setSelected(!advancedBtn.isSelected());
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
tagWrap.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
tagWrap.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
UiUtils.beginLayoutTransition((ViewGroup) view);
});
});
Switch localOnlySwitch = view.findViewById(R.id.local_only_switch);
view.findViewById(R.id.local_only)
.setOnClickListener(l -> localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
Switch localOnlySwitch=view.findViewById(R.id.local_only_switch);
view.findViewById(R.id.local_only).setOnClickListener(l->localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
EditText tagMain = view.findViewById(R.id.tag_main);
NachoTextView tagsAny = prepareChipTextView(view.findViewById(R.id.tags_any));
NachoTextView tagsAll = prepareChipTextView(view.findViewById(R.id.tags_all));
NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none));
if (item != null) {
tagMain.setText(item.getHashtagName());
boolean hasAdvanced = !TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
hasAdvanced = setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
hasAdvanced = setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
if (item.isHashtagLocalOnly()) {
localOnlySwitch.setChecked(true);
hasAdvanced = true;
}
if (hasAdvanced) {
advancedBtn.setSelected(true);
advancedBtn.setText(R.string.sk_advanced_options_hide);
EditText tagMain=view.findViewById(R.id.tag_main);
NachoTextView tagsAny=prepareChipTextView(view.findViewById(R.id.tags_any));
NachoTextView tagsAll=prepareChipTextView(view.findViewById(R.id.tags_all));
NachoTextView tagsNone=prepareChipTextView(view.findViewById(R.id.tags_none));
if(item!=null && hashtagOptionsAvailable){
tagMain.setText(item.getHashtagName());
boolean hasAdvanced=!TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
hasAdvanced=setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
hasAdvanced=setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
hasAdvanced=setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
if(item.isHashtagLocalOnly()){
localOnlySwitch.setChecked(true);
hasAdvanced=true;
}
if(hasAdvanced){
advancedBtn.setSelected(true);
advancedBtn.setText(R.string.sk_advanced_options_hide);
tagWrap.setVisibility(View.VISIBLE);
divider.setVisibility(View.VISIBLE);
}
}
}
}
ImageButton btn = view.findViewById(R.id.button);
PopupMenu popup = new PopupMenu(ctx, btn);
TimelineDefinition.Icon currentIcon = item != null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG;
btn.setImageResource(currentIcon.iconRes);
btn.setTag(currentIcon.ordinal());
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
btn.setOnTouchListener(popup.getDragToOpenListener());
btn.setOnClickListener(l -> popup.show());
ImageButton btn=view.findViewById(R.id.button);
PopupMenu popup=new PopupMenu(ctx, btn);
TimelineDefinition.Icon currentIcon=item!=null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG;
btn.setImageResource(currentIcon.iconRes);
btn.setTag(currentIcon.ordinal());
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
btn.setOnTouchListener(popup.getDragToOpenListener());
btn.setOnClickListener(l->popup.show());
Menu menu = popup.getMenu();
TimelineDefinition.Icon defaultIcon = item != null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG;
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
if (!currentIcon.equals(defaultIcon)) {
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
}
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) {
if (icon.hidden || icon.ordinal() == (int) btn.getTag()) continue;
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
}
UiUtils.enablePopupMenuIcons(ctx, popup);
Menu menu=popup.getMenu();
TimelineDefinition.Icon defaultIcon=item!=null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG;
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
if(!currentIcon.equals(defaultIcon)){
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
}
for(TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()){
if(icon.hidden || icon.ordinal()==(int) btn.getTag()) continue;
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
}
UiUtils.enablePopupMenuIcons(ctx, popup);
popup.setOnMenuItemClickListener(menuItem -> {
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()];
btn.setImageResource(icon.iconRes);
btn.setTag(menuItem.getItemId());
btn.setContentDescription(ctx.getString(icon.nameRes));
return true;
});
popup.setOnMenuItemClickListener(menuItem->{
TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[menuItem.getItemId()];
btn.setImageResource(icon.iconRes);
btn.setTag(menuItem.getItemId());
btn.setContentDescription(ctx.getString(icon.nameRes));
return true;
});
AlertDialog.Builder builder = new M3AlertDialogBuilder(ctx)
.setTitle(item == null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
.setView(view)
.setPositiveButton(R.string.save, (d, which) -> {
tagsAny.chipifyAllUnterminatedTokens();
tagsAll.chipifyAllUnterminatedTokens();
tagsNone.chipifyAllUnterminatedTokens();
String name = editText.getText().toString().trim();
String mainHashtag = tagMain.getText().toString().trim();
if (TextUtils.isEmpty(mainHashtag)) {
mainHashtag = name;
name = null;
}
if (TextUtils.isEmpty(mainHashtag) && (item != null && item.getType() == TimelineDefinition.TimelineType.HASHTAG)) {
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
onSave.accept(null);
return;
}
AlertDialog.Builder builder=new M3AlertDialogBuilder(ctx)
.setTitle(item==null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
.setView(view)
.setPositiveButton(R.string.save, (d, which)->{
String name=editText.getText().toString().trim();
TimelineDefinition tl = item != null ? item : TimelineDefinition.ofHashtag(name);
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[(int) btn.getTag()];
tl.setIcon(icon);
tl.setTitle(name);
tl.setTagOptions(
mainHashtag,
tagsAny.getChipValues(),
tagsAll.getChipValues(),
tagsNone.getChipValues(),
localOnlySwitch.isChecked()
);
onSave.accept(tl);
})
.setNegativeButton(R.string.cancel, (d, which) -> {});
String mainHashtag=tagMain.getText().toString().trim();
if(item.getType()==TimelineDefinition.TimelineType.HASHTAG){
tagsAny.chipifyAllUnterminatedTokens();
tagsAll.chipifyAllUnterminatedTokens();
tagsNone.chipifyAllUnterminatedTokens();
if(TextUtils.isEmpty(mainHashtag)){
mainHashtag=name;
name=null;
}
if(TextUtils.isEmpty(mainHashtag) && (item!=null && item.getType()==TimelineDefinition.TimelineType.HASHTAG)){
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
onSave.accept(null);
return;
}
}
if (onRemove != null) builder.setNeutralButton(R.string.sk_remove, (d, which) -> onRemove.run());
TimelineDefinition tl=item!=null ? item : TimelineDefinition.ofHashtag(name);
TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[(int) btn.getTag()];
tl.setIcon(icon);
tl.setTitle(name);
if(item.getType()==TimelineDefinition.TimelineType.HASHTAG){
tl.setTagOptions(
mainHashtag,
tagsAny.getChipValues(),
tagsAll.getChipValues(),
tagsNone.getChipValues(),
localOnlySwitch.isChecked()
);
}
onSave.accept(tl);
})
.setNegativeButton(R.string.cancel, (d, which)->{});
builder.show();
btn.requestFocus();
}
if(onRemove!=null) builder.setNeutralButton(R.string.sk_remove, (d, which)->onRemove.run());
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
@NonNull
@Override
public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new TimelineViewHolder();
}
builder.show();
btn.requestFocus();
}
@Override
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position) {
holder.bind(data.get(position));
}
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
@NonNull
@Override
public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new TimelineViewHolder();
}
@Override
public int getItemCount() {
return data.size();
}
}
@Override
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position){
holder.bind(data.get(position));
}
private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{
private final TextView title;
private final ImageView dragger;
@Override
public int getItemCount(){
return data.size();
}
}
public TimelineViewHolder(){
super(getActivity(), R.layout.item_text, list);
title=findViewById(R.id.title);
dragger=findViewById(R.id.dragger_thingy);
}
private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{
private final TextView title;
private final ImageView dragger;
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBind(TimelineDefinition item) {
title.setText(item.getTitle(getContext()));
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
dragger.setVisibility(View.VISIBLE);
dragger.setOnTouchListener((View v, MotionEvent event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
itemTouchHelper.startDrag(this);
return true;
}
return false;
});
}
public TimelineViewHolder(){
super(getActivity(), R.layout.item_text, list);
title=findViewById(R.id.title);
dragger=findViewById(R.id.dragger_thingy);
}
private void onSave(TimelineDefinition tl) {
saveTimelines();
rebind();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBind(TimelineDefinition item){
title.setText(item.getTitle(getContext()));
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
dragger.setVisibility(View.VISIBLE);
dragger.setOnTouchListener((View v, MotionEvent event)->{
if(event.getAction()==MotionEvent.ACTION_DOWN){
itemTouchHelper.startDrag(this);
return true;
}
return false;
});
}
private void onRemove() {
removeTimeline(getAbsoluteAdapterPosition());
}
private void onSave(TimelineDefinition tl){
saveTimelines();
rebind();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onClick() {
makeTimelineEditor(item, this::onSave, this::onRemove);
}
}
private void onRemove(){
removeTimeline(getAbsoluteAdapterPosition());
}
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback {
public ItemTouchHelperCallback() {
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onClick(){
makeTimelineEditor(item, this::onSave, this::onRemove);
}
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAbsoluteAdapterPosition();
int toPosition = target.getAbsoluteAdapterPosition();
if (Math.max(fromPosition, toPosition) >= data.size() || Math.min(fromPosition, toPosition) < 0) {
return false;
} else {
Collections.swap(data, fromPosition, toPosition);
adapter.notifyItemMoved(fromPosition, toPosition);
saveTimelines();
return true;
}
}
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback{
public ItemTouchHelperCallback(){
super(ItemTouchHelper.UP|ItemTouchHelper.DOWN, ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT);
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
viewHolder.itemView.animate().alpha(0.65f);
}
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target){
int fromPosition=viewHolder.getAbsoluteAdapterPosition();
int toPosition=target.getAbsoluteAdapterPosition();
if(Math.max(fromPosition, toPosition)>=data.size() || Math.min(fromPosition, toPosition)<0){
return false;
}else{
Collections.swap(data, fromPosition, toPosition);
adapter.notifyItemMoved(fromPosition, toPosition);
saveTimelines();
return true;
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.animate().alpha(1f);
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG && viewHolder!=null){
viewHolder.itemView.animate().alpha(0.65f);
}
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAbsoluteAdapterPosition();
removeTimeline(position);
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.animate().alpha(1f);
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction){
int position=viewHolder.getAbsoluteAdapterPosition();
removeTimeline(position);
}
}
}

View File

@@ -212,6 +212,7 @@ public class HomeTimelineFragment extends StatusListFragment {
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
}
}else{
// TODO: refactor this code. it's too long. incomprehensible, even
if(downwards) {
Set<String> idsBelowGap=new HashSet<>();
boolean belowGap=false;
@@ -332,7 +333,7 @@ public class HomeTimelineFragment extends StatusListFragment {
dataLoading=false;
}
if(parent!=null) parent.hideNewPostsButton();
loadNewPosts();
super.onRefresh();
}
@Override

View File

@@ -174,7 +174,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
list.addItemDecoration(new RecyclerView.ItemDecoration(){
private Paint paint=new Paint();
private Rect tmpRect=new Rect();

View File

@@ -21,8 +21,11 @@ import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.transition.ChangeBounds;
import android.transition.Fade;
import android.transition.TransitionManager;
@@ -56,6 +59,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.accounts.SetPrivateNote;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -145,7 +149,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private SwipeRefreshLayout refreshLayout;
private View followersBtn, followingBtn;
private EditText nameEdit, bioEdit;
private ProgressBar actionProgress, notifyProgress;
private ProgressBar actionProgress, notifyProgress, noteSaveProgress;
private FrameLayout[] tabViews;
private TabLayoutMediator tabLayoutMediator;
private TextView followsYouView;
@@ -186,6 +190,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
private ListImageLoaderWrapper imgLoader;
// profile note
private FrameLayout noteWrap;
private ImageButton noteSaveBtn;
private EditText noteEdit;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -257,6 +266,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
bioEditWrap=content.findViewById(R.id.bio_edit_wrap);
actionProgress=content.findViewById(R.id.action_progress);
notifyProgress=content.findViewById(R.id.notify_progress);
noteSaveProgress=content.findViewById(R.id.note_save_progress);
fab=content.findViewById(R.id.fab);
followsYouView=content.findViewById(R.id.follows_you);
countersLayout=content.findViewById(R.id.profile_counters);
@@ -271,6 +281,51 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
avatar.setClipToOutline(true);
noteEdit=content.findViewById(R.id.note_edit);
noteWrap=content.findViewById(R.id.note_edit_wrap);
noteSaveBtn=content.findViewById(R.id.note_save_btn);
noteSaveBtn.setOnClickListener((v->{
savePrivateNote(noteEdit.getText().toString());
InputMethodManager imm=(InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0);
noteEdit.clearFocus();
noteSaveBtn.clearFocus();
}));
noteEdit.setOnFocusChangeListener((v, hasFocus)->{
if(hasFocus){
hideFab();
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
}else if(!noteSaveBtn.hasFocus()){
showFab();
hideNoteSaveBtnIfNotDirty();
}
});
noteEdit.addTextChangedListener(new TextWatcher(){
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count){
if(relationship!=null && noteSaveBtn.getVisibility()!=View.VISIBLE && !s.toString().equals(relationship.note))
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
}
@Override
public void afterTextChanged(Editable s){}
});
noteSaveBtn.setOnFocusChangeListener((v, hasFocus)->{
if(!hasFocus && !noteEdit.hasFocus()){
showFab();
hideNoteSaveBtnIfNotDirty();
}
});
FrameLayout sizeWrapper=new FrameLayout(getActivity()){
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
@@ -435,6 +490,46 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return sizeWrapper;
}
private void hideNoteSaveBtnIfNotDirty(){
if(noteEdit.getText().toString().equals(relationship.note)){
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
}
}
private void showPrivateNote(){
noteWrap.setVisibility(View.VISIBLE);
noteEdit.setText(relationship.note);
}
private void hidePrivateNote(){
noteWrap.setVisibility(View.GONE);
noteEdit.setText(null);
}
private void savePrivateNote(String note){
if(note!=null && note.equals(relationship.note)){
updateRelationship();
invalidateOptionsMenu();
return;
}
V.setVisibilityAnimated(noteSaveProgress, View.VISIBLE);
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
new SetPrivateNote(profileAccountID, note).setCallback(new Callback<>() {
@Override
public void onSuccess(Relationship result) {
updateRelationship(result);
invalidateOptionsMenu();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
V.setVisibilityAnimated(noteSaveProgress, View.GONE);
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
}
}).exec(accountID);
}
private void onAccountLoaded(Account result) {
account=result;
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
@@ -793,6 +888,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}else{
blockDomain.setVisible(false);
}
menu.findItem(R.id.edit_note).setTitle(noteWrap.getVisibility()==View.GONE && (relationship.note==null || relationship.note.isEmpty())
? R.string.sk_add_note : R.string.sk_delete_note);
}
@Override
@@ -874,6 +971,26 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}else if(id==R.id.save){
if(isInEditMode)
saveAndExitEditMode();
}else if(id==R.id.edit_note){
if(noteWrap.getVisibility()==View.GONE){
showPrivateNote();
UiUtils.beginLayoutTransition(scrollableContent);
noteEdit.requestFocus();
noteEdit.postDelayed(()->{
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.showSoftInput(noteEdit, 0);
}, 100);
}else if(relationship.note.isEmpty()){
hidePrivateNote();
UiUtils.beginLayoutTransition(scrollableContent);
}else{
new M3AlertDialogBuilder(getActivity())
.setMessage(getContext().getString(R.string.sk_private_note_confirm_delete, account.getDisplayUsername()))
.setPositiveButton(R.string.delete, (dlg, btn)->savePrivateNote(null))
.setNegativeButton(R.string.cancel, null)
.show();
}
invalidateOptionsMenu();
}
return true;
}
@@ -899,15 +1016,22 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private void updateRelationship(){
if(getActivity()==null) return;
if(relationship.note!=null && !relationship.note.isEmpty()) showPrivateNote();
else hidePrivateNote();
invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
noteSaveProgress.setIndeterminateTintList(noteEdit.getTextColors());
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
notifyButton.setSelected(relationship.notifying);
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
V.setVisibilityAnimated(noteSaveProgress, View.GONE);
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
UiUtils.beginLayoutTransition(scrollableContent);
}
public ImageButton getFab() {
@@ -1477,7 +1601,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
title.setText(item.parsedName);
value.setText(item.parsedValue);
if(item.verifiedAt!=null){
int textColor=UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63;
int textColor=UiUtils.getThemeColor(getContext(), R.attr.colorM3Success);
value.setTextColor(textColor);
value.setLinkTextColor(textColor);
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_starburst_20_regular, getActivity().getTheme()).mutate();

View File

@@ -12,18 +12,22 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback;
import name.fraser.neil.plaintext.diff_match_patch;
public class StatusEditHistoryFragment extends StatusListFragment{
private String id, url;
@@ -58,7 +62,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
@Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS);
List<StatusDisplayItem> items=new ArrayList<>();
int idx=data.indexOf(s);
if(idx>=0){
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
@@ -83,8 +87,11 @@ public class StatusEditHistoryFragment extends StatusListFragment{
EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class);
Status prev=data.get(idx+1);
if(!Objects.equals(s.content, prev.content)){
// if only formatting was changed, don't even try to create a diff text
if(!Objects.equals(HtmlParser.text(s.content), HtmlParser.text(prev.content))){
changes.add(StatusEditChangeType.TEXT_CHANGED);
//update status content to display a diffs
s.content=createDiffText(prev.content, s.content);
}
if(!Objects.equals(s.spoilerText, prev.spoilerText)){
if(s.spoilerText==null){
@@ -147,15 +154,10 @@ public class StatusEditHistoryFragment extends StatusListFragment{
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s));
items.add(1, new DummyStatusDisplayItem(s.id, this));
}
items.addAll(StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER|StatusDisplayItem.FLAG_INSET|StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS));
return items;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
}
@Override
public boolean isItemEnabled(String id){
return false;
@@ -170,4 +172,28 @@ public class StatusEditHistoryFragment extends StatusListFragment{
public Uri getWebUri(Uri.Builder base) {
return Uri.parse(url);
}
private String createDiffText(String original, String modified) {
diff_match_patch dmp=new diff_match_patch();
LinkedList<diff_match_patch.Diff> diffs=dmp.diff_main(original, modified);
dmp.diff_cleanupSemantic(diffs);
StringBuilder stringBuilder=new StringBuilder();
for(diff_match_patch.Diff diff : diffs){
switch(diff.operation){
case DELETE->{
stringBuilder.append("<edit-diff-delete>");
stringBuilder.append(diff.text);
stringBuilder.append("</edit-diff-delete>");
}
case INSERT->{
stringBuilder.append("<edit-diff-insert>");
stringBuilder.append(diff.text);
stringBuilder.append("</edit-diff-insert>");
}
default->stringBuilder.append(diff.text);
}
}
return stringBuilder.toString();
}
}

View File

@@ -25,6 +25,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.parceler.Parcels;
@@ -83,8 +84,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
@Override
public void onItemClick(String id){
Status status=getContentStatusByID(id);
if(status==null)
return;
if(status==null || status.preview) return;
status.filterRevealed=true;
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -192,10 +192,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
if(firstIndex==-1) return false;
int lastIndex=firstIndex;
while(lastIndex<displayItems.size()){
if(!displayItems.get(lastIndex).parentID.equals(parentID)) break;
StatusDisplayItem item=displayItems.get(lastIndex);
if(!item.parentID.equals(parentID) || item instanceof GapStatusDisplayItem) break;
lastIndex++;
}
int count=lastIndex-firstIndex;
if(count<1) return false;
displayItems.subList(firstIndex, lastIndex).clear();
adapter.notifyItemRangeRemoved(firstIndex, count);
return true;
@@ -350,6 +352,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
Status contentStatus=status.getContentStatus();
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
updatePoll(status.id, contentStatus, ev.poll);
AccountSessionManager.get(accountID).getCacheController().updateStatus(contentStatus);
}
}
}

View File

@@ -50,21 +50,25 @@ import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
protected Status mainStatus, updatedStatus;
protected Status mainStatus, updatedStatus, replyTo;
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
private StatusContext result;
protected boolean contextInitiallyRendered, transitionFinished;
protected boolean contextInitiallyRendered, transitionFinished, preview;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mainStatus=Parcels.unwrap(getArguments().getParcelable("status"));
replyTo=Parcels.unwrap(getArguments().getParcelable("inReplyTo"));
Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount"));
refreshing=contextInitiallyRendered=getArguments().getBoolean("refresh", false);
if(inReplyToAccount!=null)
knownAccounts.put(inReplyToAccount.id, inReplyToAccount);
data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus));
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis));
preview=mainStatus.preview;
if(preview) setRefreshEnabled(false);
setTitle(preview ? getString(R.string.sk_post_preview) : HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis));
transitionFinished = getArguments().getBoolean("noTransition", false);
}
@@ -129,11 +133,21 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
@Override
protected void doLoadData(int offset, int count){
if (refreshing) loadMainStatus();
currentRequest=new GetStatusContext(mainStatus.id)
if(preview && replyTo==null){
result=new StatusContext();
result.descendants=Collections.emptyList();
result.ancestors=Collections.emptyList();
return;
}
if(refreshing && !preview) loadMainStatus();
currentRequest=new GetStatusContext(preview ? replyTo.id : mainStatus.id)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(StatusContext result){
if(preview){
result.descendants=Collections.emptyList();
result.ancestors.add(replyTo);
}
ThreadFragment.this.result = result;
maybeApplyContext();
}

View File

@@ -1,8 +1,6 @@
package org.joinmastodon.android.fragments.report;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
@@ -25,6 +23,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.CheckableHeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
@@ -97,8 +96,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
.exec(accountID);
}
@Override
public void onItemClick(String id){
public void onToggleItem(String id){
if(selectedIDs.contains(id))
selectedIDs.remove(id);
else
@@ -121,13 +119,20 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder.getAbsoluteAdapterPosition()==0 || holder instanceof CheckableHeaderStatusDisplayItem.Holder)
return;
outRect.left=V.dp(40);
boolean isRTL=parent.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL;
if(isRTL) outRect.right=V.dp(40);
else outRect.left=V.dp(40);
if(holder instanceof AudioStatusDisplayItem.Holder){
outRect.bottom=V.dp(16);
}else if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
outRect.bottom=V.dp(16);
outRect.left+=V.dp(16);
outRect.right=V.dp(16);
outRect.bottom=V.dp(8);
if(isRTL){
outRect.right+=V.dp(16);
outRect.left=V.dp(16);
}else{
outRect.left+=V.dp(16);
outRect.right=V.dp(16);
}
}
}
});
@@ -155,9 +160,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
return adapter;
}
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
}
private void onButtonClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -201,7 +203,9 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
@Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN);
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN);
items.add(new DummyStatusDisplayItem(s.getID(), this));
return items;
}
@Override

View File

@@ -204,14 +204,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
float off=paint.getStrokeWidth()/2f;
c.drawRoundRect(V.dp(16)-off, top-off, parent.getWidth()-V.dp(16)+off, bottom+off, V.dp(12), V.dp(12), paint);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder instanceof StatusDisplayItem.Holder<?>){
outRect.left=outRect.right=V.dp(16);
}
}
});
}
}

View File

@@ -2,19 +2,38 @@ package org.joinmastodon.android.fragments.settings;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
@@ -24,30 +43,46 @@ import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
private ListItem<Void> mediaCacheItem;
private static final String TAG="SettingsAboutAppFragment";
private ListItem<Void> mediaCacheItem, copyCrashLogItem;
private CheckableListItem<Void> enablePreReleasesItem;
private AccountSession session;
private boolean timelineCacheCleared=false;
private File crashLogFile=new File(MastodonApp.context.getFilesDir(), "crash.log");
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(getString(R.string.about_app, getString(R.string.sk_app_name)));
session=AccountSessionManager.get(accountID);
onDataLoaded(List.of(
String lastModified=crashLogFile.exists()
? DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT).withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(crashLogFile.lastModified()))
: getString(R.string.sk_settings_crash_log_unavailable);
List<ListItem<Void>> items=new ArrayList<>(List.of(
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, i->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag))),
new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")),
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick),
new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick)
new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick),
copyCrashLogItem=new ListItem<>(getString(R.string.sk_settings_copy_crash_log), lastModified, 0, this::onCopyCrashLog)
));
if(GithubSelfUpdater.needSelfUpdating()){
items.add(enablePreReleasesItem=new CheckableListItem<>(R.string.sk_updater_enable_pre_releases, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enablePreReleases, i->toggleCheckableItem(enablePreReleasesItem)));
}
copyCrashLogItem.isEnabled=crashLogFile.exists();
onDataLoaded(items);
updateMediaCacheItem();
}
@Override
protected void onHidden(){
super.onHidden();
GlobalUserPreferences.enablePreReleases=enablePreReleasesItem!=null && enablePreReleasesItem.checked;
GlobalUserPreferences.save();
if(timelineCacheCleared) getActivity().recreate();
}
@@ -94,4 +129,17 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
mediaCacheItem.isEnabled=size>0;
rebindItem(mediaCacheItem);
}
private void onCopyCrashLog(ListItem<?> item){
if(!crashLogFile.exists()) return;
try(InputStream is=new FileInputStream(crashLogFile)){
BufferedReader reader=new BufferedReader(new InputStreamReader(is));
StringBuilder sb=new StringBuilder();
String line;
while ((line=reader.readLine())!=null) sb.append(line).append("\n");
UiUtils.copyText(list, sb.toString());
} catch(IOException e){
Log.e(TAG, "Error reading crash log", e);
}
}
}

View File

@@ -226,7 +226,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
AlertDialog.Builder alert=new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_color_palette)
.setSingleChoiceItems(items.toArray(String[]::new),
.setSingleChoiceItems(items.stream().toArray(String[]::new),
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->save.accept(false))
.setNegativeButton(R.string.cancel, null);

View File

@@ -334,13 +334,15 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
return;
}
UnifiedPush.unregisterApp(
getContext(),
accountID
);
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()) {
UnifiedPush.unregisterApp(
getContext(),
accountSession.getID()
);
//re-register to fcm
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().registerAccountForPush(getPushSubscription());
//re-register to fcm
accountSession.getPushSubscriptionManager().registerAccountForPush(getPushSubscription());
}
unifiedPushItem.toggle();
rebindItem(unifiedPushItem);
}
@@ -350,12 +352,14 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
(dialog, which)->{
String userDistrib = distributors.get(which);
UnifiedPush.saveDistributor(getContext(), userDistrib);
UnifiedPush.registerApp(
getContext(),
accountID,
new ArrayList<>(),
getContext().getPackageName()
);
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
UnifiedPush.registerApp(
getContext(),
accountSession.getID(),
new ArrayList<>(),
getContext().getPackageName()
);
}
unifiedPushItem.toggle();
rebindItem(unifiedPushItem);
}).setOnCancelListener(d->rebindItem(unifiedPushItem)).show();

View File

@@ -0,0 +1,14 @@
package org.joinmastodon.android.model;
public class AkkomaTranslation extends BaseModel{
public String text;
public String detectedLanguage;
public Translation toTranslation() {
Translation translation=new Translation();
translation.content=text;
translation.detectedSourceLanguage=detectedLanguage;
translation.provider="Akkoma";
return translation;
}
}

View File

@@ -161,11 +161,15 @@ public class Instance extends BaseModel{
case BUBBLE_TIMELINE -> pleromaFeatures
.map(f -> f.contains("bubble_timeline"))
.orElse(false);
case MACHINE_TRANSLATION -> pleromaFeatures
.map(f -> f.contains("akkoma:machine_translation"))
.orElse(false);
};
}
public enum Feature {
BUBBLE_TIMELINE
BUBBLE_TIMELINE,
MACHINE_TRANSLATION
}
@Parcel

View File

@@ -6,6 +6,7 @@ import org.joinmastodon.android.model.Poll.Option;
import org.parceler.Parcel;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Collectors;
@@ -27,10 +28,10 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
@Override
public void postprocess() throws ObjectValidationException {
super.postprocess();
if (mediaAttachments == null) mediaAttachments = List.of();
if(mediaAttachments==null) mediaAttachments=List.of();
for(Attachment a:mediaAttachments)
a.postprocess();
if (params != null) params.postprocess();
if(params!=null) params.postprocess();
}
@Parcel
@@ -53,7 +54,7 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
@Override
public void postprocess() throws ObjectValidationException {
super.postprocess();
if (poll != null) poll.postprocess();
if(poll!=null) poll.postprocess();
}
}
@@ -67,25 +68,26 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
public boolean hideTotals;
public Poll toPoll() {
Poll p = new Poll();
p.voted = true;
p.emojis = List.of();
p.ownVotes = List.of();
p.multiple = multiple;
p.options = options.stream().map(Option::new).collect(Collectors.toList());
Poll p=new Poll();
p.voted=true;
p.emojis=List.of();
p.ownVotes=List.of();
p.multiple=multiple;
p.options=options.stream().map(Option::new).collect(Collectors.toList());
p.expiresAt=Instant.now().plus(Integer.parseInt(expiresIn)+1, ChronoUnit.SECONDS);
return p;
}
}
public Status toStatus() {
Status s = Status.ofFake(id, params.text, scheduledAt);
s.mediaAttachments = mediaAttachments;
s.inReplyToId = params.inReplyToId > 0 ? "" + params.inReplyToId : null;
s.spoilerText = params.spoilerText;
s.visibility = params.visibility;
s.language = params.language;
s.sensitive = params.sensitive;
if (params.poll != null) s.poll = params.poll.toPoll();
Status s=Status.ofFake(id, params.text, scheduledAt);
s.mediaAttachments=mediaAttachments;
s.inReplyToId=params.inReplyToId>0 ? ""+params.inReplyToId : null;
s.spoilerText=params.spoilerText;
s.visibility=params.visibility;
s.language=params.language;
s.sensitive=params.sensitive;
if(params.poll!=null) s.poll=params.poll.toPoll();
return s;
}
}

View File

@@ -4,6 +4,7 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDeserializer;
import android.text.TextUtils;
import android.util.Pair;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
@@ -96,6 +97,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation;
public transient boolean fromStatusCreated;
public transient boolean preview;
public Status(){}
@@ -123,6 +125,8 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
if(filtered!=null)
for(FilterResult fr:filtered)
fr.postprocess();
if(quote!=null)
quote.postprocess();
spoilerRevealed=!hasSpoiler();
if(!spoilerRevealed) sensitive=true;
@@ -217,16 +221,17 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
public boolean isEligibleForTranslation(AccountSession session){
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
boolean translateEnabled = instanceInfo != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
instanceInfo.v2.configuration.translation.enabled;
Instance instanceInfo=AccountSessionManager.getInstance().getInstanceInfo(session.domain);
boolean translateEnabled=instanceInfo!=null && (
(instanceInfo.v2!=null && instanceInfo.v2.configuration.translation!=null && instanceInfo.v2.configuration.translation.enabled) ||
(instanceInfo.isAkkoma() && instanceInfo.hasFeature(Instance.Feature.MACHINE_TRANSLATION))
);
try {
String bottomText = BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find()
Pair<String, List<String>> decoded=BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
if(bottomText==null || bottomText.length()==0 || bottomText.equals("\u0005")) bottomText=null;
String bottomText=decoded==null || decoded.second.stream().allMatch(s->s.trim().isEmpty()) ? null : decoded.first;
if(bottomText!=null){
translation=new Translation();
translation.content=bottomText;
@@ -263,7 +268,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
s.visibility=StatusPrivacy.PUBLIC;
s.reactions=List.of();
s.mentions=List.of();
s.tags =List.of();
s.tags=List.of();
s.emojis=List.of();
s.filtered=List.of();
return s;

View File

@@ -12,6 +12,8 @@ import androidx.annotation.StringRes;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BookmarkedStatusListFragment;
import org.joinmastodon.android.fragments.FavoritedStatusListFragment;
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.HomeTimelineFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment;
@@ -138,6 +140,8 @@ public class TimelineDefinition {
case LIST -> listTitle;
case HASHTAG -> hashtagName;
case BUBBLE -> ctx.getString(R.string.sk_timeline_bubble);
case BOOKMARKS -> ctx.getString(R.string.bookmarks);
case FAVORITES -> ctx.getString(R.string.your_favorites);
};
}
@@ -150,6 +154,8 @@ public class TimelineDefinition {
case LIST -> listIsExclusive ? Icon.EXCLUSIVE_LIST : Icon.LIST;
case HASHTAG -> Icon.HASHTAG;
case BUBBLE -> Icon.BUBBLE;
case BOOKMARKS -> Icon.BOOKMARKS;
case FAVORITES -> Icon.FAVORITES;
};
}
@@ -162,6 +168,8 @@ public class TimelineDefinition {
case HASHTAG -> new HashtagTimelineFragment();
case POST_NOTIFICATIONS -> new NotificationsListFragment();
case BUBBLE -> new BubbleTimelineFragment();
case BOOKMARKS -> new BookmarkedStatusListFragment();
case FAVORITES -> new FavoritedStatusListFragment();
};
}
@@ -228,7 +236,19 @@ public class TimelineDefinition {
return args;
}
public enum TimelineType { HOME, LOCAL, FEDERATED, POST_NOTIFICATIONS, LIST, HASHTAG, BUBBLE }
public enum TimelineType {
HOME,
LOCAL,
FEDERATED,
POST_NOTIFICATIONS,
LIST,
HASHTAG,
BUBBLE,
// not really timelines, but some people want it, so,,
BOOKMARKS,
FAVORITES
}
public enum Icon {
HEART(R.drawable.ic_fluent_heart_24_regular, R.string.sk_icon_heart),
@@ -293,6 +313,13 @@ public class TimelineDefinition {
DOCTOR(R.drawable.ic_fluent_doctor_24_regular, R.string.sk_icon_doctor),
DIAMOND(R.drawable.ic_fluent_premium_24_regular, R.string.sk_icon_diamond),
UMBRELLA(R.drawable.ic_fluent_umbrella_24_regular, R.string.sk_icon_umbrella),
WATER(R.drawable.ic_fluent_water_24_regular, R.string.sk_icon_water),
SUN(R.drawable.ic_fluent_weather_sunny_24_regular, R.string.sk_icon_sun),
SUNSET(R.drawable.ic_fluent_weather_sunny_low_24_regular, R.string.sk_icon_sunset),
CLOUD(R.drawable.ic_fluent_cloud_24_regular, R.string.sk_icon_cloud),
THUNDERSTORM(R.drawable.ic_fluent_weather_thunderstorm_24_regular, R.string.sk_icon_thunderstorm),
RAIN(R.drawable.ic_fluent_weather_rain_24_regular, R.string.sk_icon_rain),
SNOWFLAKE(R.drawable.ic_fluent_weather_snowflake_24_regular, R.string.sk_icon_snowflake),
HOME(R.drawable.ic_fluent_home_24_regular, R.string.sk_timeline_home, true),
LOCAL(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),
@@ -301,7 +328,9 @@ public class TimelineDefinition {
LIST(R.drawable.ic_fluent_people_24_regular, R.string.sk_list, true),
EXCLUSIVE_LIST(R.drawable.ic_fluent_rss_24_regular, R.string.sk_exclusive_list, true),
HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true),
BUBBLE(R.drawable.ic_fluent_circle_24_regular, R.string.sk_timeline_bubble, true);
BUBBLE(R.drawable.ic_fluent_circle_24_regular, R.string.sk_timeline_bubble, true),
BOOKMARKS(R.drawable.ic_fluent_bookmark_multiple_24_regular, R.string.bookmarks, true),
FAVORITES(R.drawable.ic_fluent_star_24_regular, R.string.your_favorites, true);
public final int iconRes, nameRes;
public final boolean hidden;
@@ -321,6 +350,8 @@ public class TimelineDefinition {
public static final TimelineDefinition LOCAL_TIMELINE = new TimelineDefinition(TimelineType.LOCAL);
public static final TimelineDefinition FEDERATED_TIMELINE = new TimelineDefinition(TimelineType.FEDERATED);
public static final TimelineDefinition POSTS_TIMELINE = new TimelineDefinition(TimelineType.POST_NOTIFICATIONS);
public static final TimelineDefinition BOOKMARKS_TIMELINE = new TimelineDefinition(TimelineType.BOOKMARKS);
public static final TimelineDefinition FAVORITES_TIMELINE = new TimelineDefinition(TimelineType.FAVORITES);
public static final TimelineDefinition BUBBLE_TIMELINE = new TimelineDefinition(TimelineType.BUBBLE) {
@Override
public boolean isCompatible(AccountSession session) {
@@ -365,6 +396,8 @@ public class TimelineDefinition {
LOCAL_TIMELINE,
FEDERATED_TIMELINE,
POSTS_TIMELINE,
BUBBLE_TIMELINE
BUBBLE_TIMELINE,
BOOKMARKS_TIMELINE,
FAVORITES_TIMELINE
);
}

View File

@@ -1,10 +1,30 @@
package org.joinmastodon.android.model;
import org.joinmastodon.android.api.AllFieldsAreRequired;
@AllFieldsAreRequired
import org.joinmastodon.android.api.RequiredField;
public class Translation extends BaseModel{
@RequiredField
public String content;
@RequiredField
public String detectedSourceLanguage;
@RequiredField
public String provider;
public String spoilerText;
public MediaAttachment[] mediaAttachments;
public PollTranslation poll;
public static class MediaAttachment {
public String id;
public String description;
}
public static class PollTranslation {
public String id;
public PollOption[] options;
}
public static class PollOption {
public String title;
}
}

View File

@@ -32,7 +32,6 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class AudioStatusDisplayItem extends StatusDisplayItem{
public final Status status;
public final Attachment attachment;
private final ImageLoaderRequest imageRequest;

View File

@@ -3,13 +3,13 @@ package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.CheckBox;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.report.ReportAddPostsChoiceFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.views.CheckableRelativeLayout;
@@ -34,8 +34,16 @@ public class CheckableHeaderStatusDisplayItem extends HeaderStatusDisplayItem{
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_header_checkable, parent);
checkbox=findViewById(R.id.checkbox);
view=(CheckableRelativeLayout) itemView;
view=findViewById(R.id.checkbox_wrap);
checkbox.setBackground(new CheckBox(activity).getButtonDrawable());
view.setOnClickListener(this::onToggle);
view.setAccessibilityDelegate(new View.AccessibilityDelegate(){
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info){
super.onInitializeAccessibilityNodeInfo(host, info);
info.setClassName(CheckBox.class.getName());
}
});
}
@Override
@@ -46,6 +54,12 @@ public class CheckableHeaderStatusDisplayItem extends HeaderStatusDisplayItem{
}
}
private void onToggle(View v){
if(item.parentFragment instanceof ReportAddPostsChoiceFragment reportFragment){
reportFragment.onToggleItem(item.parentID);
}
}
public void setIsChecked(Predicate<Holder> isChecked){
this.isChecked=isChecked;
}

View File

@@ -58,7 +58,6 @@ import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
public final Status status;
private final Drawable placeholder;
private final boolean hideEmpty, forAnnouncement, playGifs;
private final String accountID;

View File

@@ -36,7 +36,6 @@ import androidx.annotation.PluralsRes;
import me.grishka.appkit.Nav;
public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
public final Status status;
public final String accountID;
private static final DateTimeFormatter TIME_FORMATTER=DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
@@ -130,6 +129,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
}
private void startAccountListFragment(Class<? extends StatusRelatedAccountListFragment> cls){
if(item.status.preview) return;
Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID());
args.putParcelable("status", Parcels.wrap(item.status));
@@ -137,6 +137,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
}
private void startEditHistoryFragment(){
if(item.status.preview) return;
Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID());
args.putString("id", item.status.id);

View File

@@ -38,7 +38,6 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class FooterStatusDisplayItem extends StatusDisplayItem{
public final Status status;
private final String accountID;
public boolean hideCounts;
@@ -159,6 +158,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onButtonTouch(View v, MotionEvent event){
if(item.status.preview) return false;
boolean disabled = !v.isEnabled() || (v instanceof FrameLayout parentFrame &&
parentFrame.getChildCount() > 0 && !parentFrame.getChildAt(0).isEnabled());
int action = event.getAction();
@@ -181,6 +181,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onReplyClick(View v){
if(item.status.preview) return;
UiUtils.opacityIn(v);
Bundle args=new Bundle();
args.putString("account", item.accountID);
@@ -189,6 +190,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onReplyLongClick(View v) {
if(item.status.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickAccount(v.getContext(), item.accountID, R.string.sk_reply_as, R.drawable.ic_fluent_arrow_reply_28_regular, session -> {
Bundle args=new Bundle();
@@ -204,6 +206,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onBoostClick(View v){
if(item.status.preview) return;
if (GlobalUserPreferences.confirmBoost) {
UiUtils.opacityIn(v);
onBoostLongClick(v);
@@ -219,6 +222,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onBoostLongClick(View v){
if(item.status.preview) return false;
Context ctx = itemView.getContext();
View menu = LayoutInflater.from(ctx).inflate(R.layout.item_boost_menu, null);
Dialog dialog = new M3AlertDialogBuilder(ctx).setView(menu).create();
@@ -301,6 +305,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onFavoriteClick(View v){
if(item.status.preview) return;
favorite.setSelected(!item.status.favourited);
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
UiUtils.opacityIn(v);
@@ -309,6 +314,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onFavoriteLongClick(View v) {
if(item.status.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickInteractAs(v.getContext(),
item.accountID, item.status,
@@ -323,6 +329,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onBookmarkClick(View v){
if(item.status.preview) return;
bookmark.setSelected(!item.status.bookmarked);
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked, r->{
UiUtils.opacityIn(v);
@@ -330,6 +337,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onBookmarkLongClick(View v) {
if(item.status.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickInteractAs(v.getContext(),
item.accountID, item.status,
@@ -344,6 +352,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onShareClick(View v){
if(item.status.preview) return;
UiUtils.opacityIn(v);
Intent intent=new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
@@ -352,6 +361,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private boolean onShareLongClick(View v){
if(item.status.preview) return false;
UiUtils.copyText(v, item.status.url);
return true;
}

View File

@@ -8,6 +8,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -18,7 +19,6 @@ import me.grishka.appkit.utils.V;
public class GapStatusDisplayItem extends StatusDisplayItem{
public boolean loading;
private final Status status;
public GapStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status){
super(parentID, parentFragment);
@@ -63,10 +63,14 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
}
top.setClickable(!item.loading);
bottom.setClickable(!item.loading);
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
Instant dateBelow=next instanceof HeaderStatusDisplayItem h ? h.status.createdAt
: next instanceof ReblogOrReplyLineStatusDisplayItem l ? l.status.createdAt
: null;
Status next=!(item.parentFragment instanceof StatusListFragment) ? null : getNextVisibleDisplayItem(i->{
Status s=((StatusListFragment) item.parentFragment).getStatusByID(i.parentID);
return s!=null && !s.fromStatusCreated;
})
.map(i->((StatusListFragment) item.parentFragment).getStatusByID(i.parentID))
.orElse(null);
bottom.setVisibility(next==null ? View.GONE : View.VISIBLE);
Instant dateBelow=next!=null ? next.createdAt : null;
String text=dateBelow!=null && item.status.createdAt!=null && dateBelow.isBefore(item.status.createdAt)
? UiUtils.formatPeriodBetween(item.parentFragment.getContext(), dateBelow, item.status.createdAt)
: null;

View File

@@ -76,7 +76,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
private String accountID;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private SpannableStringBuilder parsedName;
public final Status status;
public boolean hasVisibilityToggle;
boolean needBottomPadding;
private CharSequence extraText;
@@ -446,6 +445,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}
private void onMoreClick(View v){
if(item.status.preview) return;
updateOptionsMenu();
optionsMenu.show();
if(relationship==null && currentRelationshipRequest==null){
@@ -546,7 +546,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
follow.setIcon(following ? R.drawable.ic_fluent_person_delete_24_regular : R.drawable.ic_fluent_person_add_24_regular);
manageUserLists.setVisible(relationship != null && relationship.following);
manageUserLists.setTitle(item.parentFragment.getString(R.string.sk_lists_with_user, username));
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow);
// ic_fluent_person_add_24_regular actually has a width of 25dp -.-
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow, following ? 0 : V.dp(-1));
}
workaroundChangingMenuItemWidths(menu, username);

View File

@@ -24,7 +24,6 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class LinkCardStatusDisplayItem extends StatusDisplayItem{
private final Status status;
private final UrlImageLoaderRequest imgRequest;
public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status){

View File

@@ -12,6 +12,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.text.TextUtils;
import android.util.Pair;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -26,6 +27,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
@@ -38,7 +40,12 @@ import org.joinmastodon.android.ui.views.MediaGridLayout;
import org.joinmastodon.android.utils.TypedObjectPool;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
@@ -52,8 +59,8 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
private final TypedObjectPool<GridItemType, MediaAttachmentViewController> viewPool;
private final List<Attachment> attachments;
private final Map<String, Pair<String, String>> translatedAttachments = new HashMap<>();
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
public final Status status;
public String sensitiveTitle;
public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List<Attachment> attachments, Status status){
@@ -189,6 +196,25 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
c.btnsWrap.setAlpha(1f);
}
controllers.add(c);
if (item.status.translation != null){
if(item.status.translationState==Status.TranslationState.SHOWN){
if(!item.translatedAttachments.containsKey(att.id)){
Optional<Translation.MediaAttachment> translatedAttachment=Arrays.stream(item.status.translation.mediaAttachments).filter(mediaAttachment->mediaAttachment.id.equals(att.id)).findFirst();
translatedAttachment.ifPresent(mediaAttachment->{
item.translatedAttachments.put(mediaAttachment.id, new Pair<>(att.description, mediaAttachment.description));
att.description=mediaAttachment.description;
});
}else{
//SAFETY: must be non-null, as we check if the map contains the attachment before
att.description=Objects.requireNonNull(item.translatedAttachments.get(att.id)).second;
}
}else{
if (item.translatedAttachments.containsKey(att.id)) {
att.description=Objects.requireNonNull(item.translatedAttachments.get(att.id)).first;
}
}
}
c.bind(att, item.status);
i++;
}
@@ -382,7 +408,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
}
public MediaAttachmentViewController getViewController(int index){
return controllers.get(index);
return index<controllers.size() ? controllers.get(index) : null;
}
public void setClipChildren(boolean clip){

View File

@@ -6,6 +6,7 @@ import static org.joinmastodon.android.ui.utils.UiUtils.generateFormattedString;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.ColorStateList;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
@@ -146,6 +147,8 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
item.emojiHelper.setImageDrawable(index-1, image);
text.invalidate();
}
if(image instanceof Animatable)
((Animatable) image).start();
}
@Override

View File

@@ -11,6 +11,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
@@ -23,6 +24,7 @@ import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private CharSequence text;
private CharSequence translatedText;
public final Poll.Option option;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private boolean showResults;
@@ -30,12 +32,15 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private boolean isMostVoted;
private final int optionIndex;
public final Poll poll;
public final Status status;
public PollOptionStatusDisplayItem(String parentID, Poll poll, int optionIndex, BaseStatusListFragment parentFragment){
public PollOptionStatusDisplayItem(String parentID, Poll poll, int optionIndex, BaseStatusListFragment parentFragment, Status status){
super(parentID, parentFragment);
this.optionIndex=optionIndex;
option=poll.options.get(optionIndex);
this.poll=poll;
this.status=status;
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
emojiHelper.setText(text);
showResults=poll.isExpired() || poll.voted;
@@ -84,7 +89,14 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(PollOptionStatusDisplayItem item){
text.setText(item.text);
if (item.status.translation != null && item.status.translationState == Status.TranslationState.SHOWN) {
if(item.translatedText==null){
item.translatedText=item.status.translation.poll.options[item.optionIndex].title;
}
text.setText(item.translatedText);
} else {
text.setText(item.text);
}
percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE);
itemView.setClickable(!item.showResults);
icon.setImageDrawable(itemView.getContext().getDrawable(item.poll.multiple ?

View File

@@ -43,7 +43,6 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
public boolean needBottomPadding;
ReblogOrReplyLineStatusDisplayItem extra;
CharSequence fullText;
Status status;
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, Status status) {
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text, status);

View File

@@ -24,9 +24,9 @@ import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class SpoilerStatusDisplayItem extends StatusDisplayItem{
public final Status status;
public final ArrayList<StatusDisplayItem> contentItems=new ArrayList<>();
private final CharSequence parsedTitle;
private CharSequence translatedTitle;
private final CustomEmojiHelper emojiHelper;
private final Type type;
private final int attachmentCount;
@@ -90,7 +90,14 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(SpoilerStatusDisplayItem item){
title.setText(item.parsedTitle);
if(item.status.translationState==Status.TranslationState.SHOWN){
if(item.translatedTitle==null){
item.translatedTitle=item.status.translation.spoilerText;
}
title.setText(item.translatedTitle);
}else{
title.setText(item.parsedTitle);
}
action.setText(item.status.spoilerRevealed ? R.string.spoiler_hide : R.string.sk_spoiler_show);
itemView.setPadding(
itemView.getPaddingLeft(),

View File

@@ -28,6 +28,7 @@ import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterResult;
@@ -42,9 +43,11 @@ import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
@@ -55,13 +58,15 @@ import me.grishka.appkit.views.UsableRecyclerView;
public abstract class StatusDisplayItem{
public final String parentID;
public final BaseStatusListFragment<?> parentFragment;
public Status status;
public boolean inset;
public int index;
public boolean
hasDescendantNeighbor=false,
hasAncestoringNeighbor=false,
isMainStatus=true,
isDirectDescendant=false;
isDirectDescendant=false,
isForQuote=false;
public static final int FLAG_INSET=1;
public static final int FLAG_NO_FOOTER=1 << 1;
@@ -70,7 +75,8 @@ public abstract class StatusDisplayItem{
public static final int FLAG_NO_HEADER=1 << 4;
public static final int FLAG_NO_TRANSLATE=1 << 5;
public static final int FLAG_NO_EMOJI_REACTIONS=1 << 6;
public static final int FLAG_IS_FOR_QUOTE=1 << 7;
public void setAncestryInfo(
boolean hasDescendantNeighbor,
boolean hasAncestoringNeighbor,
@@ -160,7 +166,7 @@ public abstract class StatusDisplayItem{
HeaderStatusDisplayItem header=null;
boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts;
if((flags & FLAG_NO_HEADER)==0){
ReblogOrReplyLineStatusDisplayItem replyLine = null;
boolean threadReply = statusForContent.inReplyToAccountId != null &&
@@ -229,27 +235,28 @@ public abstract class StatusDisplayItem{
if(statusForContent.hasSpoiler()){
if (AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) statusForContent.spoilerRevealed = true;
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, statusForContent, Type.SPOILER);
if((flags & FLAG_IS_FOR_QUOTE)!=0){
for(StatusDisplayItem item:spoilerItem.contentItems){
item.isForQuote=true;
}
}
items.add(spoilerItem);
contentItems=spoilerItem.contentItems;
}else{
contentItems=items;
}
if (statusForContent.quote != null) {
boolean hasQuoteInlineTag = statusForContent.content.contains("<span class=\"quote-inline\">");
if (!hasQuoteInlineTag) {
String quoteUrl = statusForContent.quote.url;
String quoteInline = String.format("<span class=\"quote-inline\">%sRE: <a href=\"%s\">%s</a></span>",
statusForContent.content.endsWith("</p>") ? "" : "<br/><br/>", quoteUrl, quoteUrl);
statusForContent.content += quoteInline;
}
if(statusForContent.quote!=null) {
int quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br/><br/>RE:");
if (quoteInlineIndex!=-1)
statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
}
boolean hasSpoiler=!TextUtils.isEmpty(statusForContent.spoilerText);
if(!TextUtils.isEmpty(statusForContent.content)){
SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID);
SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, fragment.getContext());
HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered);
TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, (flags & FLAG_NO_TRANSLATE) != 0);
TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, fragment.getContext()), fragment, statusForContent, (flags & FLAG_NO_TRANSLATE) != 0);
contentItems.add(text);
}else if(!hasSpoiler && header!=null){
header.needBottomPadding=true;
@@ -267,9 +274,11 @@ public abstract class StatusDisplayItem{
}
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
MediaGridStatusDisplayItem mediaGrid=new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent);
if((flags & FLAG_MEDIA_FORCE_HIDDEN)!=0)
if((flags & FLAG_MEDIA_FORCE_HIDDEN)!=0){
mediaGrid.sensitiveTitle=fragment.getString(R.string.media_hidden);
else if(statusForContent.sensitive && AccountSessionManager.get(accountID).getLocalPreferences().revealCWs && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia)
statusForContent.sensitiveRevealed=false;
statusForContent.sensitive=true;
} else if(statusForContent.sensitive && AccountSessionManager.get(accountID).getLocalPreferences().revealCWs && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia)
statusForContent.sensitiveRevealed=true;
contentItems.add(mediaGrid);
}
@@ -282,17 +291,23 @@ public abstract class StatusDisplayItem{
}
}
if(statusForContent.poll!=null){
buildPollItems(parentID, fragment, statusForContent.poll, contentItems);
buildPollItems(parentID, fragment, statusForContent.poll, status, contentItems);
}
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty()){
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty() && statusForContent.quote==null){
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent));
}
if(statusForContent.quote!=null && !(parentObject instanceof Notification)){
if(!statusForContent.mediaAttachments.isEmpty() && statusForContent.poll==null) // add spacing if immediately preceded by attachment
contentItems.add(new DummyStatusDisplayItem(parentID, fragment));
contentItems.addAll(buildItems(fragment, statusForContent.quote, accountID, parentObject, knownAccounts, filterContext, FLAG_NO_FOOTER | FLAG_INSET | FLAG_NO_EMOJI_REACTIONS | FLAG_IS_FOR_QUOTE));
}
if(contentItems!=items && statusForContent.spoilerRevealed){
items.addAll(contentItems);
}
AccountLocalPreferences lp=fragment.getLocalPrefs();
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && lp.emojiReactionsEnabled &&
(lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment)){
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && !status.preview && lp.emojiReactionsEnabled &&
(lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment) &&
statusForContent.reactions!=null){
boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id);
boolean showAddButton=lp.showEmojiReactions==ALWAYS || isMainStatus;
items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !showAddButton, false));
@@ -302,38 +317,55 @@ public abstract class StatusDisplayItem{
footer=new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID);
footer.hideCounts=hideCounts;
items.add(footer);
if(status.hasGapAfter!=null && !(fragment instanceof ThreadFragment))
items.add(new GapStatusDisplayItem(parentID, fragment, status));
}
int i=1;
boolean inset=(flags & FLAG_INSET)!=0;
boolean isForQuote=(flags & FLAG_IS_FOR_QUOTE)!=0;
// add inset dummy so last content item doesn't clip out of inset bounds
if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0){
if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0 && !isForQuote){
items.add(new DummyStatusDisplayItem(parentID, fragment));
// in case we ever need the dummy to display a margin for the media grid again:
// (i forgot why we apparently don't need this anymore)
// !contentItems.isEmpty() && contentItems
// .get(contentItems.size() - 1) instanceof MediaGridStatusDisplayItem));
}
GapStatusDisplayItem gap=null;
if((flags & FLAG_NO_FOOTER)==0 && status.hasGapAfter!=null && !(fragment instanceof ThreadFragment))
items.add(gap=new GapStatusDisplayItem(parentID, fragment, status));
int i=1;
for(StatusDisplayItem item:items){
item.inset=inset;
if(inset)
item.inset=true;
if(isForQuote){
item.status=statusForContent;
item.isForQuote=true;
}
item.index=i++;
}
if(items!=contentItems && !statusForContent.spoilerRevealed){
for(StatusDisplayItem item:contentItems){
item.inset=inset;
if(inset)
item.inset=true;
if(isForQuote){
item.status=statusForContent;
item.isForQuote=true;
}
item.index=i++;
}
}
return applyingFilter==null ? items :
new ArrayList<>(List.of(new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items, applyingFilter)));
List<StatusDisplayItem> nonGapItems=gap!=null ? items.subList(0, items.size()-1) : items;
WarningFilteredStatusDisplayItem warning=applyingFilter==null ? null :
new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, nonGapItems, applyingFilter);
return applyingFilter==null ? items : new ArrayList<>(gap!=null
? List.of(warning, gap)
: Collections.singletonList(warning)
);
}
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items){
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, Status status, List<StatusDisplayItem> items){
int i=0;
for(Poll.Option opt:poll.options){
items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment));
items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment, status));
i++;
}
items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll));
@@ -366,12 +398,15 @@ public abstract class StatusDisplayItem{
}
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
private Context context;
public Holder(View itemView){
super(itemView);
}
public Holder(Context context, int layout, ViewGroup parent){
super(context, layout, parent);
this.context=context;
}
public String getItemID(){
@@ -380,16 +415,28 @@ public abstract class StatusDisplayItem{
@Override
public void onClick(){
if(item.isForQuote){
item.status.filterRevealed=true;
Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID());
args.putParcelable("status", Parcels.wrap(item.status.clone()));
args.putBoolean("refresh", true);
Nav.go((Activity) context, ThreadFragment.class, args);
return;
}
item.parentFragment.onItemClick(item.parentID);
}
public Optional<StatusDisplayItem> getNextVisibleDisplayItem(){
return getNextVisibleDisplayItem(null);
}
public Optional<StatusDisplayItem> getNextVisibleDisplayItem(Predicate<StatusDisplayItem> predicate){
Optional<StatusDisplayItem> next=getNextDisplayItem();
for(int offset=1; next.isPresent(); next=getDisplayItemOffset(++offset)){
if(!next.map(n->
(n instanceof EmojiReactionsStatusDisplayItem e && e.isHidden()) ||
(n instanceof DummyStatusDisplayItem)
).orElse(false)) return next;
boolean isHidden=next.map(n->(n instanceof EmojiReactionsStatusDisplayItem e && e.isHidden())
|| (n instanceof DummyStatusDisplayItem)).orElse(false);
if(!isHidden && (predicate==null || predicate.test(next.get()))) return next;
}
return Optional.empty();
}
@@ -409,13 +456,13 @@ public abstract class StatusDisplayItem{
public boolean isLastDisplayItemForStatus(){
return getNextVisibleDisplayItem()
.map(n->!n.parentID.equals(item.parentID))
.map(next->!next.parentID.equals(item.parentID) || item.inset && !next.inset)
.orElse(true);
}
@Override
public boolean isEnabled(){
return item.parentFragment.isItemEnabled(item.parentID);
return item.parentFragment.isItemEnabled(item.parentID) || item.isForQuote;
}
}
}

View File

@@ -39,7 +39,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public boolean textSelectable;
public boolean reduceTopPadding;
public boolean disableTranslate;
public final Status status;
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
super(parentID, parentFragment);
@@ -113,7 +112,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
text.setText(item.text);
}
text.setTextIsSelectable(false);
if(item.textSelectable) itemView.post(() -> text.setTextIsSelectable(true));
if(item.textSelectable && !item.isForQuote) itemView.post(() -> text.setTextIsSelectable(true));
text.setInvalidateOnEveryFrame(false);
itemView.setClickable(false);
itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom());
@@ -124,8 +123,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
if(next!=null && !next.parentID.equals(item.parentID)) next=null;
int bottomPadding=next instanceof FooterStatusDisplayItem ? V.dp(6)
: item.inset ? V.dp(12)
int bottomPadding=item.inset ? V.dp(12)
: next instanceof FooterStatusDisplayItem ? V.dp(6)
: (next instanceof EmojiReactionsStatusDisplayItem || next==null) ? 0
: V.dp(12);
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), bottomPadding);
@@ -192,7 +191,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public void updateTranslation(boolean updateText){
if(item.status==null)
return;
boolean translateEnabled=!item.disableTranslate && item.status.isEligibleForTranslation(item.parentFragment.getSession());
boolean translateEnabled=!item.disableTranslate && item.status.isEligibleForTranslation(item.parentFragment.getSession()) && !item.isForQuote;
if(translationFooter==null && translateEnabled){
translationFooter=translationFooterStub.inflate();
translationInfo=findViewById(R.id.translation_info_text);
@@ -211,8 +210,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
String existingTransLang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null;
String lang=existingTransLang!=null ? existingTransLang : item.status.getContentStatus().language;
Locale locale=lang!=null ? Locale.forLanguageTag(lang) : null;
translationButton.setText(locale!=null
? item.parentFragment.getString(R.string.translate_post, locale.getDisplayLanguage())
String displayLang=locale==null || locale.getDisplayLanguage().isBlank() ? lang : locale.getDisplayLanguage();
translationButton.setText(displayLang!=null
? item.parentFragment.getString(R.string.translate_post, displayLang)
: item.parentFragment.getString(R.string.sk_translate_post));
translationButton.setClickable(true);
translationButton.animate().alpha(1).setDuration(100).start();

View File

@@ -14,7 +14,6 @@ import java.util.List;
public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{
public boolean loading;
public final Status status;
public List<StatusDisplayItem> filteredItems;
public LegacyFilter applyingFilter;

View File

@@ -0,0 +1,26 @@
package org.joinmastodon.android.ui.text;
import android.text.TextPaint;
import android.text.style.CharacterStyle;
public class DiffRemovedSpan extends CharacterStyle {
private final String text;
private final int color;
public DiffRemovedSpan(String text, int color){
this.text=text;
this.color=color;
}
@Override
public void updateDrawState(TextPaint tp) {
tp.setStrikeThruText(true);
tp.setColor(color);
}
public String getText() {
return text;
}
}

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android.ui.text;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -69,6 +70,10 @@ public class HtmlParser{
private HtmlParser(){}
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID){
return parse(source, emojis, mentions, tags, accountID, null);
}
/**
* Parse HTML and custom emoji into a spanned string for display.
* Supported tags: <ul>
@@ -81,7 +86,7 @@ public class HtmlParser{
* @param emojis Custom emojis that are present in source as <code>:code:</code>
* @return a spanned string
*/
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID){
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID, Context context){
class SpanInfo{
public Object span;
public int start;
@@ -106,6 +111,9 @@ public class HtmlParser{
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
final SpannableStringBuilder ssb=new SpannableStringBuilder();
int colorInsert=UiUtils.getThemeColor(context, R.attr.colorM3Success);
int colorDelete=UiUtils.getThemeColor(context, R.attr.colorM3Error);
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
private final ArrayList<SpanInfo> openSpans=new ArrayList<>();
@@ -171,6 +179,9 @@ public class HtmlParser{
}
case "code", "pre" -> openSpans.add(new SpanInfo(new TypefaceSpan("monospace"), ssb.length(), el));
case "blockquote" -> openSpans.add(new SpanInfo(new LeadingMarginSpan.Standard(V.dp(10)), ssb.length(), el));
// fake elements for the edit history diff view
case "edit-diff-insert" -> openSpans.add(new SpanInfo(new ForegroundColorSpan(colorInsert), ssb.length(), el));
case "edit-diff-delete" -> openSpans.add(new SpanInfo(new DiffRemovedSpan(el.text(), colorDelete), ssb.length(), el));
}
}
}

View File

@@ -779,8 +779,8 @@ public class UiUtils {
button.setText(relationship.followedBy ? R.string.follow_back : R.string.button_follow);
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
}else{
button.setText(R.string.button_following);
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal;
button.setText(relationship.followedBy ? R.string.sk_button_mutuals : R.string.button_following);
styleRes=relationship.followedBy ? R.style.Widget_Mastodon_M3_Button_Tonal_Outlined : R.style.Widget_Mastodon_M3_Button_Tonal;
}
TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
@@ -906,17 +906,26 @@ public class UiUtils {
}
public static void insetPopupMenuIcon(Context context, MenuItem item) {
ColorStateList iconTint = ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
insetPopupMenuIcon(item, iconTint);
insetPopupMenuIcon(context, item, 0);
}
public static void insetPopupMenuIcon(MenuItem item, ColorStateList iconTint) {
Drawable icon = item.getIcon().mutate();
if (Build.VERSION.SDK_INT >= 26) item.setIconTintList(iconTint);
public static void insetPopupMenuIcon(Context context, MenuItem item, int addWidth) {
ColorStateList iconTint = ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
insetPopupMenuIcon(item, iconTint, addWidth);
}
/**
* @param addWidth set if icon is too wide/narrow. if icon is 25dp in width, set to -1dp
*/
public static void insetPopupMenuIcon(MenuItem item, ColorStateList iconTint, int addWidth) {
Drawable icon=item.getIcon().mutate();
if(Build.VERSION.SDK_INT>=26) item.setIconTintList(iconTint);
else icon.setTintList(iconTint);
icon = new InsetDrawable(icon, V.dp(8), 0, V.dp(8), 0);
int pad=V.dp(8);
boolean rtl=icon.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL;
icon=new InsetDrawable(icon, rtl ? pad+addWidth : pad, 0, rtl ? pad : addWidth+pad, 0);
item.setIcon(icon);
SpannableStringBuilder ssb = new SpannableStringBuilder(item.getTitle());
SpannableStringBuilder ssb = new SpannableStringBuilder(item.getTitle());
item.setTitle(ssb);
}
@@ -950,7 +959,7 @@ public class UiUtils {
if (subMenu != null) enableMenuIcons(context, subMenu, exclude);
if (item.getIcon() == null || Arrays.stream(exclude).anyMatch(id -> id == item.getItemId()))
continue;
insetPopupMenuIcon(item, iconTint);
insetPopupMenuIcon(item, iconTint, 0);
}
}
@@ -1674,12 +1683,14 @@ public class UiUtils {
"pronouns.page/"
};
private static final Pattern trimPronouns=Pattern.compile("[^\\w*]*([\\w*].*[\\w*]|[\\w*])\\W*");
private static final String PRONOUN_CHARS="\\w*¿¡!?";
private static final Pattern trimPronouns=
Pattern.compile("[^"+PRONOUN_CHARS+"]*(["+PRONOUN_CHARS+"].*["+PRONOUN_CHARS+"]|["+PRONOUN_CHARS+"])\\W*");
private static String extractPronounsFromField(String localizedPronouns, AccountField field) {
if(!field.name.toLowerCase().contains(localizedPronouns) &&
!field.name.toLowerCase().contains("pronouns")) return null;
String text=HtmlParser.text(field.value);
if(field.value.toLowerCase().contains("https://")){
if(text.toLowerCase().contains("https://")){
for(String pronounUrl : pronounsUrls){
int index=text.indexOf(pronounUrl);
int beginPronouns=index+pronounUrl.length();
@@ -1698,13 +1709,20 @@ public class UiUtils {
Matcher matcher=trimPronouns.matcher(text);
if(!matcher.find()) return null;
String pronouns=matcher.group(1);
// crude fix to allow for pronouns like "it(/she)"
int missingClosingParens=0;
// crude fix to allow for pronouns like "it(/she)" or "(de) sie/ihr"
int missingParens=0, missingBrackets=0;
for(char c : pronouns.toCharArray()){
if(c=='(') missingClosingParens++;
if(c==')') missingClosingParens--;
if(c=='(') missingParens++;
else if(c=='[') missingBrackets++;
else if(c==')') missingParens--;
else if(c==']') missingBrackets--;
}
pronouns+=")".repeat(Math.max(0, missingClosingParens));
if(missingParens > 0) pronouns+=")".repeat(missingParens);
else if(missingParens < 0) pronouns="(".repeat(missingParens*-1)+pronouns;
if(missingBrackets > 0) pronouns+="]".repeat(missingBrackets);
else if(missingBrackets < 0) pronouns="[".repeat(missingBrackets*-1)+pronouns;
// if ends with an un-closed custom emoji
if(pronouns.matches("^.*\\s+:[a-zA-Z_]+$")) pronouns+=':';
return pronouns;

View File

@@ -42,6 +42,7 @@ public class ComposePollViewController{
30*60,
3600,
6*3600,
12*3600,
24*3600,
3*24*3600,
7*24*3600,
@@ -134,7 +135,10 @@ public class ComposePollViewController{
DraftPollOption opt=createDraftPollOption(false);
opt.edit.setText(eopt.title);
}
pollDuration=(int)fragment.editingStatus.poll.expiresAt.minus(fragment.editingStatus.createdAt.toEpochMilli(), ChronoUnit.MILLIS).getEpochSecond();
if(fragment.scheduledStatus!=null && fragment.scheduledStatus.params.poll!=null)
pollDuration=Integer.parseInt(fragment.scheduledStatus.params.poll.expiresIn);
else if(fragment.editingStatus.poll.expiresAt!=null)
pollDuration=(int)fragment.editingStatus.poll.expiresAt.minus(fragment.editingStatus.createdAt.toEpochMilli(), ChronoUnit.MILLIS).getEpochSecond();
updatePollOptionHints();
pollDurationValue.setText(UiUtils.formatDuration(fragment.getContext(), pollDuration));
pollIsMultipleChoice=fragment.editingStatus.poll.multiple;

View File

@@ -217,6 +217,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
Menu menu=contextMenu.getMenu();
Account account=item.account;
menu.findItem(R.id.edit_note).setVisible(false);
menu.findItem(R.id.manage_user_lists).setTitle(fragment.getString(R.string.sk_lists_with_user, account.getShortUsername()));
MenuItem mute=menu.findItem(R.id.mute);
mute.setTitle(fragment.getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));

View File

@@ -1,9 +1,9 @@
package org.joinmastodon.android.utils;
import android.text.TextUtils;
import org.joinmastodon.android.fragments.ComposeFragment;
import android.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
@@ -40,19 +40,22 @@ public class StatusTextEncoder {
}
// prettiest almost-exact replica of a pretty function
public String decode(String content, Pattern regex) {
Matcher m = regex.matcher(content);
StringBuilder decodedString = new StringBuilder();
int previousEnd = 0;
public Pair<String, List<String>> decode(String content, Pattern regex) {
Matcher m=regex.matcher(content);
StringBuilder decodedString=new StringBuilder();
List<String> decodedParts=new ArrayList<>();
int previousEnd=0;
while (m.find()) {
MatchResult res = m.toMatchResult();
MatchResult res=m.toMatchResult();
// everything before the match - do not decode
decodedString.append(content.substring(previousEnd, res.start()));
previousEnd = res.end();
previousEnd=res.end();
// the match - do decode
decodedString.append(fn.apply(res.group()));
String decoded=fn.apply(res.group());
decodedParts.add(decoded);
decodedString.append(decoded);
}
decodedString.append(content.substring(previousEnd));
return decodedString.toString();
return Pair.create(decodedString.toString(), decodedParts);
}
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/m3_primary_overlay">
<item android:gravity="center_vertical" android:height="40dp">
<selector>
<item android:state_enabled="true">
<shape>
<solid android:color="?colorM3SecondaryContainer"/>
<stroke android:color="?colorM3OnSecondaryContainer" android:width="1dp"/>
<corners android:radius="20dp"/>
</shape>
</item>
<item>
<shape>
<solid android:color="?colorM3DisabledBackground"/>
<corners android:radius="20dp"/>
</shape>
</item>
</selector>
</item>
<item android:id="@android:id/mask" android:gravity="center_vertical" android:height="40dp">
<shape>
<solid android:color="#000"/>
<corners android:radius="20dp"/>
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/m3_pressed_overlay">
<item android:gravity="center_vertical" android:height="36dp">
<shape>
<solid android:color="?colorM3Primary"/>
<corners android:radius="18dp"/>
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/m3_pressed_overlay">
<item android:gravity="center_vertical" android:height="36dp">
<shape>
<stroke android:color="@color/m3_on_surface_overlay" android:width="1dp"/>
<corners android:radius="18dp"/>
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="@color/m3_primary_alpha11"
android:tintMode="src_over">
<solid android:color="?colorM3Surface" />
<corners android:radius="26dp" />
</shape>

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 5.5c-2.413 0-4.383 1.9-4.495 4.285-0.019 0.4-0.349 0.715-0.75 0.715H6.5c-1.657 0-3 1.343-3 3s1.343 3 3 3h11c1.657 0 3-1.343 3-3s-1.343-3-3-3h-0.256c-0.4 0-0.73-0.315-0.749-0.715C16.383 7.4 14.413 5.5 12 5.5zM6.08 9.02C6.548 6.171 9.02 4 12 4s5.452 2.172 5.92 5.02C20.208 9.23 22 11.155 22 13.5c0 2.485-2.015 4.5-4.5 4.5h-11C4.015 18 2 15.985 2 13.5c0-2.344 1.792-4.269 4.08-4.48z" 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="M11 15c0-0.35 0.06-0.687 0.171-1H4.253c-1.242 0-2.25 1.007-2.25 2.25v0.577c0 0.892 0.32 1.756 0.9 2.435 1.565 1.834 3.951 2.74 7.097 2.74 0.398 0 0.783-0.015 1.157-0.044C11.055 21.658 11 21.335 11 21v-0.534c-0.322 0.023-0.655 0.035-1 0.035-2.739 0-4.705-0.745-5.958-2.213-0.348-0.407-0.54-0.926-0.54-1.461v-0.578c0-0.413 0.336-0.749 0.75-0.749H11V15zM10 2.005c2.762 0 5 2.239 5 5s-2.238 5-5 5c-2.761 0-5-2.239-5-5s2.239-5 5-5zm0 1.5c-1.933 0-3.5 1.567-3.5 3.5s1.567 3.5 3.5 3.5 3.5-1.567 3.5-3.5-1.567-3.5-3.5-3.5zM12 15c0-1.104 0.896-2 2-2h7c1.105 0 2 0.896 2 2v6c0 1.105-0.895 2-2 2h-7c-1.104 0-2-0.895-2-2v-6zm2.5 1c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5h6c0.277 0 0.5-0.224 0.5-0.5S20.777 16 20.5 16h-6zm0 3c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5h6c0.277 0 0.5-0.224 0.5-0.5S20.777 19 20.5 19h-6z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,3 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="25dp" android:height="24dp" android:viewportWidth="25" android:viewportHeight="24">
<path android:pathData="M12.416 2c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10zm6.517 4.543L6.96 18.517c1.477 1.238 3.38 1.983 5.457 1.983 4.694 0 8.5-3.806 8.5-8.5 0-2.077-0.745-3.98-1.983-5.457zM12.416 3.5c-4.694 0-8.5 3.806-8.5 8.5 0 2.077 0.745 3.98 1.983 5.457L17.873 5.483C16.396 4.245 14.493 3.5 12.416 3.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="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M16.088 6.412c-0.072-0.093-0.15-0.182-0.234-0.266-0.312-0.313-0.693-0.55-1.113-0.69L13.363 5.01c-0.106-0.037-0.198-0.107-0.263-0.198C13.035 4.719 13 4.609 13 4.497c0-0.113 0.035-0.223 0.1-0.315 0.065-0.091 0.157-0.16 0.263-0.198l1.378-0.448c0.414-0.143 0.789-0.379 1.096-0.69 0.299-0.304 0.525-0.67 0.663-1.072l0.011-0.034 0.448-1.377c0.038-0.106 0.107-0.198 0.2-0.263 0.091-0.065 0.2-0.1 0.313-0.1 0.113 0 0.223 0.035 0.315 0.1s0.161 0.157 0.199 0.263l0.447 1.377c0.14 0.418 0.375 0.798 0.687 1.11 0.312 0.312 0.693 0.547 1.111 0.686l1.378 0.448 0.028 0.007c0.106 0.037 0.198 0.107 0.263 0.198 0.065 0.092 0.1 0.202 0.1 0.314 0 0.113-0.035 0.223-0.1 0.315-0.065 0.091-0.157 0.16-0.263 0.198l-1.378 0.448c-0.419 0.139-0.8 0.374-1.112 0.686-0.312 0.311-0.547 0.692-0.686 1.11l-0.448 1.377L18 8.671c-0.04 0.092-0.104 0.171-0.186 0.23C17.722 8.964 17.613 9 17.5 9c-0.113 0-0.222-0.035-0.314-0.1s-0.162-0.157-0.2-0.263L16.54 7.26c-0.101-0.307-0.254-0.593-0.45-0.848zm7.695 3.801l-0.766-0.248c-0.232-0.078-0.444-0.208-0.617-0.381-0.173-0.174-0.304-0.385-0.381-0.617l-0.25-0.765c-0.02-0.06-0.059-0.11-0.11-0.146C21.61 8.018 21.547 8 21.485 8c-0.063 0-0.124 0.02-0.175 0.056-0.051 0.036-0.09 0.087-0.11 0.146l-0.25 0.764c-0.075 0.231-0.203 0.442-0.374 0.615-0.17 0.173-0.379 0.304-0.609 0.384l-0.765 0.248c-0.06 0.021-0.11 0.06-0.147 0.11C19.02 10.376 19 10.437 19 10.499c0 0.063 0.02 0.124 0.055 0.175 0.037 0.05 0.088 0.09 0.147 0.11l0.765 0.249c0.233 0.077 0.445 0.208 0.619 0.382 0.173 0.174 0.303 0.386 0.38 0.62l0.249 0.764c0.02 0.06 0.06 0.11 0.11 0.146C21.376 12.982 21.437 13 21.5 13c0.063 0 0.124-0.02 0.175-0.056 0.05-0.036 0.09-0.087 0.11-0.146l0.249-0.764c0.077-0.233 0.208-0.444 0.381-0.618 0.174-0.173 0.385-0.303 0.618-0.38l0.765-0.25c0.06-0.02 0.11-0.059 0.147-0.11C23.98 10.626 24 10.564 24 10.502c0-0.063-0.02-0.124-0.055-0.175-0.037-0.05-0.088-0.09-0.147-0.11l-0.015-0.004zM5.25 3h7.797c-0.306 0.11-0.573 0.308-0.767 0.569C12.098 3.834 12 4.148 12 4.469V4.5H5.25C4.836 4.5 4.5 4.836 4.5 5.25v12.5c0 0.966 0.784 1.75 1.75 1.75h9.25V7.327c0.034 0.072 0.065 0.146 0.09 0.222L16 8.999c0.106 0.31 0.305 0.579 0.57 0.77 0.133 0.092 0.278 0.162 0.43 0.21V14h4v3.75c0 1.795-1.455 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V5.25C3 4.007 4.007 3 5.25 3zM17 19.5h0.75c0.966 0 1.75-0.784 1.75-1.75V15.5H17v4zM7.25 7C6.836 7 6.5 7.336 6.5 7.75S6.836 8.5 7.25 8.5h5.5c0.414 0 0.75-0.336 0.75-0.75S13.164 7 12.75 7h-5.5zM6.5 11.75C6.5 11.336 6.836 11 7.25 11h5.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75h-5.5c-0.414 0-0.75-0.336-0.75-0.75zM7.25 15c-0.414 0-0.75 0.336-0.75 0.75s0.336 0.75 0.75 0.75h3c0.414 0 0.75-0.336 0.75-0.75S10.664 15 10.25 15h-3z" 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="M18.38 4c0.31 0 0.588 0.191 0.7 0.482 0.56 1.462 1.609 2.015 2.17 2.015 0.414 0 0.75 0.335 0.75 0.75 0 0.414-0.336 0.75-0.75 0.75-1.002 0-2.087-0.611-2.873-1.687-0.814 1.093-1.971 1.687-3.187 1.687-1.217 0-2.376-0.595-3.19-1.69-0.814 1.095-1.973 1.69-3.19 1.69-1.215 0-2.373-0.593-3.187-1.686C4.838 7.388 3.753 8 2.75 8 2.336 8 2 7.664 2 7.25S2.336 6.5 2.75 6.5c0.56 0 1.61-0.553 2.17-2.018C5.031 4.192 5.31 4.001 5.62 4c0.311 0 0.59 0.192 0.701 0.482 0.544 1.419 1.57 2.015 2.49 2.015 0.92 0 1.945-0.596 2.489-2.015C11.41 4.192 11.69 4 12 4c0.31 0 0.59 0.192 0.7 0.482 0.544 1.419 1.57 2.015 2.49 2.015 0.92 0 1.945-0.597 2.489-2.015C17.79 4.192 18.069 4 18.379 4zm0 6c0.31 0 0.588 0.191 0.7 0.482 0.56 1.462 1.609 2.015 2.17 2.015 0.414 0 0.75 0.335 0.75 0.75 0 0.414-0.336 0.75-0.75 0.75-1.002 0-2.087-0.611-2.873-1.687-0.814 1.093-1.971 1.687-3.187 1.687-1.217 0-2.376-0.595-3.19-1.69-0.814 1.095-1.973 1.69-3.19 1.69-1.215 0-2.373-0.593-3.187-1.685C4.838 13.388 3.753 14 2.75 14 2.336 14 2 13.664 2 13.25s0.336-0.75 0.75-0.75c0.56 0 1.61-0.553 2.17-2.018 0.111-0.29 0.39-0.481 0.7-0.481 0.311 0 0.59 0.191 0.701 0.481 0.544 1.419 1.57 2.015 2.49 2.015 0.92 0 1.945-0.596 2.489-2.015 0.11-0.29 0.39-0.481 0.7-0.481 0.31 0 0.59 0.191 0.7 0.481 0.544 1.419 1.57 2.015 2.49 2.015 0.92 0 1.945-0.597 2.489-2.015 0.111-0.29 0.39-0.482 0.7-0.482zm0.7 6.482C18.969 16.192 18.69 16 18.38 16c-0.311 0-0.59 0.192-0.701 0.482-0.544 1.418-1.57 2.015-2.49 2.015-0.92 0-1.945-0.596-2.489-2.015C12.59 16.192 12.31 16 12 16c-0.31 0-0.59 0.192-0.7 0.482-0.544 1.419-1.57 2.015-2.49 2.015-0.92 0-1.945-0.596-2.489-2.015C6.21 16.192 5.931 16 5.621 16c-0.311 0-0.59 0.192-0.7 0.482C4.358 17.947 3.31 18.5 2.75 18.5 2.336 18.5 2 18.836 2 19.25S2.336 20 2.75 20c1.003 0 2.088-0.612 2.873-1.689 0.814 1.093 1.972 1.686 3.187 1.686 1.217 0 2.376-0.595 3.19-1.69 0.814 1.096 1.973 1.69 3.19 1.69 1.216 0 2.373-0.594 3.187-1.687 0.786 1.076 1.87 1.687 2.873 1.687 0.414 0 0.75-0.336 0.75-0.75 0-0.415-0.336-0.75-0.75-0.75-0.561 0-1.61-0.553-2.17-2.015z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12 4.001c3.168 0 4.966 2.097 5.227 4.63h0.08c2.04 0 3.692 1.649 3.692 3.683 0 2.033-1.653 3.682-3.692 3.682h-0.582l-1.582 2.635c-0.207 0.358-0.666 0.481-1.025 0.274-0.329-0.19-0.46-0.591-0.32-0.933l0.046-0.091 1.149-1.885h-2.136l-1.582 2.635c-0.207 0.358-0.666 0.481-1.025 0.274-0.329-0.19-0.46-0.591-0.32-0.933l0.046-0.091 1.148-1.885H8.988l-1.582 2.635c-0.207 0.358-0.665 0.481-1.024 0.274-0.329-0.19-0.46-0.591-0.32-0.933l0.045-0.091 1.148-1.885H6.693C4.653 15.996 3 14.347 3 12.314 3 10.28 4.654 8.63 6.693 8.63h0.08C7.035 6.081 8.83 4.001 12 4.001zm0 1.498c-2.071 0-3.877 1.633-3.877 3.889 0 0.357-0.319 0.638-0.684 0.638H6.75c-1.261 0-2.284 1.001-2.284 2.236 0 1.235 1.022 2.237 2.284 2.237h10.5c1.261 0 2.284-1.002 2.284-2.237s-1.023-2.236-2.284-2.236h-0.69c-0.365 0-0.684-0.28-0.684-0.638 0-2.285-1.806-3.89-3.877-3.89z" 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="M11.75 2c0.414 0 0.75 0.336 0.75 0.75v3.155l2.133-2.134c0.293-0.292 0.768-0.292 1.061 0 0.293 0.293 0.293 0.768 0 1.061L12.5 8.026V11h2.974l3.194-3.194c0.293-0.293 0.768-0.293 1.06 0 0.293 0.293 0.293 0.768 0 1.06L17.596 11h3.155c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75h-3.155l2.134 2.133c0.293 0.293 0.293 0.768 0 1.061-0.293 0.293-0.768 0.293-1.061 0L15.474 12.5H12.5v2.974l3.194 3.194c0.293 0.293 0.293 0.768 0 1.06-0.293 0.293-0.768 0.293-1.06 0L12.5 17.596v3.155c0 0.414-0.336 0.75-0.75 0.75S11 21.164 11 20.75v-3.155L8.867 19.73c-0.293 0.293-0.768 0.293-1.061 0-0.293-0.293-0.293-0.768 0-1.061L11 15.474V12.5H8.026l-3.194 3.194c-0.293 0.293-0.768 0.293-1.06 0-0.293-0.293-0.293-0.768 0-1.06L5.904 12.5H2.75C2.336 12.5 2 12.164 2 11.75S2.336 11 2.75 11h3.155L3.77 8.867c-0.292-0.293-0.292-0.768 0-1.061 0.293-0.293 0.768-0.293 1.061 0L8.026 11H11V8.026L7.806 4.832c-0.293-0.293-0.293-0.768 0-1.06 0.293-0.293 0.768-0.293 1.06 0L11 5.904V2.75C11 2.336 11.336 2 11.75 2z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12 2c0.414 0 0.75 0.336 0.75 0.75v1.5C12.75 4.664 12.414 5 12 5s-0.75-0.336-0.75-0.75v-1.5C11.25 2.336 11.586 2 12 2zm0 15c2.761 0 5-2.239 5-5s-2.239-5-5-5-5 2.239-5 5 2.239 5 5 5zm0-1.5c-1.933 0-3.5-1.567-3.5-3.5s1.567-3.5 3.5-3.5 3.5 1.567 3.5 3.5-1.567 3.5-3.5 3.5zm9.25-2.75c0.414 0 0.75-0.336 0.75-0.75s-0.336-0.75-0.75-0.75h-1.5C19.336 11.25 19 11.586 19 12s0.336 0.75 0.75 0.75h1.5zM12 19c0.414 0 0.75 0.336 0.75 0.75v1.5c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75v-1.5c0-0.414 0.336-0.75 0.75-0.75zm-7.75-6.25C4.664 12.75 5 12.414 5 12s-0.336-0.75-0.75-0.75h-1.5C2.336 11.25 2 11.586 2 12s0.336 0.75 0.75 0.75h1.5zM4.22 4.22c0.293-0.293 0.767-0.293 1.06 0l1.5 1.5c0.293 0.293 0.293 0.768 0 1.06-0.293 0.294-0.767 0.294-1.06 0l-1.5-1.5c-0.293-0.292-0.293-0.767 0-1.06zm1.06 15.56c-0.293 0.294-0.767 0.294-1.06 0-0.293-0.292-0.293-0.767 0-1.06l1.5-1.5c0.293-0.293 0.767-0.293 1.06 0 0.293 0.293 0.293 0.768 0 1.06l-1.5 1.5zm14.5-15.56c-0.293-0.293-0.767-0.293-1.06 0l-1.5 1.5c-0.293 0.293-0.293 0.768 0 1.06 0.293 0.294 0.767 0.294 1.06 0l1.5-1.5c0.293-0.292 0.293-0.767 0-1.06zm-1.06 15.56c0.293 0.294 0.767 0.294 1.06 0 0.293-0.292 0.293-0.767 0-1.06l-1.5-1.5c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.768 0 1.06l1.5 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="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12.75 2.75C12.75 2.336 12.414 2 12 2s-0.75 0.336-0.75 0.75v1.5C11.25 4.664 11.586 5 12 5s0.75-0.336 0.75-0.75v-1.5zm6.28 2.22c0.293 0.293 0.293 0.767 0 1.06l-1.06 1.061c-0.293 0.293-0.768 0.293-1.061 0-0.293-0.293-0.293-0.768 0-1.06l1.06-1.061c0.294-0.293 0.768-0.293 1.061 0zM17.41 13c0.059-0.324 0.09-0.659 0.09-1 0-3.038-2.462-5.5-5.5-5.5S6.5 8.962 6.5 12c0 0.341 0.031 0.676 0.09 1H2.75C2.336 13 2 13.336 2 13.75s0.336 0.75 0.75 0.75h18.5c0.414 0 0.75-0.336 0.75-0.75S21.664 13 21.25 13h-3.84zM12 8c2.21 0 4 1.79 4 4 0 0.345-0.044 0.68-0.126 1H8.126C8.044 12.68 8 12.345 8 12c0-2.21 1.79-4 4-4zm-6 8.75C6 16.336 6.336 16 6.75 16h10.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75H6.75C6.336 17.5 6 17.164 6 16.75zm4 3c0-0.414 0.336-0.75 0.75-0.75h2.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75h-2.5c-0.414 0-0.75-0.336-0.75-0.75zM4.97 4.97c0.293-0.293 0.768-0.293 1.06 0l1.061 1.06c0.293 0.293 0.293 0.768 0 1.061-0.293 0.293-0.768 0.293-1.06 0L4.97 6.031c-0.293-0.294-0.293-0.768 0-1.061z" 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="M10.464 15.748L12.7 13.26c0.277-0.308 0.751-0.333 1.06-0.056 0.28 0.252 0.326 0.666 0.124 0.971l-0.068 0.088-1.111 1.236h2.276c0.594 0 0.938 0.648 0.645 1.134l-0.058 0.084-3.212 4.031c-0.258 0.324-0.73 0.378-1.054 0.12-0.294-0.235-0.365-0.646-0.182-0.963l0.063-0.091 2.242-2.815h-2.403c-0.613 0-0.953-0.685-0.623-1.168l0.065-0.083L12.7 13.26l-2.236 2.488zm2.538-10.74c3.168 0 4.966 2.098 5.227 4.631h0.08c2.04 0 3.692 1.649 3.692 3.683 0 2.033-1.653 3.682-3.692 3.682l-1.788 0.001c0.144-0.213 0.228-0.471 0.228-0.748 0-0.279-0.085-0.537-0.23-0.751h1.734c1.261 0 2.283-1 2.283-2.236 0-1.235-1.022-2.236-2.283-2.236h-0.69c-0.366 0-0.685-0.28-0.685-0.638 0-2.285-1.805-3.89-3.876-3.89-2.072 0-3.877 1.634-3.877 3.89 0 0.357-0.319 0.638-0.684 0.638H7.75c-1.262 0-2.284 1-2.284 2.236 0 1.235 1.022 2.237 2.283 2.237l1.762-0.001c-0.146 0.214-0.23 0.472-0.23 0.75s0.084 0.535 0.228 0.75l-1.816-0.002c-2.039 0-3.692-1.649-3.692-3.682 0-2.034 1.653-3.683 3.692-3.683h0.08c0.263-2.55 2.06-4.63 5.228-4.63zM10 2c1.617 0 3.05 0.815 3.9 2.062-0.29-0.035-0.59-0.053-0.898-0.053-0.395 0-0.775 0.029-1.139 0.085C11.335 3.72 10.69 3.5 10 3.5c-1.567 0-2.902 1.13-3.17 2.656L6.759 6.57c-0.084 0.478-0.5 0.827-0.985 0.827h-0.49C4.297 7.397 3.5 8.195 3.5 9.18c0 0.49 0.198 0.934 0.52 1.257-0.316 0.4-0.566 0.855-0.736 1.347C2.504 11.184 2 10.24 2 9.18 2 7.43 3.37 6 5.096 5.903l0.257-0.006C5.743 3.677 7.682 2 10 2z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,40 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<org.joinmastodon.android.ui.views.CheckableRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingLeft="16dp"
android:clipToPadding="false">
<FrameLayout
<org.joinmastodon.android.ui.views.CheckableRelativeLayout
android:id="@+id/checkbox_wrap"
android:layout_width="wrap_content"
android:layout_height="46sp"
android:duplicateParentState="true">
android:layout_width="56dp"
android:layout_height="match_parent"
android:paddingTop="16dp">
<View
android:id="@+id/checkbox"
android:layout_gravity="center_vertical"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="-4dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="12dp"
android:duplicateParentState="true"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="46sp"
android:duplicateParentState="true">
</FrameLayout>
<View
android:id="@+id/checkbox"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:duplicateParentState="true"/>
</FrameLayout>
</org.joinmastodon.android.ui.views.CheckableRelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-16dp"
android:layout_marginStart="-16dp"
android:layout_marginHorizontal="-16dp"
android:layout_toEndOf="@id/checkbox_wrap">
<include layout="@layout/display_item_header" />
</FrameLayout>
</org.joinmastodon.android.ui.views.CheckableRelativeLayout>
</RelativeLayout>

View File

@@ -13,7 +13,7 @@
<org.joinmastodon.android.ui.views.NestedRecyclerScrollView
android:id="@+id/scroller"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="true">
<org.joinmastodon.android.ui.views.CustomDrawingOrderLinearLayout
@@ -50,7 +50,7 @@
android:text="@string/follows_you"
android:textAllCaps="true"
android:textColor="#fff"
android:textSize="14dp"
android:textSize="14sp"
android:visibility="gone"
tools:visibility="visible" />
@@ -80,11 +80,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/cover"
android:layout_alignParentEnd="true">
android:layout_alignParentEnd="true"
android:clipChildren="false">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="4dp">
@@ -95,7 +97,8 @@
style="@style/Widget.Mastodon.M3.Button.Tonal"
android:background="@drawable/bg_button_m3_tonal_circle_selector"
android:paddingStart="12dp"
android:drawableStart="@drawable/ic_fluent_alert_24_selector" />
android:drawableStart="@drawable/ic_fluent_alert_24_selector"
tools:ignore="RtlSymmetry" />
<ProgressBar
android:id="@+id/notify_progress"
@@ -112,6 +115,7 @@
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp">
@@ -213,6 +217,60 @@
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/username"
android:id="@+id/note_edit_wrap"
android:layout_marginTop="4dp"
android:layout_marginBottom="12dp"
android:layout_marginHorizontal="16dp"
android:visibility="gone">
<EditText
android:id="@+id/note_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="52dp"
android:paddingVertical="15dp"
android:textColor="?colorM3OnSurface"
android:inputType="text|textMultiLine|textCapSentences"
android:singleLine="false"
android:background="@drawable/bg_note_edit"
android:paddingEnd="52dp"
android:paddingStart="20dp"
android:elevation="0dp"
android:hint="@string/sk_private_note_hint"
tools:ignore="RtlSymmetry" />
<FrameLayout
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="end">
<ImageButton
android:id="@+id/note_save_btn"
android:layout_width="52dp"
android:layout_height="52dp"
android:visibility="invisible"
android:background="@drawable/bg_button_m3_text_circle"
android:tooltipText="@string/sk_confirm_changes"
android:contentDescription="@string/sk_confirm_changes"
android:src="@drawable/ic_fluent_checkmark_24_regular"/>
<ProgressBar
android:id="@+id/note_save_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
style="?android:progressBarStyleSmall"
android:indeterminate="true"
android:visibility="gone" />
</FrameLayout>
</FrameLayout>
<org.joinmastodon.android.ui.views.LinkedTextView
android:id="@+id/bio"
android:layout_width="match_parent"
@@ -284,7 +342,8 @@
<me.grishka.appkit.views.UsableRecyclerView
android:id="@+id/metadata"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingTop="4dp" />
<LinearLayout

View File

@@ -54,7 +54,7 @@
android:id="@+id/forward_report"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginTop="8dp"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:background="?android:selectableItemBackground">

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/switcher_btn"
style="@style/Widget.Mastodon.M3.Button.Text"
android:orientation="horizontal"
android:id="@+id/switcher_btn"
style="@style/Widget.Mastodon.M3.Button.Text"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|start"
@@ -14,54 +14,44 @@
android:tooltipText="@string/sk_switch_timeline"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<ImageView
android:id="@+id/timeline_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_fluent_home_24_regular" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/collapsed_chevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_fluent_chevron_down_16_filled"
android:visibility="gone" />
<TextView
android:id="@+id/timeline_title"
style="@style/action_bar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:drawablePadding="8dp"
android:drawableEnd="@drawable/ic_fluent_chevron_down_16_filled" />
</FrameLayout>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/show_new_posts_btn"
style="@style/Widget.Mastodon.M3.Button.Tonal.Icon"
<ImageView
android:id="@+id/timeline_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_fluent_home_24_regular"/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/collapsed_chevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_fluent_chevron_down_16_filled"
android:visibility="gone"/>
<TextView
style="@style/action_bar_title"
android:id="@+id/timeline_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:drawablePadding="8dp"
android:drawableEnd="@drawable/ic_fluent_chevron_down_16_filled"/>
</FrameLayout>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
style="?toolbarActionButtonStyle"
android:id="@+id/show_new_posts_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/bg_button_m3_tonal_selector"
android:maxLines="1"
android:ellipsize="end"
android:textAppearance="@style/m3_title_medium"
android:textSize="16sp"
android:text="@string/see_new_posts"
android:drawableStart="@drawable/ic_fluent_arrow_up_16_filled"
android:layout_gravity="center" />
<!--
using the selector background because..
the selected=false state's border looks better than the one from the outline style
(as per m3 spec) :( i should probably fix this at some point
-->
</FrameLayout>
android:drawableStart="@drawable/ic_fluent_arrow_up_16_filled"
android:layout_gravity="center"/>
</FrameLayout>
</FrameLayout>

View File

@@ -5,4 +5,5 @@
<item android:id="@+id/draft" android:title="@string/sk_mark_as_draft" android:icon="@drawable/ic_fluent_drafts_24_regular" />
<item android:id="@+id/undraft" android:title="@string/sk_mark_as_draft" android:icon="@drawable/ic_fluent_drafts_24_filled" android:contentDescription="@string/sk_compose_no_draft" />
<item android:id="@+id/drafts" android:title="@string/sk_unsent_posts" android:icon="@drawable/ic_fluent_folder_open_24_regular" />
<item android:id="@+id/preview" android:title="@string/sk_open_post_preview" android:icon="@drawable/ic_fluent_receipt_sparkles_24_regular" android:visible="false" />
</menu>

View File

@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/menu_group1">
<item android:id="@+id/edit_note" android:title="@string/sk_add_note" android:icon="@drawable/ic_fluent_person_note_24_regular" />
</group>
<group android:id="@+id/menu_group2">
<item android:id="@+id/manage_user_lists" android:title="@string/sk_lists_with_user" android:icon="@drawable/ic_fluent_people_24_regular"/>
<item android:id="@+id/mute" android:title="@string/mute_user" android:icon="@drawable/ic_fluent_speaker_off_24_regular"/>
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user" android:icon="@drawable/ic_fluent_arrow_repeat_all_off_24_regular"/>
@@ -9,7 +12,7 @@
<item android:id="@+id/report" android:title="@string/report_user" android:icon="@drawable/ic_fluent_warning_24_regular"/>
<item android:id="@+id/block_domain" android:title="@string/block_domain" android:icon="@drawable/ic_fluent_shield_prohibited_24_regular"/>
</group>
<group android:id="@+id/menu_group2">
<group android:id="@+id/menu_group3">
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser" android:icon="@drawable/ic_fluent_globe_24_regular"/>
<item android:id="@+id/share" android:title="@string/share_user" android:icon="@drawable/ic_fluent_share_24_regular"/>
<item android:id="@+id/open_with_account" android:title="@string/sk_open_with_account" android:visible="false" android:icon="@drawable/ic_fluent_person_swap_24_regular">

View File

@@ -1,474 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="get_started">الخطوات الأولى</string>
<string name="log_in">تسجيلُ الدخول</string>
<string name="next">التالي</string>
<string name="loading_instance">يَجري الحُصُول على معلومات المَثيل…</string>
<string name="error">خطأ</string>
<string name="not_a_mastodon_instance">%s لا يبدو كمثيل ماستدون.</string>
<string name="ok">حسنًا</string>
<string name="preparing_auth">جَارٍ الإعدَادُ لِلمُصادَقَة…</string>
<string name="finishing_auth">يُنهي المصادقة…</string>
<string name="user_boosted">أعادَ %s تَدوينَها</string>
<string name="in_reply_to">ردًا على %s</string>
<string name="notifications">الإشعارات</string>
<string name="user_followed_you">بَدَأ بِمُتابَعَتِك</string>
<string name="user_sent_follow_request">أرسَلَ طَلَبًا لِمُتابَعَتِك</string>
<string name="user_favorited">فَضَّلَ مَنشُورَك</string>
<string name="notification_boosted">أعادَ تَدوينَ مَنشُورَك</string>
<string name="poll_ended">انتهى استطلاعُ الرأي</string>
<string name="time_seconds">%d ثا</string>
<string name="time_minutes">%d د</string>
<string name="time_hours">%d سا</string>
<string name="time_days">%d يوم</string>
<string name="share_toot_title">شارك</string>
<string name="settings">الإعدادات</string>
<string name="publish">انشر</string>
<string name="discard_draft">أتريد التخلص من المسودة؟</string>
<string name="discard">تخلص</string>
<string name="cancel">إلغاء</string>
<plurals name="followers">
<item quantity="zero">لا متابِعين</item>
<item quantity="one">متابِع</item>
<item quantity="two">متابِعان</item>
<item quantity="few">متابِعين</item>
<item quantity="many">متابِعًا</item>
<item quantity="other">متابِع</item>
</plurals>
<plurals name="following">
<item quantity="zero">لا متابَعين</item>
<item quantity="one">متابَع</item>
<item quantity="two">متابَعان</item>
<item quantity="few">متابَعين</item>
<item quantity="many">متابَعًا</item>
<item quantity="other">متابَع</item>
</plurals>
<plurals name="posts">
<item quantity="zero">لا منشورات</item>
<item quantity="one">منشور</item>
<item quantity="two">منشوران</item>
<item quantity="few">منشورات</item>
<item quantity="many">منشورًا</item>
<item quantity="other">منشور</item>
</plurals>
<string name="posts">منشورات</string>
<string name="posts_and_replies">مَنشُوراتٌ وَرُدُود</string>
<string name="media">وسائط</string>
<string name="profile_about">حَول</string>
<string name="button_follow">تابِع</string>
<string name="button_following">يُتابِع</string>
<string name="edit_profile">حرّر الملف الشخصي</string>
<string name="mention_user">ذِكر @%s</string>
<string name="share_user">مُشارَكَةُ %s</string>
<string name="mute_user">كَتمُ %s</string>
<string name="unmute_user">إلغاء الكَتم عن @%s</string>
<string name="block_user">حَظرُ %s</string>
<string name="unblock_user">رفع الحَظر عن %s</string>
<string name="report_user">الإبلاغُ عَن %s</string>
<string name="block_domain">حَظرُ %s</string>
<string name="unblock_domain">رفع الحَظر عن %s</string>
<plurals name="x_posts">
<item quantity="zero">لا مَنشورات</item>
<item quantity="one">منشورٌ واحِد</item>
<item quantity="two">منشورانِ اثنان</item>
<item quantity="few">%,d منشورات</item>
<item quantity="many">%,d منشورًا</item>
<item quantity="other">%,d منشور</item>
</plurals>
<string name="profile_joined">انضم في</string>
<string name="done">تمّ</string>
<string name="loading">يحمل…</string>
<string name="field_label">التسمية</string>
<string name="field_content">المحتوى</string>
<string name="saving">يحفظ…</string>
<string name="post_from_user">نُشر من %s</string>
<string name="poll_option_hint">الخيار %d</string>
<plurals name="x_minutes">
<item quantity="zero">أقل من دقيقة</item>
<item quantity="one">دقيقة واحدة</item>
<item quantity="two">دقيقتان</item>
<item quantity="few">%d دقائق</item>
<item quantity="many">%d دقيقة</item>
<item quantity="other">%d دقيقة</item>
</plurals>
<plurals name="x_hours">
<item quantity="zero">أقل من ساعة</item>
<item quantity="one">ساعة واحدة</item>
<item quantity="two">ساعتان</item>
<item quantity="few">%d ساعات</item>
<item quantity="many">%d ساعة</item>
<item quantity="other">%d ساعة</item>
</plurals>
<plurals name="x_days">
<item quantity="zero">أقل من يوم</item>
<item quantity="one">يومٌ واحِد</item>
<item quantity="two">يَومان</item>
<item quantity="few">%d أيام</item>
<item quantity="many">%d يومًا</item>
<item quantity="other">%d يوم</item>
</plurals>
<string name="compose_poll_duration">المُدَّة: %s</string>
<plurals name="x_seconds_left">
<item quantity="zero">تتبقى لَحظة</item>
<item quantity="one">تتبقى ثانية واحِدة</item>
<item quantity="two">تتبقى ثانيتان</item>
<item quantity="few">تتبقى %d ثوان</item>
<item quantity="many">تتبقى %d ثانية</item>
<item quantity="other">تتبقى %d ثانية</item>
</plurals>
<plurals name="x_minutes_left">
<item quantity="zero">تبقت أقل من دقيقة</item>
<item quantity="one">تبقت دقيقة</item>
<item quantity="two">تبقت دقيقتان</item>
<item quantity="few">تبقت %d دقائق</item>
<item quantity="many">تبقت %d دقيقة</item>
<item quantity="other">تبقت %d دقيقة</item>
</plurals>
<plurals name="x_hours_left">
<item quantity="zero">تبقت أقل من ساعة</item>
<item quantity="one">تبقت ساعة واحدة</item>
<item quantity="two">تبقت ساعتان</item>
<item quantity="few">تبقت %d ساعات</item>
<item quantity="many">تبقت %d ساعة</item>
<item quantity="other">تبقت %d ساعة</item>
</plurals>
<plurals name="x_days_left">
<item quantity="zero">تبقى أقل من يوم</item>
<item quantity="one">تبقى يوم واحد</item>
<item quantity="two">تبقى يومان</item>
<item quantity="few">تبقى %d أيام</item>
<item quantity="many">تبقى %d يومًا</item>
<item quantity="other">تبقى %d يوم</item>
</plurals>
<plurals name="x_voters">
<item quantity="zero">لا يوجد مصوتون</item>
<item quantity="one">مصوت واحد</item>
<item quantity="two">مصوتان</item>
<item quantity="few">%,d مصوتين</item>
<item quantity="many">%,d مصوتًا</item>
<item quantity="other">%,d مصوت</item>
</plurals>
<string name="poll_closed">انتهى</string>
<string name="confirm_mute_title">اكتم الحساب</string>
<string name="confirm_mute">أكّد كتم %s</string>
<string name="do_mute">اكتم</string>
<string name="confirm_unmute_title">ارفع الكتم عن الحساب</string>
<string name="confirm_unmute">أكِّد رفع الكتم عن %s</string>
<string name="do_unmute">ارفع الكتم</string>
<string name="confirm_block_title">احجب الحساب</string>
<string name="confirm_block_domain_title">احجب النطاق</string>
<string name="confirm_block">أكّد حجب %s</string>
<string name="do_block">احجب</string>
<string name="confirm_unblock_title">ارفع الحجب عن الحساب</string>
<string name="confirm_unblock_domain_title">ارفع الحجب عن النطاق</string>
<string name="confirm_unblock">أكّد رفع الحجب عن %s</string>
<string name="do_unblock">ارفع الحجب</string>
<string name="button_muted">مَكتوم</string>
<string name="button_blocked">محجوب</string>
<string name="action_vote">صَوّت</string>
<string name="tap_to_reveal">اُنقُر لِلكَشف</string>
<string name="delete">احذف</string>
<string name="confirm_delete_title">احذف المنشور</string>
<string name="confirm_delete">أمتأكد من حذف هذا المنشور؟</string>
<string name="deleting">يحذف…</string>
<string name="notification_channel_audio_player">تشغيل الصوت</string>
<string name="play">شغّل</string>
<string name="pause">ألبث</string>
<string name="log_out">خروج</string>
<string name="add_account">أضف حساباً</string>
<string name="search_hint">ابحث</string>
<string name="hashtags">وُسُوم</string>
<string name="news">الأخبار</string>
<string name="for_you">لأجلك</string>
<string name="all_notifications">الكل</string>
<string name="mentions">الذِكر</string>
<plurals name="x_people_talking">
<item quantity="zero">لا أحد يتحدث</item>
<item quantity="one">شخص واحد يتحدث</item>
<item quantity="two">شخصان يتحدثان</item>
<item quantity="few">%d أشخاص يتحدثون</item>
<item quantity="many">%d شخصًا يتحدثون</item>
<item quantity="other">%d شخص يتحدثون</item>
</plurals>
<plurals name="discussed_x_times">
<item quantity="zero">لم يُناقش</item>
<item quantity="one">نوقش مرة واحدة</item>
<item quantity="two">نوقش مرتين</item>
<item quantity="few">نوقش %d مرات</item>
<item quantity="many">نوقش %d مرة</item>
<item quantity="other">نوقش %d مرة</item>
</plurals>
<string name="report_title">بلّغ عن %s</string>
<string name="report_choose_reason">ما هي المشكلة في هذا المنشور؟</string>
<string name="report_choose_reason_account">ما هي المشكلة مع %s؟</string>
<string name="report_choose_reason_subtitle">اختر أفضل تطابق</string>
<string name="report_reason_personal">لا يعجبني</string>
<string name="report_reason_personal_subtitle">ألا ترغب برؤيته</string>
<string name="report_reason_spam">إزعاج</string>
<string name="report_reason_spam_subtitle">روابط خبيثة أو تفاعل كاذب أو ردود متكررة</string>
<string name="report_reason_violation">ينتهك قواعد الخادم</string>
<string name="report_reason_violation_subtitle">تعلم أنه ينتهك قواعد محددة</string>
<string name="report_reason_other">شيء آخر</string>
<string name="report_reason_other_subtitle">لا تندرج هذه المشكلة ضمن فئات أخرى</string>
<string name="report_choose_rule">ما هي القواعد المنتهكة؟</string>
<string name="report_choose_rule_subtitle">اختر كل ما ينطبق</string>
<string name="report_choose_posts">هل توجد منشورات تدعم صحة هذا البلاغ؟</string>
<string name="report_choose_posts_subtitle">اختر كل ما ينطبق</string>
<string name="report_comment_title">هل لديك شيء آخر لتخبرنا به؟</string>
<string name="report_comment_hint">تعليقات إضافية</string>
<string name="sending_report">يرسل البلاغ…</string>
<string name="report_sent_title">شُكرًا لَكَ على التبليغ، سَنَنظُرُ فِي هَذَا الأمر.</string>
<string name="report_sent_subtitle">في أثناء مراجعتنا للبلاغ، يمكنك اتخاذ إجراء ضد @%s.</string>
<string name="unfollow_user">ألغ متابعة %s</string>
<string name="unfollow">ألغ المتابعة</string>
<string name="mute_user_explain">لن ترى منشوراتهم أو إعادة تدوينهم في التغذية الرئيسية. ولن يعلموا أنهم كتموا.</string>
<string name="block_user_explain">لن يتمكنوا من متابعتك أو رؤية منشوراتك، وسيكون بديهيًا لهم أنهم حجبوا.</string>
<string name="report_personal_title">لاترغب في مشاهدة هذا؟</string>
<string name="report_personal_subtitle">عندما ترى ما لا يعجبك في ماستدون، يمكنك إزالة صاحبها من تجربتك كمستخدم.</string>
<string name="back">العودة</string>
<string name="instance_catalog_title">يتكوّن ماستدون من مستخدمين موزّعين عبر خوادم مختلفة.</string>
<string name="instance_catalog_subtitle">اختر خادمًا بناءً على اهتماماتك، منطقتك أو يمكنك حتى اختيارُ مجتمعٍ ذي غرضٍ عام. وسيضل بامكانك التواصل مع المستخدمين من الخوادم الأخرى.</string>
<string name="search_communities">ابحث عن خادم أو أدخل رابطه</string>
<string name="instance_rules_title">بعض القواعد الأساسية</string>
<string name="instance_rules_subtitle">خذ دقيقة لمراجعة القواعد التي حددها وفرضها مديروا %s.</string>
<string name="signup_title">دعنا نجهزك في %s</string>
<string name="edit_photo">حرّر</string>
<string name="display_name">الاسم العلني</string>
<string name="username">اسم المستخدم</string>
<string name="email">البريد الإلكتروني</string>
<string name="password">كلمة المرور</string>
<string name="password_note">ضمّن الأحرف الكبيرة والأحرف الخاصة والأرقام لزيادة قوة كلمة المرور.</string>
<string name="category_academia">أكاديمي</string>
<string name="category_activism">النشطاء</string>
<string name="category_all">الكل</string>
<string name="category_art">فنون</string>
<string name="category_food">طعام</string>
<string name="category_furry">حيوان ذو فرو</string>
<string name="category_games">ألعاب</string>
<string name="category_general">عام</string>
<string name="category_journalism">صحافة</string>
<string name="category_lgbt">LGBT</string>
<string name="category_music">موسيقى</string>
<string name="category_regional">إقليمي</string>
<string name="category_tech">تقني</string>
<string name="confirm_email_title">شيءٌ أخير</string>
<string name="confirm_email_subtitle">أنقر على الرابط المرسل إليك لاستيثاق حسابك.</string>
<string name="resend">أعد الإرسال</string>
<string name="open_email_app">افتح تطبيق البريد الإلكتروني</string>
<string name="resent_email">أُرسلت رسالة التأكيد</string>
<string name="compose_hint">عَبِّر عَمَّ يَجُولُ فِي ذِهنِك</string>
<string name="content_warning">تحذير من المحتوى</string>
<string name="add_image_description">أضف وصفًا للصورة…</string>
<string name="retry_upload">حاول الرفع مجددًا</string>
<string name="edit_image">حرّر الصورة</string>
<string name="save">احفظ</string>
<string name="add_alt_text">أضف نصًا بديلًا</string>
<string name="alt_text_subtitle">يصف النص البديل محتوى الصور للمكفوفين وضعاف البصر. حاول تضمين أكبر قدر ممكن من التفاصيل ليفهموا السياق.</string>
<string name="alt_text_hint">مثال: كلب ينظر حوله بارتياب وعيناه مثبتتان على الكاميرا.</string>
<string name="visibility_public">علني</string>
<string name="visibility_followers_only">للمُتابِعينَ فقط</string>
<string name="visibility_private">لمن ذكرتُهم فقط</string>
<string name="search_all">الكل</string>
<string name="search_people">أشخاص</string>
<string name="recent_searches">عَمَليَّاُت البَحثِ الأخيرَة</string>
<string name="step_x_of_n">الخطوة %1$d من %2$d</string>
<string name="skip">تخطى</string>
<string name="notification_type_follow">متابعُون جُدُد</string>
<string name="notification_type_favorite">المفضلة</string>
<string name="notification_type_reblog">المعاد تدوينها</string>
<string name="notification_type_mention">الذِكر</string>
<string name="notification_type_poll">استطلاع رأي</string>
<string name="choose_account">اختر حسابًا</string>
<string name="err_not_logged_in">يرجى تسجيل الدخول إلى حساب ماستدون أولًا</string>
<plurals name="cant_add_more_than_x_attachments">
<item quantity="zero">يجب عليك إرفاق ملف</item>
<item quantity="one">لا يمكنك إرفاق ملف</item>
<item quantity="two">لا يمكنك إرفاق أكثر من ملفين</item>
<item quantity="few">لا يمكنك إرفاق أكثر من %d ملفات</item>
<item quantity="many">لا يمكنك إرفاق أكثر من %d ملفًا</item>
<item quantity="other">لا يمكنك إرفاق أكثر من %d ملف</item>
</plurals>
<string name="media_attachment_unsupported_type">نوع الملف %s غير مدعوم</string>
<string name="media_attachment_too_big">الملف %1$s يتجاوز حدّ %2$s مب</string>
<string name="settings_theme">المظهر</string>
<string name="theme_auto">تلقائي</string>
<string name="theme_light">فاتح</string>
<string name="theme_dark">داكن</string>
<string name="theme_true_black">الوضع الداكن الحقيقي</string>
<string name="settings_behavior">السلوك</string>
<string name="settings_gif">تشغيل الصور الرمزية المتحركة والرموز التعبيرية المتحركة</string>
<string name="settings_custom_tabs">استخدم المتصفح المضمن</string>
<string name="settings_notifications">الإشعارات</string>
<string name="notify_me_when">أشعِرني عند قيام</string>
<string name="notify_anyone">أيَّ شخصٍ</string>
<string name="notify_follower">مُتابِع</string>
<string name="notify_followed">شخص أُتابِعُه</string>
<string name="notify_none">لَا أحد</string>
<string name="notify_favorites">بِالإعْجاب بِمَنشوري</string>
<string name="notify_follow">بمتابعتي</string>
<string name="notify_reblog">بإعادة تدوين مَنشوري</string>
<string name="notify_mention">ذكرني</string>
<string name="settings_boring">المنطِقَةُ المُملَّة</string>
<string name="settings_account">إعدادات الحساب</string>
<string name="settings_contribute">ساهم في ماستدون</string>
<string name="settings_tos">شروط الخدمة</string>
<string name="settings_privacy_policy">سياسة الخصوصية</string>
<string name="settings_spicy">المنطِقَةُ اللَّاذِعَة</string>
<string name="settings_clear_cache">امسح التخزين المؤقت للوسائط</string>
<string name="settings_app_version">تطبيق ماستدون لأندرويد نسخة %1$s (%2$d)</string>
<string name="media_cache_cleared">مُسح التخزين المؤقت للوسائط</string>
<string name="confirm_log_out">أمتأكد من الخروج؟</string>
<string name="sensitive_content">محتوى حساس</string>
<string name="sensitive_content_explain">علّم المؤلف هته الوسائط كحساسة. اضغط لكشفها.</string>
<string name="media_hidden">اُنقُر لِلكَشف</string>
<string name="avatar_description">انتقل للصفحة الشخصية لـ %s</string>
<string name="more_options">مزيد من الخيارات</string>
<string name="reveal_content">اكشف المحتوى</string>
<string name="hide_content">اخف المحتوى</string>
<string name="new_post">منشور جديد</string>
<string name="button_reply">ردّ</string>
<string name="button_reblog">أعد تدوين</string>
<string name="button_favorite">فضّل</string>
<string name="button_share">شارك</string>
<string name="media_no_description">وسائط بدون وصف</string>
<string name="add_media">أضف وسائط</string>
<string name="add_poll">أضف استطلاع رأي</string>
<string name="emoji">إيموجي</string>
<string name="post_visibility">مرئية المنشور</string>
<string name="home_timeline">الخيط الزمني الرئيسي</string>
<string name="my_profile">ملفي الشخصي</string>
<string name="media_viewer">عارض الوسائط</string>
<string name="follow_user">تابع %s</string>
<string name="unfollowed_user">ألغ متابعة %s</string>
<string name="followed_user">أنت تتابع %s</string>
<string name="open_in_browser">افتح في المتصفح</string>
<string name="hide_boosts_from_user">اخف ما أعاد %s تدوينه</string>
<string name="show_boosts_from_user">أظهر ما أعاد %s تدوينه</string>
<string name="signup_reason">لماذا ترغب في الانضمام؟</string>
<string name="signup_reason_note">هذا سوف يساعدنا في مراجعة تطبيقك.</string>
<string name="clear">امسح</string>
<string name="profile_header">الصورة الفوقية</string>
<string name="profile_picture">صورة الملفّ الشخصي</string>
<string name="reorder">أعد الترتيب</string>
<string name="download">نزّل</string>
<string name="permission_required">يتطلب أذونات</string>
<string name="storage_permission_to_download">يحتاج هذا التطبيق أذن الوصول للتخزين لحفظ الملف.</string>
<string name="open_settings">افتح الإعدادات</string>
<string name="error_saving_file">خطأ أثناء حفظ الملف</string>
<string name="file_saved">حُفظ الملف</string>
<string name="downloading">ينزّل…</string>
<string name="no_app_to_handle_action">لا يوجد تطبيق لمعالجة هذا الإجراء</string>
<string name="local_timeline">المجتمع</string>
<string name="trending_posts_info_banner">هَذِهِ هِيَ المَنشُوراتُ الَّتي تَكْتَسِبُ شَعبِيَّةً فِي الرُّكنِ الخاصِّ بِكَ مِن مَاستودُون.</string>
<string name="trending_hashtags_info_banner">هَذِهِ هِيَ الوُسُومُ الَّتي تَكْتَسِبُ شَعبِيَّةً فِي الرُّكنِ الخاصِّ بِكَ مِن مَاستودُون.</string>
<string name="trending_links_info_banner">هَذِهِ هِيَ القِصَصُ الأخبارِيَّةُ المُتَنَاقَلَةُ بِكِثرَةٍ فِي الرُّكنِ الخاصِّ بِكَ مِن مَاستودُون.</string>
<string name="local_timeline_info_banner">هذه هي أحدث منشورات المستخدمين المتواجدين على نفس الخادم الذي تستخدمه.</string>
<string name="dismiss">رفض</string>
<string name="see_new_posts">استعرض المنشورات الجديدة</string>
<string name="load_missing_posts">حمّل المَنشورات المَفقودَة</string>
<string name="follow_back">رُدّ المتابعة</string>
<string name="button_follow_pending">معلق</string>
<string name="follows_you">يُتابِعُك</string>
<string name="manually_approves_followers">الموافقة اليدوية على طلبات المتابعة</string>
<string name="current_account">الحِسابُ الحاليّ</string>
<string name="log_out_account">تَسجيلُ الخُرُوجِ مِن %s</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<plurals name="x_followers">
<item quantity="zero">ليس له متابِعون</item>
<item quantity="one">متابِع واحد</item>
<item quantity="two">متابِعان</item>
<item quantity="few">%,d متابِعين</item>
<item quantity="many">%,d متابِعًا</item>
<item quantity="other">%,d متابِع</item>
</plurals>
<plurals name="x_following">
<item quantity="zero">ليس له متابَعون</item>
<item quantity="one">متابَع واحد</item>
<item quantity="two">متابَعان</item>
<item quantity="few">%,d متابَعين</item>
<item quantity="many">%,d متابَعًا</item>
<item quantity="other">%,d متابَع</item>
</plurals>
<plurals name="x_favorites">
<item quantity="zero">دون تفضيلات</item>
<item quantity="one">تفضيل واحد</item>
<item quantity="two">تفضيلان</item>
<item quantity="few">%,d تفضيلات</item>
<item quantity="many">%,d تفضيلًا</item>
<item quantity="other">%,d تفضيل</item>
</plurals>
<plurals name="x_reblogs">
<item quantity="zero">لا إعادات تدوين</item>
<item quantity="one">إعاد تدوين واحدة</item>
<item quantity="two">إعادتا تدوين</item>
<item quantity="few">%,d إعادات تدوين</item>
<item quantity="many">%,d إعادة تدوين</item>
<item quantity="other">%,d إعادة تدوين</item>
</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="zero">منذ %d ثانية</item>
<item quantity="one">منذ ثانية</item>
<item quantity="two">منذ ثانيتان</item>
<item quantity="few">%d ثواني</item>
<item quantity="many">منذ %d ثانية</item>
<item quantity="other">%d ثواني مضت</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="zero">الان</item>
<item quantity="one">منذ دقيقة</item>
<item quantity="two">منذ دقيقتان</item>
<item quantity="few">%d دقائق مضت</item>
<item quantity="many">منذ %d دقائق</item>
<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 -->
<string name="update_available">ماستدون %s للأندرويد جاهز للتنزيل.</string>
<!-- %s is version like 1.2.3 -->
<string name="update_ready">تم تنزيل ماستدون %s للأندرويد ومستعد لتثبيته.</string>
<!-- %s is file size -->
<string name="download_update">جارٍ التنزيل (%s)</string>
<string name="install_update">تثبيت</string>
<string name="privacy_policy_title">ماستدون وخصوصيتك</string>
<string name="privacy_policy_subtitle">على الرغم من أن تطبيق ماستدون لا يجمع أي بيانات، فإن الخادم الذي قمت بالتسجيل من خلاله قد تكون له سياسة مختلفة. خذ دقيقة للمراجعة والموافقة على سياسة خصوصية التطبيق ماستدون وسياسة الخصوصية للخادم الخاص بك.</string>
<string name="i_agree">أنا مُوافِق</string>
<string name="empty_list">هذه القائمة فارغة</string>
<string name="instance_signup_closed">هذا الخادم لا يقبل تسجيلات جديدة.</string>
<string name="text_copied">تم النسخ إلى الحافظة</string>
<string name="add_bookmark">إضافة إلى الفواصل المرجعية</string>
<string name="remove_bookmark">إزالة من الفواصل المرجعية</string>
<string name="bookmarks">الفواصل المرجعية</string>
<string name="your_favorites">مفضلاتك</string>
</resources>

View File

@@ -714,4 +714,5 @@
<!-- Screen reader description for the menu on the home timeline screen -->
<!-- %s is the name of the list -->
<!-- %s is a username -->
<string name="see_new_posts">استعرض المنشورات الجديدة</string>
</resources>

View File

@@ -531,4 +531,5 @@
<!-- Screen reader description for the menu on the home timeline screen -->
<!-- %s is the name of the list -->
<!-- %s is a username -->
<string name="see_new_posts">Паказаць новыя допісы</string>
</resources>

View File

@@ -297,4 +297,5 @@
<!-- Screen reader description for the menu on the home timeline screen -->
<!-- %s is the name of the list -->
<!-- %s is a username -->
<string name="see_new_posts">Publicacions noves</string>
</resources>

View File

@@ -643,4 +643,5 @@
<!-- Screen reader description for the menu on the home timeline screen -->
<!-- %s is the name of the list -->
<!-- %s is a username -->
<string name="see_new_posts">Zobrazit nové příspěvky</string>
</resources>

View File

@@ -507,4 +507,5 @@
<!-- Screen reader description for the menu on the home timeline screen -->
<!-- %s is the name of the list -->
<!-- %s is a username -->
<string name="see_new_posts">Se nye indlæg</string>
</resources>

View File

@@ -43,8 +43,8 @@
<string name="block_user">%s sperren</string>
<string name="unblock_user">%s entsperren</string>
<string name="report_user">%s melden</string>
<string name="block_domain">%s sperren</string>
<string name="unblock_domain">%s nicht mehr sperren</string>
<string name="block_domain">%s blockieren</string>
<string name="unblock_domain">%s nicht mehr blockieren</string>
<plurals name="x_posts">
<item quantity="one">%,d Beitrag</item>
<item quantity="other">%,d Beiträge</item>
@@ -98,12 +98,12 @@
<string name="do_unmute">Nicht mehr stummschalten</string>
<string name="confirm_block_title">Konto sperren</string>
<string name="confirm_block_domain_title">Domain sperren</string>
<string name="confirm_block">Bestätigen, um %s zu sperren</string>
<string name="confirm_block">Bestätigen, um %s zu blockieren</string>
<string name="do_block">Sperren</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 Sperre von %s aufzuheben</string>
<string name="do_unblock">Sperre aufheben</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_blocked">Blockiert</string>
<string name="action_vote">Abstimmen</string>
<string name="delete">Löschen</string>
@@ -262,6 +262,7 @@
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">Dies sind alle Beiträge von allen Benutzern auf deinem Server (%s).</string>
<string name="recommended_accounts_info_banner">Dir könnten diese Konten gefallen, basierend auf Leuten, denen du folgst.</string>
<string name="see_new_posts">Neue Beiträge</string>
<string name="load_missing_posts">Weitere Beiträge laden</string>
<string name="follow_back">Zurückfolgen</string>
<string name="button_follow_pending">Ausstehend</string>
@@ -386,7 +387,7 @@
<string name="welcome_to_mastodon">Willkommen auf Mastodon</string>
<string name="welcome_paragraph1">Mastodon ist ein dezentrales, soziales Netzwerk. Das bedeutet, dass es nicht von einem einzigen Unternehmen kontrolliert wird. Das Netzwerk besteht aus unabhängig voneinander betriebenen Servern, die miteinander verbunden sind.</string>
<string name="what_are_servers">Was sind Server?</string>
<string name="welcome_paragraph2"><![CDATA[Jedes Mastodon-Konto wird auf einem Server gehostet. Jeder Server hat dabei seine eigenen Werte, Regeln und Administrator*innen. Aber egal, für welchen Server Du Dich entscheidest: Du kannst mit Leuten von anderen Servern interagieren und ihnen folgen.]]></string>
<string name="welcome_paragraph2">Jedes Mastodon-Konto wird auf einem Server erstellt jeder mit eigenen Werten, Regeln &amp; Administrator*innen. Du kannst trotzdem mit Personen interagieren, die ihr Konto auf einem anderen Server erstellt haben.</string>
<string name="opening_link">Link wird geöffnet…</string>
<string name="link_not_supported">Dieser Link wird in der App nicht unterstützt</string>
<string name="log_out_all_accounts">Von allen Konten abmelden</string>
@@ -582,8 +583,48 @@
<string name="time_hours_ago_short">vor %d Stunden</string>
<string name="time_days_ago_short">vor %d Tagen</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Übersetzen aus %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Übersetzt aus %1$s mit %2$s</string>
<string name="translation_show_original">Original anzeigen</string>
<string name="translation_failed">Übersetzung fehlgeschlagen. Möglicherweise haben die Administrator*innen die Übersetzungen auf diesem Server nicht aktiviert oder dieser Server läuft mit einer älteren Version von Mastodon, in der Übersetzungen noch nicht unterstützt wurden.</string>
<string name="settings_privacy">Datenschutz und Reichweite</string>
<string name="settings_discoverable">Profil und Beiträge in Suchalgorithmen berücksichtigen</string>
<string name="settings_indexable">Öffentliche Beiträge in die Suchergebnisse einbeziehen</string>
<string name="error_playing_video">Fehler bei Videowiedergabe</string>
<string name="lists">Listen</string>
<string name="followed_hashtags">Gefolgte Hashtags</string>
<string name="no_lists">Du hast noch keine Listen.</string>
<string name="no_followed_hashtags">Du folgst keinen Hashtags.</string>
<string name="manage_lists">Listen verwalten</string>
<string name="manage_hashtags">Hashtags verwalten</string>
<!-- Screen reader description for the menu on the home timeline screen -->
<string name="dropdown_menu">Dropdown-Menü</string>
<string name="edit_list">Liste bearbeiten</string>
<string name="list_members">Mitglieder auflisten</string>
<string name="delete_list">Liste löschen</string>
<!-- %s is the name of the list -->
<string name="delete_list_confirm">„%s“ löschen?</string>
<string name="list_exclusive">Mitglieder in „Folge ich“ ausblenden</string>
<string name="list_exclusive_subtitle">Wenn jemand in dieser Liste ist, sollen deren Beiträge nicht in der „Folge ich“-Timeline angezeigt werden.</string>
<string name="list_name">Listenname</string>
<string name="list_show_replies_to">Mit Antworten von</string>
<string name="list_replies_no_one">Niemandem</string>
<string name="list_replies_members">Mitgliedern der Liste</string>
<string name="list_replies_anyone">Benutzer*innen, denen ich folge</string>
<string name="confirm_remove_list_members">Mitglieder entfernen?</string>
<string name="remove">Entfernen</string>
<string name="add_list_member">Mitglied hinzufügen</string>
<string name="search_among_people_you_follow">Suche nach Leuten, denen du folgst</string>
<string name="add_user_to_list">Zur Liste hinzufügen…</string>
<string name="add_user_to_list_title">Zur Liste hinzufügen</string>
<!-- %s is a username -->
<string name="manage_user_lists">Listen mit %s verwalten</string>
<string name="remove_from_list">Aus Liste entfernen</string>
<string name="confirm_remove_list_member">Mitglied entfernen?</string>
<string name="no_followed_hashtags_title">Halte Interessen im Blick, indem du Hashtags folgst</string>
<string name="no_followed_hashtags_subtitle">Gefolge Hashtags erscheinen hier</string>
<string name="no_lists_title">Startseite mit Listen organisieren</string>
<string name="no_lists_subtitle">Deine werden hier erscheinen</string>
<string name="manage_accounts">Konten hinzufügen oder wechseln</string>
</resources>

View File

@@ -415,4 +415,23 @@
<string name="sk_settings_default_visibility">Standard-Sichtbarkeit für Posts</string>
<string name="sk_settings_lock_account">Neue Follower_innen manuell genehmigen</string>
<string name="sk_timeline_cache_cleared">Start-Timeline geleert</string>
<string name="sk_button_mutuals">Befreundet</string>
<string name="sk_icon_snowflake">Schneeflocke</string>
<string name="sk_icon_cloud">Wolke</string>
<string name="sk_icon_sunset">Sonnenuntergang</string>
<string name="sk_private_note_hint">Private Notiz über dieses Profil hinzufügen</string>
<string name="sk_icon_water">Wasser</string>
<string name="sk_icon_sun">Sonne</string>
<string name="sk_icon_rain">Regen</string>
<string name="sk_icon_thunderstorm">Gewitter</string>
<string name="sk_private_note_update_failed">Notiz speichern fehlgeschlagen</string>
<string name="sk_delete_note">Private Notiz löschen</string>
<string name="sk_confirm_changes">Änderungen bestätigen</string>
<string name="sk_settings_crash_log_unavailable">Keines verfügbar… noch</string>
<string name="sk_crash_log_copied">Absturzprotokoll kopiert</string>
<string name="sk_add_note">Private Notiz hinzufügen</string>
<string name="sk_settings_copy_crash_log">Neuestes Absturzprotokoll kopieren</string>
<string name="sk_open_post_preview">Vorschau öffnen</string>
<string name="sk_post_preview">Vorschau</string>
<string name="sk_private_note_confirm_delete">Private Notiz über %s löschen\?</string>
</resources>

View File

@@ -262,6 +262,7 @@
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">Αυτές είναι όλες οι αναρτήσεις από όλους τους χρήστες στο διακομιστή σου (%s).</string>
<string name="recommended_accounts_info_banner">Μπορεί να σου αρέσουν αυτοί οι λογαριασμοί με βάση άλλους που ακολουθείς.</string>
<string name="see_new_posts">Νέες αναρτήσεις</string>
<string name="load_missing_posts">Φόρτωση αναρτήσεων που λείπουν</string>
<string name="follow_back">Ακολούθησε και εσύ</string>
<string name="button_follow_pending">Εκκρεμεί</string>
@@ -599,7 +600,7 @@
<item quantity="other">%,d αναρτήσεις σήμερα</item>
</plurals>
<string name="error_playing_video">Σφάλμα αναπαραγωγής βίντεο</string>
<string name="timeline_following">Ακολουθείς</string>
<string name="timeline_following">Αρχική</string>
<string name="lists">Λίστες</string>
<string name="followed_hashtags">Ετικέτες που ακολουθούνται</string>
<string name="no_lists">Δεν έχεις καμία λίστα ακόμα.</string>
@@ -614,5 +615,17 @@
<!-- %s is the name of the list -->
<string name="delete_list_confirm">Διαγραφή “%s”;</string>
<string name="list_exclusive">Απόκρυψη μελών στο Ακολουθείς</string>
<string name="list_exclusive_subtitle">Αν κάποιος είναι σε αυτή τη λίστα, κρύψε τον στη ροή Ακολουθείς σας για να αποφεύγεις να βλέπεις τις αναρτήσεις του δύο φορές.</string>
<string name="list_name">Όνομα λίστας</string>
<string name="list_show_replies_to">Εμφάνιση απαντήσεων σε</string>
<string name="list_replies_no_one">Κανένας</string>
<string name="list_replies_members">Μέλη της λίστας</string>
<string name="list_replies_anyone">Όποιον ακολουθώ</string>
<string name="confirm_remove_list_members">Αφαίρεση μελών;</string>
<string name="remove">Αφαίρεση</string>
<string name="add_list_member">Προσθήκη μέλους</string>
<string name="search_among_people_you_follow">Αναζήτησε μεταξύ των ανθρώπων που ακουλουθείς</string>
<string name="add_user_to_list">Προσθήκη στη λίστα…</string>
<string name="add_user_to_list_title">Προσθήκη στη λίστα</string>
<!-- %s is a username -->
</resources>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -592,7 +592,6 @@
<string name="settings_discoverable">Mostrar perfil y publicaciones en algoritmos de descubrimiento</string>
<string name="settings_indexable">Incluir publicaciones públicas en los resultados de búsqueda</string>
<string name="error_playing_video">Error al reproducir el video</string>
<string name="timeline_following">Siguiendo</string>
<string name="lists">Listas</string>
<string name="followed_hashtags">Etiquetas seguidas</string>
<string name="no_lists">Aún no tienes ninguna lista.</string>

View File

@@ -402,10 +402,28 @@
<string name="sk_trending_links_info_banner">Estas noticias están dando que hablar en todo el Fediverso.</string>
<string name="sk_blocked_accounts">Cuentas bloqueadas</string>
<string name="sk_muted_accounts">Cuentas silenciadas</string>
<string name="sk_settings_like_icon">Utilizar el corazón como icono favorito</string>
<string name="sk_settings_like_icon">Utilizar un corazón como icono de favorito</string>
<string name="sk_recently_used">Utilizado recientemente</string>
<string name="sk_set_as_default">Establecer por defecto</string>
<string name="sk_settings_color_palette_default">Por defecto (%s)</string>
<string name="sk_settings_underlined_links">Enlaces subrayados</string>
<string name="sk_settings_underlined_links">Subrayar enlaces</string>
<string name="sk_edit_alt_text">Editar el texto alternativo</string>
<string name="sk_settings_default_visibility">Visibilidad de publicación predeterminada</string>
<string name="sk_settings_lock_account">Aprobar nuevos seguidores manualmente</string>
<string name="sk_timeline_cache_cleared">Caché de la cronología de inicio borrada</string>
<string name="sk_settings_clear_timeline_cache">Borrar la caché de la cronología de inicio</string>
<string name="sk_button_mutuals">Amigos</string>
<string name="sk_icon_snowflake">Copos de nieve</string>
<string name="sk_private_note_confirm">Confirmar los cambios en la nota</string>
<string name="sk_private_note_update_failed">No se pudo guardar la nota</string>
<string name="sk_icon_cloud">Nube</string>
<string name="sk_delete_note">Borrar la nota personal</string>
<string name="sk_icon_sunset">Puesta de sol</string>
<string name="sk_private_note_hint">Añadir una nota personal sobre este perfil</string>
<string name="sk_icon_water">Agua</string>
<string name="sk_icon_sun">Sol</string>
<string name="sk_add_note">Añadir una nota personal</string>
<string name="sk_icon_rain">Lluvia</string>
<string name="sk_icon_thunderstorm">Tormenta eléctrica</string>
<string name="sk_private_note_confirm_delete">¿Borrar la nota personal sobre %s\?</string>
</resources>

View File

@@ -15,6 +15,7 @@
<string name="user_sent_follow_request">%s-(e)k jarraitzeko eskaera bidali dizu</string>
<string name="user_favorited">%s(e)k zure bidalketa gogoko du</string>
<string name="notification_boosted">%s(e)k zure bidalketa bultzatu du</string>
<string name="poll_ended">Ikusi botoa eman zunuen inkestaren emaitzak</string>
<string name="share_toot_title">Partekatu</string>
<string name="settings">Ezarpenak</string>
<string name="publish">Argitaratu</string>
@@ -84,6 +85,10 @@
<item quantity="one">%d egun falta da</item>
<item quantity="other">%d egun falta dira</item>
</plurals>
<plurals name="x_votes">
<item quantity="one">Boto %,d</item>
<item quantity="other">%,d boto</item>
</plurals>
<string name="poll_closed">Itxita</string>
<string name="confirm_mute_title">Mututu kontua</string>
<string name="confirm_mute">Berretsi %s mututzea</string>
@@ -140,8 +145,11 @@
<string name="report_comment_hint">Iruzkin gehigarriak</string>
<string name="sending_report">Txostena bidaltzen…</string>
<string name="report_sent_title">Mila esker salaketagatik, berrikusiko dugu.</string>
<string name="report_sent_subtitle">Hau berrikusten dugun bitartean, %s erabiltzailearen aurkako neurriak hartu ditzakezu:</string>
<string name="unfollow_user">%s jarraitzeari utzi</string>
<string name="unfollow">Utzi jarraitzeari</string>
<string name="mute_user_explain">Ez dituzu bere bidalketak ikusiko. Zuri jarraitu eta zure bidalketak ikusteko aukera izango dute eta ezingo dute jakin mututu dituzula.</string>
<string name="block_user_explain">Ez dituzu bere bidalketak ikusiko. Ezingo dituzte zure bidalketak ikusi eta ez jarraitu. Blokeatu dituzula jakin dezakete.</string>
<string name="report_personal_title">Ez duzu hau ikusi nahi?</string>
<string name="report_personal_subtitle">Hemen dituzu Mastodonen ikusiko duzuna kontrolatzeko aukerak:</string>
<string name="back">Atzera</string>
@@ -229,6 +237,8 @@
<string name="unfollowed_user">Utzi %s jarraitzeari</string>
<string name="followed_user">%s jarraitzen ari zara</string>
<string name="open_in_browser">Ireki nabigatzailean</string>
<string name="hide_boosts_from_user">Ezkutatu %s(r)en bultzadak</string>
<string name="show_boosts_from_user">Erakutsi @%s(r)en bultzadak</string>
<string name="signup_reason">Zergatik elkartu nahi duzu?</string>
<string name="signup_reason_note">Honek zure eskaera berrikustean lagunduko digu.</string>
<string name="clear">Garbitu</string>
@@ -246,6 +256,7 @@
<string name="local_timeline">Lokala</string>
<string name="trending_posts_info_banner">Hauek dira zure Mastodon txokoan beraien lekua hartzen ari diren argitalpenak.</string>
<!-- %s is the server domain -->
<string name="see_new_posts">Bidalketa berriak</string>
<string name="load_missing_posts">Falta diren bidalketak kargatu</string>
<string name="follow_back">Jarraitu</string>
<string name="button_follow_pending">Zain</string>
@@ -319,6 +330,7 @@
<string name="server_url">Zerbitzariaren URLa</string>
<string name="server_filter_any_language">Edozein hizkuntza</string>
<string name="server_filter_instant_signup">Berehalako erregistroa</string>
<string name="server_filter_manual_review">Eskuzko berrikuspena</string>
<string name="server_filter_region_europe">Europa</string>
<string name="server_filter_region_north_america">Ipar Amerika</string>
<string name="server_filter_region_south_america">Hego Amerika</string>
@@ -326,9 +338,12 @@
<string name="server_filter_region_asia">Asia</string>
<string name="server_filter_region_oceania">Ozeania</string>
<string name="not_accepting_new_members">Ez da kide berririk onartzen</string>
<string name="category_special_interests">Interes bereziak</string>
<string name="signup_passwords_dont_match">Pasahitzak ez datoz bat</string>
<string name="pick_server_for_me">Aukeratu niretzat</string>
<string name="profile_add_row">Gehitu errenkada</string>
<string name="profile_setup">Profilaren konfigurazioa</string>
<string name="profile_setup_subtitle">Beti osa dezakezu hau geroago Profila fitxan.</string>
<string name="popular_on_mastodon">Mastodonen pil-pilean</string>
<string name="follow_all">Jarraitu denak</string>
<string name="server_rules_disagree">Ez ados</string>
@@ -343,6 +358,7 @@
<string name="profile_timeline">Denbora-lerroa</string>
<string name="view_all">Ikusi guztia</string>
<string name="profile_endorsed_accounts">Kontuak</string>
<string name="verified_link">Lotura egiaztatua</string>
<string name="show">Erakutsi</string>
<string name="hide">Ezkutatu</string>
<string name="pick_server">Aukeratu beste zerbitzari bat</string>
@@ -353,19 +369,29 @@
<string name="opening_link">Lotura irekitzen…</string>
<string name="retry">Berriro saiatu</string>
<!-- %s is formatted file size ("467 KB image") -->
<string name="attachment_description_image">irudi %s</string>
<string name="attachment_description_video">bideo %s</string>
<string name="attachment_description_audio">audio %s</string>
<string name="attachment_description_unknown">fitxategi %s</string>
<string name="attachment_type_image">Irudia</string>
<string name="attachment_type_video">Bideoa</string>
<string name="attachment_type_audio">Audioa</string>
<string name="attachment_type_gif">GIF-a</string>
<string name="attachment_type_unknown">Fitxategia</string>
<string name="add_poll_option">Gehitu inkesta aukera</string>
<string name="poll_length">Inkesta luzera</string>
<string name="poll_style">Estiloa</string>
<string name="compose_poll_single_choice">Aukeratu bat</string>
<string name="compose_poll_multiple_choice">Aukera anitza</string>
<string name="delete_poll_option">Ezabatu inkesta aukera</string>
<string name="poll_style_title">Inkesta estiloa</string>
<string name="alt_text">Alt testua</string>
<string name="help">Laguntza</string>
<string name="what_is_alt_text">Zer da alt testua?</string>
<string name="edit_post">Editatu argitalpena</string>
<string name="no_verified_link">Lotura ez egiaztatua</string>
<string name="compose_autocomplete_emoji_empty">Esploratu emojiak</string>
<string name="no_search_results">Ez da emaitzarik aurkitu bilaketa-termino horientzat</string>
<string name="language">Hizkuntza</string>
<string name="language_default">Lehenetsia</string>
<string name="language_system">Sistema</string>
@@ -373,7 +399,9 @@
<string name="language_cant_detect">Ezin da hizkuntza detektatu</string>
<string name="language_detected">Detektatuta</string>
<string name="media_hidden">Multimedia ezkutatua</string>
<string name="forward_report_explanation">Kontua beste zerbitzari batekoa da. Salaketaren kopia anonimo bat bidali nahi duzu zerbitzati horretara?</string>
<!-- %s is the server domain -->
<string name="forward_report_to_server">Birbidali hona: %s</string>
<!-- Shown on the "stamp" on the screen that appears after you report a post/user. Please keep the translation short, preferably a single word -->
<string name="reported">Salatua</string>
<string name="muted_user">Mututu %s</string>
@@ -391,6 +419,7 @@
<string name="notifications_policy_no_one">Bat ere ez</string>
<string name="pause_all_notifications_title">Pausatu jakinarazpen guztiak</string>
<!-- %1$s is the date (may be relative, e.g. "today" or "yesterday"), %2$s is the time. You can reorder these placeholders if that works better for your language -->
<string name="date_at_time">%1$s %2$s-etan</string>
<string name="today">gaur</string>
<string name="yesterday">atzo</string>
<string name="tomorrow">bihar</string>
@@ -424,6 +453,7 @@
<string name="selection_2_options">%1$s eta %2$s</string>
<string name="selection_3_options">%1$s, %2$s, eta %3$s</string>
<string name="selection_4_or_more">%1$s, %2$s, eta beste %3$d</string>
<string name="filter_context_home_lists">Hasierako denbora-lerroa</string>
<string name="filter_context_notifications">Jakinarazpenak</string>
<string name="filter_context_public_timelines">Denbora-lerro publikoak</string>
<string name="filter_context_profiles">Profilak</string>
@@ -440,9 +470,45 @@
<string name="clear_all">Garbitu dena</string>
<string name="search_open_url">Ireki URLa Mastodonen</string>
<!-- Shown in the post header. Please keep it short -->
<string name="time_seconds_ago_short">Duela %d segundo</string>
<string name="time_minutes_ago_short">Duela %d minutu</string>
<string name="time_hours_ago_short">Duela %d ordu</string>
<string name="time_days_ago_short">Duela %d egun</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="translation_show_original">Erakutsi jatorrizkoa</string>
<string name="settings_privacy">Pribatutasuna eta irismena</string>
<string name="settings_discoverable">Ezagutarazi profila eta bidalketak bilaketa algoritmoetan</string>
<string name="settings_indexable">Gehitu argitalpen publikoak bilaketa-emaitzetan</string>
<string name="timeline_following">Hasiera</string>
<string name="lists">Zerrendak</string>
<string name="followed_hashtags">Jarraitutako traolak</string>
<string name="no_lists">Ez duzu zerrendarik oraindik.</string>
<string name="manage_lists">Kudeatu zerrendak</string>
<string name="manage_hashtags">Kudeatu traolak</string>
<!-- Screen reader description for the menu on the home timeline screen -->
<string name="dropdown_menu">Menu zabalgarria</string>
<string name="edit_list">Editatu zerrenda</string>
<string name="list_members">Kideen zerrenda</string>
<string name="delete_list">Ezabatu zerrenda</string>
<!-- %s is the name of the list -->
<string name="delete_list_confirm">Ezabatu “%s”?</string>
<string name="list_name">Zerrendaren izena</string>
<string name="list_show_replies_to">Erakutsi erantzunak</string>
<string name="list_replies_no_one">Bat ere ez</string>
<string name="list_replies_members">Zerrendako kideak</string>
<string name="list_replies_anyone">Jarraitzen dudan edonor</string>
<string name="confirm_remove_list_members">Kendu kideak?</string>
<string name="remove">Kendu</string>
<string name="add_list_member">Gehitu kidea</string>
<string name="search_among_people_you_follow">Bilatu jarraitzen dituzun pertsonen artean</string>
<string name="add_user_to_list">Gehitu zerrendara…</string>
<string name="add_user_to_list_title">Gehitu zerrendara</string>
<!-- %s is a username -->
<string name="remove_from_list">Kendu zerrendatik</string>
<string name="confirm_remove_list_member">Kendu kideak?</string>
<string name="create_list">Sortu zerrenda</string>
<string name="step_x_of_y">%1$d pausua %2$d -(e)tik</string>
<string name="create">Sortu</string>
<string name="list_no_members">Oraindik ez dago kiderik</string>
</resources>

View File

@@ -600,7 +600,6 @@
<item quantity="other">%,d فرسته امروز</item>
</plurals>
<string name="error_playing_video">خطا در پخش ویدئو</string>
<string name="timeline_following">پی‌گرفته</string>
<string name="lists">سیاهه‌ها</string>
<string name="followed_hashtags">برچسب‌های پی‌گرفته</string>
<string name="no_lists">شما هنوز هیچ سیاهه‌ای ندارید.</string>

View File

@@ -403,4 +403,9 @@
<string name="sk_muted_accounts">حساب‌های خموش شده</string>
<string name="sk_recently_used">اخیرا مورد استفاده قرار گرفته</string>
<string name="sk_edit_alt_text">ویرایش متن جایگزین</string>
<string name="sk_settings_default_visibility">نمایانی فرسته پیش‌گزیده</string>
<string name="sk_settings_lock_account">تایید دستی پیگیران جدید</string>
<string name="sk_timeline_cache_cleared">کش خط زمانی خانه پاک شد</string>
<string name="sk_button_mutuals">متقابل</string>
<string name="sk_settings_clear_timeline_cache">پاک کردن کش خط زمانی خانه</string>
</resources>

View File

@@ -14,7 +14,7 @@
<string name="user_followed_you">%s seurasi sinua</string>
<string name="user_sent_follow_request">%s lähetti sinulle seurauspyynnön</string>
<string name="user_favorited">%s tykkäsi julkaisustasi</string>
<string name="notification_boosted">%s tehosti viestiäsi</string>
<string name="notification_boosted">%s tehosti julkaisuasi</string>
<string name="poll_ended">Katso tulokset äänestyksestä johon osallistuit</string>
<string name="share_toot_title">Jaa</string>
<string name="settings">Asetukset</string>
@@ -30,8 +30,8 @@
<item quantity="one">seurataan</item>
<item quantity="other">seurataan</item>
</plurals>
<string name="posts">Viestit</string>
<string name="posts_and_replies">Viestit ja vastaukset</string>
<string name="posts">Julkaisut</string>
<string name="posts_and_replies">Julkaisut ja vastaukset</string>
<string name="media">Media</string>
<string name="profile_about">Tietoja</string>
<string name="button_follow">Seuraa</string>
@@ -148,7 +148,7 @@
<string name="report_sent_subtitle">Sillä välin kun tarkistamme tätä, voit ryhtyä toimenpiteisiin käyttäjää @%s vastaan:</string>
<string name="unfollow_user">Lopeta käyttäjän %s seuraaminen</string>
<string name="unfollow">Lopeta seuraaminen</string>
<string name="mute_user_explain">Et näe hänen viestejään. Hän voi silti seurata sinua ja nähdä viestisi. Hän ei tiedä, että on mykistetty.</string>
<string name="mute_user_explain">Et näe hänen julkaisujaan. Hän voi silti seurata sinua ja nähdä julkaisusi. Hän ei tiedä, että hänet on mykistetty.</string>
<string name="block_user_explain">Et näe hänen viestejään, eikä hän voi nähdä viestejäsi tai seurata sinua. Hän näkevät, että olet estänyt hänet.</string>
<string name="report_personal_title">Etkö halua nähdä tätä?</string>
<string name="report_personal_subtitle">Tässä on vaihtoehtosi hallita näkemääsi Mastodonissa:</string>
@@ -260,8 +260,9 @@
<string name="trending_posts_info_banner">Nämä julkaisut ovat saamassa vetoa eri puolilla Mastodonia.</string>
<string name="trending_links_info_banner">Näistä uutisista puhutaan Mastodonissa.</string>
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">Nämä ovat viestit kaikilta palvelimesi (%s) käyttäjiltä.</string>
<string name="local_timeline_info_banner">Nämä ovat julkaisut kaikilta palvelimesi (%s) käyttäjiltä.</string>
<string name="recommended_accounts_info_banner">Muiden seuraamiesi perusteella saattaisit pitää näistä tileistä.</string>
<string name="see_new_posts">Uudet julkaisut</string>
<string name="load_missing_posts">Lataa puuttuvat julkaisut</string>
<string name="follow_back">Seuraa takaisin</string>
<string name="button_follow_pending">Pyydetty</string>
@@ -298,7 +299,7 @@
<item quantity="other">%d minuuttia sitten</item>
</plurals>
<string name="edited_timestamp">muokattu %s</string>
<string name="edit_original_post">Alkuperäinen viesti</string>
<string name="edit_original_post">Alkuperäinen jukaisu</string>
<string name="edit_text_edited">Tekstiä muokattu</string>
<string name="edit_spoiler_added">Sisältövaroitus</string>
<string name="edit_spoiler_edited">Sisältövaroitus muokattu</string>
@@ -392,7 +393,7 @@
<string name="log_out_all_accounts">Kirjaudu ulos kaikista tileistä</string>
<string name="confirm_log_out_all_accounts">Kirjaudu ulos kaikista tileistä?</string>
<string name="retry">Yritä uudelleen</string>
<string name="post_failed">Viestin lähettäminen epäonnistui</string>
<string name="post_failed">Julkaiseminen epäonnistui</string>
<!-- %s is formatted file size ("467 KB image") -->
<string name="attachment_description_image">%s kuva</string>
<string name="attachment_description_video">%s video</string>
@@ -477,7 +478,7 @@
<string name="about_server">Tietoja</string>
<string name="server_rules">Säännöt</string>
<string name="server_administrator">Ylläpitäjä</string>
<string name="send_email_to_server_admin">Viestin ylläpitäjä</string>
<string name="send_email_to_server_admin">Lähetä viesti ylläpitäjälle</string>
<string name="notifications_disabled_in_system">Ota ilmoitukset käyttöön laitteesi asetuksista nähdäksesi päivityksiä mistä tahansa.</string>
<string name="settings_even_more">Vielä enemmän asetuksia</string>
<string name="settings_show_cws">Näytä sisältövaroitukset</string>
@@ -548,7 +549,7 @@
<string name="edit_muted_word">Muokkaa mykistettyä sanaa</string>
<string name="add">Lisää</string>
<string name="filter_word_or_phrase">Sana tai lause</string>
<string name="filter_add_word_help">Sanat ovat tapauskohtaisia ja vastaavat vain kokonaisia sanoja.\n\nJos suodattaa avainsana “Apple”, se piilottaa viestit sisältävät “omena” tai “aPPLe” mutta ei “ananas\".</string>
<string name="filter_add_word_help">Sanat ovat tapauskohtaisia ja vastaavat vain kokonaisia sanoja.\n\nJos suodatat avainsanat “Apple”, se piilottaa julkaisut joissa on “apple” tai “aPpLe” mutta ei “pineapple\"</string>
<string name="settings_delete_filter_word">Poista sana “%s”?</string>
<string name="enter_selection_mode">Valitse</string>
<string name="select_all">Valitse kaikki</string>
@@ -596,10 +597,53 @@
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d viesti tänään</item>
<item quantity="other">%,d viestiä tänään</item>
<item quantity="other">%,d julkaisua tänään</item>
</plurals>
<string name="error_playing_video">Virhe videon toistossa</string>
<string name="timeline_following">Koti</string>
<string name="lists">Listat</string>
<string name="followed_hashtags">Seuratut aihetunnisteet</string>
<string name="no_lists">Sinulla ei ole vielä yhtään listaa.</string>
<string name="no_followed_hashtags">Et seuraa yhtään aihetunnistetta.</string>
<string name="manage_lists">Hallitse listoja</string>
<string name="manage_hashtags">Hallitse aihetunnisteita</string>
<!-- Screen reader description for the menu on the home timeline screen -->
<string name="dropdown_menu">Pudotusvalikko</string>
<string name="edit_list">Muokkaa listaa</string>
<string name="list_members">Listan jäsenet</string>
<string name="delete_list">Poista lista</string>
<!-- %s is the name of the list -->
<string name="delete_list_confirm">Poistetaanko “%s”?</string>
<string name="list_exclusive">Piilota jäsenet Seuratuissa</string>
<string name="list_exclusive_subtitle">Jos joku on tällä listalla, piilota hänet Seuratut-aikajanalla jottet näe julkaisuja kahdesti.</string>
<string name="list_name">Listan nimi</string>
<string name="list_show_replies_to">Näytä vastaukset</string>
<string name="list_replies_no_one">Ei kellekään</string>
<string name="list_replies_members">Listan jäsenille</string>
<string name="list_replies_anyone">Kenelle tahansa seuraamalleni</string>
<string name="confirm_remove_list_members">Poista jäsenet?</string>
<string name="remove">Poista</string>
<string name="add_list_member">Lisää jäsen</string>
<string name="search_among_people_you_follow">Etsi seuraamistasi henkilöistä</string>
<string name="add_user_to_list">Lisää listalle…</string>
<string name="add_user_to_list_title">Lisää listalle</string>
<!-- %s is a username -->
<string name="manage_user_lists">Hallitse listoja joissa %s on mukana</string>
<string name="remove_from_list">Poista listalta</string>
<string name="confirm_remove_list_member">Poista jäsen?</string>
<string name="no_followed_hashtags_title">Pysy ajan tasalla seuraamalla kiinnostavia aihetunnisteita</string>
<string name="no_followed_hashtags_subtitle">Seuratut näkyvät täällä</string>
<string name="no_lists_title">Järjestä kotiaikajanasi listojen avulla</string>
<string name="no_lists_subtitle">Omasi näkyy täällä</string>
<string name="manage_accounts">Lisää tai vaihda tiliä</string>
<plurals name="x_posts_recently">
<item quantity="one">%,d äskeinen julkaisu</item>
<item quantity="other">%,d äskeistä julkaisua</item>
</plurals>
<string name="create_list">Luo lista</string>
<string name="step_x_of_y">Vaihe %1$d / %2$d</string>
<string name="create">Luo</string>
<string name="manage_list_members">Hallitse listan jäseniä</string>
<string name="list_no_members">Ei vielä jäseniä</string>
<string name="list_find_users">Etsi lisättäviä käyttäjiä</string>
</resources>

View File

@@ -8,7 +8,7 @@
<string name="sk_settings_emoji_reactions_in_lists_explanation">Näytetäänkö emojireaktiot aikajanoissa. Jos tämä on pois käytöstä, emojireaktiot näkyvät vain lankaa katsottaessa.</string>
<string name="sk_button_react">Reagoi emojilla</string>
<string name="sk_again_for_system_keyboard">Järjestelmän näppäimistö seuraavalla painalluksella</string>
<string name="sk_enter_emoji_toast">Lisää emoji</string>
<string name="sk_enter_emoji_toast">Käytäthän emojia</string>
<string name="sk_enter_emoji_hint">Lisää emoji tai etsi</string>
<string name="sk_mute_label">Kesto</string>
<string name="sk_duration_indefinite">Loputon</string>
@@ -25,13 +25,13 @@
<string name="sk_delete_and_redraft">Poista ja kirjoita uudestaan</string>
<string name="sk_pin_post">Kiinnitä profiiliin</string>
<string name="sk_unpin_post">Irrota profiilista</string>
<string name="sk_confirm_delete_and_redraft_title">Poista ja kirjoita viesti uudelleen</string>
<string name="sk_confirm_pin_post_title">Kiinnitä viesti profiiliin</string>
<string name="sk_confirm_pin_post">Haluatko kiinnittää tämän viestin profiiliisi\?</string>
<string name="sk_confirm_delete_and_redraft">Oletko varma että haluat poistaa ja kirjoittaa viestin uudelleen\?</string>
<string name="sk_confirm_unpin_post_title">Poista viestin kiinnitys profiilista</string>
<string name="sk_unpinning">Viestiä irrotetaan…</string>
<string name="sk_pinning">Viestiä kiinnitetään…</string>
<string name="sk_confirm_delete_and_redraft_title">Poista ja kirjoita julkaisu uudelleen</string>
<string name="sk_confirm_pin_post_title">Kiinnitä julkaisu profiiliin</string>
<string name="sk_confirm_pin_post">Haluatko kiinnittää tämän julkaisun profiiliisi\?</string>
<string name="sk_confirm_delete_and_redraft">Oletko varma että haluat poistaa ja kirjoittaa julkaisun uudelleen\?</string>
<string name="sk_confirm_unpin_post_title">Poista julkaisun kiinnitys profiilista</string>
<string name="sk_unpinning">Julkaisua irrotetaan…</string>
<string name="sk_pinning">Julkaisua kiinnitetään…</string>
<string name="sk_image_description">Kuvaselitys</string>
<string name="sk_visibility_unlisted">Listaamaton</string>
<string name="sk_settings_show_replies">Näytä vastaukset</string>
@@ -43,7 +43,7 @@
<string name="sk_settings_auto_reveal_nobody">Ei koskaan</string>
<string name="sk_settings_auto_reveal_author">Saman tilin vastauksissa</string>
<string name="sk_settings_auto_reveal_anyone">Kaikkien vastaukset</string>
<string name="sk_settings_translate_only_opened">Käännä vain avatut viestit</string>
<string name="sk_settings_translate_only_opened">Käännä vain avatut julkaisut</string>
<string name="sk_translate_show_original">Näytä alkuperäinen</string>
<string name="sk_available_languages">Käytettävissä olevat kielet</string>
<string name="sk_translated_using">Kännetty käyttäen %s</string>
@@ -63,12 +63,12 @@
<string name="sk_loading_fediverse_resource_title">Etsitään Fediversestä</string>
<string name="sk_undo_reblog">Peru tehostus</string>
<string name="sk_reblog_with_visibility">Tehostuksen näkyvyysasetus</string>
<string name="sk_copy_link_to_post">Kopioi linkki viestiin</string>
<string name="sk_copy_link_to_post">Kopioi linkki julkaisuun</string>
<string name="sk_open_with_account">Avaa toisella tilillä</string>
<string name="sk_resource_not_found">Kohdetta ei löytynyt</string>
<string name="sk_settings_allow_remote_loading">Hae tietoa etäinstansseista</string>
<string name="sk_forward_report_to">Lähetä edelleen kohteeseen %s</string>
<string name="sk_unsent_posts">Julkaisemattomat viestit</string>
<string name="sk_unsent_posts">Odottavat julkaisut</string>
<string name="sk_settings_prefix_replies_always">Vastaus kenelle tahansa</string>
<string name="sk_settings_prefix_replies_never">Ei koskaan</string>
<string name="sk_confirm_save_draft">Tallenna luonnos\?</string>
@@ -81,7 +81,7 @@
<string name="sk_icon_diamond">Timantti</string>
<string name="sk_icon_umbrella">Sateenverjo</string>
<string name="sk_add_timeline">Lisää aikajana</string>
<string name="sk_edit_timeline_tag_main">Viestit joissa on aihetunniste…</string>
<string name="sk_edit_timeline_tag_main">Julkaisut, joissa on aihetunniste…</string>
<string name="sk_edit_timeline_tag_any">...tai jokin näistä</string>
<string name="sk_icon_beaker">Kannu</string>
<string name="sk_content_type_unspecified">Ei määritelty</string>
@@ -105,27 +105,27 @@
<string name="sk_icon_dog">Koira</string>
<string name="sk_icon_pin">Nasta</string>
<string name="sk_searching">Etsitään…</string>
<string name="sk_settings_see_new_posts_button">”Katso uudet viestit” -painike</string>
<string name="sk_settings_see_new_posts_button">”Katso uudet julkaisut” -painike</string>
<string name="sk_icon_feed">Syöte</string>
<string name="sk_post_edited">muokattu</string>
<string name="sk_notification_type_update">Muokatut viestit</string>
<string name="sk_notification_type_update">Muokatut julkaisut</string>
<string name="sk_attach_file">Liitä tiedosto</string>
<string name="sk_settings_hide_interaction">Piilota reaktiopainikkeet</string>
<string name="sk_followed_as">Seurattu tilinä %s</string>
<string name="sk_settings_hide_fab">Piilota Julkaise-painike automaattisesti</string>
<string name="sk_follow_as">Seuraa toisena tilinä</string>
<string name="sk_notification_type_posts">Viesti-ilmoitukset</string>
<string name="sk_notification_type_posts">Ilmotukset julkaisuista</string>
<string name="sk_settings_tabs_disable_swipe">Estä välilehdestä toiseen pyyhkäiseminen</string>
<string name="sk_settings_enable_delete_notifications">Salli ilmoitusten poistaminen</string>
<string name="sk_gif_badge">GIF</string>
<string name="sk_no_alt_text">Selitys puuttuu</string>
<string name="sk_inline_direct">vain maininnat</string>
<string name="sk_settings_glitch_instance">Glitch local-only mode</string>
<string name="sk_settings_glitch_instance">Glitch vain paikallinen</string>
<string name="sk_settings_glitch_mode_explanation">Ota tämä käyttöön, jos koti-instanssisi käyttää Glitchiä. Hometown tai Akkoma eivät tarvitse tätä.</string>
<string name="sk_unfinished_attachments_message">Joitakin liitteitä ei ole vielä ladattu valmiiksi.</string>
<string name="sk_settings_content_types">Ota viestin muotoilu käyttöön</string>
<string name="sk_settings_content_types">Ota julkaisun muotoilu käyttöön</string>
<string name="sk_settings_default_content_type">Sisältötyypin oletusarvo</string>
<string name="sk_settings_default_content_type_explanation">Tämä mahdollistaa uusien viestien sisältötyypin ennaltavalinnan, ohittaa julkaisuasetuksissa asetetun arvon.</string>
<string name="sk_settings_default_content_type_explanation">Tämä mahdollistaa uusien julkaisun sisältötyypin ennaltavalinnan, ohittaa julkaisuasetusten oletusarvon.</string>
<string name="sk_instance_info_unavailable">Tietoa instanssista ei juuri nyt ole saatavilla</string>
<string name="sk_open_in_app">Avaa sovelluksessa</string>
<string name="sk_external_share_or_open_title">Jaa tai avaa tilinä</string>
@@ -139,8 +139,8 @@
<item quantity="other">%1$,d käyttäjää reagoi näin: %2$s</item>
</plurals>
<plurals name="sk_posts_count_label">
<item quantity="one">viesti</item>
<item quantity="other">viestiä</item>
<item quantity="one">julkaisu</item>
<item quantity="other">julkaisut</item>
</plurals>
<string name="sk_settings_continues_playback">Ääniraita</string>
<string name="sk_settings_reply_visibility_following">Vastaukset seuraamilleni</string>
@@ -148,9 +148,9 @@
<string name="sk_settings_show_interaction_counts">Näytä laskimet</string>
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
<string name="sk_mark_media_as_sensitive">Merkitse media arkaluontoiseksi</string>
<string name="sk_federated_timeline_info_banner">Nämä ovat uusimmat viestit yleiseltä aikajanalta.</string>
<string name="sk_federated_timeline_info_banner">Nämä ovat uusimmat julkaisut yleiseltä aikajanalta.</string>
<string name="sk_lists_with_user">Listoja joissa on %s</string>
<string name="sk_notification_type_status">Viestit</string>
<string name="sk_notification_type_status">Julkaisut</string>
<string name="sk_color_palette_blue">Sininen</string>
<string name="sk_color_palette_brown">Ruskea</string>
<string name="sk_color_palette_red">Punainen</string>
@@ -166,7 +166,7 @@
<string name="sk_settings_donate">Lahjoita</string>
<string name="sk_delete_notification_confirm">Oletko varma, että haluat poistaa ilmoituksen\?</string>
<string name="sk_settings_publish_button_text">Julkaisupainikkeen teksti</string>
<string name="sk_quote_post">Kirjoita viesti aiheenamä</string>
<string name="sk_quote_post">Kirjoita julkaisustä</string>
<string name="sk_bookmark_as">Tee kirjamerkki toiselle tilille</string>
<string name="sk_already_bookmarked">Kirjamerkki jo tallennettu</string>
<string name="sk_favorite_as">Tykkä toisena tilinä</string>
@@ -176,12 +176,12 @@
<string name="sk_draft">Luonnos</string>
<string name="sk_schedule">Ajasta</string>
<string name="sk_confirm_delete_draft_title">Poista luonnos</string>
<string name="sk_confirm_delete_draft">Oletko varma, että haluat poistaa tämän viestiluonnoksen\?</string>
<string name="sk_confirm_delete_draft">Oletko varma, että haluat poistaa tämän luonnoksen\?</string>
<string name="sk_confirm_delete_scheduled_post_title">Poista ajastettu julkaisu</string>
<string name="sk_user_post_notifications_off">Ilmoitukset käyttäjän %s viesteistä poistettu käyttöstä</string>
<string name="sk_settings_show_boosts">Näytä tehostukset</string>
<string name="sk_settings_load_new_posts">Lataa uudet viestit automaattisesti</string>
<string name="sk_draft_or_schedule">Luonnos tai ajastettu viesti</string>
<string name="sk_settings_load_new_posts">Lataa uudet julkaisut automaattisesti</string>
<string name="sk_draft_or_schedule">Luonnos tai ajastus</string>
<string name="sk_compose_scheduled">Ajastettu ajalle</string>
<string name="sk_draft_saved">Luonnos tallennettu</string>
<string name="sk_post_scheduled">Julkaisu ajastettu</string>
@@ -209,9 +209,9 @@
<string name="sk_alt_text_missing_title">Kuvaselitys puuttuu</string>
<string name="sk_publish_anyway">Julkaise silti</string>
<string name="sk_settings_disable_alt_text_reminder">Poista muistutus mediakuvauksesta</string>
<string name="sk_notify_posts_info_banner">Jos otat käyttöön ilmoitukset joidenkin henkilöiden viesteistä, heidän uudet viestinsä näkyvät täällä.</string>
<string name="sk_notify_posts_info_banner">Jos otat käyttöön ilmoitukset joidenkin henkilöiden julkaisuista, heidän uudet julkaisunsa näkyvät täällä.</string>
<string name="sk_timelines">Aikajanat</string>
<string name="sk_timeline_posts">Viestit</string>
<string name="sk_timeline_posts">Julkaisut</string>
<string name="sk_timelines_add">Lisää</string>
<string name="sk_timeline">Aikajana</string>
<string name="sk_list">Lista</string>
@@ -263,7 +263,7 @@
<string name="sk_edit_timeline_tag_none">...muttei mitään näistä</string>
<string name="sk_edit_timeline_tag_hint">Kirjoita aihetunniste…</string>
<string name="sk_edit_timeline_tags_hint">Kirjoita aihetunnisteet…</string>
<string name="sk_hashtag_timeline_local_only_switch">Näytetäänkö vain paikalliset viestit\?</string>
<string name="sk_hashtag_timeline_local_only_switch">Näytetäänkö vain paikalliset julkaisut\?</string>
<string name="sk_add_timeline_tag_error_empty">Aihetunniste ei voi olla tyhjä</string>
<string name="sk_alt_button">ALT</string>
<string name="sk_no_results">Ei tuloksia</string>
@@ -274,12 +274,12 @@
<string name="sk_inline_local_only">vain paikallinen</string>
<string name="sk_separator">·</string>
<string name="sk_local_only">Vain paikallinen instanssi</string>
<string name="sk_settings_local_only_explanation">Jotta tämä toimii, koti-instanssisi on tuettava viestien julkaisemista ainoastaan paikallisesti. Useimmat Mastodonin muunnellut versiot tukevat, mutta Mastodon ei.</string>
<string name="sk_settings_local_only_explanation">Jotta tämä toimii, koti-instanssisi on tuettava sisällön julkaisemista ainoastaan paikallisesti. Useimmat Mastodonin muunnellut versiot tukevat, mutta Mastodon ei.</string>
<string name="sk_signed_up">rekisteröity</string>
<string name="sk_reported">raportoitu</string>
<string name="sk_sign_ups">Käyttäjät rekisteröityvät</string>
<string name="sk_settings_prefix_reply_cw_with_re">Lisää sisältövaroitukseen \"re:\" kun vastaat</string>
<string name="sk_unfinished_attachments">Korjataanko liitteet\?</string>
<string name="sk_unfinished_attachments">Liitteitä ladataan</string>
<string name="sk_content_type_bbcode">BBCode</string>
<string name="sk_content_type_mfm">MFM</string>
<string name="sk_settings_prefix_replies_to_others">Vastaukset muille</string>
@@ -310,7 +310,7 @@
<string name="sk_notify_poll_results">Kyselyn tulokset</string>
<string name="sk_expand">Avaa</string>
<string name="sk_collapse">Kätke</string>
<string name="sk_settings_collapse_long_posts">Kätke pitkien viestien loppuosa</string>
<string name="sk_settings_collapse_long_posts">Kätke pitkien julkaisujen loppuosa</string>
<string name="sk_reacted_with">%1$s reagoi %2$s</string>
<string name="sk_notification_action_replied">Vastaus lähetetty käyttäjälle %s</string>
<string name="sk_update_ready">Megalodon %s on ladattu ja valmis asennettavaksi.</string>
@@ -340,8 +340,8 @@
<string name="sk_user_post_notifications_on">Ilmoitukset käyttäjän %s viesteistä otettu käyttöön</string>
<string name="sk_federated_timeline">Yleinen</string>
<string name="sk_delete_notification_confirm_action">Poista ilmoitus</string>
<string name="sk_settings_content_types_explanation">Mahdollistaa viestien sisältötyypin valinnan, esimerkiksi Markdown. Kaikki instanssit eivät tue tätä.</string>
<string name="sk_bubble_timeline_info_banner">Nämä ovat uusimmat viestit ihmisiltä, joiden tilejä sinun instanssisi isännöi.</string>
<string name="sk_settings_content_types_explanation">Mahdollistaa julkaisujen sisältötyypin valinnan, esimerkiksi Markdown. Kaikki instanssit eivät tue tätä.</string>
<string name="sk_bubble_timeline_info_banner">Nämä ovat uusimmat julkaisut ihmisiltä, joiden tilejä sinun instanssisi isännöi.</string>
<string name="sk_settings_publish_button_text_title">Muokkaa Julkaise-painikkeen tekstiä</string>
<string name="sk_no_remote_info_hint">etäinfoa ei saatavilla</string>
<string name="sk_reject_follow_request">Hylkää seuraamispyyntö</string>
@@ -357,9 +357,9 @@
<string name="sk_confirm_delete_scheduled_post">Oletko varma, että haluat poistaa tämän ajastetun julkaisun\?</string>
<string name="sk_compose_draft">Julkaisu tallennetaan luonnoksena.</string>
<string name="sk_scheduled_too_soon">Julkaisu on ajastettava vähintään 10 minuuttia tulevaisuuteen.</string>
<string name="sk_confirm_unpin_post">Oletko varma että haluat irrottaa tämän viestin\?</string>
<string name="sk_confirm_unpin_post">Oletko varma että haluat irrottaa tämän julkaisun\?</string>
<string name="sk_settings_unifiedpush_no_distributor_body">On asennettava jakelijaohjelma jotta UnifiedPush-ilmoitukset toimivat. Lisätietoja: https://unifiedpush.org/</string>
<string name="sk_settings_emoji_reactions_explanation">Näyttää viestien emojireaktiot ja mahdollistaa omien lisäämisen. Jotkin Mastodonin muokatut versiot tukevat tätä, mutta Mastodon ei.</string>
<string name="sk_settings_emoji_reactions_explanation">Näyttää julkaisujen emojireaktiot ja antaa lisätä omasi. Jotkin Fediversumin serverit tukevat tätä, mutta ei Mastodon.</string>
<string name="sk_recent_searches_placeholder">Hae kirjoittamalla tähän</string>
<string name="sk_alt_text_missing">Selitys puuttuu vähintään yhdestä liitteestä.</string>
<string name="sk_pinned_timeline">Kiinnitetty kotinäyttöön</string>
@@ -370,7 +370,7 @@
<string name="sk_icon_recycle_bin">Roskakori</string>
<string name="sk_edit_timelines">Muokkaa aikajanoja</string>
<string name="sk_edit_timeline_tags_explanation">Ota huomioon, että palvelin tekee nämä operaatiot. Niiden yhdistelyä ei välttämättä tueta.</string>
<string name="sk_notify_update">Tämä muokkaa tehostettua viestiä</string>
<string name="sk_notify_update">Tämä muokkaa tehostettua julkaisua</string>
<string name="sk_save_draft_message">Haluatko tallentaa muutokset tähän luonnokseen vai julkaista sen nyt\?</string>
<string name="sk_settings_support_local_only">Palvelin tukee paikallista julkaisemista</string>
<string name="sk_settings_continues_playback_summary">Anna mediatoiston jatkua lisäten uusi raita</string>
@@ -379,7 +379,7 @@
<item quantity="one">%d sekunti</item>
<item quantity="other">%d sekuntia</item>
</plurals>
<string name="sk_settings_show_emoji_reactions_only_opened">Vain kun julkaisu on avattu</string>
<string name="sk_settings_show_emoji_reactions_only_opened">Vain jos julkaisu on avattu</string>
<plurals name="sk_time_hours">
<item quantity="one">%d tunti</item>
<item quantity="other">%d tuntia</item>
@@ -388,26 +388,35 @@
<string name="sk_settings_show_emoji_reactions">Näytä emojireaktiot aikajanoissa</string>
<string name="sk_blocked_accounts">Estetyt tilit</string>
<string name="sk_suicide_search_terms">Itsemurha</string>
<string name="sk_load_missing_posts_above">Lataa uudempia julkaisuja</string>
<string name="sk_load_missing_posts_above">Lataa uusia viestejä</string>
<plurals name="sk_time_days">
<item quantity="one">%d päivä</item>
<item quantity="other">%d päivää</item>
</plurals>
<string name="sk_settings_show_emoji_reactions_always">Näytä lisäyspainike aina</string>
<string name="sk_settings_show_emoji_reactions_always">Näytä aina \"lisää\" -painike</string>
<string name="sk_search_suicide_hotlines">Löydä kriisipuhelin</string>
<string name="sk_muted_accounts">Hiljennetyt tilit</string>
<string name="sk_do_not_show_again">Älä näytä toista kertaa</string>
<string name="sk_do_not_show_again">Älä näytä uudelleen</string>
<string name="sk_suicide_helplines_url">https://mieli.fi/tukea-ja-apua/kriisipuhelin/</string>
<string name="sk_trending_posts_info_banner">Nämä julkaisut ovat saamassa vetoa Fediversumissa.</string>
<string name="sk_load_missing_posts_below">Lataa vanhempia julkaisuja</string>
<string name="sk_trending_posts_info_banner">Nämä julkaisut saavat huomiota Fediversumissa.</string>
<string name="sk_load_missing_posts_below">Lataa vanhoja viestejä</string>
<string name="sk_search_suicide_title">Jos tarvitset apua…</string>
<plurals name="sk_time_minutes">
<item quantity="one">%d minuutti</item>
<item quantity="other">%d minuuttia</item>
</plurals>
<string name="sk_search_suicide_message">Jos haluat merkin siitä, että elämää kannattaa jatkaa, tämä on se. Jos olet hädässä, voit ottaa yhteyttä paikalliseen kriisipuhelimeen.</string>
<string name="sk_trending_links_info_banner">Näistä uutisjutuista keskustellaan Fediversumissa.</string>
<string name="sk_post_contains_media">Julkaisussa on mediaa</string>
<string name="sk_search_suicide_message">Jos odotat merkkiä, että sinun ei kannata tehdä itsemurhaa, niin se on tämä. Harkitse yhteydenottoa valtakunnalliseen kriisipuhelimeen 09 2525 0111.</string>
<string name="sk_trending_links_info_banner">Näistä uutisista puhutaan Fediversumissa.</string>
<string name="sk_post_contains_media">Julkaisussa on mediasisältöä</string>
<string name="sk_settings_like_icon">Käytä sydäntä suosikkikuvakkeena</string>
<string name="sk_recently_used">Äskettäin käytetty</string>
<string name="sk_set_as_default">Aseta oletusarvoksi</string>
<string name="sk_settings_color_palette_default">Oletusarvo (%s)</string>
<string name="sk_settings_default_visibility">Julkaisun oletusnäkyvyys</string>
<string name="sk_settings_underlined_links">Alleviivatut linkit</string>
<string name="sk_settings_lock_account">Hyväksy uudet seuraajat käsin</string>
<string name="sk_timeline_cache_cleared">Kotiaikajanan välimuisti tyhjennetty</string>
<string name="sk_button_mutuals">Ystävät</string>
<string name="sk_settings_clear_timeline_cache">Tyhjennä kotiaikajanan välimuisti</string>
<string name="sk_edit_alt_text">Muuta selitystä</string>
</resources>

View File

@@ -288,4 +288,5 @@
<!-- Screen reader description for the menu on the home timeline screen -->
<!-- %s is the name of the list -->
<!-- %s is a username -->
<string name="see_new_posts">Tingnan ang mga bagong post</string>
</resources>

View File

@@ -262,6 +262,7 @@
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">Voici tous les messages de tous les comptes de votre serveur (%s).</string>
<string name="recommended_accounts_info_banner">Vous pourriez aimer ces comptes en fonction des autres que vous suivez.</string>
<string name="see_new_posts">Nouveaux messages </string>
<string name="load_missing_posts">Charger les messages manquants</string>
<string name="follow_back">Suivre en retour</string>
<string name="button_follow_pending">En attente</string>
@@ -587,6 +588,9 @@
<string name="post_translated">Traduit depuis %1$s via %2$s</string>
<string name="translation_show_original">Afficher loriginal</string>
<string name="translation_failed">La traduction a échoué. Peut-être que ladministrateur na pas activé les traductions sur ce serveur ou que ce serveur utilise une ancienne version de Mastodon où les traductions ne sont pas encore prises en charge.</string>
<string name="settings_privacy">Vie privée et visibilité</string>
<string name="settings_discoverable">Autoriser des algorithmes de découverte à mettre en avant votre profil et vos messages</string>
<string name="settings_indexable">Inclure mes messages publics dans les résultats de recherche</string>
<plurals name="x_participants">
<item quantity="one">%,d participant</item>
<item quantity="other">%,d participants</item>
@@ -595,7 +599,51 @@
<item quantity="one">%,d message aujourdhui</item>
<item quantity="other">%,d messages aujourdhui</item>
</plurals>
<string name="error_playing_video">Erreur lors de la lecture de la vidéo</string>
<string name="timeline_following">Accueil</string>
<string name="lists">Listes</string>
<string name="followed_hashtags">Hashtags suivis</string>
<string name="no_lists">Vous navez pas encore de listes.</string>
<string name="no_followed_hashtags">Vous ne suivez aucun hashtag.</string>
<string name="manage_lists">Gérer les listes</string>
<string name="manage_hashtags">Gérer les hashtags</string>
<!-- Screen reader description for the menu on the home timeline screen -->
<string name="dropdown_menu">Menu déroulant</string>
<string name="edit_list">Modifier la liste</string>
<string name="list_members">Membres de la liste</string>
<string name="delete_list">Supprimer la liste</string>
<!-- %s is the name of the list -->
<string name="delete_list_confirm">Supprimer « %s » ?</string>
<string name="list_exclusive">Cacher les membres dans Abonné⋅e</string>
<string name="list_exclusive_subtitle">Si quelqu\'un est dans cette liste, les cacher dans votre fil pour éviter de voir leurs messages deux fois.</string>
<string name="list_name">Nom de la liste</string>
<string name="list_show_replies_to">Montrer les réponses à</string>
<string name="list_replies_no_one">Personne</string>
<string name="list_replies_members">Membres de la liste</string>
<string name="list_replies_anyone">Toute personne que je suis</string>
<string name="confirm_remove_list_members">Supprimer les membres ?</string>
<string name="remove">Supprimer</string>
<string name="add_list_member">Ajouter un membre</string>
<string name="search_among_people_you_follow">Rechercher parmi les gens que vous suivez</string>
<string name="add_user_to_list">Ajouter à la liste…</string>
<string name="add_user_to_list_title">Ajouter à la liste</string>
<!-- %s is a username -->
<string name="manage_user_lists">Gérer les listes où %s apparaît</string>
<string name="remove_from_list">Supprimer de la liste</string>
<string name="confirm_remove_list_member">Supprimer le membre ?</string>
<string name="no_followed_hashtags_title">Restez au courant des sujets qui vous intéressent en suivant des hashtags</string>
<string name="no_followed_hashtags_subtitle">Les abonnements apparaîtront ici</string>
<string name="no_lists_title">Organisez votre fil d\'accueil avec des listes</string>
<string name="no_lists_subtitle">Vos listes apparaîtront ici</string>
<string name="manage_accounts">Ajouter ou changer de compte</string>
<plurals name="x_posts_recently">
<item quantity="one">%,d message récemment</item>
<item quantity="other">%,d messages récemment</item>
</plurals>
<string name="create_list">Créer une liste</string>
<string name="step_x_of_y">Étape %1$d sur %2$d</string>
<string name="create">Créer</string>
<string name="manage_list_members">Gérer les membres de la liste</string>
<string name="list_no_members">Aucun membre pour l\'instant</string>
<string name="list_find_users">Trouver des utilisateurs à ajouter</string>
</resources>

View File

@@ -297,7 +297,7 @@
<string name="sk_settings_allow_remote_loading">Charger des informations à partir d\'instances distantes</string>
<string name="sk_no_remote_info_hint">informations distantes indisponibles</string>
<string name="sk_error_loading_profile">Échec du chargement du profil via %s</string>
<string name="sk_settings_allow_remote_loading_explanation">Essayez de récupérer des listes plus précises pour les abonnés, les likes et les boosts en chargeant les informations à partir de l\'instance d\'origine.</string>
<string name="sk_settings_allow_remote_loading_explanation">Essaye de récupérer des listes plus précises pour les abonné·e·s, les favoris et les boosts en chargeant les informations à partir de l\'instance d\'origine.</string>
<string name="sk_settings_auto_reveal_equal_spoilers">Révéler les CW identiques dans les réponses</string>
<string name="sk_settings_auto_reveal_nobody">Jamais</string>
<string name="sk_settings_auto_reveal_author">Réponses du même auteur</string>
@@ -412,6 +412,7 @@
<string name="sk_message_cache_cleared">Cache des messages vidé</string>
<string name="sk_settings_clear_timeline_cache">Effacer le cache du fil d\'accueil</string>
<string name="sk_settings_default_visibility">Visibilité de publication par défaut</string>
<string name="sk_settings_lock_account">Approuver manuellement les nouveaux abonnés</string>
<string name="sk_settings_lock_account">Approuver manuellement les nouveaux·elles abonné·e·s</string>
<string name="sk_timeline_cache_cleared">Cache du fil d\'accueil vidé</string>
<string name="sk_button_mutuals">Suivi mutuel</string>
</resources>

View File

@@ -288,6 +288,7 @@
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">Seo gach post o gach cleachdaiche an fhrithealaiche agad (%s).</string>
<string name="recommended_accounts_info_banner">Dhfhaoidte gun còrd na cunntasan seo riut stèidhichte air feadhainn eile a tha thu a leantainn.</string>
<string name="see_new_posts">Postaichean ùra</string>
<string name="load_missing_posts">Luchdaich postaichean a dhìth</string>
<string name="follow_back">Lean air ais</string>
<string name="button_follow_pending">Ri dhèiligeadh</string>
@@ -640,8 +641,40 @@
<string name="time_hours_ago_short">%du air ais</string>
<string name="time_days_ago_short">%dl air ais</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Eadar-theangaich o %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Air eadar-theangachadh o %1$s le %2$s</string>
<string name="translation_show_original">Seall an tionndadh tùsail</string>
<string name="translation_failed">Dhfhàillig an t-eadar-theangachadh. Dhfhaoidte nach do chuir an rianaire na h-eadar-theangachaidhean an comas air an fhrithealaiche seo no gu bheil am frithealaiche a ruith seann-tionndadh de Mhastodon far nach eil taic ri eadar-theangachadh fhathast.</string>
<string name="settings_privacy">Prìobhaideachd s ruigse</string>
<string name="settings_discoverable">Brosnaich a phròifil is postaichean agad sna h-algairimean luirg</string>
<string name="settings_indexable">Gabh na postaichean poblach a-staigh ann an toraidhean luirg</string>
<plurals name="x_participants">
<item quantity="one">%,d chom-pàirtiche</item>
<item quantity="two">%,d chom-pàirtiche</item>
<item quantity="few">%,d com-pàirtichean</item>
<item quantity="other">%,d com-pàirtiche</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d phost an-diugh</item>
<item quantity="two">%,d phost an-diugh</item>
<item quantity="few">%,d postaichean an-diugh</item>
<item quantity="other">%,d post an-diugh</item>
</plurals>
<string name="error_playing_video">Mearachd le cluich a video</string>
<string name="timeline_following">Dachaigh</string>
<string name="lists">Liostaichean</string>
<string name="followed_hashtags">Tagaichean hais gan leantainn</string>
<string name="no_lists">Chan eil liosta agad fhathast.</string>
<string name="no_followed_hashtags">Chan eil thu a leantainn taga hais fhathast.</string>
<string name="manage_lists">Stiùirich na liostaichean</string>
<string name="manage_hashtags">Stiùirich na tagaichean hais</string>
<!-- Screen reader description for the menu on the home timeline screen -->
<string name="dropdown_menu">Clàr-taice teàrnach</string>
<string name="edit_list">Deasaich an liosta</string>
<string name="list_members">Buill na liosta</string>
<string name="delete_list">Sguab às an liosta</string>
<!-- %s is the name of the list -->
<string name="delete_list_confirm">A bheil thu airson “%s” a sguabadh às?</string>
<!-- %s is a username -->
</resources>

View File

@@ -600,7 +600,6 @@
<item quantity="other">%,d publicacións hoxe</item>
</plurals>
<string name="error_playing_video">Erro ao reproducir o vídeo</string>
<string name="timeline_following">A Seguir</string>
<string name="lists">Listas</string>
<string name="followed_hashtags">Cancelos seguidos</string>
<string name="no_lists">Aínda non tes ningunha lista.</string>

View File

@@ -256,7 +256,7 @@
<string name="sk_filtered">Filtrados: %s</string>
<string name="sk_expand">Expandir</string>
<string name="sk_settings_collapse_long_posts">Contraer publicacións moi longas</string>
<string name="sk_unfinished_attachments">Arranxar arquivos adxuntos\?</string>
<string name="sk_unfinished_attachments">Subindo arquivos adxuntos</string>
<string name="sk_spectator_mode">Modo espectador</string>
<string name="sk_settings_hide_interaction">Ocultar botóns de interacción</string>
<string name="sk_follow_as">Seguir dende outra conta</string>
@@ -330,10 +330,10 @@
<string name="sk_gif_badge">GIF</string>
<string name="sk_settings_unifiedpush_no_distributor_body">Debe instalar un distribuidor para que as notificacións UnifiedPush funcionen. Para máis información, visita https://unifiedpush.org/</string>
<string name="sk_settings_unifiedpush_no_distributor">Non se atopou ningún distribuidor</string>
<string name="sk_trending_posts_info_banner">Estas son publicacións que están a gañar tracción no teu servidor.</string>
<string name="sk_trending_posts_info_banner">Estas publicacións están a gañar tracción no Fediverso.</string>
<string name="sk_settings_unifiedpush_choose">Escoller un distribuidor</string>
<string name="sk_settings_continues_playback">Superposición de audio</string>
<string name="sk_trending_links_info_banner">Estas son as novas historias das que se está a falar no teu servidor.</string>
<string name="sk_trending_links_info_banner">Destas novas historias estase a falar en todo o Fediverso.</string>
<string name="sk_settings_unifiedpush">Empregar UnifiedPush</string>
<string name="sk_disable_pill_shaped_active_indicator">Desactivar o indicador de lapela activa en forma de pílula</string>
<string name="sk_tab_profile">Perfil</string>
@@ -373,7 +373,7 @@
<string name="sk_search_suicide_hotlines">Buscar unha liña de axuda</string>
<string name="sk_duration_hours_1">1 hora</string>
<string name="sk_duration_hours_6">6 horas</string>
<string name="sk_enter_emoji_hint">Escribe para reaccionar cunha emoticona</string>
<string name="sk_enter_emoji_hint">Escribe unha emoticona ou busca</string>
<string name="sk_duration_days_7">7 días</string>
<string name="sk_do_not_show_again">Non amosar outra vez</string>
<string name="sk_suicide_helplines_url">https://findahelpline.com/es</string>
@@ -396,4 +396,12 @@
<string name="sk_blocked_accounts">Contas bloqueadas</string>
<string name="sk_muted_accounts">Contas silenciadas</string>
<string name="sk_recently_used">Empregados recentemente</string>
<string name="sk_set_as_default">Establecer como predefinido</string>
<string name="sk_settings_color_palette_default">Defecto (%s)</string>
<string name="sk_settings_default_visibility">Visibilidade de publicación por defecto</string>
<string name="sk_settings_underlined_links">Ligazóns subliñadas</string>
<string name="sk_settings_lock_account">Aprobar manualmente novos seguidores</string>
<string name="sk_timeline_cache_cleared">Caché da cronoloxía de inicio borrada</string>
<string name="sk_settings_clear_timeline_cache">Borrar a caché da cronoloxía de inicio</string>
<string name="sk_edit_alt_text">Editar texto alternativo</string>
</resources>

View File

@@ -157,7 +157,7 @@
<string name="sk_visibility_unlisted">Nenavedeno</string>
<string name="sk_lists_with_user">Liste s %s</string>
<string name="sk_settings_show_federated_timeline">Prikaži objedinjenu vremensku traku</string>
<string name="sk_translated_using">"Prevedeno uporabom %s"</string>
<string name="sk_translated_using">Prevedeno uporabom %s</string>
<string name="sk_settings_translate_only_opened">Prevodi samo otvorene objave</string>
<string name="sk_loading_fediverse_resource_title">Pretraga na Fediversu</string>
<string name="sk_loading_resource_on_instance_title">Pretraga na %s</string>

View File

@@ -376,4 +376,5 @@
<!-- Screen reader description for the menu on the home timeline screen -->
<!-- %s is the name of the list -->
<!-- %s is a username -->
<string name="see_new_posts">Új bejegyzések megtekintése</string>
</resources>

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