Compare commits
212 Commits
v1.1.3+for
...
v1.1.4+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58abdad62a | ||
|
|
c693414cc3 | ||
|
|
ee158e1aba | ||
|
|
40d478aaec | ||
|
|
e7fb96b3ff | ||
|
|
ef75427b45 | ||
|
|
2ff4f00774 | ||
|
|
534fd8c119 | ||
|
|
2d2cd89454 | ||
|
|
9f3f5ca7c1 | ||
|
|
c0e6f17c83 | ||
|
|
677f1cb42d | ||
|
|
1ac6a04a46 | ||
|
|
21927d2e25 | ||
|
|
c35441f5f7 | ||
|
|
3718fe6601 | ||
|
|
095f234bd5 | ||
|
|
f10c0e06db | ||
|
|
ebbd5d1fa3 | ||
|
|
c8abf26040 | ||
|
|
bc733af147 | ||
|
|
77a2fd2a60 | ||
|
|
5cfc5eb08a | ||
|
|
b832d2df26 | ||
|
|
db34dc40ba | ||
|
|
bfa48c2d3e | ||
|
|
b5e229a84d | ||
|
|
ef207f885b | ||
|
|
c4eee28335 | ||
|
|
6fe466779e | ||
|
|
e71db1b883 | ||
|
|
b6efafe99d | ||
|
|
688d466f8e | ||
|
|
3c5797932e | ||
|
|
48a5e262ce | ||
|
|
63b0365208 | ||
|
|
972c05d60b | ||
|
|
acb5778e0b | ||
|
|
7694c50358 | ||
|
|
7ffa368d10 | ||
|
|
53f8f41d88 | ||
|
|
93d57d847e | ||
|
|
83a4f5eec2 | ||
|
|
836c2dba8d | ||
|
|
13ae8d2ebe | ||
|
|
98dafb4e49 | ||
|
|
85b8bae42e | ||
|
|
64416ef9ee | ||
|
|
6274ab3384 | ||
|
|
4ea5d94dc6 | ||
|
|
db4dd436b7 | ||
|
|
6906ac6c8f | ||
|
|
bc0b56a90e | ||
|
|
349d95b8ee | ||
|
|
8c8eb395de | ||
|
|
50c844aa25 | ||
|
|
85b232330b | ||
|
|
b4c305d094 | ||
|
|
be2ee3a029 | ||
|
|
596799bf2f | ||
|
|
10a405ef13 | ||
|
|
118c5d4b44 | ||
|
|
a4cb05080a | ||
|
|
699ececc42 | ||
|
|
4f8f698911 | ||
|
|
30083837a5 | ||
|
|
ea7aa6c52f | ||
|
|
244f2ed911 | ||
|
|
99a71c67e4 | ||
|
|
b3c0550a86 | ||
|
|
39a0c6d08a | ||
|
|
0ee494bcfc | ||
|
|
4233c743e2 | ||
|
|
eea00b0d53 | ||
|
|
7cef4d282c | ||
|
|
df6e085c97 | ||
|
|
70cf48355e | ||
|
|
da06c798d7 | ||
|
|
9055c094e8 | ||
|
|
5ab3ae3d59 | ||
|
|
92ebb2f339 | ||
|
|
d8c034dfa3 | ||
|
|
247516849e | ||
|
|
aad9996b6f | ||
|
|
76c1ad07a3 | ||
|
|
49599f48bb | ||
|
|
158d128316 | ||
|
|
00897e7388 | ||
|
|
1867f294c8 | ||
|
|
eaa189d6b0 | ||
|
|
87d11dcb0a | ||
|
|
ba99394890 | ||
|
|
6b8236021e | ||
|
|
68b12a579c | ||
|
|
3f0ae887b8 | ||
|
|
187f9a2246 | ||
|
|
e8566bc24b | ||
|
|
e375fc7d4d | ||
|
|
89157efce8 | ||
|
|
5e95291016 | ||
|
|
cbd6e668dc | ||
|
|
898e62490b | ||
|
|
326ab4edf1 | ||
|
|
f25a965478 | ||
|
|
f3f3bea7b3 | ||
|
|
583b0788c6 | ||
|
|
ca29cee586 | ||
|
|
ec905448b0 | ||
|
|
e8fa82d0de | ||
|
|
e381de812c | ||
|
|
1a9752d53b | ||
|
|
09cc5c5fd2 | ||
|
|
4e8d510d38 | ||
|
|
7f7f6bae80 | ||
|
|
358ff12fba | ||
|
|
8ff3ecb4d4 | ||
|
|
47df35f0fd | ||
|
|
e226851e03 | ||
|
|
7228907682 | ||
|
|
1dbabed716 | ||
|
|
4224cd037d | ||
|
|
9aa6d3f531 | ||
|
|
c9823ae9d0 | ||
|
|
1fa8a9e858 | ||
|
|
212e8893b9 | ||
|
|
367057421b | ||
|
|
c821480842 | ||
|
|
3ef7c11e3b | ||
|
|
1112756bc2 | ||
|
|
878ed43135 | ||
|
|
01b746d30a | ||
|
|
a900351729 | ||
|
|
4114a6e5d8 | ||
|
|
04dd232e38 | ||
|
|
bc26dfe856 | ||
|
|
0322f845af | ||
|
|
7daa2d63e6 | ||
|
|
3a8a41f568 | ||
|
|
65cd7d076b | ||
|
|
a1f71091fe | ||
|
|
131834af86 | ||
|
|
c0789cbdb9 | ||
|
|
7d12d866ab | ||
|
|
7c2589c35b | ||
|
|
fd2031ccf5 | ||
|
|
6a165ec9f4 | ||
|
|
fd8dad487a | ||
|
|
e9443b841a | ||
|
|
89ec6acac9 | ||
|
|
9b389b346f | ||
|
|
4a522bffc0 | ||
|
|
9b62f8b7a2 | ||
|
|
96342d67e3 | ||
|
|
95fc2bef9e | ||
|
|
4f36fbe3b9 | ||
|
|
a501d8b995 | ||
|
|
74691650b3 | ||
|
|
9325781cbc | ||
|
|
b383dd6419 | ||
|
|
fe98ecf0cc | ||
|
|
01970ab69b | ||
|
|
3aa252f681 | ||
|
|
18633291e6 | ||
|
|
b79619b6e4 | ||
|
|
dbb03ee688 | ||
|
|
00894b41d2 | ||
|
|
8868ace90d | ||
|
|
a7586eeba8 | ||
|
|
19b89c606a | ||
|
|
b6c1e7d11e | ||
|
|
ca90c89b2a | ||
|
|
170a758f5f | ||
|
|
43dcd6e7f4 | ||
|
|
063542b2f5 | ||
|
|
70e4ae4fb4 | ||
|
|
8f1a4c60df | ||
|
|
07212dba96 | ||
|
|
fb95caadbe | ||
|
|
51e86b686d | ||
|
|
6ec335087d | ||
|
|
96040e15fd | ||
|
|
12a5670441 | ||
|
|
b756fe2cdb | ||
|
|
3732a4c844 | ||
|
|
5bd59bd999 | ||
|
|
90ba5551d2 | ||
|
|
484b6891fd | ||
|
|
0435d5f0c7 | ||
|
|
864c8de8de | ||
|
|
8bcea5bfb8 | ||
|
|
beded04579 | ||
|
|
5dcd41170c | ||
|
|
8aeda56fc8 | ||
|
|
3bb921a859 | ||
|
|
38edbde645 | ||
|
|
f531a90b41 | ||
|
|
ff52c37868 | ||
|
|
8fb2b454dd | ||
|
|
265b2ad32c | ||
|
|
ba3219d9fc | ||
|
|
bcac7401ee | ||
|
|
b44e3424e3 | ||
|
|
c15f6519c0 | ||
|
|
2809b27be0 | ||
|
|
1b6e096bf9 | ||
|
|
85db37f6a7 | ||
|
|
0cee490466 | ||
|
|
a148c92da2 | ||
|
|
6272797834 | ||
|
|
c048134ef2 | ||
|
|
6f57cd9ffe | ||
|
|
76815f8194 |
43
README.md
43
README.md
@@ -1,34 +1,47 @@
|
||||

|
||||
|
||||
# Mastodon for Android Fork
|
||||
# Mastodos
|
||||
|
||||
> A fork of the [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly won’t ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
|
||||
|
||||
[](https://github.com/sk22/mastodon-android-fork/releases/latest/download/mastodos.apk)
|
||||
|
||||
## Changes
|
||||
|
||||
* [Enable "Unlisted" as a visibility option](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted)
|
||||
([Pull request](https://github.com/mastodon/mastodon-android/pull/103)) and
|
||||
[set as default](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted-as-default)
|
||||
* [Add "Federation" tab and change Discover tab order](https://github.com/sk22/mastodon-android-fork/tree/feature/add-federated-timeline) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/8))
|
||||
### Features
|
||||
|
||||
* [Add “Unlisted” as a post visibility option](https://github.com/sk22/mastodon-android-fork/tree/feature/enable-unlisted)
|
||||
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
|
||||
* [Add “Federation” tab and change Discover tab order](https://github.com/sk22/mastodon-android-fork/tree/feature/add-federated-timeline) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/8))
|
||||
* [Add image description button and viewer](https://github.com/sk22/mastodon-android-fork/tree/feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
|
||||
* [Implement pinning posts and displaying pinned posts](https://github.com/sk22/mastodon-android-fork/tree/feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
|
||||
* [Display full image when adding image description](https://github.com/sk22/mastodon-android-fork/tree/feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
|
||||
* [Always preserve content warnings when replying](https://github.com/sk22/mastodon-android-fork/tree/feature/always-preserve-cw) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
||||
* [Make back button return to the home tab before exiting the app](https://github.com/sk22/mastodon-android-fork/tree/feature/back-returns-home) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
||||
* [Implement a bookmark button and list](https://github.com/sk22/mastodon-android-fork/tree/feature/bookmarks) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/22))
|
||||
* [Add “Mark media as sensitive” option](https://github.com/sk22/mastodon-android-fork/tree/feature/mark-media-as-sensitive)
|
||||
* [Add “Check for update” button in addition to integrated update checker](https://github.com/sk22/mastodon-android-fork/commits/feature/check-for-update-button)
|
||||
|
||||
### Behavior
|
||||
|
||||
* [Make back button return to the home tab before exiting the app](https://github.com/sk22/mastodon-android-fork/tree/feature/back-returns-home) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
||||
* [Always preserve content warnings when replying](https://github.com/sk22/mastodon-android-fork/tree/feature/always-preserve-cw) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
||||
* [Display full image when adding image description](https://github.com/sk22/mastodon-android-fork/tree/feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
|
||||
* [Implement deleting and re-drafting](https://github.com/sk22/mastodon-android-fork/tree/feature/delete-redraft) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/21))
|
||||
* [Set spoiler height independently to content height](https://github.com/sk22/mastodon-android-fork/commits/spoiler-height-independent) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/166))
|
||||
|
||||
## Fork-specific changes
|
||||
### Installation
|
||||
|
||||
* Custom app name
|
||||
To install this app on your Android device, download the [latest release from GitHub](https://github.com/sk22/mastodon-android-fork/releases/latest/download/mastodos.apk) and open it. You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/sk22/mastodon-android-fork/releases) page.
|
||||
|
||||
Mastodos makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Mastodos will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
|
||||
|
||||
### Branding
|
||||
|
||||
* App name “Mastodos”
|
||||
* Pink primary color
|
||||
* Custom icon: Modulate upstream icon using ImageMagick
|
||||
|
||||
```bash
|
||||
mogrify -modulate 90,100,140 mastodon/src/main/res/mipmap-*/ic_launcher*.png
|
||||
```
|
||||
|
||||
* Custom primary color: Hue of all `primary` colors in `colors.xml` is rotated, on basis of upstream Mastodon's [old branding](https://github.com/mastodon/mastodon-android/commit/74f03026cfcfcfd23237c38ff47d2b2a98a6f92a#diff-59134ec2a1cf3761f80b0ecccbbf8b9e433d9780d2f5c5d6ac3ac8cc254e808f)
|
||||
by `109.8°` (equivalent of `161%`, done by hand using
|
||||
[PineTools](https://pinetools.com/shift-hue-color))
|
||||
|
||||
## Building
|
||||
|
||||
As this app is using Java 17 features, you need JDK 17 or newer to build it. Other than that, everything is pretty standard. You can either import the project into Android Studio and build it from there, or run the following command in the project directory:
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
Mastodon is the largest decentralized social network on the internet. Instead of a single website, it’s a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what you’re into, you can meet passionate people posting about it on Mastodon!
|
||||
Mastodon je největší decentralizovanou sociální sítí na internetu. Místo jednné webové stránky je to síť pro miliony uživatelů v nezávislých komunitách, kteří mohou všichni vzájemně a bezproblémově komunikovat. Bez ohledu na to, co vás baví, můžete se setkat s vášnivými lidmi, kteří o tom vysílají na Mastodon!
|
||||
|
||||
Join a community and create your profile. Find and and follow fascinating folks and read their posts in an ad-free, chronological timeline. Express yourself with custom emoji, images, GIFs, videos, and audio in 500-character posts. Reply to threads and reblog posts from anyone to share great stuff. Find new accounts to follow and trending hashtags to expand your network.
|
||||
Připojte se ke komunitě a vytvořte svůj profil. Najděte a sledujte fascinující lidi a přečtěte si jejich příspěvky v bezreklamní a chronologické časové linii. Vyjádřete se pomocí vlastních emojí, obrázků, GIFů, videí a zvuku v 500-znakových příspěvcích. Odpovězte na vlákna a reblogujte příspěvky od kohokoliv, abyste mohli sdílet skvělé věci. Najděte nové účty pro sledování a populární hashtagy pro rozšíření vaší sítě.
|
||||
|
||||
Mastodon is built with a focus on privacy and safety. Decide whether your posts are shared with your followers, just the people you mention, or the whole world. Content warnings let you hide posts containing sensitive or triggering material until you're ready to engage with them. Each community has its own guidelines and moderators to keep its members safe, and robust blocking and reporting tools help prevent abuse.
|
||||
Mastodon je postaven se zaměřením na soukromí a bezpečnost. Rozhodněte, zda jsou vaše příspěvky sdíleny se svými sledujícími, jen s lidmi, které zmiňujete, nebo s celým světem. Upozornění na obsah vám umožní skrýt příspěvky obsahující citlivý nebo spouštěcí materiál, dokud se s nimi nezačnete zabývat. Každá komunita má vlastní pokyny a moderátory, aby udržela své členy v bezpečí, a robustní blokování a hlášení nástrojů pomáhá předcházet zneužití.
|
||||
|
||||
More features:
|
||||
Další funkce:
|
||||
|
||||
• Dark Mode: Read posts in light, dark, or true black mode
|
||||
• Polls: Ask followers for their opinion and tally the votes
|
||||
• Explore: Trending hashtags and accounts are a tap away
|
||||
• Notifications: Get notified about new follows, replies, and reblogs
|
||||
• Sharing: Post directly to Mastodon from any share sheet in any app
|
||||
• Cuteness: Our mascot is an adorable elephant, and you'll see them pop up from time to time
|
||||
• Tmavý režim: Čtěte příspěvky ve světlém, tmavém nebo zcela černém režimu
|
||||
• Ankety: Požádejte sledující o jejich názor a spojte se s jejich hlasováním
|
||||
• Průzkum: Trendové hashtagy a účty jsou pryč na jedno klepnutí
|
||||
• Upozornění: Dostávejte upozornění na nové sledování, odpovědi a reblogy
|
||||
• Sdílení: Odesílání přímo do Mastodonu z libovolného seznamu sdílení v jakékoliv aplikaci
|
||||
• Roztomilost: Naším maskotem je roztomilý slon, kterého čas od času uvidíte
|
||||
|
||||
Mastodon is a registered nonprofit and development is supported directly by your donations. There’s no advertising, no monetization, and no venture capital, and we plan to keep it that way.
|
||||
Mastodon je registrovaný neziskový projekt a vývojový program je podporován přímo vašimi dary. Neexistuje žádná reklama, žádná monetizace a žádný rizikový kapitál a my máme v plánu to udržet.
|
||||
@@ -1 +1 @@
|
||||
Decentralized social network
|
||||
Decentralizovaná sociální síť
|
||||
@@ -1,6 +1,6 @@
|
||||
Mastodon ist das größte dezentralisierte soziale Netzwerk im Internet. Statt einer einzigen Website ist es ein Netzwerk von Millionen von Benutzer*innen in unabhängigen Gemeinschaften, die alle miteinander interagieren können. Egal was dich interessiert, auf Mastodon kannst du interessierte Leute treffen, die darüber schreiben!
|
||||
|
||||
Tritt einer Gemeinschaft bei und erstelle dein Profil. Finde und folge faszinierenden Leuten und lies ihre Beiträge in einer werbefreien, chronologischen Zeitachse. Drücke dich mit benutzerdefinierten Emojis, Bilderns, GIFs, Videos und Audio in 500-Zeichen-Beiträgen aus. Antworte auf Threads und teile Beiträge von anderen, um großartige Sachen zu verbreiten. Finde neue Accounts zum Folgen und angesagte Hashtags, um dein Netzwerk zu erweitern.
|
||||
Tritt einer Gemeinschaft bei und erstelle dein Profil. Finde und folge faszinierenden Leuten, und lies ihre Beiträge in einer werbefreien, chronologischen Zeitachse. Drücke dich mit benutzerdefinierten Emojis, Bildern, GIFs, Videos und Audio in 500-Zeichen-Beiträgen aus. Antworte auf Threads und teile Beiträge von anderen, um großartige Sachen zu verbreiten. Finde neue Accounts zum Folgen und angesagte Hashtags, um dein Netzwerk zu erweitern.
|
||||
|
||||
Mastodon wurde mit einem Schwerpunkt auf Privatsphäre und Sicherheit gebaut. Entscheide, ob du deine Beiträge mit deinen Followern, nur mit den Menschen, die du erwähnst, oder mit der ganzen Welt teilen möchtest. Mit Inhaltswarnungen kannst du Beiträge mit sensiblem oder triggerndem Inhalt ausblenden, bis du bereit bist, dich damit auseinanderzusetzen. Jede Gemeinschaft hat ihre eigenen Regeln und Moderator*innen, um die Sicherheit ihrer Mitglieder zu gewährleisten, sowie robuste Sperr- und Meldewerkzeuge, um Missbrauch vorzubeugen.
|
||||
|
||||
@@ -8,9 +8,9 @@ Weitere Funktionen:
|
||||
|
||||
• Dunkler Modus: Beiträge im hellen, dunklen oder schwarzen Modus lesen
|
||||
• Umfragen: Frage deine Follower nach ihrer Meinung und zähle die Stimmen
|
||||
• Entdecken: Trending Hashtags und Accounts sind nur einen Fingertipp entfernt
|
||||
• Entdecken: trendende Hashtags und Profile sind nur einen Fingertipp entfernt
|
||||
• Benachrichtigungen: Erhalte Benachrichtigungen über neue Follower, Antworten und geteilte Beiträge
|
||||
• Teilen: Veröffentliche auf Mastodon aus jeder beliebigen anderen App
|
||||
• Niedlichkeit: Unser Maskottchen ist ein entzückender Elefant, und du wirst ihn von Zeit zu Zeit auftauchen sehen
|
||||
|
||||
Mastodon ist eine eingetragene gemeinnützige Organisation, und die Entwicklung wird direkt durch deine Spenden unterstützt. Es gibt keine Werbung, keine Monetisierung und kein Venture-Capital, und wir planen es so zu erhalten.
|
||||
Mastodon ist eine eingetragene gemeinnützige Organisation, und die Entwicklung wird direkt durch deine Spenden unterstützt. Es gibt keine Werbung, keine Monetisierung und kein Venture-Capital, und wir planen, das auch so beizubehalten.
|
||||
@@ -1,6 +1,6 @@
|
||||
Mastodon là mạng xã hội liên hợp lớn nhất trên internet. Thay vì một trang web duy nhất, nó là một mạng lưới hàng triệu người dùng trong các máy chủ độc lập, tất cả đều có thể tương tác với nhau một cách liền mạch. Bất kể bạn thích gì, bạn đều có thể gặp gỡ những người đăng tút về nó trên Mastodon!
|
||||
|
||||
Tham gia một máy chủ và tạo trang hồ sơ của bạn. Tìm, theo dõi những người thú vị và đọc tút của họ theo trình tự thời gian, không có quảng cáo. Thể hiện bản thân bằng emoji, hình ảnh, GIF, video và âm thanh trong tút tối đa 500 ký tự. Trả lời tút và đăng lại tút từ bất kỳ ai để chia sẻ những điều tuyệt vời. Tìm những người dùng mới để theo dõi và các hashtag xu hướng để mở rộng mạng lưới của bạn.
|
||||
Tham gia một máy chủ và tạo trang hồ sơ của bạn. Tìm, theo dõi những người thú vị và đọc tút của họ theo trình tự thời gian, không có quảng cáo. Thể hiện bản thân bằng emoji, hình ảnh, GIF, video và âm thanh trong tút tối đa 500 ký tự. Trả lời tút và đăng lại tút từ bất kỳ ai để chia sẻ những điều tuyệt vời. Tìm những người dùng mới để theo dõi và các hashtag nổi bật để mở rộng mạng lưới của bạn.
|
||||
|
||||
Mastodon được xây dựng tập trung vào sự riêng tư và an toàn. Quyết định xem tút của bạn được chia sẻ với những người theo dõi, chỉ những người bạn nhắc đến hay cả thế giới. Nội dung ẩn cho phép bạn ẩn các tút chứa nội dung nhạy cảm hoặc chơi chữ cho đến khi bạn sẵn sàng tương tác với chúng. Mỗi máy chủ có các nguyên tắc riêng và kiểm duyệt viên riêng để giữ an toàn cho các thành viên, song song với các công cụ chặn và báo cáo mạnh mẽ giúp ngăn chặn hành vi bậy.
|
||||
|
||||
@@ -8,7 +8,7 @@ Tính năng khác:
|
||||
|
||||
• Chế độ Tối: Đọc tút ở chế độ sáng, tối hoặc đen
|
||||
• Bình chọn: Hỏi cộng đồng về ý kiến của họ và đếm lượt bình chọn
|
||||
• Khám phá: Xem hashtag xu hướng và tài khoản chỉ bằng một nhấn
|
||||
• Khám phá: Xem hashtag nổi bật và tài khoản chỉ bằng một nhấn
|
||||
• Thông báo: Nhận thông báo về người theo dõi, lượt trả lời và đăng lại mới
|
||||
• Chia sẻ: Đăng trực tiếp lên Mastodon từ bất kỳ ứng dụng nào
|
||||
• Đáng yêu: Linh vật của chúng tôi là một chú voi ma mút và bạn sẽ thấy anh ấy thỉnh thoảng xuất hiện
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
Mastodon 是網際網路上最大的去中心化社交網路。 它是一個由能無縫互動的獨立社群中,數百萬使用者組成的網路,而非單一網站。 無論您對什麼事情感興趣,您都能在 Mastodon 上遇到充滿熱情的人們討論該話題。
|
||||
|
||||
加入社群並建立您的個人檔案。 尋找並追蹤迷人的夥伴,並在無廣告、按時間順序排列的時間軸上閱讀他們的貼文。 在 500 個字元的貼文中使用自訂表情符號、GIF、視訊與音訊來表達您自己。 回覆任何人的話題與轉發貼文以分享精彩內容。 尋找要追蹤的新帳號與熱門主題標籤來拓展您的網路。
|
||||
加入社群並建立您的個人檔案。 尋找並跟隨迷人的朋友們,並在無廣告、按時間順序排列的時間軸上閱讀他們的嘟文。 在 500 個字元的嘟文中使用自訂表情符號、GIF、視訊與音訊來表達您自己。 回覆任何人的話題與轉發貼文以分享精彩內容。 尋找要跟隨的新帳號與熱門主題標籤來拓展您的網路。
|
||||
|
||||
Mastodon 以隱私與安全為要。 決定您的貼文要與您的追蹤者分享、只與您提及的人們分享,又或是與全世界分享。 內容警告可讓您隱藏包含敏感或可能觸發強烈情緒反應的貼文,直到您準備好與它們進行互動。 每個社群都有它們自己的指導方針與管理原來確保其成員安全,強大的封鎖與回報工具有助於防止濫用。
|
||||
Mastodon 以隱私與安全為要。 決定您的嘟文要與您的跟隨者分享、只與您提及的人們分享,又或是與全世界分享。 內容警告可讓您隱藏包含敏感或可能觸發強烈情緒反應的嘟文,直到您準備好與它們進行互動。 每個社群都有它們自己的指導方針與管理原來確保其成員安全,強大的封鎖與回報工具有助於防止濫用。
|
||||
|
||||
更多功能:
|
||||
|
||||
• 深色模式:以淺色、深色或純黑色模式閱讀貼文
|
||||
• 投票:詢問追蹤的意見並計票
|
||||
• 投票:詢問跟隨者們的意見並計票
|
||||
• 探索:僅需輕點一下,即可看到熱門主題標籤與帳號
|
||||
• 通知:取得關於新追蹤、回覆與轉發的通知
|
||||
• 分享:從任何應用程式中的分享表中直接發表貼文到 Mastodon 中
|
||||
• 通知:取得關於新跟隨者們、回覆與轉發的通知
|
||||
• 分享:從任何應用程式中的分享表中直接發表嘟文到 Mastodon 中
|
||||
• 可愛:我們的吉祥物是一隻可愛的大象,您會不時看到牠出現
|
||||
|
||||
Mastodon 是一家註冊的非營利組織,您的捐款會直接支援開發工作。 沒有廣告、沒有貨幣化、沒有風險投資,我們計畫維持這種狀態。
|
||||
@@ -4,14 +4,18 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 31
|
||||
compileSdk 33
|
||||
defaultConfig {
|
||||
applicationId "org.joinmastodon.android.sk"
|
||||
minSdk 23
|
||||
targetSdk 31
|
||||
versionCode 20
|
||||
versionName '1.1.3+fork.20'
|
||||
targetSdk 33
|
||||
versionCode 30
|
||||
versionName "1.1.4+fork.30"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs "en", "ar-rSA", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES",
|
||||
"eu-rES", "fi-rFI", "fr-rFR", "gl-rES", "hr-rHR", "hy-rAM", "it-rIT", "iw-rIL",
|
||||
"ja-rJP", "kab", "ko-rKR", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ru-rRU",
|
||||
"sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -33,6 +37,9 @@ android {
|
||||
initWith release
|
||||
versionNameSuffix "-beta"
|
||||
}
|
||||
githubRelease{
|
||||
initWith release
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
@@ -46,6 +53,16 @@ android {
|
||||
appcenterPublicBeta{
|
||||
setRoot "src/appcenter"
|
||||
}
|
||||
githubRelease{
|
||||
setRoot "src/github"
|
||||
}
|
||||
debug {
|
||||
setRoot "src/github"
|
||||
}
|
||||
}
|
||||
lintOptions{
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
mastodon/src/github/AndroidManifest.xml
Normal file
21
mastodon/src/github/AndroidManifest.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.joinmastodon.android">
|
||||
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||
|
||||
<application>
|
||||
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$InstallerStatusReceiver" android:exported="false"/>-->
|
||||
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$AfterUpdateRestartReceiver" android:exported="true" android:enabled="false">-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </receiver>-->
|
||||
<provider
|
||||
android:authorities="${applicationId}.self_update_provider"
|
||||
android:name=".updater.SelfUpdateContentProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,356 @@
|
||||
package org.joinmastodon.android.updater;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
@Keep
|
||||
public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
private static final long CHECK_PERIOD=6*3600*1000L;
|
||||
private static final String TAG="GithubSelfUpdater";
|
||||
|
||||
private UpdateState state=UpdateState.NO_UPDATE;
|
||||
private UpdateInfo info;
|
||||
private long downloadID;
|
||||
private BroadcastReceiver downloadCompletionReceiver=new BroadcastReceiver(){
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
if(downloadID!=0 && intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)==downloadID){
|
||||
MastodonApp.context.unregisterReceiver(this);
|
||||
setState(UpdateState.DOWNLOADED);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public GithubSelfUpdaterImpl(){
|
||||
SharedPreferences prefs=getPrefs();
|
||||
int checkedByBuild=prefs.getInt("checkedByBuild", 0);
|
||||
if(prefs.contains("version") && checkedByBuild==BuildConfig.VERSION_CODE){
|
||||
info=new UpdateInfo();
|
||||
info.version=prefs.getString("version", null);
|
||||
info.size=prefs.getLong("apkSize", 0);
|
||||
downloadID=prefs.getLong("downloadID", 0);
|
||||
if(downloadID==0 || !getUpdateApkFile().exists()){
|
||||
state=UpdateState.UPDATE_AVAILABLE;
|
||||
}else{
|
||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||
state=dm.getUriForDownloadedFile(downloadID)==null ? UpdateState.DOWNLOADING : UpdateState.DOWNLOADED;
|
||||
if(state==UpdateState.DOWNLOADING){
|
||||
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
}
|
||||
}
|
||||
}else if(checkedByBuild!=BuildConfig.VERSION_CODE && checkedByBuild>0){
|
||||
// We are in a new version, running for the first time after update. Gotta clean things up.
|
||||
long id=getPrefs().getLong("downloadID", 0);
|
||||
if(id!=0){
|
||||
MastodonApp.context.getSystemService(DownloadManager.class).remove(id);
|
||||
}
|
||||
getUpdateApkFile().delete();
|
||||
getPrefs().edit()
|
||||
.remove("apkSize")
|
||||
.remove("version")
|
||||
.remove("apkURL")
|
||||
.remove("checkedByBuild")
|
||||
.remove("downloadID")
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
|
||||
private SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("githubUpdater", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeCheckForUpdates(){
|
||||
if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
|
||||
return;
|
||||
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", CHECK_PERIOD);
|
||||
if(timeSinceLastCheck>=CHECK_PERIOD){
|
||||
setState(UpdateState.CHECKING);
|
||||
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkForUpdates() {
|
||||
setState(UpdateState.CHECKING);
|
||||
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
|
||||
}
|
||||
|
||||
private void actuallyCheckForUpdates(){
|
||||
Request req=new Request.Builder()
|
||||
.url("https://api.github.com/repos/sk22/mastodon-android-fork/releases/latest")
|
||||
.build();
|
||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||
try(Response resp=call.execute()){
|
||||
JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
|
||||
String tag=obj.get("tag_name").getAsString();
|
||||
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
||||
Matcher matcher=pattern.matcher(tag);
|
||||
if(!matcher.find()){
|
||||
Log.w(TAG, "actuallyCheckForUpdates: release tag has wrong format: "+tag);
|
||||
return;
|
||||
}
|
||||
int newMajor=Integer.parseInt(matcher.group(1)),
|
||||
newMinor=Integer.parseInt(matcher.group(2)),
|
||||
newRevision=Integer.parseInt(matcher.group(3)),
|
||||
newForkNumber=Integer.parseInt(matcher.group(4));
|
||||
matcher=pattern.matcher(BuildConfig.VERSION_NAME);
|
||||
String[] currentParts=BuildConfig.VERSION_NAME.split("[.+]");
|
||||
if(!matcher.find()){
|
||||
Log.w(TAG, "actuallyCheckForUpdates: current version has wrong format: "+BuildConfig.VERSION_NAME);
|
||||
return;
|
||||
}
|
||||
int curMajor=Integer.parseInt(matcher.group(1)),
|
||||
curMinor=Integer.parseInt(matcher.group(2)),
|
||||
curRevision=Integer.parseInt(matcher.group(3)),
|
||||
curForkNumber=Integer.parseInt(matcher.group(4));
|
||||
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
|
||||
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
|
||||
if(newVersion>curVersion || newForkNumber>curForkNumber || BuildConfig.DEBUG){
|
||||
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
|
||||
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
||||
for(JsonElement el:obj.getAsJsonArray("assets")){
|
||||
JsonObject asset=el.getAsJsonObject();
|
||||
if("application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
|
||||
long size=asset.get("size").getAsLong();
|
||||
String url=asset.get("browser_download_url").getAsString();
|
||||
|
||||
UpdateInfo info=new UpdateInfo();
|
||||
info.size=size;
|
||||
info.version=version;
|
||||
this.info=info;
|
||||
|
||||
getPrefs().edit()
|
||||
.putLong("apkSize", size)
|
||||
.putString("version", version)
|
||||
.putString("apkURL", url)
|
||||
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
||||
.remove("downloadID")
|
||||
.apply();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
|
||||
}catch(Exception x){
|
||||
Log.w(TAG, "actuallyCheckForUpdates", x);
|
||||
}finally{
|
||||
setState(info==null ? UpdateState.NO_UPDATE : UpdateState.UPDATE_AVAILABLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setState(UpdateState state){
|
||||
this.state=state;
|
||||
E.post(new SelfUpdateStateChangedEvent(state));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateState getState(){
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateInfo getUpdateInfo(){
|
||||
return info;
|
||||
}
|
||||
|
||||
public File getUpdateApkFile(){
|
||||
return new File(MastodonApp.context.getExternalCacheDir(), "update.apk");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downloadUpdate(){
|
||||
if(state==UpdateState.DOWNLOADING)
|
||||
throw new IllegalStateException();
|
||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
downloadID=dm.enqueue(
|
||||
new DownloadManager.Request(Uri.parse(getPrefs().getString("apkURL", null)))
|
||||
.setDestinationUri(Uri.fromFile(getUpdateApkFile()))
|
||||
);
|
||||
getPrefs().edit().putLong("downloadID", downloadID).apply();
|
||||
setState(UpdateState.DOWNLOADING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installUpdate(Activity activity){
|
||||
if(state!=UpdateState.DOWNLOADED)
|
||||
throw new IllegalStateException();
|
||||
Uri uri;
|
||||
Intent intent=new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||
uri=new Uri.Builder().scheme("content").authority(activity.getPackageName()+".self_update_provider").path("update.apk").build();
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}else{
|
||||
uri=Uri.fromFile(getUpdateApkFile());
|
||||
}
|
||||
intent.setDataAndType(uri, "application/vnd.android.package-archive");
|
||||
activity.startActivity(intent);
|
||||
|
||||
// TODO figure out how to restart the app when updating via this new API
|
||||
/*
|
||||
PackageInstaller installer=activity.getPackageManager().getPackageInstaller();
|
||||
try{
|
||||
final int sid=installer.createSession(new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
|
||||
installer.registerSessionCallback(new PackageInstaller.SessionCallback(){
|
||||
@Override
|
||||
public void onCreated(int i){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBadgingChanged(int i){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveChanged(int i, boolean b){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(int id, float progress){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished(int id, boolean success){
|
||||
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
});
|
||||
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
|
||||
PackageInstaller.Session session=installer.openSession(sid);
|
||||
try(OutputStream out=session.openWrite("mastodon.apk", 0, info.size); InputStream in=new FileInputStream(getUpdateApkFile())){
|
||||
byte[] buffer=new byte[16384];
|
||||
int read;
|
||||
while((read=in.read(buffer))>0){
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
// PendingIntent intent=PendingIntent.getBroadcast(activity, 1, new Intent(activity, InstallerStatusReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
PendingIntent intent=PendingIntent.getActivity(activity, 1, new Intent(activity, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
session.commit(intent.getIntentSender());
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "installUpdate", x);
|
||||
Toast.makeText(activity, x.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDownloadProgress(){
|
||||
if(state!=UpdateState.DOWNLOADING)
|
||||
throw new IllegalStateException();
|
||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||
try(Cursor cursor=dm.query(new DownloadManager.Query().setFilterById(downloadID))){
|
||||
if(cursor.moveToFirst()){
|
||||
long loaded=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
|
||||
long total=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
|
||||
// Log.d(TAG, "getDownloadProgress: "+loaded+" of "+total);
|
||||
return total>0 ? (float)loaded/total : 0f;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelDownload(){
|
||||
if(state!=UpdateState.DOWNLOADING)
|
||||
throw new IllegalStateException();
|
||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||
dm.remove(downloadID);
|
||||
downloadID=0;
|
||||
getPrefs().edit().remove("downloadID").apply();
|
||||
setState(UpdateState.UPDATE_AVAILABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleIntentFromInstaller(Intent intent, Activity activity){
|
||||
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
|
||||
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
|
||||
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
activity.startActivity(confirmIntent);
|
||||
}else if(status!=PackageInstaller.STATUS_SUCCESS){
|
||||
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
Toast.makeText(activity, activity.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
/*public static class InstallerStatusReceiver extends BroadcastReceiver{
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
|
||||
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
|
||||
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
context.startActivity(confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}else if(status!=PackageInstaller.STATUS_SUCCESS){
|
||||
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
Toast.makeText(context, context.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class AfterUpdateRestartReceiver extends BroadcastReceiver{
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
if(Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())){
|
||||
context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
Toast.makeText(context, R.string.update_installed, Toast.LENGTH_SHORT).show();
|
||||
Intent restartIntent=new Intent(context, MainActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setPackage(context.getPackageName());
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.P){
|
||||
context.startActivity(restartIntent);
|
||||
}else{
|
||||
// Bypass activity starting restrictions by starting it from a notification
|
||||
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
||||
NotificationChannel chan=new NotificationChannel("selfUpdateRestart", context.getString(R.string.update_installed), NotificationManager.IMPORTANCE_HIGH);
|
||||
nm.createNotificationChannel(chan);
|
||||
Notification n=new Notification.Builder(context, "selfUpdateRestart")
|
||||
.setContentTitle(context.getString(R.string.update_installed))
|
||||
.setContentIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||
.setFullScreenIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE), true)
|
||||
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||
.build();
|
||||
nm.notify(1, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.joinmastodon.android.updater;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class SelfUpdateContentProvider extends ContentProvider{
|
||||
@Override
|
||||
public boolean onCreate(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri){
|
||||
if(isCorrectUri(uri))
|
||||
return "application/vnd.android.package-archive";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException{
|
||||
if(isCorrectUri(uri)){
|
||||
return ParcelFileDescriptor.open(((GithubSelfUpdaterImpl)GithubSelfUpdater.getInstance()).getUpdateApkFile(), ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
private boolean isCorrectUri(Uri uri){
|
||||
return "/update.apk".equals(uri.getPath());
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
||||
|
||||
@@ -15,6 +16,7 @@
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Application;
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -17,6 +21,7 @@ import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -59,6 +64,8 @@ public class MainActivity extends FragmentStackActivity{
|
||||
showFragmentForNotification(notification, session.getID());
|
||||
}else if(intent.getBooleanExtra("compose", false)){
|
||||
showCompose();
|
||||
}else{
|
||||
maybeRequestNotificationsPermission();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +75,8 @@ public class MainActivity extends FragmentStackActivity{
|
||||
try{
|
||||
Class.forName("org.joinmastodon.android.AppCenterWrapper").getMethod("init", Application.class).invoke(null, getApplication());
|
||||
}catch(ClassNotFoundException|NoSuchMethodException|IllegalAccessException|InvocationTargetException ignore){}
|
||||
}else if(GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater.getInstance().maybeCheckForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +105,9 @@ public class MainActivity extends FragmentStackActivity{
|
||||
}
|
||||
}else if(intent.getBooleanExtra("compose", false)){
|
||||
showCompose();
|
||||
}
|
||||
}/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this);
|
||||
}*/
|
||||
}
|
||||
|
||||
private void showFragmentForNotification(Notification notification, String accountID){
|
||||
@@ -131,4 +142,10 @@ public class MainActivity extends FragmentStackActivity{
|
||||
compose.setArguments(composeArgs);
|
||||
showFragment(compose);
|
||||
}
|
||||
|
||||
private void maybeRequestNotificationsPermission(){
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)!=PackageManager.PERMISSION_GRANTED){
|
||||
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class CacheController{
|
||||
.exec(accountID);
|
||||
}catch(SQLiteException x){
|
||||
Log.w(TAG, x);
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500)));
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500, x)));
|
||||
}finally{
|
||||
closeDelayed();
|
||||
}
|
||||
@@ -184,7 +184,7 @@ public class CacheController{
|
||||
.exec(accountID);
|
||||
}catch(SQLiteException x){
|
||||
Log.w(TAG, x);
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500)));
|
||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500, x)));
|
||||
}finally{
|
||||
closeDelayed();
|
||||
}
|
||||
|
||||
@@ -96,11 +96,11 @@ public class MastodonAPIController{
|
||||
if(call.isCanceled())
|
||||
return;
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" failed: "+e);
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" failed", e);
|
||||
synchronized(req){
|
||||
req.okhttpCall=null;
|
||||
}
|
||||
req.onError(e.getLocalizedMessage(), 0);
|
||||
req.onError(e.getLocalizedMessage(), 0, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,7 +133,7 @@ public class MastodonAPIController{
|
||||
}catch(JsonIOException|JsonSyntaxException x){
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
|
||||
req.onError(x.getLocalizedMessage(), response.code());
|
||||
req.onError(x.getLocalizedMessage(), response.code(), x);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public class MastodonAPIController{
|
||||
}catch(IOException x){
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error post-processing or validating response", x);
|
||||
req.onError(x.getLocalizedMessage(), response.code());
|
||||
req.onError(x.getLocalizedMessage(), response.code(), x);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public class MastodonAPIController{
|
||||
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error);
|
||||
if(error.has("details")){
|
||||
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code());
|
||||
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code(), null);
|
||||
HashMap<String, List<MastodonDetailedErrorResponse.FieldError>> details=new HashMap<>();
|
||||
JsonObject errorDetails=error.getAsJsonObject("details");
|
||||
for(String key:errorDetails.keySet()){
|
||||
@@ -172,12 +172,12 @@ public class MastodonAPIController{
|
||||
err.detailedErrors=details;
|
||||
req.onError(err);
|
||||
}else{
|
||||
req.onError(error.get("error").getAsString(), response.code());
|
||||
req.onError(error.get("error").getAsString(), response.code(), null);
|
||||
}
|
||||
}catch(JsonIOException|JsonSyntaxException x){
|
||||
req.onError(response.code()+" "+response.message(), response.code());
|
||||
req.onError(response.code()+" "+response.message(), response.code(), x);
|
||||
}catch(Exception x){
|
||||
req.onError("Error parsing an API error", response.code());
|
||||
req.onError("Error parsing an API error", response.code(), x);
|
||||
}
|
||||
}
|
||||
}catch(Exception x){
|
||||
@@ -189,7 +189,7 @@ public class MastodonAPIController{
|
||||
}catch(Exception x){
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] error creating and sending http request", x);
|
||||
req.onError(x.getLocalizedMessage(), 0);
|
||||
req.onError(x.getLocalizedMessage(), 0, x);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
@@ -197,4 +197,8 @@ public class MastodonAPIController{
|
||||
public static void runInBackground(Runnable action){
|
||||
thread.postRunnable(action, 0);
|
||||
}
|
||||
|
||||
public static OkHttpClient getHttpClient(){
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
account.getApiController().submitRequest(this);
|
||||
}catch(Exception x){
|
||||
Log.e(TAG, "exec: this shouldn't happen, but it still did", x);
|
||||
invokeErrorCallback(new MastodonErrorResponse(x.getLocalizedMessage(), -1));
|
||||
invokeErrorCallback(new MastodonErrorResponse(x.getLocalizedMessage(), -1, x));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -194,8 +194,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
invokeErrorCallback(err);
|
||||
}
|
||||
|
||||
void onError(String msg, int httpStatus){
|
||||
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus));
|
||||
void onError(String msg, int httpStatus, Throwable exception){
|
||||
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus, exception));
|
||||
}
|
||||
|
||||
void onSuccess(T resp){
|
||||
|
||||
@@ -7,8 +7,8 @@ import java.util.Map;
|
||||
public class MastodonDetailedErrorResponse extends MastodonErrorResponse{
|
||||
public Map<String, List<FieldError>> detailedErrors;
|
||||
|
||||
public MastodonDetailedErrorResponse(String error, int httpStatus){
|
||||
super(error, httpStatus);
|
||||
public MastodonDetailedErrorResponse(String error, int httpStatus, Throwable exception){
|
||||
super(error, httpStatus, exception);
|
||||
}
|
||||
|
||||
public static class FieldError{
|
||||
|
||||
@@ -12,10 +12,12 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
public class MastodonErrorResponse extends ErrorResponse{
|
||||
public final String error;
|
||||
public final int httpStatus;
|
||||
public final Throwable underlyingException;
|
||||
|
||||
public MastodonErrorResponse(String error, int httpStatus){
|
||||
public MastodonErrorResponse(String error, int httpStatus, Throwable exception){
|
||||
this.error=error;
|
||||
this.httpStatus=httpStatus;
|
||||
this.underlyingException=exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
addQueryParameter("exclude_replies", "true");
|
||||
addQueryParameter("exclude_reblogs", "true");
|
||||
}
|
||||
case OWN_POSTS_AND_REPLIES -> addQueryParameter("exclude_reblogs", "true");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
INCLUDE_REPLIES,
|
||||
PINNED,
|
||||
MEDIA,
|
||||
NO_REBLOGS
|
||||
NO_REBLOGS,
|
||||
OWN_POSTS_AND_REPLIES
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
|
||||
public class GetPreferences extends MastodonAPIRequest<Preferences> {
|
||||
public GetPreferences(){
|
||||
super(HttpMethod.GET, "/preferences", Preferences.class);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public String clientName="Mastodon for Android Fork";
|
||||
public String clientName="Mastodos";
|
||||
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
||||
public String scopes=AccountSessionManager.SCOPE;
|
||||
public String website="https://github.com/sk22/mastodon-android-fork";
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class EditStatus extends MastodonAPIRequest<Status>{
|
||||
public EditStatus(CreateStatus.Request req, String id){
|
||||
super(HttpMethod.PUT, "/statuses/"+id, Status.class);
|
||||
setRequestBody(req);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class GetAttachmentByID extends MastodonAPIRequest<Attachment>{
|
||||
public GetAttachmentByID(String id){
|
||||
super(HttpMethod.GET, "/media/"+id, Attachment.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAndPostprocessResponse(Attachment respObj, Response httpResponse) throws IOException{
|
||||
if(httpResponse.code()==206)
|
||||
respObj.url="";
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class GetStatusEditHistory extends MastodonAPIRequest<List<Status>>{
|
||||
public GetStatusEditHistory(String id){
|
||||
super(HttpMethod.GET, "/statuses/"+id+"/history", new TypeToken<>(){});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAndPostprocessResponse(List<Status> respObj, Response httpResponse) throws IOException{
|
||||
int i=0;
|
||||
for(Status s:respObj){
|
||||
s.uri="";
|
||||
s.id="fakeID"+i;
|
||||
s.visibility=StatusPrivacy.PUBLIC;
|
||||
s.mentions=Collections.emptyList();
|
||||
s.tags=Collections.emptyList();
|
||||
i++;
|
||||
}
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
|
||||
public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{
|
||||
public GetStatusSourceText(String id){
|
||||
super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class);
|
||||
}
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public static class Response extends BaseModel{
|
||||
public String id;
|
||||
public String text;
|
||||
public String spoilerText;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import java.io.IOException;
|
||||
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class UploadAttachment extends MastodonAPIRequest<Attachment>{
|
||||
private Uri uri;
|
||||
@@ -40,6 +41,18 @@ public class UploadAttachment extends MastodonAPIRequest<Attachment>{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPrefix(){
|
||||
return "/api/v2";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAndPostprocessResponse(Attachment respObj, Response httpResponse) throws IOException{
|
||||
if(respObj.url==null)
|
||||
respObj.url="";
|
||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestBody getRequestBody() throws IOException{
|
||||
MultipartBody.Builder builder=new MultipartBody.Builder()
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
|
||||
public class SelfUpdateStateChangedEvent{
|
||||
public final GithubSelfUpdater.UpdateState state;
|
||||
|
||||
public SelfUpdateStateChangedEvent(GithubSelfUpdater.UpdateState state){
|
||||
this.state=state;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class StatusCountersUpdatedEvent{
|
||||
public String id;
|
||||
public int favorites, reblogs, replies;
|
||||
public long favorites, reblogs, replies;
|
||||
public boolean favorited, reblogged, pinned;
|
||||
|
||||
public StatusCountersUpdatedEvent(Status s){
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class StatusUpdatedEvent{
|
||||
public Status status;
|
||||
|
||||
public StatusUpdatedEvent(Status status){
|
||||
this.status=status;
|
||||
}
|
||||
}
|
||||
@@ -457,7 +457,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
status.spoilerRevealed=true;
|
||||
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||
if(text!=null)
|
||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()+getMainAdapterOffset());
|
||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
||||
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
||||
if(header!=null)
|
||||
header.rebind();
|
||||
@@ -579,6 +579,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
return true;
|
||||
}
|
||||
|
||||
public ArrayList<StatusDisplayItem> getDisplayItems(){
|
||||
return displayItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0 && wantsOverlaySystemNavigation()){
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,13 +2,9 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -18,20 +14,14 @@ import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.PushNotificationReceiver;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||
import org.joinmastodon.android.fragments.discover.SearchFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.TabBar;
|
||||
import org.parceler.Parcels;
|
||||
@@ -41,15 +31,12 @@ import java.util.ArrayList;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.LoaderFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
||||
@@ -141,7 +128,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
}
|
||||
});
|
||||
}
|
||||
}else{
|
||||
}
|
||||
|
||||
return content;
|
||||
|
||||
@@ -23,9 +23,11 @@ import android.widget.Toolbar;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
@@ -33,6 +35,7 @@ import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -101,6 +104,11 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
E.register(this);
|
||||
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,7 +142,6 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusCreated(StatusCreatedEvent ev){
|
||||
prependItems(Collections.singletonList(ev.status), true);
|
||||
}
|
||||
@@ -397,4 +404,22 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
scrollToTop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView(){
|
||||
super.onDestroyView();
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
E.unregister(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
|
||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING)
|
||||
getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_24_badged);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
||||
updateUpdateState(ev.state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
@@ -16,24 +12,21 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
@@ -84,9 +77,11 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
if(titleItem!=null)
|
||||
items.add(0, titleItem);
|
||||
return items;
|
||||
}else{
|
||||
}else if(titleItem!=null){
|
||||
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, n.account);
|
||||
return Arrays.asList(titleItem, card);
|
||||
}else{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,91 +155,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private int bgColor=UiUtils.getThemeColor(getActivity(), android.R.attr.colorBackground);
|
||||
private int borderColor=UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted);
|
||||
private RectF rect=new RectF();
|
||||
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
int pos=0;
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
pos=holder.getAbsoluteAdapterPosition();
|
||||
boolean inset=(holder instanceof StatusDisplayItem.Holder<?> sdi) && sdi.getItem().inset;
|
||||
if(inset){
|
||||
if(rect.isEmpty()){
|
||||
rect.set(child.getX(), i==0 && pos>0 && displayItems.get(pos-1).inset ? V.dp(-10) : child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight());
|
||||
}else{
|
||||
rect.bottom=Math.max(rect.bottom, child.getY()+child.getHeight());
|
||||
}
|
||||
}else if(!rect.isEmpty()){
|
||||
drawInsetBackground(c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
if(!rect.isEmpty()){
|
||||
if(pos<displayItems.size()-1 && displayItems.get(pos+1).inset){
|
||||
rect.bottom=parent.getHeight()+V.dp(10);
|
||||
}
|
||||
drawInsetBackground(c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawInsetBackground(Canvas c){
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(bgColor);
|
||||
rect.left=V.dp(12);
|
||||
rect.right=list.getWidth()-V.dp(12);
|
||||
rect.inset(V.dp(4), V.dp(4));
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(V.dp(1));
|
||||
paint.setColor(borderColor);
|
||||
rect.inset(paint.getStrokeWidth()/2f, paint.getStrokeWidth()/2f);
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), 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<?> sdi){
|
||||
boolean inset=sdi.getItem().inset;
|
||||
int pos=holder.getAbsoluteAdapterPosition();
|
||||
if(inset){
|
||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
int pad;
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||
pad=V.dp(16);
|
||||
else
|
||||
pad=V.dp(12);
|
||||
boolean insetLeft=true, insetRight=true;
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> img){
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=img.getItem().tiledLayout;
|
||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=img.getItem().thisTile;
|
||||
// only inset those items that are on the edges of the layout
|
||||
insetLeft=tile.startCol==0;
|
||||
insetRight=tile.startCol+tile.colSpan==layout.columnSizes.length;
|
||||
// inset all items in the bottom row
|
||||
if(tile.startRow+tile.rowSpan==layout.rowSizes.length)
|
||||
bottomSiblingInset=false;
|
||||
}
|
||||
if(insetLeft)
|
||||
outRect.left=pad;
|
||||
if(insetRight)
|
||||
outRect.right=pad;
|
||||
if(!topSiblingInset)
|
||||
outRect.top=pad;
|
||||
if(!bottomSiblingInset)
|
||||
outRect.bottom=pad;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||
}
|
||||
|
||||
private Notification getNotificationByID(String id){
|
||||
@@ -268,4 +179,5 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -414,16 +414,23 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
private void bindHeaderView(){
|
||||
setTitle(account.displayName);
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, account.statusesCount, account.statusesCount));
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
|
||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
||||
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
||||
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
||||
name.setText(ssb);
|
||||
setTitle(ssb);
|
||||
|
||||
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||
|
||||
if(account.locked){
|
||||
ssb=new SpannableStringBuilder("@");
|
||||
ssb.append(account.acct);
|
||||
if(isSelf){
|
||||
ssb.append('@');
|
||||
ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
|
||||
}
|
||||
ssb.append(" ");
|
||||
Drawable lock=username.getResources().getDrawable(R.drawable.ic_fluent_lock_closed_20_filled, getActivity().getTheme()).mutate();
|
||||
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
||||
@@ -431,7 +438,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BOTTOM), 0);
|
||||
username.setText(ssb);
|
||||
}else{
|
||||
username.setText('@'+account.acct);
|
||||
// noinspection SetTextI18n
|
||||
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
|
||||
}
|
||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
if(TextUtils.isEmpty(parsedBio)){
|
||||
@@ -443,9 +451,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, Math.min(999, account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, Math.min(999, account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, Math.min(999, account.statusesCount)));
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, account.statusesCount)));
|
||||
|
||||
UiUtils.loadCustomEmojiInTextView(name);
|
||||
UiUtils.loadCustomEmojiInTextView(bio);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
@@ -14,15 +15,22 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
@@ -31,11 +39,13 @@ import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
@@ -47,7 +57,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
@@ -63,6 +72,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
private PushSubscription pushSubscription;
|
||||
|
||||
private ImageView themeTransitionWindowView;
|
||||
private TextItem checkForUpdateItem;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -73,6 +83,14 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
accountID=getArguments().getString("account");
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
GithubSelfUpdater.UpdateState state=updater.getState();
|
||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING){
|
||||
items.add(new UpdateItem());
|
||||
}
|
||||
}
|
||||
|
||||
items.add(new HeaderItem(R.string.settings_theme));
|
||||
items.add(themeItem=new ThemeItem());
|
||||
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
|
||||
@@ -102,6 +120,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
|
||||
|
||||
items.add(new RedHeaderItem(R.string.settings_spicy));
|
||||
if (GithubSelfUpdater.needSelfUpdating()) {
|
||||
checkForUpdateItem = new TextItem(R.string.check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||
items.add(checkForUpdateItem);
|
||||
}
|
||||
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
||||
|
||||
@@ -131,7 +153,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
// Add 32dp gaps between sections
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if((holder instanceof HeaderViewHolder || holder instanceof FooterViewHolder) && holder.getAbsoluteAdapterPosition()>0)
|
||||
if((holder instanceof HeaderViewHolder || holder instanceof FooterViewHolder) && holder.getAbsoluteAdapterPosition()>1)
|
||||
outRect.top=V.dp(32);
|
||||
}
|
||||
});
|
||||
@@ -155,6 +177,20 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if(GithubSelfUpdater.needSelfUpdating())
|
||||
E.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView(){
|
||||
super.onDestroyView();
|
||||
if(GithubSelfUpdater.needSelfUpdating())
|
||||
E.unregister(this);
|
||||
}
|
||||
|
||||
private void onThemePreferenceClick(GlobalUserPreferences.ThemePreference theme){
|
||||
GlobalUserPreferences.theme=theme;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -294,6 +330,30 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
||||
checkForUpdateItem.loading = ev.state == GithubSelfUpdater.UpdateState.CHECKING;
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(checkForUpdateItem)) instanceof TextViewHolder tvh) tvh.rebind();
|
||||
|
||||
UpdateItem updateItem = null;
|
||||
if(items.get(0) instanceof UpdateItem item0) {
|
||||
updateItem = item0;
|
||||
} else if (ev.state != GithubSelfUpdater.UpdateState.CHECKING
|
||||
&& ev.state != GithubSelfUpdater.UpdateState.NO_UPDATE) {
|
||||
updateItem = new UpdateItem();
|
||||
items.add(0, updateItem);
|
||||
list.setAdapter(new SettingsAdapter());
|
||||
}
|
||||
|
||||
if(updateItem != null && list.findViewHolderForAdapterPosition(0) instanceof UpdateViewHolder uvh){
|
||||
uvh.bind(updateItem);
|
||||
}
|
||||
|
||||
if (ev.state == GithubSelfUpdater.UpdateState.NO_UPDATE) {
|
||||
Toast.makeText(getActivity(), R.string.no_update_available, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class Item{
|
||||
public abstract int getViewType();
|
||||
}
|
||||
@@ -358,10 +418,16 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
private class TextItem extends Item{
|
||||
private String text;
|
||||
private Runnable onClick;
|
||||
private boolean loading;
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick){
|
||||
public TextItem(@StringRes int text, Runnable onClick) {
|
||||
this(text, onClick, false);
|
||||
}
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick, boolean loading){
|
||||
this.text=getString(text);
|
||||
this.onClick=onClick;
|
||||
this.loading=loading;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -395,6 +461,14 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateItem extends Item{
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
||||
private class SettingsAdapter extends RecyclerView.Adapter<BindableViewHolder<Item>>{
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -408,6 +482,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
case 4 -> new TextViewHolder();
|
||||
case 5 -> new HeaderViewHolder(true);
|
||||
case 6 -> new FooterViewHolder();
|
||||
case 7 -> new UpdateViewHolder();
|
||||
default -> throw new IllegalStateException("Unexpected value: "+viewType);
|
||||
};
|
||||
}
|
||||
@@ -581,14 +656,18 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
|
||||
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
||||
private final TextView text;
|
||||
private final ProgressBar progress;
|
||||
|
||||
public TextViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_text, list);
|
||||
text=(TextView) itemView;
|
||||
text = itemView.findViewById(R.id.text);
|
||||
progress = itemView.findViewById(R.id.progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(TextItem item){
|
||||
text.setText(item.text);
|
||||
progress.animate().alpha(item.loading ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -609,4 +688,75 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
text.setText(item.text);
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
||||
|
||||
private final TextView text;
|
||||
private final Button button;
|
||||
private final ImageButton cancelBtn;
|
||||
private final ProgressBar progress;
|
||||
|
||||
private ObjectAnimator rotationAnimator;
|
||||
private Runnable progressUpdater=this::updateProgress;
|
||||
|
||||
public UpdateViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_update, list);
|
||||
text=findViewById(R.id.text);
|
||||
button=findViewById(R.id.button);
|
||||
cancelBtn=findViewById(R.id.cancel_btn);
|
||||
progress=findViewById(R.id.progress);
|
||||
button.setOnClickListener(v->{
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
switch(updater.getState()){
|
||||
case UPDATE_AVAILABLE -> updater.downloadUpdate();
|
||||
case DOWNLOADED -> updater.installUpdate(getActivity());
|
||||
}
|
||||
});
|
||||
cancelBtn.setOnClickListener(v->GithubSelfUpdater.getInstance().cancelDownload());
|
||||
rotationAnimator=ObjectAnimator.ofFloat(progress, View.ROTATION, 0f, 360f);
|
||||
rotationAnimator.setInterpolator(new LinearInterpolator());
|
||||
rotationAnimator.setDuration(1500);
|
||||
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(UpdateItem item){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
GithubSelfUpdater.UpdateState state=updater.getState();
|
||||
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
||||
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
||||
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
||||
text.setText(getString(R.string.update_available, info.version));
|
||||
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
||||
}else{
|
||||
text.setText(getString(R.string.update_ready, info.version));
|
||||
button.setText(R.string.install_update);
|
||||
}
|
||||
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
||||
rotationAnimator.start();
|
||||
button.setVisibility(View.INVISIBLE);
|
||||
cancelBtn.setVisibility(View.VISIBLE);
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
updateProgress();
|
||||
}else{
|
||||
rotationAnimator.cancel();
|
||||
button.setVisibility(View.VISIBLE);
|
||||
cancelBtn.setVisibility(View.GONE);
|
||||
progress.setVisibility(View.GONE);
|
||||
progress.removeCallbacks(progressUpdater);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProgress(){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
if(updater.getState()!=GithubSelfUpdater.UpdateState.DOWNLOADING)
|
||||
return;
|
||||
int value=Math.round(progress.getMax()*updater.getDownloadProgress());
|
||||
if(Build.VERSION.SDK_INT>=24)
|
||||
progress.setProgress(value, true);
|
||||
else
|
||||
progress.setProgress(value);
|
||||
progress.postDelayed(progressUpdater, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
private String id;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
id=getArguments().getString("id");
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.edit_history);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetStatusEditHistory(id)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false);
|
||||
int idx=data.indexOf(s);
|
||||
if(idx>=0){
|
||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||
String action="";
|
||||
if(idx==data.size()-1){
|
||||
action=getString(R.string.edit_original_post);
|
||||
}else{
|
||||
enum StatusEditChangeType{
|
||||
TEXT_CHANGED,
|
||||
SPOILER_ADDED,
|
||||
SPOILER_REMOVED,
|
||||
SPOILER_CHANGED,
|
||||
POLL_ADDED,
|
||||
POLL_REMOVED,
|
||||
POLL_CHANGED,
|
||||
MEDIA_ADDED,
|
||||
MEDIA_REMOVED,
|
||||
MEDIA_REORDERED,
|
||||
MARKED_SENSITIVE,
|
||||
MARKED_NOT_SENSITIVE
|
||||
}
|
||||
EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class);
|
||||
Status prev=data.get(idx+1);
|
||||
|
||||
if(!Objects.equals(s.content, prev.content)){
|
||||
changes.add(StatusEditChangeType.TEXT_CHANGED);
|
||||
}
|
||||
if(!Objects.equals(s.spoilerText, prev.spoilerText)){
|
||||
if(s.spoilerText==null){
|
||||
changes.add(StatusEditChangeType.SPOILER_REMOVED);
|
||||
}else if(prev.spoilerText==null){
|
||||
changes.add(StatusEditChangeType.SPOILER_ADDED);
|
||||
}else{
|
||||
changes.add(StatusEditChangeType.SPOILER_CHANGED);
|
||||
}
|
||||
}
|
||||
if(s.poll!=null || prev.poll!=null){
|
||||
if(s.poll==null){
|
||||
changes.add(StatusEditChangeType.POLL_REMOVED);
|
||||
}else if(prev.poll==null){
|
||||
changes.add(StatusEditChangeType.POLL_ADDED);
|
||||
}else if(!s.poll.id.equals(prev.poll.id)){
|
||||
changes.add(StatusEditChangeType.POLL_CHANGED);
|
||||
}
|
||||
}
|
||||
List<String> newAttachmentIDs=s.mediaAttachments.stream().map(att->att.id).collect(Collectors.toList());
|
||||
List<String> prevAttachmentIDs=s.mediaAttachments.stream().map(att->att.id).collect(Collectors.toList());
|
||||
boolean addedOrRemoved=false;
|
||||
if(!newAttachmentIDs.containsAll(prevAttachmentIDs)){
|
||||
changes.add(StatusEditChangeType.MEDIA_REMOVED);
|
||||
addedOrRemoved=true;
|
||||
}
|
||||
if(!prevAttachmentIDs.containsAll(newAttachmentIDs)){
|
||||
changes.add(StatusEditChangeType.MEDIA_ADDED);
|
||||
addedOrRemoved=true;
|
||||
}
|
||||
if(!addedOrRemoved && !newAttachmentIDs.equals(prevAttachmentIDs)){
|
||||
changes.add(StatusEditChangeType.MEDIA_REORDERED);
|
||||
}
|
||||
if(s.sensitive && !prev.sensitive){
|
||||
changes.add(StatusEditChangeType.MARKED_SENSITIVE);
|
||||
}else if(prev.sensitive && !s.sensitive){
|
||||
changes.add(StatusEditChangeType.MARKED_NOT_SENSITIVE);
|
||||
}
|
||||
|
||||
if(changes.size()==1){
|
||||
action=getString(switch(changes.iterator().next()){
|
||||
case TEXT_CHANGED -> R.string.edit_text_edited;
|
||||
case SPOILER_ADDED -> R.string.edit_spoiler_added;
|
||||
case SPOILER_REMOVED -> R.string.edit_spoiler_removed;
|
||||
case SPOILER_CHANGED -> R.string.edit_spoiler_edited;
|
||||
case POLL_ADDED -> R.string.edit_poll_added;
|
||||
case POLL_REMOVED -> R.string.edit_poll_removed;
|
||||
case POLL_CHANGED -> R.string.edit_poll_edited;
|
||||
case MEDIA_ADDED -> R.string.edit_media_added;
|
||||
case MEDIA_REMOVED -> R.string.edit_media_removed;
|
||||
case MEDIA_REORDERED -> R.string.edit_media_reordered;
|
||||
case MARKED_SENSITIVE -> R.string.edit_marked_sensitive;
|
||||
case MARKED_NOT_SENSITIVE -> R.string.edit_marked_not_sensitive;
|
||||
});
|
||||
}else{
|
||||
action=getString(R.string.edit_multiple_changed);
|
||||
}
|
||||
}
|
||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0));
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,14 @@ import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -61,7 +62,58 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
|
||||
protected void onStatusCreated(StatusCreatedEvent ev){}
|
||||
|
||||
protected void onStatusUnpinned(StatusUnpinnedEvent ev){}
|
||||
protected void onStatusUpdated(StatusUpdatedEvent ev){
|
||||
ArrayList<Status> statusesForDisplayItems=new ArrayList<>();
|
||||
for(int i=0;i<data.size();i++){
|
||||
Status s=data.get(i);
|
||||
if(s.reblog!=null && s.reblog.id.equals(ev.status.id)){
|
||||
s.reblog=ev.status;
|
||||
statusesForDisplayItems.add(s);
|
||||
}else if(s.id.equals(ev.status.id)){
|
||||
data.set(i, ev.status);
|
||||
statusesForDisplayItems.add(ev.status);
|
||||
}
|
||||
}
|
||||
for(int i=0;i<preloadedData.size();i++){
|
||||
Status s=preloadedData.get(i);
|
||||
if(s.reblog!=null && s.reblog.id.equals(ev.status.id)){
|
||||
s.reblog=ev.status;
|
||||
}else if(s.id.equals(ev.status.id)){
|
||||
preloadedData.set(i, ev.status);
|
||||
}
|
||||
}
|
||||
|
||||
if(statusesForDisplayItems.isEmpty())
|
||||
return;
|
||||
|
||||
for(Status s:statusesForDisplayItems){
|
||||
int i=0;
|
||||
for(StatusDisplayItem item:displayItems){
|
||||
if(item.parentID.equals(s.id)){
|
||||
int start=i;
|
||||
for(;i<displayItems.size();i++){
|
||||
if(!displayItems.get(i).parentID.equals(s.id))
|
||||
break;
|
||||
}
|
||||
List<StatusDisplayItem> postItems=displayItems.subList(start, i);
|
||||
postItems.clear();
|
||||
postItems.addAll(buildDisplayItems(s));
|
||||
int oldSize=i-start, newSize=postItems.size();
|
||||
if(oldSize==newSize){
|
||||
adapter.notifyItemRangeChanged(start, newSize);
|
||||
}else if(oldSize<newSize){
|
||||
adapter.notifyItemRangeChanged(start, oldSize);
|
||||
adapter.notifyItemRangeInserted(start+oldSize, newSize-oldSize);
|
||||
}else{
|
||||
adapter.notifyItemRangeChanged(start, newSize);
|
||||
adapter.notifyItemRangeRemoved(start+newSize, oldSize-newSize);
|
||||
}
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Status getContentStatusByID(String id){
|
||||
Status s=getStatusByID(id);
|
||||
@@ -139,8 +191,8 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusUnpinned(StatusUnpinnedEvent ev){
|
||||
StatusListFragment.this.onStatusUnpinned(ev);
|
||||
public void onStatusUpdated(StatusUpdatedEvent ev){
|
||||
StatusListFragment.this.onStatusUpdated(ev);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ThreadFragment extends StatusListFragment{
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=super.buildDisplayItems(s);
|
||||
if(s==mainStatus){
|
||||
if(s.id.equals(mainStatus.id)){
|
||||
for(StatusDisplayItem item:items){
|
||||
if(item instanceof TextStatusDisplayItem text)
|
||||
text.textSelectable=true;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, account.followersCount, account.followersCount));
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, (int)(account.followersCount%1000), account.followersCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,7 +12,7 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_following, account.followingCount, account.followingCount));
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_following, (int)(account.followingCount%1000), account.followingCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,7 @@ public class StatusFavoritesListFragment extends StatusRelatedAccountListFragmen
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(getResources().getQuantityString(R.plurals.x_favorites, status.favouritesCount, status.favouritesCount));
|
||||
setTitle(getResources().getQuantityString(R.plurals.x_favorites, (int)(status.favouritesCount%1000), status.favouritesCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,7 @@ public class StatusReblogsListFragment extends StatusRelatedAccountListFragment{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(getResources().getQuantityString(R.plurals.x_reblogs, status.reblogsCount, status.reblogsCount));
|
||||
setTitle(getResources().getQuantityString(R.plurals.x_reblogs, (int)(status.reblogsCount%1000), status.reblogsCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -220,9 +220,9 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
||||
followersCount.setText(UiUtils.abbreviateNumber(item.account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(item.account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, Math.min(999, item.account.statusesCount)));
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
relationship=relationships.get(item.account.id);
|
||||
if(relationship==null){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
private UsableRecyclerView list;
|
||||
private MergeRecyclerAdapter adapter;
|
||||
private Button btn;
|
||||
private View buttonBar;
|
||||
private Instance instance;
|
||||
private ArrayList<Item> items=new ArrayList<>();
|
||||
private Call currentRequest;
|
||||
private ItemsAdapter itemsAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||
|
||||
items.add(new Item("Mastodon for Android Privacy Policy", "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png"));
|
||||
loadServerPrivacyPolicy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
if(currentRequest!=null){
|
||||
currentRequest.cancel();
|
||||
currentRequest=null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||
|
||||
list=view.findViewById(R.id.list);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
|
||||
TextView title=headerView.findViewById(R.id.title);
|
||||
TextView subtitle=headerView.findViewById(R.id.subtitle);
|
||||
headerView.findViewById(R.id.step_counter).setVisibility(View.GONE);
|
||||
title.setText(R.string.privacy_policy_title);
|
||||
subtitle.setText(R.string.privacy_policy_subtitle);
|
||||
|
||||
adapter=new MergeRecyclerAdapter();
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
||||
list.setAdapter(adapter);
|
||||
list.setSelector(null);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
if(parent.getChildViewHolder(view) instanceof ItemViewHolder){
|
||||
outRect.left=outRect.right=V.dp(18.5f);
|
||||
outRect.top=V.dp(16);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
}
|
||||
|
||||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), SignupFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=27){
|
||||
int inset=insets.getSystemWindowInsetBottom();
|
||||
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
||||
}else{
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
}
|
||||
}
|
||||
|
||||
private void loadServerPrivacyPolicy(){
|
||||
Request req=new Request.Builder()
|
||||
.url("https://"+instance.uri+"/terms")
|
||||
.addHeader("Accept-Language", Locale.getDefault().toLanguageTag())
|
||||
.build();
|
||||
currentRequest=MastodonAPIController.getHttpClient().newCall(req);
|
||||
currentRequest.enqueue(new Callback(){
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e){
|
||||
currentRequest=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{
|
||||
currentRequest=null;
|
||||
try(ResponseBody body=response.body()){
|
||||
if(!response.isSuccessful())
|
||||
return;
|
||||
Document doc=Jsoup.parse(Objects.requireNonNull(body).byteStream(), Objects.requireNonNull(body.contentType()).charset(StandardCharsets.UTF_8).name(), req.url().toString());
|
||||
final Item item=new Item(doc.title(), instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico");
|
||||
Activity activity=getActivity();
|
||||
if(activity!=null){
|
||||
activity.runOnUiThread(()->{
|
||||
items.add(item);
|
||||
itemsAdapter.notifyItemInserted(items.size()-1);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class ItemsAdapter extends RecyclerView.Adapter<ItemViewHolder>{
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new ItemViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position){
|
||||
holder.bind(items.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return items.size();
|
||||
}
|
||||
}
|
||||
|
||||
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
||||
private final TextView domain, title;
|
||||
private final ImageView favicon;
|
||||
|
||||
public ItemViewHolder(){
|
||||
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
||||
domain=findViewById(R.id.domain);
|
||||
title=findViewById(R.id.title);
|
||||
favicon=findViewById(R.id.favicon);
|
||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(10));
|
||||
itemView.setClipToOutline(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Item item){
|
||||
domain.setText(item.domain);
|
||||
title.setText(item.title);
|
||||
|
||||
ViewImageLoader.load(favicon, null, new UrlImageLoaderRequest(item.faviconUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
UiUtils.launchWebBrowser(getActivity(), item.url);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Item{
|
||||
public String title, domain, url, faviconUrl;
|
||||
|
||||
public Item(String title, String domain, String url, String faviconUrl){
|
||||
this.title=title;
|
||||
this.domain=domain;
|
||||
this.url=url;
|
||||
this.faviconUrl=faviconUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class InstanceRulesFragment extends AppKitFragment{
|
||||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), SignupFragment.class, args);
|
||||
Nav.go(getActivity(), GoogleMadeMeAddThisFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -85,7 +85,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.NO_REBLOGS)
|
||||
currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
@@ -102,6 +102,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
else
|
||||
selectedIDs.add(id);
|
||||
list.invalidate();
|
||||
btn.setEnabled(!selectedIDs.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -96,15 +96,15 @@ public class Account extends BaseModel{
|
||||
/**
|
||||
* How many statuses are attached to this account.
|
||||
*/
|
||||
public int statusesCount;
|
||||
public long statusesCount;
|
||||
/**
|
||||
* The reported followers of this profile.
|
||||
*/
|
||||
public int followersCount;
|
||||
public long followersCount;
|
||||
/**
|
||||
* The reported follows of this profile.
|
||||
*/
|
||||
public int followingCount;
|
||||
public long followingCount;
|
||||
|
||||
// Optional attributes
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public enum ExpandMedia {
|
||||
@SerializedName("default")
|
||||
DEFAULT,
|
||||
@SerializedName("show_all")
|
||||
SHOW_ALL,
|
||||
@SerializedName("hide_all")
|
||||
HIDE_ALL;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Preferred common behaviors to be shared across clients.
|
||||
*/
|
||||
public class Preferences extends BaseModel {
|
||||
/**
|
||||
* Default visibility for new posts
|
||||
*/
|
||||
@SerializedName("posting:default:visibility")
|
||||
public StatusPrivacy postingDefaultVisibility;
|
||||
|
||||
/**
|
||||
* Default sensitivity flag for new posts
|
||||
*/
|
||||
@SerializedName("posting:default:sensitive")
|
||||
public boolean postingDefaultSensitive;
|
||||
|
||||
/**
|
||||
* Default language for new posts
|
||||
*/
|
||||
@SerializedName("posting:default:language")
|
||||
public String postingDefaultLanguage;
|
||||
|
||||
/**
|
||||
* Whether media attachments should be automatically displayed or blurred/hidden.
|
||||
*/
|
||||
@SerializedName("reading:expand:media")
|
||||
public ExpandMedia readingExpandMedia;
|
||||
|
||||
/**
|
||||
* Whether CWs should be expanded by default.
|
||||
*/
|
||||
@SerializedName("reading:expand:spoilers")
|
||||
public boolean readingExpandSpoilers;
|
||||
}
|
||||
@@ -34,9 +34,10 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
public List<Hashtag> tags;
|
||||
@RequiredField
|
||||
public List<Emoji> emojis;
|
||||
public int reblogsCount;
|
||||
public int favouritesCount;
|
||||
public int repliesCount;
|
||||
public long reblogsCount;
|
||||
public long favouritesCount;
|
||||
public long repliesCount;
|
||||
public Instant editedAt;
|
||||
|
||||
public String url;
|
||||
public String inReplyToId;
|
||||
|
||||
@@ -4,11 +4,25 @@ import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public enum StatusPrivacy{
|
||||
@SerializedName("public")
|
||||
PUBLIC,
|
||||
PUBLIC(0),
|
||||
@SerializedName("unlisted")
|
||||
UNLISTED,
|
||||
UNLISTED(1),
|
||||
@SerializedName("private")
|
||||
PRIVATE,
|
||||
PRIVATE(2),
|
||||
@SerializedName("direct")
|
||||
DIRECT;
|
||||
DIRECT(3);
|
||||
|
||||
private int privacy;
|
||||
|
||||
StatusPrivacy(int privacy) {
|
||||
this.privacy = privacy;
|
||||
}
|
||||
|
||||
public boolean isLessVisibleThan(StatusPrivacy other) {
|
||||
return privacy > other.getPrivacy();
|
||||
}
|
||||
|
||||
public int getPrivacy() {
|
||||
return privacy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,9 +115,9 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
|
||||
followersCount.setText(UiUtils.abbreviateNumber(item.account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(item.account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
||||
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, Math.min(999, item.account.statusesCount)));
|
||||
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
relationship=item.parentFragment.getRelationship(item.account.id);
|
||||
if(relationship==null){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
@@ -12,6 +13,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.StatusEditHistoryFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusFavoritesListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusReblogsListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusRelatedAccountListFragment;
|
||||
@@ -43,39 +45,35 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<ExtendedFooterStatusDisplayItem>{
|
||||
private final TextView reblogs, favorites, time;
|
||||
private final View buttonsView;
|
||||
private final TextView time, favoritesCount, reblogsCount, lastEditTime;
|
||||
private final View favorites, reblogs, editHistory;
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.display_item_extended_footer, parent);
|
||||
reblogs=findViewById(R.id.reblogs);
|
||||
favorites=findViewById(R.id.favorites);
|
||||
editHistory=findViewById(R.id.edit_history);
|
||||
time=findViewById(R.id.timestamp);
|
||||
buttonsView=findViewById(R.id.button_bar);
|
||||
favoritesCount=findViewById(R.id.favorites_count);
|
||||
reblogsCount=findViewById(R.id.reblogs_count);
|
||||
lastEditTime=findViewById(R.id.last_edited);
|
||||
|
||||
reblogs.setOnClickListener(v->startAccountListFragment(StatusReblogsListFragment.class));
|
||||
favorites.setOnClickListener(v->startAccountListFragment(StatusFavoritesListFragment.class));
|
||||
editHistory.setOnClickListener(v->startEditHistoryFragment());
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public void onBind(ExtendedFooterStatusDisplayItem item){
|
||||
Status s=item.status;
|
||||
if(s.favouritesCount>0){
|
||||
favorites.setVisibility(View.VISIBLE);
|
||||
favorites.setText(getFormattedPlural(R.plurals.x_favorites, s.favouritesCount));
|
||||
favoritesCount.setText(String.format("%,d", s.favouritesCount));
|
||||
reblogsCount.setText(String.format("%,d", s.reblogsCount));
|
||||
if(s.editedAt!=null){
|
||||
editHistory.setVisibility(View.VISIBLE);
|
||||
lastEditTime.setText(item.parentFragment.getString(R.string.last_edit_at_x, UiUtils.formatRelativeTimestampAsMinutesAgo(itemView.getContext(), s.editedAt)));
|
||||
}else{
|
||||
favorites.setVisibility(View.GONE);
|
||||
}
|
||||
if(s.reblogsCount>0){
|
||||
reblogs.setVisibility(View.VISIBLE);
|
||||
reblogs.setText(getFormattedPlural(R.plurals.x_reblogs, s.reblogsCount));
|
||||
}else{
|
||||
reblogs.setVisibility(View.GONE);
|
||||
}
|
||||
if(s.favouritesCount==0 && s.reblogsCount==0){
|
||||
buttonsView.setVisibility(View.GONE);
|
||||
}else{
|
||||
buttonsView.setVisibility(View.VISIBLE);
|
||||
editHistory.setVisibility(View.GONE);
|
||||
}
|
||||
String timeStr=TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault()));
|
||||
if(item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)){
|
||||
@@ -108,5 +106,12 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
args.putParcelable("status", Parcels.wrap(item.status));
|
||||
Nav.go(item.parentFragment.getActivity(), cls, args);
|
||||
}
|
||||
|
||||
private void startEditHistoryFragment(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putString("id", item.status.id);
|
||||
Nav.go(item.parentFragment.getActivity(), StatusEditHistoryFragment.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
||||
}
|
||||
|
||||
private void bindButton(TextView btn, int count){
|
||||
private void bindButton(TextView btn, long count){
|
||||
if(count>0 && !item.hideCounts){
|
||||
btn.setText(DecimalFormat.getIntegerInstance().format(count));
|
||||
btn.setCompoundDrawablePadding(V.dp(8));
|
||||
|
||||
@@ -21,8 +21,10 @@ import android.widget.Toast;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
@@ -135,7 +137,31 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
optionsMenu.setOnMenuItemClickListener(menuItem->{
|
||||
Account account=item.user;
|
||||
int id=menuItem.getItemId();
|
||||
if(id==R.id.delete){
|
||||
if(id==R.id.edit){
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putParcelable("editStatus", Parcels.wrap(item.status));
|
||||
if(TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
|
||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||
}else{
|
||||
new GetStatusSourceText(item.status.id)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(GetStatusSourceText.Response result){
|
||||
args.putString("sourceText", result.text);
|
||||
args.putString("sourceSpoiler", result.spoilerText);
|
||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(item.parentFragment.getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(item.parentFragment.getActivity(), R.string.loading, true)
|
||||
.exec(item.parentFragment.getAccountID());
|
||||
}
|
||||
}else if(id==R.id.delete){
|
||||
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
||||
}else if(id==R.id.delete_and_redraft) {
|
||||
UiUtils.confirmDeleteAndRedraftPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
||||
@@ -179,7 +205,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onBind(HeaderStatusDisplayItem item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText('@'+item.user.acct);
|
||||
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
|
||||
if(item.status==null || item.status.editedAt==null)
|
||||
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
|
||||
else
|
||||
timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt)));
|
||||
visibility.setVisibility(item.hasVisibilityToggle && !item.inset ? View.VISIBLE : View.GONE);
|
||||
if(item.hasVisibilityToggle){
|
||||
visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility);
|
||||
@@ -253,6 +282,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
Account account=item.user;
|
||||
Menu menu=optionsMenu.getMenu();
|
||||
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
||||
menu.findItem(R.id.edit).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.delete_and_redraft).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.pin).setVisible(item.status!=null && isOwnPost && !item.status.pinned);
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||
@@ -114,7 +115,7 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
if(addFooter){
|
||||
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
|
||||
if(status.hasGapAfter)
|
||||
if(status.hasGapAfter && !(fragment instanceof ThreadFragment))
|
||||
items.add(new GapStatusDisplayItem(parentID, fragment));
|
||||
}
|
||||
int i=1;
|
||||
|
||||
@@ -83,7 +83,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
itemView.setClickable(false);
|
||||
}else{
|
||||
spoilerOverlay.setVisibility(View.VISIBLE);
|
||||
text.setVisibility(View.INVISIBLE);
|
||||
text.setVisibility(View.GONE);
|
||||
itemView.setClickable(true);
|
||||
}
|
||||
}else{
|
||||
|
||||
@@ -129,7 +129,16 @@ public class HtmlParser{
|
||||
}
|
||||
|
||||
public static void parseCustomEmoji(SpannableStringBuilder ssb, List<Emoji> emojis){
|
||||
Map<String, Emoji> emojiByCode=emojis.stream().collect(Collectors.toMap(e->e.shortcode, Function.identity()));
|
||||
Map<String, Emoji> emojiByCode =
|
||||
emojis.stream()
|
||||
.collect(
|
||||
Collectors.toMap(e->e.shortcode, Function.identity(), (emoji1, emoji2) -> {
|
||||
// Ignore duplicate shortcodes and just take the first, it will be
|
||||
// the same emoji anyway
|
||||
return emoji1;
|
||||
})
|
||||
);
|
||||
|
||||
Matcher matcher=EMOJI_CODE_PATTERN.matcher(ssb);
|
||||
int spanCount=0;
|
||||
CustomEmojiSpan lastSpan=null;
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
private final BaseStatusListFragment<?> listFragment;
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private int bgColor;
|
||||
private int borderColor;
|
||||
private RectF rect=new RectF();
|
||||
|
||||
public InsetStatusItemDecoration(BaseStatusListFragment<?> listFragment){
|
||||
this.listFragment=listFragment;
|
||||
bgColor=UiUtils.getThemeColor(listFragment.getActivity(), android.R.attr.colorBackground);
|
||||
borderColor=UiUtils.getThemeColor(listFragment.getActivity(), R.attr.colorPollVoted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||
int pos=0;
|
||||
for(int i=0; i<parent.getChildCount(); i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
pos=holder.getAbsoluteAdapterPosition();
|
||||
boolean inset=(holder instanceof StatusDisplayItem.Holder<?> sdi) && sdi.getItem().inset;
|
||||
if(inset){
|
||||
if(rect.isEmpty()){
|
||||
rect.set(child.getX(), i==0 && pos>0 && displayItems.get(pos-1).inset ? V.dp(-10) : child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight());
|
||||
}else{
|
||||
rect.bottom=Math.max(rect.bottom, child.getY()+child.getHeight());
|
||||
}
|
||||
}else if(!rect.isEmpty()){
|
||||
drawInsetBackground(parent, c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
if(!rect.isEmpty()){
|
||||
if(pos<displayItems.size()-1 && displayItems.get(pos+1).inset){
|
||||
rect.bottom=parent.getHeight()+V.dp(10);
|
||||
}
|
||||
drawInsetBackground(parent, c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawInsetBackground(RecyclerView list, Canvas c){
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(bgColor);
|
||||
rect.left=V.dp(12);
|
||||
rect.right=list.getWidth()-V.dp(12);
|
||||
rect.inset(V.dp(4), V.dp(4));
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(V.dp(1));
|
||||
paint.setColor(borderColor);
|
||||
rect.inset(paint.getStrokeWidth()/2f, paint.getStrokeWidth()/2f);
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
|
||||
boolean inset=sdi.getItem().inset;
|
||||
int pos=holder.getAbsoluteAdapterPosition();
|
||||
if(inset){
|
||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
int pad;
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||
pad=V.dp(16);
|
||||
else
|
||||
pad=V.dp(12);
|
||||
boolean insetLeft=true, insetRight=true;
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> img){
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=img.getItem().tiledLayout;
|
||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=img.getItem().thisTile;
|
||||
// only inset those items that are on the edges of the layout
|
||||
insetLeft=tile.startCol==0;
|
||||
insetRight=tile.startCol+tile.colSpan==layout.columnSizes.length;
|
||||
// inset all items in the bottom row
|
||||
if(tile.startRow+tile.rowSpan==layout.rowSizes.length)
|
||||
bottomSiblingInset=false;
|
||||
}
|
||||
if(insetLeft)
|
||||
outRect.left=pad;
|
||||
if(insetRight)
|
||||
outRect.right=pad;
|
||||
if(!topSiblingInset)
|
||||
outRect.top=pad;
|
||||
if(!bottomSiblingInset)
|
||||
outRect.bottom=pad;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
public class TransferSpeedTracker{
|
||||
private final double SMOOTHING_FACTOR=0.05;
|
||||
|
||||
private long lastKnownPos;
|
||||
private long lastKnownPosTime;
|
||||
private double lastSpeed;
|
||||
private double averageSpeed;
|
||||
private long totalBytes;
|
||||
|
||||
public void addSample(long position){
|
||||
if(lastKnownPosTime==0){
|
||||
lastKnownPosTime=SystemClock.uptimeMillis();
|
||||
lastKnownPos=position;
|
||||
}else{
|
||||
long time=SystemClock.uptimeMillis();
|
||||
lastSpeed=(position-lastKnownPos)/((double)(time-lastKnownPosTime)/1000.0);
|
||||
lastKnownPos=position;
|
||||
lastKnownPosTime=time;
|
||||
}
|
||||
}
|
||||
|
||||
public double getLastSpeed(){
|
||||
return lastSpeed;
|
||||
}
|
||||
|
||||
public double getAverageSpeed(){
|
||||
return averageSpeed;
|
||||
}
|
||||
|
||||
public long updateAndGetETA(){ // must be called at a constant interval
|
||||
if(averageSpeed==0.0)
|
||||
averageSpeed=lastSpeed;
|
||||
else
|
||||
averageSpeed=SMOOTHING_FACTOR*lastSpeed+(1.0-SMOOTHING_FACTOR)*averageSpeed;
|
||||
return Math.round((totalBytes-lastKnownPos)/averageSpeed);
|
||||
}
|
||||
|
||||
public void setTotalBytes(long totalBytes){
|
||||
this.totalBytes=totalBytes;
|
||||
}
|
||||
|
||||
public void reset(){
|
||||
lastKnownPos=lastKnownPosTime=0;
|
||||
lastSpeed=averageSpeed=0.0;
|
||||
totalBytes=0;
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -98,6 +99,7 @@ import okhttp3.MediaType;
|
||||
public class UiUtils{
|
||||
private static Handler mainHandler=new Handler(Looper.getMainLooper());
|
||||
private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR=DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT=DateTimeFormatter.ofPattern("d MMM");
|
||||
public static final DateTimeFormatter DATE_TIME_FORMATTER=DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
|
||||
|
||||
private UiUtils(){}
|
||||
|
||||
@@ -142,6 +144,23 @@ public class UiUtils{
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatRelativeTimestampAsMinutesAgo(Context context, Instant instant){
|
||||
long t=instant.toEpochMilli();
|
||||
long now=System.currentTimeMillis();
|
||||
long diff=now-t;
|
||||
if(diff<1000L){
|
||||
return context.getString(R.string.time_just_now);
|
||||
}else if(diff<60_000L){
|
||||
int secs=(int)(diff/1000L);
|
||||
return context.getResources().getQuantityString(R.plurals.x_seconds_ago, secs, secs);
|
||||
}else if(diff<3600_000L){
|
||||
int mins=(int)(diff/60_000L);
|
||||
return context.getResources().getQuantityString(R.plurals.x_minutes_ago, mins, mins);
|
||||
}else{
|
||||
return DATE_TIME_FORMATTER.format(instant.atZone(ZoneId.systemDefault()));
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatTimeLeft(Context context, Instant instant){
|
||||
long t=instant.toEpochMilli();
|
||||
long now=System.currentTimeMillis();
|
||||
@@ -174,6 +193,15 @@ public class UiUtils{
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public static String abbreviateNumber(long n){
|
||||
if(n<1_000_000_000L)
|
||||
return abbreviateNumber((int)n);
|
||||
|
||||
double a=n/1_000_000_000.0;
|
||||
return a>99f ? String.format("%,dB", (int)Math.floor(a)) : String.format("%,.1fB", n/1_000_000_000.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Android 6.0 has a bug where start and end compound drawables don't get tinted.
|
||||
* This works around it by setting the tint colors directly to the drawables.
|
||||
@@ -195,6 +223,14 @@ public class UiUtils{
|
||||
mainHandler.post(runnable);
|
||||
}
|
||||
|
||||
public static void runOnUiThread(Runnable runnable, long delay){
|
||||
mainHandler.postDelayed(runnable, delay);
|
||||
}
|
||||
|
||||
public static void removeCallbacks(Runnable runnable){
|
||||
mainHandler.removeCallbacks(runnable);
|
||||
}
|
||||
|
||||
/** Linear interpolation between {@code startValue} and {@code endValue} by {@code fraction}. */
|
||||
public static int lerp(int startValue, int endValue, float fraction) {
|
||||
return startValue + Math.round(fraction * (endValue - startValue));
|
||||
@@ -212,6 +248,18 @@ public class UiUtils{
|
||||
return uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
public static String formatFileSize(Context context, long size, boolean atLeastKB){
|
||||
if(size<1024 && !atLeastKB){
|
||||
return context.getString(R.string.file_size_bytes, size);
|
||||
}else if(size<1024*1024){
|
||||
return context.getString(R.string.file_size_kb, size/1024.0);
|
||||
}else if(size<1024*1024*1024){
|
||||
return context.getString(R.string.file_size_mb, size/(1024.0*1024.0));
|
||||
}else{
|
||||
return context.getString(R.string.file_size_gb, size/(1024.0*1024.0*1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
public static MediaType getFileMediaType(File file){
|
||||
String name=file.getName();
|
||||
return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.')+1)));
|
||||
|
||||
@@ -54,12 +54,13 @@ public class ComposeEditText extends EditText{
|
||||
// Support receiving images from keyboards
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo outAttrs){
|
||||
final var ic = super.onCreateInputConnection(outAttrs);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N_MR1){
|
||||
outAttrs.contentMimeTypes=selectionListener.onGetAllowedMediaMimeTypes();
|
||||
inputConnectionWrapper.setTarget(super.onCreateInputConnection(outAttrs));
|
||||
inputConnectionWrapper.setTarget(ic);
|
||||
return inputConnectionWrapper;
|
||||
}
|
||||
return super.onCreateInputConnection(outAttrs);
|
||||
return ic;
|
||||
}
|
||||
|
||||
// Support pasting images
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.joinmastodon.android.updater;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
|
||||
public abstract class GithubSelfUpdater{
|
||||
private static GithubSelfUpdater instance;
|
||||
|
||||
public static GithubSelfUpdater getInstance(){
|
||||
if(instance==null){
|
||||
try{
|
||||
Class<?> c=Class.forName("org.joinmastodon.android.updater.GithubSelfUpdaterImpl");
|
||||
instance=(GithubSelfUpdater) c.newInstance();
|
||||
}catch(IllegalAccessException|InstantiationException|ClassNotFoundException ignored){
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static boolean needSelfUpdating(){
|
||||
return BuildConfig.BUILD_TYPE.equals("githubRelease") || BuildConfig.BUILD_TYPE.equals("debug");
|
||||
}
|
||||
|
||||
public abstract void checkForUpdates();
|
||||
|
||||
public abstract void maybeCheckForUpdates();
|
||||
|
||||
public abstract GithubSelfUpdater.UpdateState getState();
|
||||
|
||||
public abstract GithubSelfUpdater.UpdateInfo getUpdateInfo();
|
||||
|
||||
public abstract void downloadUpdate();
|
||||
|
||||
public abstract void installUpdate(Activity activity);
|
||||
|
||||
public abstract float getDownloadProgress();
|
||||
|
||||
public abstract void cancelDownload();
|
||||
|
||||
public abstract void handleIntentFromInstaller(Intent intent, Activity activity);
|
||||
|
||||
public enum UpdateState{
|
||||
NO_UPDATE,
|
||||
CHECKING,
|
||||
UPDATE_AVAILABLE,
|
||||
DOWNLOADING,
|
||||
DOWNLOADED
|
||||
}
|
||||
|
||||
public static class UpdateInfo{
|
||||
public String version;
|
||||
public long size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M16.7096,17.7682C19.4819,17.4391 21.8955,15.7408 22.199,14.1888C22.6769,11.7442 22.6376,8.2231 22.6376,8.2231C22.6376,3.4504 19.4929,2.0516 19.4929,2.0516C17.9073,1.3274 15.1846,1.023 12.356,1H12.2865C9.4579,1.023 6.7369,1.3274 5.1513,2.0516C5.1513,2.0516 2.0066,3.4504 2.0066,8.2231C2.0066,8.5125 2.0051,8.8169 2.0035,9.1339C1.9991,10.0135 1.9943,10.9896 2.0199,12.0083C2.1341,16.6755 2.8805,21.2752 7.2202,22.4175C9.2213,22.944 10.9392,23.0542 12.323,22.9785C14.832,22.8403 16.2406,22.0883 16.2406,22.0883L16.1577,20.2779C16.1577,20.2779 14.3648,20.8402 12.3511,20.7717C10.356,20.7037 8.2496,20.5577 7.9269,18.1221C7.8972,17.9082 7.8823,17.6794 7.8823,17.4391C7.8823,17.4391 9.8408,17.9152 12.323,18.0283C13.8407,18.0974 15.2639,17.9399 16.7096,17.7682ZM18.8747,14.3719V8.5932C18.8747,7.4121 18.5723,6.4736 17.9648,5.7792C17.3382,5.0849 16.518,4.729 15.4997,4.729C14.3212,4.729 13.4291,5.1792 12.8392,6.0799L12.2657,7.0359L11.692,6.0799C11.1023,5.1792 10.21,4.729 9.0316,4.729C8.0134,4.729 7.193,5.0849 6.5664,5.7792C5.9589,6.4736 5.6565,7.4121 5.6565,8.5932V14.3719H7.959V8.763C7.959,7.5805 8.4594,6.9806 9.4602,6.9806C10.5665,6.9806 11.1211,7.6925 11.1211,9.1001V12.1701H13.4101V9.1001C13.4101,7.6925 13.9647,6.9806 15.071,6.9806C16.0718,6.9806 16.5722,7.5805 16.5722,8.763V14.3719H18.8747Z"
|
||||
android:fillColor="#fff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
5
mastodon/src/main/res/drawable/bg_settings_update.xml
Normal file
5
mastodon/src/main/res/drawable/bg_settings_update.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?colorPollVoted"/>
|
||||
<corners android:radius="4dp"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorForeground">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#18000000"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="?android:colorBackground"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
8
mastodon/src/main/res/drawable/bg_upload_progress.xml
Normal file
8
mastodon/src/main/res/drawable/bg_upload_progress.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/highlight_over_dark">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/gray_600"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M12 4.75c-4.004 0-7.25 3.246-7.25 7.25s3.246 7.25 7.25 7.25 7.25-3.246 7.25-7.25c0-0.286-0.017-0.567-0.049-0.844C19.133 10.568 19.56 10 20.151 10c0.515 0 0.968 0.358 1.03 0.87 0.046 0.37 0.069 0.747 0.069 1.13 0 5.109-4.141 9.25-9.25 9.25S2.75 17.109 2.75 12 6.891 2.75 12 2.75c2.173 0 4.171 0.75 5.75 2.004V4.25c0-0.552 0.448-1 1-1s1 0.448 1 1v2.698L19.784 7H19.75v0.25c0 0.552-0.448 1-1 1h-3c-0.552 0-1-0.448-1-1s0.448-1 1-1h0.666c-1.222-0.94-2.754-1.5-4.416-1.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="16dp" android:height="16dp" android:viewportWidth="16" android:viewportHeight="16">
|
||||
<path android:pathData="M2.397 2.554L2.47 2.47c0.266-0.267 0.683-0.29 0.976-0.073L3.53 2.47 8 6.939l4.47-4.47c0.293-0.292 0.767-0.292 1.06 0 0.293 0.294 0.293 0.768 0 1.061L9.061 8l4.47 4.47c0.266 0.266 0.29 0.683 0.072 0.976L13.53 13.53c-0.266 0.267-0.683 0.29-0.976 0.073L12.47 13.53 8 9.061l-4.47 4.47c-0.293 0.292-0.767 0.292-1.06 0-0.293-0.294-0.293-0.768 0-1.061L6.939 8l-4.47-4.47C2.204 3.264 2.18 2.847 2.398 2.554L2.47 2.47 2.397 2.554z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M4.21 4.387l0.083-0.094c0.36-0.36 0.928-0.388 1.32-0.083l0.094 0.083L12 10.585l6.293-6.292c0.39-0.39 1.024-0.39 1.414 0 0.39 0.39 0.39 1.024 0 1.414L13.415 12l6.292 6.293c0.36 0.36 0.388 0.928 0.083 1.32l-0.083 0.094c-0.36 0.36-0.928 0.388-1.32 0.083l-0.094-0.083L12 13.415l-6.293 6.292c-0.39 0.39-1.024 0.39-1.414 0-0.39-0.39-0.39-1.024 0-1.414L10.585 12 4.293 5.707c-0.36-0.36-0.388-0.928-0.083-1.32l0.083-0.094L4.21 4.387z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -1,3 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M10.946 2.047l0.005 0.007C11.296 2.02 11.646 2 12 2c5.522 0 10 4.477 10 10s-4.478 10-10 10c-3.21 0-6.066-1.512-7.896-3.862H4.102v-0.003C2.786 16.441 2 14.312 2 12c0-5.162 3.911-9.41 8.932-9.944l0.014-0.009zM12 3.5c-0.053 0-0.106 0-0.16 0.002 0.123 0.244 0.255 0.532 0.374 0.85 0.347 0.921 0.666 2.28 0.1 3.486-0.522 1.113-1.424 1.4-2.09 1.573L10.14 9.432c-0.657 0.17-0.91 0.235-1.093 0.514-0.17 0.257-0.144 0.582 0.061 1.25l0.046 0.148c0.082 0.258 0.18 0.57 0.23 0.863 0.064 0.364 0.082 0.827-0.152 1.275-0.231 0.444-0.538 0.747-0.9 0.945-0.341 0.185-0.694 0.256-0.958 0.302l-0.093 0.017c-0.515 0.09-0.761 0.134-1 0.39-0.187 0.2-0.307 0.553-0.377 1.079-0.029 0.214-0.046 0.427-0.064 0.646l-0.01 0.117c-0.02 0.242-0.044 0.521-0.099 0.76v0.002c1.554 1.696 3.787 2.76 6.27 2.76 1.576 0 3.053-0.43 4.319-1.178-0.099-0.1-0.205-0.218-0.31-0.35-0.34-0.428-0.786-1.164-0.631-2.033 0.074-0.418 0.298-0.768 0.515-1.036 0.22-0.274 0.486-0.526 0.72-0.74l0.158-0.146c0.179-0.163 0.33-0.301 0.46-0.437 0.172-0.18 0.21-0.262 0.212-0.267 0.068-0.224-0.015-0.384-0.106-0.454-0.046-0.035-0.107-0.06-0.19-0.061-0.084 0-0.22 0.024-0.401 0.14-0.21 0.132-0.515 0.214-0.836 0.085-0.267-0.108-0.415-0.314-0.486-0.432-0.144-0.237-0.225-0.546-0.278-0.772-0.04-0.174-0.08-0.372-0.115-0.553l-0.04-0.206c-0.05-0.25-0.094-0.428-0.134-0.54l-0.02-0.037c-0.014-0.027-0.035-0.062-0.064-0.105-0.058-0.089-0.133-0.192-0.227-0.317l-0.11-0.143c-0.16-0.212-0.353-0.463-0.516-0.712-0.196-0.298-0.417-0.688-0.487-1.104-0.037-0.22-0.036-0.475 0.055-0.734 0.094-0.264 0.265-0.482 0.487-0.649 0.483-0.362 1.193-1.172 1.823-1.959 0.288-0.359 0.544-0.695 0.736-0.95C15.222 3.98 13.667 3.5 12 3.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
<path android:pathData="M10.946 2.047l0.005 0.007C11.296 2.02 11.646 2 12 2c5.522 0 10 4.477 10 10s-4.478 10-10 10c-3.21 0-6.066-1.512-7.896-3.862H4.102v-0.003C2.786 16.441 2 14.312 2 12c0-5.162 3.911-9.41 8.932-9.944l0.014-0.009zM12 3.5c-0.053 0-0.106 0-0.16 0.002 0.123 0.244 0.255 0.532 0.374 0.85 0.347 0.921 0.666 2.28 0.1 3.486-0.522 1.113-1.424 1.4-2.09 1.573L10.14 9.432c-0.657 0.17-0.91 0.235-1.093 0.514-0.17 0.257-0.144 0.582 0.061 1.25l0.046 0.148c0.082 0.258 0.18 0.57 0.23 0.863 0.064 0.364 0.082 0.827-0.152 1.275-0.231 0.444-0.538 0.747-0.9 0.945-0.341 0.185-0.694 0.256-0.958 0.302l-0.093 0.017c-0.515 0.09-0.761 0.134-1 0.39-0.187 0.2-0.307 0.553-0.377 1.079-0.029 0.214-0.046 0.427-0.064 0.646l-0.01 0.117c-0.02 0.242-0.044 0.521-0.099 0.76v0.002c1.554 1.696 3.787 2.76 6.27 2.76 1.576 0 3.053-0.43 4.319-1.178-0.099-0.1-0.205-0.218-0.31-0.35-0.34-0.428-0.786-1.164-0.631-2.033 0.074-0.418 0.298-0.768 0.515-1.036 0.22-0.274 0.486-0.526 0.72-0.74l0.158-0.146c0.179-0.163 0.33-0.301 0.46-0.437 0.172-0.18 0.21-0.262 0.212-0.267 0.068-0.224-0.015-0.384-0.106-0.454-0.046-0.035-0.107-0.06-0.19-0.061-0.084 0-0.22 0.024-0.401 0.14-0.21 0.132-0.515 0.214-0.836 0.085-0.267-0.108-0.415-0.314-0.486-0.432-0.144-0.237-0.225-0.546-0.278-0.772-0.04-0.174-0.08-0.372-0.115-0.553l-0.04-0.206c-0.05-0.25-0.094-0.428-0.134-0.54l-0.02-0.037c-0.014-0.027-0.035-0.062-0.064-0.105-0.058-0.089-0.133-0.192-0.227-0.317l-0.11-0.143c-0.16-0.212-0.353-0.463-0.516-0.712-0.196-0.298-0.417-0.688-0.487-1.104-0.037-0.22-0.036-0.475 0.055-0.734 0.094-0.264 0.265-0.482 0.487-0.649 0.483-0.362 1.193-1.172 1.823-1.959 0.288-0.359 0.544-0.695 0.736-0.95C15.222 3.98 13.667 3.5 12 3.5zm5.727 2.22c-0.197 0.263-0.461 0.608-0.757 0.978-0.602 0.751-1.4 1.685-2.05 2.187 0.026 0.1 0.1 0.262 0.255 0.498 0.131 0.2 0.281 0.397 0.44 0.604l0.129 0.17c0.172 0.229 0.411 0.548 0.52 0.844 0.087 0.234 0.149 0.519 0.198 0.762l0.049 0.246c0.025 0.13 0.049 0.253 0.075 0.37 0.601-0.172 1.201-0.068 1.67 0.294 0.608 0.47 0.862 1.286 0.624 2.074-0.11 0.362-0.364 0.66-0.563 0.869-0.17 0.177-0.372 0.362-0.556 0.53l-0.132 0.12c-0.23 0.212-0.423 0.4-0.568 0.579-0.148 0.184-0.195 0.299-0.205 0.356-0.04 0.219 0.067 0.51 0.328 0.838 0.118 0.148 0.244 0.274 0.341 0.362l0.033 0.03C19.36 16.872 20.5 14.569 20.5 12c0-2.488-1.069-4.726-2.773-6.28zM3.5 12c0 1.398 0.338 2.718 0.936 3.881 0.085-0.557 0.262-1.248 0.748-1.768 0.6-0.642 1.335-0.763 1.798-0.839l0.13-0.021c0.248-0.044 0.391-0.083 0.502-0.143 0.088-0.049 0.188-0.128 0.288-0.321 0.015-0.028 0.042-0.107 0.004-0.325-0.032-0.187-0.093-0.381-0.172-0.636-0.02-0.06-0.04-0.125-0.06-0.192-0.185-0.604-0.48-1.602 0.12-2.515 0.522-0.792 1.36-0.994 1.893-1.123l0.162-0.04c0.563-0.145 0.883-0.28 1.108-0.758 0.295-0.629 0.168-1.485-0.146-2.32-0.15-0.396-0.324-0.744-0.463-0.994-0.043-0.078-0.083-0.146-0.116-0.202C6.386 4.498 3.5 7.912 3.5 12z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -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="M21.03 2.97c1.398 1.397 1.398 3.663 0 5.06L9.062 20c-0.277 0.277-0.621 0.477-0.999 0.58l-5.116 1.395c-0.56 0.153-1.073-0.361-0.92-0.921l1.395-5.116c0.103-0.377 0.302-0.722 0.58-0.999L15.97 2.97c1.397-1.398 3.663-1.398 5.06 0zM15 6.06L5.062 16c-0.092 0.092-0.159 0.207-0.193 0.333l-1.05 3.85 3.85-1.05C7.793 19.096 7.908 19.03 8 18.938L17.94 9 15 6.06zm2.03-2.03L16.06 5 19 7.94l0.97-0.97c0.811-0.812 0.811-2.128 0-2.94-0.812-0.811-2.128-0.811-2.94 0z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M3 3.748c0-0.414 0.336-0.75 0.75-0.75h16.504c0.618 0 0.971 0.706 0.6 1.2L16.69 9.75l4.164 5.551c0.371 0.495 0.018 1.2-0.6 1.2H4.5v4.75c0 0.38-0.282 0.693-0.648 0.743L3.75 22c-0.38 0-0.693-0.282-0.743-0.648L3 21.25V3.748z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M3 3.748c0-0.414 0.336-0.75 0.75-0.75h16.504c0.618 0 0.971 0.706 0.6 1.2L16.69 9.75l4.164 5.551c0.371 0.495 0.018 1.2-0.6 1.2H4.5v4.75c0 0.38-0.282 0.693-0.648 0.743L3.75 22c-0.38 0-0.693-0.282-0.743-0.648L3 21.25V3.748zm15.754 0.75H4.5v10.503h14.254l-3.602-4.802c-0.2-0.266-0.2-0.633 0-0.9l3.602-4.8z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--~ Copyright (c) 2022. ~ Microsoft Corporation. All rights reserved.-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_fluent_flag_24_filled" android:state_activated="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_flag_24_filled" android:state_checked="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_flag_24_filled" android:state_selected="true"/>
|
||||
<item android:drawable="@drawable/ic_fluent_flag_24_regular"/>
|
||||
</selector>
|
||||
10
mastodon/src/main/res/drawable/ic_settings_24_badged.xml
Normal file
10
mastodon/src/main/res/drawable/ic_settings_24_badged.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_fluent_settings_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
||||
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
||||
<shape android:shape="oval">
|
||||
<stroke android:color="?android:colorPrimary" android:width="2dp"/>
|
||||
<solid android:color="@color/primary_600"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
12
mastodon/src/main/res/drawable/update_progress.xml
Normal file
12
mastodon/src/main/res/drawable/update_progress.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/progress">
|
||||
<shape
|
||||
android:innerRadius="16dp"
|
||||
android:shape="ring"
|
||||
android:thickness="4dp"
|
||||
android:useLevel="true">
|
||||
<solid android:color="?colorSearchHint"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -6,7 +6,7 @@
|
||||
android:shape="ring"
|
||||
android:thickness="4dp"
|
||||
android:useLevel="true">
|
||||
<solid android:color="?android:colorAccent"/>
|
||||
<solid android:color="@color/gray_100"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -65,30 +65,68 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#cc000000"
|
||||
android:backgroundTint="?colorWindowBackground"
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false"
|
||||
tools:visibility="visible"
|
||||
android:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_gravity="center"
|
||||
android:progressDrawable="@drawable/upload_progress"
|
||||
android:max="1000"
|
||||
android:padding="0dp"
|
||||
android:indeterminateOnly="false"
|
||||
android:indeterminate="false"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/retry_upload"
|
||||
android:layout_width="wrap_content"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|bottom"
|
||||
style="?secondaryButtonStyle"
|
||||
android:text="@string/retry_upload"/>
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/retry_or_cancel_upload"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:src="@drawable/ic_fluent_dismiss_24_filled"
|
||||
android:contentDescription="@string/cancel"
|
||||
android:tint="@color/gray_100"
|
||||
android:background="@drawable/bg_upload_progress"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:progressDrawable="@drawable/upload_progress"
|
||||
android:max="1000"
|
||||
android:padding="0dp"
|
||||
android:indeterminateOnly="false"
|
||||
android:indeterminate="false"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/state_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="16dp"
|
||||
android:layout_below="@id/retry_or_cancel_upload"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textColor="@color/gray_200"
|
||||
android:textSize="14dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:includeFontPadding="false"
|
||||
tools:text="Upload failed"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/state_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_below="@id/state_title"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/gray_200"
|
||||
android:gravity="center_horizontal|top"
|
||||
android:lines="2"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Your device lost connection to the internet"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/remove_btn2"
|
||||
@@ -98,6 +136,7 @@
|
||||
android:layout_gravity="end|bottom"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:tint="#D92C2C"
|
||||
android:contentDescription="@string/delete"
|
||||
android:src="@drawable/ic_fluent_delete_20_regular"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -2,49 +2,146 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorBackgroundLightest">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.joinmastodon.android.ui.views.AutoOrientationLinearLayout
|
||||
android:id="@+id/button_bar"
|
||||
<RelativeLayout
|
||||
android:id="@+id/reblogs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="64dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<Button
|
||||
android:id="@+id/reblogs"
|
||||
android:layout_width="wrap_content"
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@drawable/ic_fluent_arrow_repeat_all_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:textSize="14sp"
|
||||
android:minHeight="36dp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:background="@drawable/bg_text_button"
|
||||
android:fontFamily="sans-serif"
|
||||
tools:text="4 reblogs"/>
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:minHeight="22dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/post_info_reblogs"
|
||||
android:textAppearance="@style/m3_body_large" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/favorites"
|
||||
android:layout_width="wrap_content"
|
||||
<TextView
|
||||
android:id="@+id/reblogs_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:textSize="14sp"
|
||||
android:minHeight="36dp"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:background="@drawable/bg_text_button"
|
||||
android:fontFamily="sans-serif"
|
||||
tools:text="12 favorites"/>
|
||||
tools:text="123 456"/>
|
||||
|
||||
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/favorites"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@drawable/ic_fluent_star_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:minHeight="22dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/post_info_favorites"
|
||||
android:textAppearance="@style/m3_body_large" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/favorites_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
tools:text="123 456"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/edit_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@drawable/ic_fluent_edit_24_regular"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_toEndOf="@id/icon"
|
||||
android:minHeight="22dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/edit_history"
|
||||
android:textAppearance="@style/m3_body_large" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/last_edited"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_alignStart="@id/title"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
tools:text="123 456"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timestamp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_margin="16dp"
|
||||
android:minHeight="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textSize="14sp"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingVertical="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -154,6 +154,38 @@
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/sensitive_item"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layoutDirection="locale"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sensitive_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:src="@drawable/ic_fluent_flag_24_selector"
|
||||
android:tint="?android:textColorPrimary"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="16sp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/mark_media_as_sensitive" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
@@ -227,7 +259,7 @@
|
||||
android:tint="@color/compose_button"
|
||||
android:tintMode="src_in"
|
||||
android:contentDescription="@string/post_visibility"
|
||||
android:src="@drawable/ic_fluent_earth_24_filled"/>
|
||||
android:src="@drawable/ic_fluent_earth_24_regular"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0px"
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
android:layout_margin="16dp"
|
||||
android:minWidth="145dp"
|
||||
style="?primaryLargeButtonStyle"
|
||||
android:text="@string/next" />
|
||||
android:text="@string/i_agree" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
44
mastodon/src/main/res/layout/item_privacy_policy_link.xml
Normal file
44
mastodon/src/main/res/layout/item_privacy_policy_link.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:elevation="3dp"
|
||||
android:background="?colorBackgroundLightest"
|
||||
android:foreground="?android:selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/favicon"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/domain"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_toEndOf="@id/favicon"
|
||||
android:layout_marginStart="4dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="joinmastodon.org"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:layout_below="@id/domain"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="Mastodon for Android privacy policy"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -1,13 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="daffdsa"/>
|
||||
android:layoutDirection="locale">
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="48dp"
|
||||
android:paddingRight="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="Account settings"/>
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:alpha="0"
|
||||
/>
|
||||
</LinearLayout>
|
||||
75
mastodon/src/main/res/layout/item_settings_update.xml
Normal file
75
mastodon/src/main/res/layout/item_settings_update.xml
Normal file
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="64dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:background="@drawable/bg_settings_update"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
tools:text="@string/update_available"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:textColor="?colorAccentLight"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="14dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:stateListAnimator="@null"
|
||||
tools:text="@string/install_update"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cancel_btn"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/bg_update_download_progress"
|
||||
android:tint="?colorSearchHint"
|
||||
android:contentDescription="@string/cancel"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/ic_fluent_dismiss_16_filled"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:progressDrawable="@drawable/update_progress"
|
||||
android:max="1000"
|
||||
android:padding="0dp"
|
||||
android:visibility="gone"
|
||||
android:indeterminateOnly="false"
|
||||
android:indeterminate="false"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/vis_public"
|
||||
android:icon="@drawable/ic_fluent_earth_24_filled"
|
||||
android:icon="@drawable/ic_fluent_earth_24_regular"
|
||||
android:title="@string/visibility_public"/>
|
||||
<item android:id="@+id/vis_unlisted"
|
||||
android:icon="@drawable/ic_fluent_people_community_24_regular"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/notify_anyone" android:title="@string/notify_anyone" android:icon="@drawable/ic_fluent_earth_24_filled"/>
|
||||
<item android:id="@+id/notify_anyone" android:title="@string/notify_anyone" android:icon="@drawable/ic_fluent_earth_24_regular"/>
|
||||
<item android:id="@+id/notify_follower" android:title="@string/notify_follower" android:icon="@drawable/ic_fluent_people_checkmark_24_regular"/>
|
||||
<item android:id="@+id/notify_followed" android:title="@string/notify_followed" android:icon="@drawable/ic_fluent_people_checkmark_24_regular"/>
|
||||
<item android:id="@+id/notify_none" android:title="@string/notify_none" android:icon="@drawable/ic_fluent_prohibited_24_regular"/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/edit" android:title="@string/edit"/>
|
||||
<item android:id="@+id/delete" android:title="@string/delete"/>
|
||||
<item android:id="@+id/delete_and_redraft" android:title="@string/delete_and_redraft"/>
|
||||
<item android:id="@+id/pin" android:title="@string/pin_post"/>
|
||||
|
||||
@@ -2,4 +2,9 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome>
|
||||
<layer-list>
|
||||
<item android:drawable="@drawable/ic_launcher_monochrome" android:gravity="center"/>
|
||||
</layer-list>
|
||||
</monochrome>
|
||||
</adaptive-icon>
|
||||
@@ -2,4 +2,9 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome>
|
||||
<layer-list>
|
||||
<item android:drawable="@drawable/ic_launcher_monochrome" android:gravity="center"/>
|
||||
</layer-list>
|
||||
</monochrome>
|
||||
</adaptive-icon>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="get_started">الخطوات الأولى</string>
|
||||
<string name="log_in">تسجيل الدخول</string>
|
||||
<string name="log_in">تسجيلُ الدخول</string>
|
||||
<string name="next">التالي</string>
|
||||
<string name="loading_instance">يَجري الحُصُول على معلومات المَثيل…</string>
|
||||
<string name="error">خطأ</string>
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="user_sent_follow_request">أرسَلَ طَلَبًا لِمُتابَعَتِك</string>
|
||||
<string name="user_favorited">فَضَّلَ مَنشُورَك</string>
|
||||
<string name="notification_boosted">أعادَ تَدوينَ مَنشُورَك</string>
|
||||
<string name="poll_ended">انتهى استطلاع الرأي</string>
|
||||
<string name="poll_ended">انتهى استطلاعُ الرأي</string>
|
||||
<string name="time_seconds">%d ثا</string>
|
||||
<string name="time_minutes">%d د</string>
|
||||
<string name="time_hours">%d سا</string>
|
||||
@@ -54,7 +54,7 @@
|
||||
<string name="posts">منشورات</string>
|
||||
<string name="posts_and_replies">مَنشُوراتٌ وَرُدُود</string>
|
||||
<string name="media">وسائط</string>
|
||||
<string name="profile_about">عن</string>
|
||||
<string name="profile_about">حَول</string>
|
||||
<string name="button_follow">تابِع</string>
|
||||
<string name="button_following">يُتابِع</string>
|
||||
<string name="edit_profile">حرّر الملف الشخصي</string>
|
||||
@@ -260,8 +260,6 @@
|
||||
<string name="content_warning">تحذير من المحتوى</string>
|
||||
<string name="add_image_description">أضف وصفًا للصورة…</string>
|
||||
<string name="retry_upload">حاول الرفع مجددًا</string>
|
||||
<string name="image_upload_failed">فشل رفع الصورة</string>
|
||||
<string name="video_upload_failed">فشل رفع الفيديو</string>
|
||||
<string name="edit_image">حرّر الصورة</string>
|
||||
<string name="save">احفظ</string>
|
||||
<string name="add_alt_text">أضف نصًا بديلًا</string>
|
||||
@@ -409,4 +407,32 @@
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s عبر %2$s</string>
|
||||
<string name="time_now">الآن</string>
|
||||
<string name="post_info_reblogs">إعادات التدوين</string>
|
||||
<string name="post_info_favorites">المفضلة</string>
|
||||
<string name="last_edit_at_x">آخر تعديل %s</string>
|
||||
<string name="time_just_now">للتوّ</string>
|
||||
<string name="edited_timestamp">عُدّل في %s</string>
|
||||
<string name="edit_original_post">المنشور الأصلي</string>
|
||||
<string name="edit_text_edited">تم تعديل النص</string>
|
||||
<string name="edit_spoiler_added">تم إضافة تحذير المحتوى</string>
|
||||
<string name="edit_spoiler_edited">تم تعديل تحذير المحتوى</string>
|
||||
<string name="edit_spoiler_removed">تم حذف تحذير المحتوى</string>
|
||||
<string name="edit_poll_added">تمت إضافة استطلاع للرأي</string>
|
||||
<string name="edit_poll_edited">تم تعديل الاستطلاع</string>
|
||||
<string name="edit_poll_removed">تمت إزالة الاستطلاع</string>
|
||||
<string name="edit_media_added">تمت إضافة الوسائط</string>
|
||||
<string name="edit_media_removed">تمت إزالة الوسائط</string>
|
||||
<string name="edit_media_reordered">تمت إعادة ترتيب الوسائط</string>
|
||||
<string name="edit_marked_sensitive">مُعَين كحساس</string>
|
||||
<string name="edit_marked_not_sensitive">مُعَين كمنشور غير حساس</string>
|
||||
<string name="edit_multiple_changed">عُدّل المنشور</string>
|
||||
<string name="edit">تعديل</string>
|
||||
<string name="discard_changes">تجاهل التغييرات؟</string>
|
||||
<string name="upload_failed">فشلت عملية التحميل</string>
|
||||
<string name="file_upload_time_remaining">%s متبقية</string>
|
||||
<string name="upload_error_connection_lost">فقد جهازك الاتصال بالإنترنت</string>
|
||||
<string name="upload_processing">قيد المعالجة…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
</resources>
|
||||
|
||||
@@ -162,8 +162,6 @@
|
||||
<string name="content_warning">Pizanje sadržaja</string>
|
||||
<string name="add_image_description">Dodaj opis slike…</string>
|
||||
<string name="retry_upload">Pokušaj ponovo</string>
|
||||
<string name="image_upload_failed">Slika se nije mogla sačuvati</string>
|
||||
<string name="video_upload_failed">Video se nije mogao sačuvati</string>
|
||||
<string name="edit_image">Uredi sliku</string>
|
||||
<string name="save">Sačuvaj</string>
|
||||
<string name="add_alt_text">Dodaj alt tekst</string>
|
||||
@@ -256,4 +254,7 @@
|
||||
<string name="file_saved">Datoteka sačuvana</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
</resources>
|
||||
|
||||
@@ -199,8 +199,6 @@
|
||||
<string name="content_warning">Avís de contingut</string>
|
||||
<string name="add_image_description">Afegir una descripció a la imatge…</string>
|
||||
<string name="retry_upload">Torna a provar de pujar</string>
|
||||
<string name="image_upload_failed">La imatge no s\'ha pogut penjar</string>
|
||||
<string name="video_upload_failed">No s\'ha pogut penjar el vídeo</string>
|
||||
<string name="edit_image">Editar imatge</string>
|
||||
<string name="save">Desar</string>
|
||||
<string name="add_alt_text">Afegir text alternatiu</string>
|
||||
@@ -319,4 +317,7 @@
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s través de %2$s</string>
|
||||
<string name="time_now">ara</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="get_started">Začínáme</string>
|
||||
<string name="log_in">Přihlásit</string>
|
||||
<string name="get_started">Začít</string>
|
||||
<string name="log_in">Přihlásit se</string>
|
||||
<string name="next">Další</string>
|
||||
<string name="loading_instance">Získávání informací o instanci…</string>
|
||||
<string name="error">Chyba</string>
|
||||
@@ -9,13 +9,13 @@
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Příprava na ověřování…</string>
|
||||
<string name="finishing_auth">Dokončení ověřování…</string>
|
||||
<string name="user_boosted">%s sdíleno</string>
|
||||
<string name="user_boosted">%s boostnul</string>
|
||||
<string name="in_reply_to">V odpovědi na %s</string>
|
||||
<string name="notifications">Upozornění</string>
|
||||
<string name="user_followed_you">vás sleduje</string>
|
||||
<string name="user_sent_follow_request">vám poslal žádost o sledování</string>
|
||||
<string name="user_favorited">si oblíbil váš příspěvek</string>
|
||||
<string name="notification_boosted">sdílel váš příspěvek</string>
|
||||
<string name="notification_boosted">boostnul váš příspěvek</string>
|
||||
<string name="poll_ended">anketa skončila</string>
|
||||
<string name="time_seconds">%d s</string>
|
||||
<string name="time_minutes">%d m</string>
|
||||
@@ -27,6 +27,24 @@
|
||||
<string name="discard_draft">Zahodit koncept?</string>
|
||||
<string name="discard">Zahodit</string>
|
||||
<string name="cancel">Zrušit</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="one">sledující</item>
|
||||
<item quantity="few">sledující</item>
|
||||
<item quantity="many">sledujících</item>
|
||||
<item quantity="other">sledujících</item>
|
||||
</plurals>
|
||||
<plurals name="following">
|
||||
<item quantity="one">sledovaní</item>
|
||||
<item quantity="few">sledování</item>
|
||||
<item quantity="many">sledování</item>
|
||||
<item quantity="other">sledování</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">příspěvek</item>
|
||||
<item quantity="few">příspěvky</item>
|
||||
<item quantity="many">příspěvků</item>
|
||||
<item quantity="other">příspěvků</item>
|
||||
</plurals>
|
||||
<string name="posts">Příspěvky</string>
|
||||
<string name="posts_and_replies">Příspěvky a odpovědi</string>
|
||||
<string name="media">Média</string>
|
||||
@@ -43,6 +61,12 @@
|
||||
<string name="report_user">Nahlásit %s</string>
|
||||
<string name="block_domain">Blokovat %s</string>
|
||||
<string name="unblock_domain">Odblokovat %s</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="one">%,d příspěvek</item>
|
||||
<item quantity="few">%,d příspěvky</item>
|
||||
<item quantity="many">%,d příspěvků</item>
|
||||
<item quantity="other">%,d příspěvků</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Účet vytvořen</string>
|
||||
<string name="done">Hotovo</string>
|
||||
<string name="loading">Načítání…</string>
|
||||
@@ -169,7 +193,7 @@
|
||||
<string name="report_sent_subtitle">Zatímco to posuzujeme, můžete podniknout kroky proti %s.</string>
|
||||
<string name="unfollow_user">Přestat sledovat %s</string>
|
||||
<string name="unfollow">Přestat sledovat</string>
|
||||
<string name="mute_user_explain">Neuvidíte jejich příspěvky nebo sdílení v domovském kanálu. Nebudou vědět, že jsou skrytí.</string>
|
||||
<string name="mute_user_explain">Neuvidíte jejich příspěvky nebo boostnutí v domovském kanálu. Nebudou vědět, že jsou skrytí.</string>
|
||||
<string name="block_user_explain">Už nebudou moci sledovat nebo vidět vaše příspěvky, ale mohou vidět, že byli blokováni.</string>
|
||||
<string name="report_personal_title">Nechcete tohle vidět?</string>
|
||||
<string name="report_personal_subtitle">Když uvidíte něco, co se vám nelíbí na Mastodonu, můžete odstranit tuto osobu ze svého zážitku.</string>
|
||||
@@ -204,15 +228,182 @@
|
||||
<string name="resend">Poslat znovu</string>
|
||||
<string name="open_email_app">Otevřít e-mailovou aplikaci</string>
|
||||
<string name="resent_email">Potvrzující e-mail odeslán</string>
|
||||
<string name="compose_hint">Napište nebo vložte, co máte na mysli</string>
|
||||
<string name="content_warning">Varování o obsahu</string>
|
||||
<string name="add_image_description">Přidat popis obrázku…</string>
|
||||
<string name="retry_upload">Opakovat nahrání</string>
|
||||
<string name="edit_image">Upravit obrázek</string>
|
||||
<string name="save">Uložit</string>
|
||||
<string name="add_alt_text">Přidat alt text</string>
|
||||
<string name="alt_text_subtitle">Alt text popisuje obrázky pro lidi se špatným nebo žádným zrakem. Pokuste se zahrnout jen tolik obsahu, kolik je potřeba pro pochopení kontextu.</string>
|
||||
<string name="alt_text_hint">Např. podezřívavě rozhlížející se pes se zúženýma očima namířenýma na kameru.</string>
|
||||
<string name="visibility_public">Veřejný</string>
|
||||
<string name="visibility_followers_only">Pouze sledující</string>
|
||||
<string name="visibility_private">Pouze lidé, které zmíním</string>
|
||||
<string name="search_all">Vše</string>
|
||||
<string name="search_people">Lidé</string>
|
||||
<string name="recent_searches">Nedávná hledání</string>
|
||||
<string name="step_x_of_n">Krok %1$d z %2$d</string>
|
||||
<string name="skip">Přeskočit</string>
|
||||
<string name="notification_type_follow">Noví sledující</string>
|
||||
<string name="notification_type_favorite">Oblíbené</string>
|
||||
<string name="notification_type_reblog">Boostnutí</string>
|
||||
<string name="notification_type_mention">Zmínky</string>
|
||||
<string name="notification_type_poll">Ankety</string>
|
||||
<string name="choose_account">Vybrat účet</string>
|
||||
<string name="err_not_logged_in">Nejprve se přihlaste do Mastodonu</string>
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
<item quantity="one">Nelze přidat více než %d multimediálních příloh</item>
|
||||
<item quantity="few">Nelze přidat více než %d multimediální přílohy</item>
|
||||
<item quantity="many">Nelze přidat více než %d multimediálních příloh</item>
|
||||
<item quantity="other">Nelze přidat více než %d multimediálních příloh</item>
|
||||
</plurals>
|
||||
<string name="media_attachment_unsupported_type">Soubor %s nepatří mezi podporované typy</string>
|
||||
<string name="media_attachment_too_big">Soubor %1$s překračuje limit velikosti %2$s MB</string>
|
||||
<string name="settings_theme">Vizuální podoba</string>
|
||||
<string name="theme_auto">Automatická</string>
|
||||
<string name="theme_light">Světlá</string>
|
||||
<string name="theme_dark">Tmavá</string>
|
||||
<string name="theme_true_black">Režim skutečně černé</string>
|
||||
<string name="settings_behavior">Chování</string>
|
||||
<string name="settings_gif">Přehrávat animované avatary a emoji</string>
|
||||
<string name="settings_custom_tabs">Používat interní prohlížeč</string>
|
||||
<string name="settings_notifications">Upozornění</string>
|
||||
<string name="notify_me_when">Upozornit mě, když</string>
|
||||
<string name="notify_anyone">kdokoliv</string>
|
||||
<string name="notify_follower">sledující</string>
|
||||
<string name="notify_followed">někdo, koho sleduji</string>
|
||||
<string name="notify_none">nikoho</string>
|
||||
<string name="notify_favorites">Oblíbil si můj příspěvek</string>
|
||||
<string name="notify_follow">Sleduje mě</string>
|
||||
<string name="notify_reblog">Sdílel můj příspěvek</string>
|
||||
<string name="notify_reblog">Boostnul můj příspěvek</string>
|
||||
<string name="notify_mention">Zmiňuje mě</string>
|
||||
<string name="settings_boring">Nudná část</string>
|
||||
<string name="settings_account">Nastavení účtu</string>
|
||||
<string name="settings_contribute">Přispějte do Mastodonu</string>
|
||||
<string name="settings_tos">Podmínky používání</string>
|
||||
<string name="settings_privacy_policy">Zásady ochrany osobních údajů</string>
|
||||
<string name="settings_spicy">Ostrá část</string>
|
||||
<string name="settings_clear_cache">Vymazat mezipaměť médií</string>
|
||||
<string name="settings_app_version">Mastodon pro Android v%1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">Mezipaměť médií vymazána</string>
|
||||
<string name="confirm_log_out">Opravdu se chcete odhlásit?</string>
|
||||
<string name="sensitive_content">Citlivý obsah</string>
|
||||
<string name="sensitive_content_explain">Autor označil toto médium za citlivé. Klepnutím zobrazíte.</string>
|
||||
<string name="media_hidden">Klepnutím zobrazit</string>
|
||||
<string name="avatar_description">Jít na profil %s</string>
|
||||
<string name="more_options">Více možností</string>
|
||||
<string name="reveal_content">Zobrazit obsah</string>
|
||||
<string name="hide_content">Skrýt obsah</string>
|
||||
<string name="new_post">Nový příspěvek</string>
|
||||
<string name="button_reply">Odpovědět</string>
|
||||
<string name="button_reblog">Boostnout</string>
|
||||
<string name="button_favorite">Oblíbit</string>
|
||||
<string name="button_share">Sdílet</string>
|
||||
<string name="media_no_description">Média bez popisu</string>
|
||||
<string name="add_media">Přidat média</string>
|
||||
<string name="add_poll">Přidat anketu</string>
|
||||
<string name="emoji">Emoji</string>
|
||||
<string name="post_visibility">Viditelnost příspěvku</string>
|
||||
<string name="home_timeline">Domovská časová osa</string>
|
||||
<string name="my_profile">Můj profil</string>
|
||||
<string name="media_viewer">Prohlížeč médií</string>
|
||||
<string name="follow_user">Sledovat %s</string>
|
||||
<string name="unfollowed_user">Sledování %s ukončeno</string>
|
||||
<string name="followed_user">Nyní sledujete %s</string>
|
||||
<string name="open_in_browser">Otevřít v prohlížeči</string>
|
||||
<string name="hide_boosts_from_user">Skrýt boosty od %s</string>
|
||||
<string name="show_boosts_from_user">Zobrazit boosty od %s</string>
|
||||
<string name="signup_reason">proč se chcete připojit?</string>
|
||||
<string name="signup_reason_note">Toto nám pomůže posoudit vaši žádost.</string>
|
||||
<string name="clear">Vyčistit</string>
|
||||
<string name="profile_header">Obrázek v záhlaví</string>
|
||||
<string name="profile_picture">Profilová fotografie</string>
|
||||
<string name="reorder">Změnit pořadí</string>
|
||||
<string name="download">Stáhnout</string>
|
||||
<string name="permission_required">Vyžadováno oprávnění</string>
|
||||
<string name="storage_permission_to_download">Aplikace potřebuje přístup k vašemu úložišti, aby mohla uložit tento soubor.</string>
|
||||
<string name="open_settings">Otevřít nastavení</string>
|
||||
<string name="error_saving_file">Chyba při ukládání souboru</string>
|
||||
<string name="file_saved">Soubor uložen</string>
|
||||
<string name="downloading">Stahování…</string>
|
||||
<string name="no_app_to_handle_action">Nebyly nalezeny žádné aplikace pro tuto úlohu</string>
|
||||
<string name="local_timeline">Komunita</string>
|
||||
<string name="trending_posts_info_banner">Toto jsou příspěvky, které získávají pozornost ve vašem koutu Mastodonu.</string>
|
||||
<string name="trending_hashtags_info_banner">Toto jsou hashtagy, které získávají pozornost ve vašem koutu Mastodonu.</string>
|
||||
<string name="trending_links_info_banner">Toto jsou zprávy, které jsou nejvíce sdíleny ve vašem koutu Mastodonu.</string>
|
||||
<string name="local_timeline_info_banner">Toto jsou nejnovější příspěvky od lidí, kteří používají stejný server Mastodonu jako vy.</string>
|
||||
<string name="dismiss">Zavřít</string>
|
||||
<string name="see_new_posts">Zobrazit nové příspěvky</string>
|
||||
<string name="load_missing_posts">Načíst chybějící příspěvky</string>
|
||||
<string name="follow_back">Sledovat zpátky</string>
|
||||
<string name="button_follow_pending">Čekající</string>
|
||||
<string name="follows_you">Sleduje vás</string>
|
||||
<string name="manually_approves_followers">Ručně schvaluje sledující</string>
|
||||
<string name="current_account">Současný účet</string>
|
||||
<string name="log_out_account">Odhlásit %s</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<plurals name="x_followers">
|
||||
<item quantity="one">%,d sledující</item>
|
||||
<item quantity="few">%,d sledující</item>
|
||||
<item quantity="many">%,d sledujících</item>
|
||||
<item quantity="other">%,d sledujících</item>
|
||||
</plurals>
|
||||
<plurals name="x_following">
|
||||
<item quantity="one">%,d sledující</item>
|
||||
<item quantity="few">%,d sledování</item>
|
||||
<item quantity="many">%,d sledování</item>
|
||||
<item quantity="other">%,d sledování</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d oblíbený</item>
|
||||
<item quantity="few">%,d oblíbené</item>
|
||||
<item quantity="many">%,d oblíbených</item>
|
||||
<item quantity="other">%,d oblíbených</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="one">%,d boostnul</item>
|
||||
<item quantity="few">%,d boostnuli</item>
|
||||
<item quantity="many">%,d boostnulo</item>
|
||||
<item quantity="other">%,d boostnulo</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s přes %2$s</string>
|
||||
<string name="time_now">teď</string>
|
||||
<string name="post_info_reblogs">Reblogy</string>
|
||||
<string name="post_info_favorites">Oblíbené</string>
|
||||
<string name="edit_history">Historie změn</string>
|
||||
<string name="last_edit_at_x">Poslední úprava %s</string>
|
||||
<string name="time_just_now">právě teď</string>
|
||||
<plurals name="x_seconds_ago">
|
||||
<item quantity="one">Před 1 vteřinou</item>
|
||||
<item quantity="few">Před %d vteřinami</item>
|
||||
<item quantity="many">Před %d vteřinami</item>
|
||||
<item quantity="other">Před %d vteřinami</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_ago">
|
||||
<item quantity="one">před %d minutou</item>
|
||||
<item quantity="few">před %d minutami</item>
|
||||
<item quantity="many">před %d minutami</item>
|
||||
<item quantity="other">před %d minutami</item>
|
||||
</plurals>
|
||||
<string name="edited_timestamp">upraveno %s</string>
|
||||
<string name="edit_original_post">Původní příspěvek</string>
|
||||
<string name="edit_text_edited">Text upraven</string>
|
||||
<string name="edit_spoiler_added">Upozornění na obsah bylo přidáno</string>
|
||||
<string name="edit_spoiler_edited">Upozornění na obsah upraveno</string>
|
||||
<string name="edit_spoiler_removed">Upozornění na obsah odstraněno</string>
|
||||
<string name="edit_poll_added">Anketa přidána</string>
|
||||
<string name="edit_poll_edited">Anketa upravena</string>
|
||||
<string name="edit_poll_removed">Anketa odstraněna</string>
|
||||
<string name="edit_media_added">Média přidána</string>
|
||||
<string name="edit_media_removed">Média odstraněna</string>
|
||||
<string name="edit_media_reordered">Média přeřazena</string>
|
||||
<string name="edit_marked_sensitive">Označeno jako citlivé</string>
|
||||
<string name="edit_marked_not_sensitive">Označeno, že není citlivé</string>
|
||||
<string name="edit_multiple_changed">Příspěvek upraven</string>
|
||||
<string name="edit">Upravit</string>
|
||||
<string name="discard_changes">Zrušit změny?</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
</resources>
|
||||
|
||||
@@ -3,27 +3,27 @@
|
||||
<string name="get_started">Loslegen</string>
|
||||
<string name="log_in">Anmelden</string>
|
||||
<string name="next">Weiter</string>
|
||||
<string name="loading_instance">Lade Serverinformationen…</string>
|
||||
<string name="loading_instance">Instanzinformationen werden geladen …</string>
|
||||
<string name="error">Fehler</string>
|
||||
<string name="not_a_mastodon_instance">%s scheint keine Mastodon-Instanz zu sein.</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="preparing_auth">Bereite Authentifizierung vor…</string>
|
||||
<string name="finishing_auth">Authentifizierung erfolgt…</string>
|
||||
<string name="user_boosted">%s teilte</string>
|
||||
<string name="in_reply_to">Antwort auf %s</string>
|
||||
<string name="preparing_auth">Authentifizierung wird vorbereitet …</string>
|
||||
<string name="finishing_auth">Authentifizierung abschließen …</string>
|
||||
<string name="user_boosted">%s hat diesen Beitrag geteilt</string>
|
||||
<string name="in_reply_to">Antwort auf den Beitrag von %s</string>
|
||||
<string name="notifications">Benachrichtigungen</string>
|
||||
<string name="user_followed_you">ist dir gefolgt</string>
|
||||
<string name="user_followed_you">folgt dir jetzt</string>
|
||||
<string name="user_sent_follow_request">hat dir eine Folgeanfrage gesendet</string>
|
||||
<string name="user_favorited">hat deinen Beitrag favorisiert</string>
|
||||
<string name="notification_boosted">hat deinen Beitrag geteilt</string>
|
||||
<string name="poll_ended">Abstimmung beendet</string>
|
||||
<string name="time_seconds">%dSek.</string>
|
||||
<string name="time_minutes">%dMin.</string>
|
||||
<string name="time_hours">%d Std.</string>
|
||||
<string name="time_days">%d T</string>
|
||||
<string name="time_seconds">vor %d Sekunden</string>
|
||||
<string name="time_minutes">vor %d Minuten</string>
|
||||
<string name="time_hours">vor %d Stunden</string>
|
||||
<string name="time_days">vor %d Tagen</string>
|
||||
<string name="share_toot_title">Teilen</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="publish">Tröt</string>
|
||||
<string name="publish">Veröffentlichen</string>
|
||||
<string name="discard_draft">Entwurf verwerfen?</string>
|
||||
<string name="discard">Verwerfen</string>
|
||||
<string name="cancel">Abbrechen</string>
|
||||
@@ -32,15 +32,15 @@
|
||||
<item quantity="other">Follower</item>
|
||||
</plurals>
|
||||
<plurals name="following">
|
||||
<item quantity="one">Folgender</item>
|
||||
<item quantity="other">Folgende</item>
|
||||
<item quantity="one">Folge ich</item>
|
||||
<item quantity="other">Folge ich</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">Beitrag</item>
|
||||
<item quantity="other">Beiträge</item>
|
||||
</plurals>
|
||||
<string name="posts">Beiträge</string>
|
||||
<string name="posts_and_replies">Beiträge und Antworten</string>
|
||||
<string name="posts_and_replies">Beiträge & Antworten</string>
|
||||
<string name="pinned_posts">Angeheftet</string>
|
||||
<string name="media">Medien</string>
|
||||
<string name="profile_about">Über</string>
|
||||
@@ -48,26 +48,26 @@
|
||||
<string name="button_following">Folge ich</string>
|
||||
<string name="edit_profile">Profil bearbeiten</string>
|
||||
<string name="mention_user">%s erwähen</string>
|
||||
<string name="share_user">Teile das Profil von %s</string>
|
||||
<string name="share_user">%s teilen</string>
|
||||
<string name="mute_user">%s stummschalten</string>
|
||||
<string name="unmute_user">%s nicht mehr stummschalten</string>
|
||||
<string name="block_user">%s blockieren</string>
|
||||
<string name="unblock_user">%s nicht mehr blockieren</string>
|
||||
<string name="report_user">%s melden</string>
|
||||
<string name="block_domain">%s blockieren</string>
|
||||
<string name="unblock_domain">%s nicht mehr blockieren</string>
|
||||
<string name="block_domain">Domain %s blockieren</string>
|
||||
<string name="unblock_domain">Domain %s nicht mehr blockieren</string>
|
||||
<plurals name="x_posts">
|
||||
<item quantity="one">%,d Beitrag</item>
|
||||
<item quantity="other">%,d Beiträge</item>
|
||||
</plurals>
|
||||
<string name="profile_joined">Beigetreten</string>
|
||||
<string name="done">Fertig</string>
|
||||
<string name="loading">Lädt…</string>
|
||||
<string name="loading">wird geladen …</string>
|
||||
<string name="field_label">Beschriftung</string>
|
||||
<string name="field_content">Inhalt</string>
|
||||
<string name="saving">Speichern…</string>
|
||||
<string name="saving">Speichern …</string>
|
||||
<string name="post_from_user">Beitrag von %s</string>
|
||||
<string name="poll_option_hint">Auswahlmöglichkeit %d</string>
|
||||
<string name="poll_option_hint">%d. Auswahl</string>
|
||||
<plurals name="x_minutes">
|
||||
<item quantity="one">%d Minute</item>
|
||||
<item quantity="other">%d Minuten</item>
|
||||
@@ -98,23 +98,23 @@
|
||||
<item quantity="other">%d Tage verbleibend</item>
|
||||
</plurals>
|
||||
<plurals name="x_voters">
|
||||
<item quantity="one">%,d hat gewählt</item>
|
||||
<item quantity="other">%,d haben gewählt</item>
|
||||
<item quantity="one">%,d Stimme</item>
|
||||
<item quantity="other">%,d Stimmen</item>
|
||||
</plurals>
|
||||
<string name="poll_closed">Geschlossen</string>
|
||||
<string name="poll_closed">Beendet</string>
|
||||
<string name="confirm_mute_title">Konto stummschalten</string>
|
||||
<string name="confirm_mute">Bestätigen um %s stummzuschalten</string>
|
||||
<string name="confirm_mute">Bestätigen, um %s stummzuschalten</string>
|
||||
<string name="do_mute">Stummschalten</string>
|
||||
<string name="confirm_unmute_title">Konto nicht mehr stummschalten</string>
|
||||
<string name="confirm_unmute">Bestätigen, um %s nicht mehr stummzuschalten</string>
|
||||
<string name="do_unmute">Nicht mehr stummschalten</string>
|
||||
<string name="confirm_block_title">Konto blockieren</string>
|
||||
<string name="confirm_block_title">Konto sperren</string>
|
||||
<string name="confirm_block_domain_title">Domain blockieren</string>
|
||||
<string name="confirm_block">Bestätigen um %s zu blockieren</string>
|
||||
<string name="do_block">Blockieren</string>
|
||||
<string name="confirm_unblock_title">Konto nicht mehr blockieren</string>
|
||||
<string name="confirm_block">Bestätigen, um %s zu blockieren</string>
|
||||
<string name="do_block">Ja, blockieren</string>
|
||||
<string name="confirm_unblock_title">Konto nicht mehr sperren</string>
|
||||
<string name="confirm_unblock_domain_title">Domain nicht mehr blockieren</string>
|
||||
<string name="confirm_unblock">Bestätigen um %s nicht mehr zu blockieren</string>
|
||||
<string name="confirm_unblock">Bestätigen, um %s nicht mehr zu blockieren</string>
|
||||
<string name="do_unblock">Nicht mehr blockieren</string>
|
||||
<string name="button_muted">Stummgeschaltet</string>
|
||||
<string name="button_blocked">Blockiert</string>
|
||||
@@ -143,7 +143,7 @@
|
||||
<string name="search_hint">Suchen</string>
|
||||
<string name="hashtags">Hashtags</string>
|
||||
<string name="news">Nachrichten</string>
|
||||
<string name="for_you">Für dich</string>
|
||||
<string name="for_you">Vorschläge</string>
|
||||
<string name="all_notifications">Alle</string>
|
||||
<string name="mentions">Erwähnungen</string>
|
||||
<plurals name="x_people_talking">
|
||||
@@ -151,15 +151,15 @@
|
||||
<item quantity="other">%d Personen reden darüber</item>
|
||||
</plurals>
|
||||
<plurals name="discussed_x_times">
|
||||
<item quantity="one">%d mal diskutiert</item>
|
||||
<item quantity="other">%d mal diskutiert</item>
|
||||
<item quantity="one">%d Mal diskutiert</item>
|
||||
<item quantity="other">%d × auf dieser Mastodon-Instanz geteilt</item>
|
||||
</plurals>
|
||||
<string name="report_title">%s melden</string>
|
||||
<string name="report_choose_reason">Sag uns, was mit diesem Beitrag vor sich geht?</string>
|
||||
<string name="report_choose_reason">Was stimmt mit diesem Beitrag nicht?</string>
|
||||
<string name="report_choose_reason_account">Was ist los mit %s?</string>
|
||||
<string name="report_choose_reason_subtitle">Wähle die beste Zugehörigkeit</string>
|
||||
<string name="report_reason_personal">Das gefällt mir nicht</string>
|
||||
<string name="report_reason_personal_subtitle">Das ist nicht etwas, was ihr nicht sehen wollt</string>
|
||||
<string name="report_choose_reason_subtitle">Wähle die bestmögliche Option aus</string>
|
||||
<string name="report_reason_personal">Der Beitrag gefällt mir nicht</string>
|
||||
<string name="report_reason_personal_subtitle">Den Inhalt kann man nicht allen zumuten</string>
|
||||
<string name="report_reason_spam">Das ist Spam</string>
|
||||
<string name="report_reason_spam_subtitle">Bösartige Links, gefälschtes Engagement oder wiederholte Antworten</string>
|
||||
<string name="report_reason_violation">Es verstößt gegen Serverregeln</string>
|
||||
@@ -172,27 +172,27 @@
|
||||
<string name="report_choose_posts_subtitle">Alles Zutreffende auswählen</string>
|
||||
<string name="report_comment_title">Gibt es etwas anderes, was wir wissen sollten?</string>
|
||||
<string name="report_comment_hint">Zusätzliche Kommentare</string>
|
||||
<string name="sending_report">Sende Meldung…</string>
|
||||
<string name="report_sent_title">Vielen Dank für die Berichterstattung, wir werden uns damit befassen.</string>
|
||||
<string name="sending_report">Bericht wird gesendet …</string>
|
||||
<string name="report_sent_title">Vielen Dank für die Meldung, wir werden uns damit befassen.</string>
|
||||
<string name="report_sent_subtitle">Während wir dies überprüfen, kannst du gegen %s vorgehen.</string>
|
||||
<string name="unfollow_user">%s entfolgen</string>
|
||||
<string name="unfollow">Entfolgen</string>
|
||||
<string name="mute_user_explain">Du wirst die Beiträge vom Konto nicht mehr sehen. Das Konto kann dir immer noch folgen, und die Person hinter dem Konto wird deine Beiträge sehen können und nicht wissen, dass du sie stummgeschaltet hast.</string>
|
||||
<string name="mute_user_explain">Du wirst die eigenen und geteilten Beiträge des Kontos nicht mehr sehen können. Dass du das Profil stummgeschaltet hast, erfährt die Person hinter dem Account aber nicht.</string>
|
||||
<string name="block_user_explain">Du wirst die Beiträge von diesem Konto nicht sehen. Das Konto wird nicht in der Lage sein, deine Beiträge zu sehen oder dir zu folgen. Die Person hinter dem Konto wird wissen, dass du das Konto blockiert hast.</string>
|
||||
<string name="report_personal_title">Du willst das nicht mehr sehen?</string>
|
||||
<string name="report_personal_subtitle">Wenn du etwas auf Mastodon nicht sehen willst, kannst du den Nutzer aus deiner Erfahrung streichen.</string>
|
||||
<string name="back">Zurück</string>
|
||||
<string name="instance_catalog_title">Mastodon besteht aus Benutzern auf verschiedenen Servern.</string>
|
||||
<string name="instance_catalog_subtitle">Wähle einen Server basierend auf deinen Interessen, deiner Region oder einen Allgemeinen. Du kannst trotzdem mit jedem Interagieren, egal auf welchem Server.</string>
|
||||
<string name="search_communities">Nach Server suchen oder URL eingeben</string>
|
||||
<string name="instance_rules_title">Hier ein paar Regeln</string>
|
||||
<string name="instance_rules_subtitle">Nimm dir eine Minute Zeit und gehe kurz durch alle Regeln durch, die %s machen.</string>
|
||||
<string name="instance_catalog_subtitle">Wähle einen Server basierend auf deinen Interessen oder deiner Region – oder einfach einen allgemeinen. Du kannst trotzdem mit jedem interagieren, egal auf welchem Server.</string>
|
||||
<string name="search_communities">Server suchen oder Adresse eingeben</string>
|
||||
<string name="instance_rules_title">Einige Grundregeln</string>
|
||||
<string name="instance_rules_subtitle">Nimm dir eine Minute Zeit, und gehe kurz alle Regeln von %s durch.</string>
|
||||
<string name="signup_title">Okay, lass uns mit %s anfangen</string>
|
||||
<string name="edit_photo">bearbeiten</string>
|
||||
<string name="display_name">anzeigename</string>
|
||||
<string name="username">nutzername</string>
|
||||
<string name="display_name">Anzeigename</string>
|
||||
<string name="username">Nutzername</string>
|
||||
<string name="email">E-Mail</string>
|
||||
<string name="password">passwort</string>
|
||||
<string name="password">Passwort</string>
|
||||
<string name="password_note">Verwende Großbuchstaben, Sonderzeichen und Zahlen, um deine Passwortstärke zu erhöhen.</string>
|
||||
<string name="category_academia">Bildung</string>
|
||||
<string name="category_activism">Aktivismus</string>
|
||||
@@ -214,80 +214,81 @@
|
||||
<string name="resent_email">Bestätigungs-E-Mail gesendet</string>
|
||||
<string name="compose_hint">Was gibt\'s Neues?</string>
|
||||
<string name="content_warning">Inhaltswarnung</string>
|
||||
<string name="add_image_description">Bildbeschreibung hinzufügen…</string>
|
||||
<string name="retry_upload">Hochladen erneut versuchen</string>
|
||||
<string name="image_description">Bildbeschreibung</string>
|
||||
<string name="add_image_description">Füge eine Bildbeschreibung hinzu…</string>
|
||||
<string name="retry_upload">Upload erneut versuchen</string>
|
||||
<string name="image_upload_failed">Fehler beim Hochladen des Bildes</string>
|
||||
<string name="video_upload_failed">Fehler beim Hochladen des Videos</string>
|
||||
<string name="edit_image">Bild bearbeiten</string>
|
||||
<string name="save">Speichern</string>
|
||||
<string name="add_alt_text">Alternativtext hinzufügen</string>
|
||||
<string name="alt_text_subtitle">Alternativtext erscheint für blinde Menschen. Versuche, nur so viele Details einzubeziehen, um den Kontext zu verstehen.</string>
|
||||
<string name="alt_text_hint">z.B. Eine Giraffe auf einem Dreirad während sie eine Banane isst</string>
|
||||
<string name="add_alt_text">Bildbeschreibung hinzufügen</string>
|
||||
<string name="alt_text_subtitle">Die Bildbeschreibung („Alt-Text“) ist eine wichtige Unterstützung für blinde und sehbehinderte Menschen. Beschränke dich bei der Formulierung auf das nötigste, interpretiere nicht und beschreibe nur, was zu sehen ist, damit der Kontext verständlich ist und alle Menschen daran teilhaben können.</string>
|
||||
<string name="alt_text_hint">z. B. „Eine Giraffe auf einem Dreirad, während sie eine Banane isst.“</string>
|
||||
<string name="visibility_public">Öffentlich</string>
|
||||
<string name="visibility_unlisted">Nicht gelistet</string>
|
||||
<string name="visibility_followers_only">Nur Folgende</string>
|
||||
<string name="visibility_private">Nur Leute, die ich erwähne</string>
|
||||
<string name="visibility_followers_only">Nur Follower</string>
|
||||
<string name="visibility_private">Nur erwähnte Profile</string>
|
||||
<string name="search_all">Alle</string>
|
||||
<string name="search_people">Personen</string>
|
||||
<string name="recent_searches">Letzte Suchanfragen</string>
|
||||
<string name="step_x_of_n">Schritt %1$d von %2$d</string>
|
||||
<string name="skip">Überspringen</string>
|
||||
<string name="notification_type_follow">Neue Follower</string>
|
||||
<string name="notification_type_follow">Neue Folgende</string>
|
||||
<string name="notification_type_favorite">Favoriten</string>
|
||||
<string name="notification_type_reblog">Teilungen</string>
|
||||
<string name="notification_type_reblog">Geteilte Beiträge</string>
|
||||
<string name="notification_type_mention">Erwähnungen</string>
|
||||
<string name="notification_type_poll">Umfragen</string>
|
||||
<string name="choose_account">Konto auswählen</string>
|
||||
<string name="err_not_logged_in">Melde dich zuerst mit Mastodon an</string>
|
||||
<string name="err_not_logged_in">Bitte zuerst in Mastodon anmelden</string>
|
||||
<plurals name="cant_add_more_than_x_attachments">
|
||||
<item quantity="one">Du kannst nicht mehr als %d Medienanhang hinzufügen</item>
|
||||
<item quantity="other">Du kannst nicht mehr als %d Medienanhänge hinzufügen</item>
|
||||
<item quantity="one">Du kannst nicht mehr als %d Medien-Datei anhängen</item>
|
||||
<item quantity="other">Du kannst nicht mehr als %d Medien-Dateien anhängen</item>
|
||||
</plurals>
|
||||
<string name="media_attachment_unsupported_type">Datei %s wird nicht unterstützt</string>
|
||||
<string name="media_attachment_too_big">Die Dateigröße %1$s übersteigt das Uploadlimit von %2$s MB</string>
|
||||
<string name="settings_theme">Visuelle Darstellung</string>
|
||||
<string name="theme_auto">Automatisch</string>
|
||||
<string name="settings_theme">Design</string>
|
||||
<string name="theme_auto">Systembedingt</string>
|
||||
<string name="theme_light">Hell</string>
|
||||
<string name="theme_dark">Dunkel</string>
|
||||
<string name="theme_true_black">AMOLED-Modus</string>
|
||||
<string name="settings_behavior">Verhalten</string>
|
||||
<string name="settings_gif">Spiele animierte Avatare und Emojis ab</string>
|
||||
<string name="settings_custom_tabs">Verwende In-App-Browser</string>
|
||||
<string name="settings_behavior">App-Verhalten</string>
|
||||
<string name="settings_gif">Spiele animierte GIFs, Avatare und Emojis ab</string>
|
||||
<string name="settings_custom_tabs">In-App-Browser verwenden</string>
|
||||
<string name="settings_notifications">Benachrichtigungen</string>
|
||||
<string name="notify_me_when">Benachrichtige mich, wenn</string>
|
||||
<string name="notify_anyone">jeder</string>
|
||||
<string name="notify_follower">ein Follower</string>
|
||||
<string name="notify_followed">jemand, dem ich folge</string>
|
||||
<string name="notify_anyone">irgendjemand</string>
|
||||
<string name="notify_follower">ein Follower von mir</string>
|
||||
<string name="notify_followed">jemand, dem ich folge,</string>
|
||||
<string name="notify_none">niemand</string>
|
||||
<string name="notify_favorites">Meinen Beitrag favorisiert</string>
|
||||
<string name="notify_follow">Mir folgt</string>
|
||||
<string name="notify_reblog">Meinen Beitrag teilt</string>
|
||||
<string name="notify_mention">Mich erwähnt</string>
|
||||
<string name="settings_boring">Die Langeweile-Zone</string>
|
||||
<string name="notify_favorites">meinen Beitrag favorisiert</string>
|
||||
<string name="notify_follow">mir folgt</string>
|
||||
<string name="notify_reblog">einen meiner Beiträge geteilt hat</string>
|
||||
<string name="notify_mention">mich erwähnt</string>
|
||||
<string name="settings_boring">Langweiliges</string>
|
||||
<string name="settings_account">Kontoeinstellungen</string>
|
||||
<string name="settings_contribute">Zu Mastodon beitragen</string>
|
||||
<string name="settings_tos">AGBs</string>
|
||||
<string name="settings_privacy_policy">Datenschutzerklärung</string>
|
||||
<string name="settings_spicy">Die gefährliche Zone</string>
|
||||
<string name="settings_contribute">Bei Mastodon unterstützen und mitmachen</string>
|
||||
<string name="settings_tos">Nutzungsbedingungen</string>
|
||||
<string name="settings_privacy_policy">Datenschutzbestimmungen</string>
|
||||
<string name="settings_spicy">Gefährliches</string>
|
||||
<string name="settings_clear_cache">Medien-Cache leeren</string>
|
||||
<string name="settings_app_version">Mastodon für Android v%1$s (%2$d)</string>
|
||||
<string name="settings_app_version">Mastodos v%1$s (%2$d)</string>
|
||||
<string name="media_cache_cleared">Medien-Cache geleert</string>
|
||||
<string name="confirm_log_out">Bist du dir sicher, dass du dich abmelden möchtest?</string>
|
||||
<string name="sensitive_content">NSFW-Inhalt</string>
|
||||
<string name="sensitive_content_explain">Der Autor hat den Inhalt als NSFW markiert. Tippen zum Öffnen.</string>
|
||||
<string name="sensitive_content">Sensibler Inhalt</string>
|
||||
<string name="sensitive_content_explain">Der Autor hat den Inhalt als sensiblen Inhalt markiert. Tippen zum Anzeigen.</string>
|
||||
<string name="media_hidden">Tippen zum Öffnen</string>
|
||||
<string name="avatar_description">Gehe zu %s\'s Profil</string>
|
||||
<string name="avatar_description">Das Profil von %s öffnen</string>
|
||||
<string name="more_options">Mehr Optionen</string>
|
||||
<string name="reveal_content">Inhalt anzeigen</string>
|
||||
<string name="hide_content">Inhalt verstecken</string>
|
||||
<string name="new_post">Neuer Beitrag</string>
|
||||
<string name="button_reply">Antworten</string>
|
||||
<string name="button_reblog">Erneut teilen</string>
|
||||
<string name="button_reblog">Teilen</string>
|
||||
<string name="button_favorite">Favorisieren</string>
|
||||
<string name="button_share">Teilen</string>
|
||||
<string name="media_no_description">Medien ohne Beschreibung</string>
|
||||
<string name="add_media">Medien hinzufügen</string>
|
||||
<string name="mark_media_as_sensitive">Medien als NSFW markieren</string>
|
||||
<string name="add_poll">Umfrage hinzufügen</string>
|
||||
<string name="emoji">Emoji</string>
|
||||
<string name="post_visibility">Sichtbarkeit des Beitrages</string>
|
||||
@@ -297,54 +298,104 @@
|
||||
<string name="follow_user">%s folgen</string>
|
||||
<string name="unfollowed_user">%s entfolgt</string>
|
||||
<string name="followed_user">Du folgst nun %s</string>
|
||||
<string name="open_in_browser">Im Browser öffnen</string>
|
||||
<string name="hide_boosts_from_user">Verstecke Teilungen von %s</string>
|
||||
<string name="show_boosts_from_user">Zeige Teilungen von %s</string>
|
||||
<string name="signup_reason">Warum möchtest du beitreten?</string>
|
||||
<string name="open_in_browser">Beitrag im Browser öffnen</string>
|
||||
<string name="hide_boosts_from_user">Verberge geteilte Beiträge von %s</string>
|
||||
<string name="show_boosts_from_user">Zeige geteilte Beiträge von %s</string>
|
||||
<string name="signup_reason">Weshalb möchtest du beitreten?</string>
|
||||
<string name="signup_reason_note">Dies wird uns dabei helfen, deine Anmeldungsanfrage besser zu verarbeiten.</string>
|
||||
<string name="clear">Löschen</string>
|
||||
<string name="profile_header">Header-Bild</string>
|
||||
<string name="profile_header">Kopfbild</string>
|
||||
<string name="profile_picture">Profilbild</string>
|
||||
<string name="reorder">Neu sortieren</string>
|
||||
<string name="download">Herunterladen</string>
|
||||
<string name="permission_required">Berechtigung erforderlich</string>
|
||||
<string name="storage_permission_to_download">Die App benötigt Zugriff auf Ihren Speicher, um diese Datei zu speichern.</string>
|
||||
<string name="storage_permission_to_download">Die App benötigt Zugriff auf den Speicher deines Geräts, um diese Datei zu speichern.</string>
|
||||
<string name="open_settings">Einstellungen öffnen</string>
|
||||
<string name="error_saving_file">Fehler beim Speichern der Datei</string>
|
||||
<string name="file_saved">Datei gespeichert</string>
|
||||
<string name="downloading">Herunterladen…</string>
|
||||
<string name="downloading">wird heruntergeladen …</string>
|
||||
<string name="no_app_to_handle_action">Es gibt keine App, um diese Aktion auszuführen</string>
|
||||
<string name="local_timeline">Community</string>
|
||||
<string name="trending_posts_info_banner">Dies sind die Beiträge, die in deiner Mastodon-Bubble beliebter werden.</string>
|
||||
<string name="trending_hashtags_info_banner">Dies sind die Hashtags in deiner Mastodon-Bubble.</string>
|
||||
<string name="trending_links_info_banner">Dies sind die Nachrichten, die am meisten in deiner Mastodon-Bubble geteilt werden.</string>
|
||||
<string name="federated_timeline">Föderation</string>
|
||||
<string name="trending_posts_info_banner">Dies sind Beiträge, die auf deinem Mastodon-Server gerade angesagt sind.</string>
|
||||
<string name="trending_hashtags_info_banner">Dies sind Hashtags, die auf deinem Mastodon-Server gerade angesagt sind.</string>
|
||||
<string name="trending_links_info_banner">Dies sind journalistische Nachrichten, die auf deinem Mastodon-Server gerade am häufigsten geteilt werden.</string>
|
||||
<string name="local_timeline_info_banner">Dies sind die neuesten Beiträge der Leute, die den gleichen Mastodon-Server verwenden wie du.</string>
|
||||
<string name="federated_timeline_info_banner">Dies sind die neusten Beiträge der Leute, die in der Föderation deines Servers sind.</string>
|
||||
<string name="dismiss">Verwerfen</string>
|
||||
<string name="see_new_posts">Neue Beiträge anzeigen</string>
|
||||
<string name="load_missing_posts">Fehlende Beiträge laden</string>
|
||||
<string name="load_missing_posts">Weitere Beiträge laden</string>
|
||||
<string name="follow_back">Zurück folgen</string>
|
||||
<string name="button_follow_pending">Ausstehend</string>
|
||||
<string name="follows_you">Folgt dir</string>
|
||||
<string name="manually_approves_followers">Genehmigt Folgende manuell</string>
|
||||
<string name="current_account">Aktuelles Konto</string>
|
||||
<string name="log_out_account">Ausloggen</string>
|
||||
<string name="log_out_account">%s abmelden</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<plurals name="x_followers">
|
||||
<item quantity="one">%,d Follower</item>
|
||||
<item quantity="other">%,d Follower</item>
|
||||
</plurals>
|
||||
<plurals name="x_following">
|
||||
<item quantity="one">%,d Gefolgt</item>
|
||||
<item quantity="other">%,d Gefolgt</item>
|
||||
<item quantity="one">%,d folge ich</item>
|
||||
<item quantity="other">%,d folge ich</item>
|
||||
</plurals>
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d Favorit</item>
|
||||
<item quantity="one">%,d × favorisiert</item>
|
||||
<item quantity="other">%,d Favoriten</item>
|
||||
</plurals>
|
||||
<plurals name="x_reblogs">
|
||||
<item quantity="one">%,d Reblog</item>
|
||||
<item quantity="other">%,d Reblogs</item>
|
||||
<item quantity="one">%,d × geteilt</item>
|
||||
<item quantity="other">%,d × geteilt</item>
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s via %2$s</string>
|
||||
<string name="time_now">jetzt</string>
|
||||
<string name="post_info_reblogs">Geteilte Beiträge</string>
|
||||
<string name="post_info_favorites">Favoriten</string>
|
||||
<string name="edit_history">Verlauf bearbeiten</string>
|
||||
<string name="last_edit_at_x">Letzte Bearbeitung: %s</string>
|
||||
<string name="time_just_now">gerade eben</string>
|
||||
<plurals name="x_seconds_ago">
|
||||
<item quantity="one">vor %d Sekunde</item>
|
||||
<item quantity="other">vor %d Sekunden</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_ago">
|
||||
<item quantity="one">vor %d Minute</item>
|
||||
<item quantity="other">vor %d Minuten</item>
|
||||
</plurals>
|
||||
<string name="edited_timestamp">bearbeitet %s</string>
|
||||
<string name="edit_original_post">Ursprünglicher Beitrag</string>
|
||||
<string name="edit_text_edited">Text bearbeitet</string>
|
||||
<string name="edit_spoiler_added">Inhaltwarnung hinzugefügt</string>
|
||||
<string name="edit_spoiler_edited">Inhaltwarnung bearbeitet</string>
|
||||
<string name="edit_spoiler_removed">Inhaltwarnung entfernt</string>
|
||||
<string name="edit_poll_added">Umfrage hinzugefügt</string>
|
||||
<string name="edit_poll_edited">Umfrage bearbeitet</string>
|
||||
<string name="edit_poll_removed">Umfrage entfernt</string>
|
||||
<string name="edit_media_added">Medien hinzugefügt</string>
|
||||
<string name="edit_media_removed">Medien entfernt</string>
|
||||
<string name="edit_media_reordered">Medien umsortiert</string>
|
||||
<string name="edit_marked_sensitive">Als NSFW markiert</string>
|
||||
<string name="edit_marked_not_sensitive">Als nicht NSFW markiert</string>
|
||||
<string name="edit_multiple_changed">Beitrag bearbeitet</string>
|
||||
<string name="edit">Bearbeiten</string>
|
||||
<string name="discard_changes">Änderungen verwerfen?</string>
|
||||
<string name="upload_failed">Upload fehlgeschlagen</string>
|
||||
<string name="file_size_bytes">%d bytes</string>
|
||||
<string name="file_size_kb">%.2f KB</string>
|
||||
<string name="file_size_mb">%.2f MB</string>
|
||||
<string name="file_size_gb">%.2f GB</string>
|
||||
<string name="file_upload_progress">%1$s von %2$s</string>
|
||||
<string name="file_upload_time_remaining">%s verbleibend</string>
|
||||
<string name="upload_error_connection_lost">Dein Gerät hat gerade keinen Zugang zum Internet</string>
|
||||
<string name="upload_processing">wird verarbeitet …</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<string name="update_available">Mastodos %s kann nun heruntergeladen werden.</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<string name="update_ready">Mastodos %s wurde heruntergeladen und kann nun installiert werden.</string>
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">Download (%s)</string>
|
||||
<string name="install_update">Installieren</string>
|
||||
<string name="check_for_update">Auf Update prüfen</string>
|
||||
<string name="no_update_available">Kein Update verfügbar</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
</resources>
|
||||
|
||||
@@ -204,8 +204,6 @@
|
||||
<string name="content_warning">Advertencia de contenido</string>
|
||||
<string name="add_image_description">Añadir descripción de la imagen…</string>
|
||||
<string name="retry_upload">Reintentar subida</string>
|
||||
<string name="image_upload_failed">Error al subir la imagen</string>
|
||||
<string name="video_upload_failed">Error al subir el vídeo</string>
|
||||
<string name="edit_image">Editar imagen</string>
|
||||
<string name="save">Guardar</string>
|
||||
<string name="add_alt_text">Añadir texto alternativo</string>
|
||||
@@ -333,4 +331,37 @@
|
||||
</plurals>
|
||||
<string name="timestamp_via_app">%1$s a través de %2$s</string>
|
||||
<string name="time_now">ahora</string>
|
||||
<string name="post_info_reblogs">Reblogueos</string>
|
||||
<string name="post_info_favorites">Favoritos</string>
|
||||
<string name="edit_history">Editar historial</string>
|
||||
<string name="last_edit_at_x">Última edición: %s</string>
|
||||
<string name="time_just_now">justo ahora</string>
|
||||
<plurals name="x_seconds_ago">
|
||||
<item quantity="one">Hace %d segundo</item>
|
||||
<item quantity="other">Hace %d segundos</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_ago">
|
||||
<item quantity="one">Hace %d minuto</item>
|
||||
<item quantity="other">Hace %d minutos</item>
|
||||
</plurals>
|
||||
<string name="edited_timestamp">editado %s</string>
|
||||
<string name="edit_original_post">Publicación original</string>
|
||||
<string name="edit_text_edited">Texto editado</string>
|
||||
<string name="edit_spoiler_added">Advertencia de contenido añadida</string>
|
||||
<string name="edit_spoiler_edited">Advertencia de contenido editada</string>
|
||||
<string name="edit_spoiler_removed">Advertencia de contenido eliminada</string>
|
||||
<string name="edit_poll_added">Encuesta añadida</string>
|
||||
<string name="edit_poll_edited">Encuesta editada</string>
|
||||
<string name="edit_poll_removed">Encuesta eliminada</string>
|
||||
<string name="edit_media_added">Contenido multimedia añadido</string>
|
||||
<string name="edit_media_removed">Contenido multimedia eliminado</string>
|
||||
<string name="edit_media_reordered">Contenido multimedia reordenado</string>
|
||||
<string name="edit_marked_sensitive">Marcado como sensible</string>
|
||||
<string name="edit_marked_not_sensitive">Marcado como no sensible</string>
|
||||
<string name="edit_multiple_changed">Publicación editada</string>
|
||||
<string name="edit">Editar</string>
|
||||
<string name="discard_changes">¿Descartar cambios?</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
</resources>
|
||||
|
||||
@@ -184,8 +184,6 @@
|
||||
<string name="content_warning">Edukiaren abisua</string>
|
||||
<string name="add_image_description">Gehitu irudiaren deskribapena…</string>
|
||||
<string name="retry_upload">Saiatu berriro igotzen</string>
|
||||
<string name="image_upload_failed">Irudia ezin izan da kargatu</string>
|
||||
<string name="video_upload_failed">Bideoa ezin izan da kargatu</string>
|
||||
<string name="edit_image">Editatu irudia</string>
|
||||
<string name="save">Gorde</string>
|
||||
<string name="add_alt_text">Gehitu ordezko testua</string>
|
||||
@@ -258,4 +256,7 @@
|
||||
<string name="file_saved">Fitxategia gorde da</string>
|
||||
<string name="downloading">Jeisten…</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
</resources>
|
||||
|
||||
@@ -49,4 +49,7 @@
|
||||
<string name="downloading">Ladataan…</string>
|
||||
<string name="follow_back">Seuraa takaisin</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<!-- %s is file size -->
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user