Compare commits

..

73 Commits

Author SHA1 Message Date
LucasGGamerM
90bef7fddb Merge branch 'material3_dynamic_color_theme' 2022-12-10 13:18:06 -03:00
LucasGGamerM
c1b382ef34 Fixing some minor inconsistencies 2022-12-10 13:17:28 -03:00
LucasGGamerM
028b88aa24 Bumping version code 2022-12-10 13:02:28 -03:00
LucasGGamerM
9d0ce33f5e Making material you setting work fine. Its ready for release 2022-12-10 12:59:24 -03:00
LucasGGamerM
dbb23d952c Adding the color things 2022-12-10 11:58:29 -03:00
LucasGGamerM
7fe7e47d53 Updating the readme again 2022-12-10 10:21:15 -03:00
LucasGGamerM
d0c93dfd4d Updating the readme 2022-12-10 10:18:19 -03:00
LucasGGamerM
acdccaf80a Bumping version code 2022-12-10 10:01:57 -03:00
LucasGGamerM
769293ce1a Changing translate icon 2022-12-10 09:56:53 -03:00
LucasGGamerM
8d0fe18b70 Fixing the notification color, and app name 2022-12-10 09:33:36 -03:00
LucasGGamerM
6926432a6c Bumping version number 2022-12-09 20:10:06 -03:00
LucasGGamerM
83f12b0840 Fixing the sk references. Readding them for easier translation 2022-12-09 19:58:33 -03:00
LucasGGamerM
290b7db7e4 This is unbrokey. Merged changes from upstream. 2022-12-09 19:35:15 -03:00
LucasGGamerM
f352c20ed9 This is quite brokey 2022-12-09 19:20:47 -03:00
LucasGGamerM
2ccbffa165 Merge branch 'main' into fork
# Conflicts:
#	README.md
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	mastodon/src/main/res/color/button_text_primary_light_on_dark.xml
#	mastodon/src/main/res/drawable/logo.xml
#	mastodon/src/main/res/layout/item_settings_color_picker.xml
#	mastodon/src/main/res/menu/color_picker.xml
#	mastodon/src/main/res/values-night/styles.xml
#	mastodon/src/main/res/values-v27/colors.xml
#	mastodon/src/main/res/values/colors.xml
#	mastodon/src/main/res/values/strings.xml
#	mastodon/src/main/res/values/styles.xml
2022-12-09 19:08:35 -03:00
LucasGGamerM
06cd80a352 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java
2022-12-09 18:35:52 -03:00
LucasGGamerM
de97493e6a Update README.md 2022-12-09 14:04:38 -03:00
LucasGGamerM
3a24ff0d15 Bumping version code 2022-12-09 13:44:46 -03:00
LucasGGamerM
c463a3fc39 The front end if finally ready! 2022-12-09 13:42:29 -03:00
LucasGGamerM
fc845685cc Almost there 2022-12-09 12:25:17 -03:00
LucasGGamerM
0ef0aa1a44 The api side is finally working perfectly! 2022-12-09 10:30:24 -03:00
LucasGGamerM
337689aa45 The api side is actually working! 2022-12-08 21:17:11 -03:00
LucasGGamerM
f7e3423f9c Bumping version number 2022-12-08 15:16:22 -03:00
LucasGGamerM
b465c09cc8 Fixing the "Megalodon" name on logging into a new account 2022-12-08 15:01:46 -03:00
LucasGGamerM
ac6c0651d6 Update README.md 2022-12-07 14:32:42 -03:00
LucasGGamerM
18af6f5a12 Bumping version code 2022-12-07 14:24:52 -03:00
LucasGGamerM
d11ee3a702 Update README.md 2022-12-07 14:16:48 -03:00
LucasGGamerM
6d9f9ce2d2 Update README.md 2022-12-07 14:14:34 -03:00
LucasGGamerM
ec1496a4cc Editing the readme 2022-12-07 14:08:28 -03:00
LucasGGamerM
41e19185e8 Edited the color of the new instance search box 2022-12-07 14:06:28 -03:00
LucasGGamerM
e15dd6024f Bumped version number 2022-12-07 13:28:07 -03:00
LucasGGamerM
e52dffeece Fix notification logo and lets start splash screen button color 2022-12-07 12:46:26 -03:00
LucasGGamerM
5b85bb427d bumping version code 2022-12-07 09:50:03 -03:00
LucasGGamerM
4d62388617 Fixing the notification icon once again... 2022-12-07 09:36:50 -03:00
LucasGGamerM
04b8055474 Fixing the notification icon once more 2022-12-07 09:22:18 -03:00
LucasGGamerM
3c34b6a7d2 Upping the version code once more, and fixing the self updater 2022-12-06 22:06:49 -03:00
LucasGGamerM
de4964c2cd Upping the version code and changing notification icon. This should be the first release 2022-12-06 21:58:35 -03:00
LucasGGamerM
fbcaa05c03 Changing the name of the archivesBaseName 2022-12-06 21:49:46 -03:00
LucasGGamerM
883f28696e Editing a while lot of files. New icon, new notification icon, a bunch of new stuff! 2022-12-06 21:21:43 -03:00
LucasGGamerM
df52230837 Editing the readme just in case 2022-12-06 19:23:28 -03:00
LucasGGamerM
a90f26a37a Setting update client 2022-12-06 19:20:47 -03:00
LucasGGamerM
8c1f76d7fa Initial Moshidon "release" 2022-12-06 19:17:07 -03:00
LucasGGamerM
f384d44f8f Changing my app id 2022-12-06 13:03:26 -03:00
LucasGGamerM
4ab6ed55f5 Changing my version number and string 2022-12-06 12:41:03 -03:00
LucasGGamerM
cf99bf5152 Merge branch 'proper_implementation_of_the_color_picker'
Just fixing stuff here and there
2022-12-05 17:47:03 -03:00
LucasGGamerM
10779717cf Make follow requests icon badge follow the color scheme and also make it that the profile top bar menu also follows the theme. This should be it 2022-12-05 17:20:40 -03:00
LucasGGamerM
4e5c2a9ecf add new megalodon logo text
closes #129
2022-12-05 18:29:03 +01:00
LucasGGamerM
db4c1bfe47 Merge branch 'proper_implementation_of_the_color_picker'
Just making it tidy and better :D
2022-12-05 14:11:20 -03:00
LucasGGamerM
27afba1cf2 Making it so that the boost icon is also following the theme when clicked 2022-12-05 14:10:34 -03:00
LucasGGamerM
4895425b40 Adding a proper logo to the top of the home timeline 2022-12-05 13:27:09 -03:00
LucasGGamerM
004c414fba Editing some drawable files to make them also follow the theme 2022-12-05 09:41:56 -03:00
LucasGGamerM
c8e38b134c Fixing weird bug with the buttons for the second time 2022-12-05 09:03:48 -03:00
LucasGGamerM
de5a911286 Fixing weird bug with the buttons 2022-12-04 22:52:27 -03:00
LucasGGamerM
606cd7442e Make it so that the publish button also follows the theme 2022-12-04 14:45:42 -03:00
LucasGGamerM
3ebc972268 Fixing the TrueBlack themes for everything 2022-12-04 14:00:10 -03:00
LucasGGamerM
4e39bb381c Making it so that the fab follows the theme 2022-12-04 13:14:38 -03:00
LucasGGamerM
b6178681b0 Adding yellow theme 2022-12-04 11:42:41 -03:00
LucasGGamerM
29abf70cec Adding orange theme, tweaking the blue and green theme 2022-12-04 11:16:58 -03:00
LucasGGamerM
8d63be513d Fix readability issue on the light blue theme 2022-12-04 10:24:51 -03:00
LucasGGamerM
e63b9d0dd6 Adding an icon to the color picker setting 2022-12-03 22:29:41 -03:00
LucasGGamerM
b1fda17ac7 Make badged settings icon follow accent colors 2022-12-03 16:48:12 -03:00
LucasGGamerM
bad44b145c Adding blue theme and refactoring styles.xml 2022-12-03 16:25:28 -03:00
LucasGGamerM
77669cedf6 More polishes over the green theme 2022-12-03 13:44:40 -03:00
LucasGGamerM
19238c389f Making the green theme more readable 2022-12-03 12:29:51 -03:00
LucasGGamerM
1747ff98b5 Adding a green theme 2022-12-02 14:00:58 -03:00
LucasGGamerM
8fa5824e3e Disabling the icons for the color picker menu 2022-12-02 11:58:40 -03:00
LucasGGamerM
6a674d7a7e Polishes 2022-12-01 19:55:53 -03:00
LucasGGamerM
dad3b8cd6b Proper implementation on the color picker. 2022-12-01 19:42:21 -03:00
LucasGGamerM
9179d2198d Fully fixed now, it should be ready to release. 2022-11-29 13:09:32 -03:00
LucasGGamerM
d096bef234 Fixing the fix of the bug I found. 2022-11-28 21:30:32 -03:00
LucasGGamerM
f2c47a1b84 Fixing another bug I found. 2022-11-28 21:24:00 -03:00
LucasGGamerM
bc2ac4e915 Fixed a few bugs from the earlier commit. 2022-11-28 16:47:04 -03:00
LucasGGamerM
ff215412c8 Adding mastodon original colors toggle. Partially fixing #90 2022-11-28 15:40:29 -03:00
224 changed files with 1853 additions and 4436 deletions

101
README.md
View File

@@ -1,23 +1,30 @@
![Pink logo with pink shark](mastodon/src/main/res/mipmap-xhdpi/ic_launcher_round.png)
# Megalodon
# Moshidon
[![Translation status](https://translate.codeberg.org/widgets/megalodon/-/svg-badge.svg)](https://translate.codeberg.org/engage/megalodon/)
 
[![Download latest release](https://img.shields.io/badge/dynamic/json?color=d92aad&label=Download%20APK&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2Fsk22%2Fmegalodon%2Freleases%2Flatest&style=flat)](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
> A fork of [megalodon](https://github.com/sk22/megalodon) which is a fork of [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly wont ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
&nbsp;
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk"><img height="50" alt="Get it on F-Droid" src="img/f-droid-badge.png"></a>
> A fork of the [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly wont ever be implemented, such as the federated timeline, unlisted posting and an image description viewer.
[![Download latest release](https://img.shields.io/badge/dynamic/json?color=d92aad&label=download%20apk&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2Fsk22%2Fmastodon-android-fork%2Freleases%2Flatest&style=for-the-badge)](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
---
## Key features
### **Translate button**
**Allows you to translate posts in instances with the translate feature!**
**Screenshots**
![Screenshot_20221209-135457_1](https://user-images.githubusercontent.com/71328265/206753830-cdb8bc65-7732-4a6a-8bcd-bbc4ca311d19.png)
![Screenshot_20221209-135409_1](https://user-images.githubusercontent.com/71328265/206753831-7af92a48-d7a5-4780-9beb-90acef4e141b.png)
### **Color themes**
**Allows you to change theme within the app. Supports Purple, pink, green, blue, orange and yellow!**
### **Unlisted posting**
**Allows you to post publicly without having your post show up in trends, hashtags or public timelines (i.e., in the tabs “Local”, “Community” and “Posts”).**
@@ -46,63 +53,29 @@ This is important to **ensure the content youre sharing is as accessible as p
On the Fediverse, its quite common for people to pin posts they want others to read before following them. You can pin/unpin posts yourself by clicking the `⋯` button in the top right corner of your posts.
### **Bookmarks**
**They allow for quickly saving posts and viewing them through the Bookmarks button on the top right of your profile.**
To bookmark a post, press the button between the Favorite and Share buttons on the bottom of the post. Bookmarks are saved privately, so the post authors wont know you saved their post the list of bookmarked posts is only visible to you.
## Installation
### From app stores
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Moshidon will automatically notify you about new updates inside the app.**
* **[Izzy's F-Droid repository](https://apt.izzysoft.de/fdroid/repo)**: [apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
To install this app on your Android device, download the [latest release from GitHub](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.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/LucasGGamerM/moshidon/releases) page.
Note that you'll need to add Izzy's F-Droid repository to your F-Droid app first:
`https://apt.izzysoft.de/fdroid/repo`
Moshidon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)s automatic update checker. Megalodon 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!
* **[Google Play Store](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)**: [play.google.com/store/apps/details?id=org.joinmastodon.android.sk](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)
* **[F-Droid.org](https://f-droid.org)?** Not yet, sorry!
If you want, you can help me figure out if something's missing in the [Issue #47: F-Droid.org](https://github.com/sk22/megalodon/issues/47)
### Directly from GitHub
Press the download button to download the APK. Open the downloaded file on your Android device to install it. Megalodon will automatically notify you about new updates inside the app.
[![Download latest release](https://img.shields.io/badge/dynamic/json?color=d92aad&label=Download%20APK&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2Fsk22%2Fmegalodon%2Freleases%2Flatest&style=flat)](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
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/megalodon/releases) page.
Megalodon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)s automatic update checker. Megalodon 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!
---
## Release variants
All downloads can be found on the [Releases](https://github.com/sk22/megalodon/releases) page.
All downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
**`megalodon.apk`**
Variant with an integrated updater. If you download Megalodon from here (and not from an app store), just download the regular `megalodon.apk`.
**`upstream-1234abc.apk`**
This is an **unmodified version** of the official [Mastodon for Android](https://github.com/mastodon/mastodon-android) app the respective Megalodon release is based on. Should you find any bugs in Megalodon (which you will), try to see if it occurs with this variant, too. The last 7 digits of the file name are important to know which version of the official app you're using.
<!-- **`megalodon-fdroid.apk`**
Variant without the integrated updater. This is the variant to be published to F-Droid.org where an integrated updater is not necessary. -->
---
## Contribution
### Translation
As with the source code, the translation is sourced from the official project, which you can contribute to on the official “**Mastodon for Android**” Crowdin project: https://crowdin.com/project/mastodon-for-android
There's also a handful of custom strings exclusive to this projects that would need to be translated. You can help translate **Megalodon** on Weblate: https://translate.codeberg.org/projects/megalodon/
[![Translation status](https://translate.codeberg.org/widgets/megalodon/-/horizontal-auto.svg)](https://translate.codeberg.org/engage/megalodon/)
**`moshidon.apk`**
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
---
@@ -131,10 +104,6 @@ There's also a handful of custom strings exclusive to this projects that would n
* [Show visibility of original post when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-reply-visibility)
* [Clickable reply/boost line above posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:clickable-boost-reply-line)
* [Clickable reply line while replying to open original post](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/clickable-reply-line-compose)
* [Add push notification setting for post notifications](https://github.com/sk22/megalodon/commit/b190480d7739be47f23543d9e7644660f9b4b4ee)
* [Add option to allow voting for multiple options on polls](https://github.com/sk22/megalodon/commit/5b28468efd49387b4f8b83f142f3adf3104ca60c)
* [Add translate function](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/translate-button)
* [Add language selector](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/language-selector)
### Behavior
@@ -146,12 +115,6 @@ There's also a handful of custom strings exclusive to this projects that would n
* [Option to hide interaction numbers](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/hide-interaction-numbers)
* [Option to always reveal content warnings](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
* [Option to disable scrolling title bars](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/disable-marquee)
* [No ellipsis for long poll answers](https://github.com/mastodon/mastodon-android/commit/c9aae828e2518adccdc092e41f8d1f0489636271)
* [Show poll vote button for multiple and single answer polls](https://github.com/mastodon/mastodon-android/commit/e14dfda2fdf32f0fa3043504ac5831683a87559a)
* [Show own vote after voting](https://github.com/mastodon/mastodon-android/commit/4ab9e25fec4fd9c10b7a8ddd1be522b3cc12cf28) ([Closes issue](https://github.com/mastodon/mastodon-android/commit/4ab9e25fec4fd9c10b7a8ddd1be522b3cc12cf28))
* [Make inline emoji search case-insensitive and don't only search from start of emoji names](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:better-inline-emoji-search) ([Pull request](https://github.com/mastodon/mastodon-android/pull/445))
* [Include subject line when sharing e.g. a website to Megalodon](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:external-share-include-subject)
* [Improve semantics for voting on polls (radio buttons and checkboxes)](https://github.com/sk22/megalodon/commit/6fd58c96827cb1d2da329cebdc170a1425dd18d7)
### Visual
@@ -159,12 +122,6 @@ There's also a handful of custom strings exclusive to this projects that would n
* [Custom extended footer redesign](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:compact-extended-footer)
* [Improvements to the true black mode](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:true-black-improvements)
* [Profile header tweaks](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:ui/profile-header-tweaks)
* [Custom color themes](https://github.com/sk22/megalodon/pull/124) by [@LucasGGamerM](https://github.com/LucasGGamerM)
* [Custom "megalodon" text logo](https://github.com/sk22/megalodon/commit/563afd487ca5c608cfbb00fa3909d3c27384acc0) by [@LucasGGamerM](https://github.com/LucasGGamerM)
* [Custom login screen](https://github.com/sk22/megalodon/commit/9bbf8c4618dbe13accaeb3b5482bf3fe88cac4c0)
* [More distinct filled boost icon](https://github.com/sk22/megalodon/commits/more-distinct-filled-boost-icon)
* [Material You color theme] by [@LucasGGamerM](https://github.com/LucasGGamerM)
* [Animations for interaction buttons](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/animate-buttons)
## Building
@@ -181,4 +138,4 @@ This project is released under the [GPL-3 License](./LICENSE).
## Links
<a rel="me" href="https://floss.social/@megalodon">@megalodon<wbr>@floss.social</a>
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>

View File

@@ -0,0 +1,16 @@
Mastodon je největší decentralizovanou sociální sítí na internetu. Místo jediné webové stránky je to síť pro miliony uživatelů v nezávislých komunitách, ve kterých 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 přispívají na Mastodon!
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 chronologické časové ose bez reklam. Vyjádřete se pomocí vlastních emoji, obrázků, GIFů, videí a zvuku v 500-znakových příspěvcích. Odpovězte na vlákna a boostujte 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 je postaven se zaměřením na soukromí a bezpečnost. Rozhodněte, zda jsou vaše příspěvky sdíleny se vašimi sledujícími, jen s lidmi, které zmíníte, 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 nahlašovací nástroje pomáhácí předcházení zneužití.
Více funkcí:
• Tmavý režim: Čtěte příspěvky ve světlém, tmavém nebo pravém černém režimu
• Ankety: Požádejte sledující o jejich názor a sečtěte jejich hlasy
• Objevit: Populární hashtagy a účty jsou pryč na jedno klepnutí
• Oznámení: Dostávejte oznámení o nových sledujících, odpovědích a boostech
• 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 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 máme v plánu to udržet.

View File

@@ -0,0 +1,16 @@
Mastodon ist das größte dezentralisierte soziale Netzwerk im Internet. Statt einer einzigen Webseite ist es ein Netzwerk von Millionen von Benutzer*innen in unabhängigen Gemeinschaften, die alle miteinander interagieren können. Egal, was du magst, auf Mastodon kannst du begeisterte Menschen 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 eigenen Emojis, Bildern, GIFs, Videos und Klängen in 500-Zeichen-Beiträgen aus. Antworte auf Themen und teile Beiträge von anderen, um tolle Dinge zu verbreiten. Finde neue Konten 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 bedenklichen Inhalten 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.
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: 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 Monetarisierung und kein Risikokapital und so soll es auch bleiben.

View File

@@ -0,0 +1,16 @@
Mastodon is the largest decentralized social network on the internet. Instead of a single website, its a network of millions of users in independent communities that can all interact with one another, seamlessly. No matter what youre into, you can meet passionate people posting about it on Mastodon!
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.
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.
More features:
• 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
Mastodon is a registered nonprofit and development is supported directly by your donations. Theres no advertising, no monetization, and no venture capital, and we plan to keep it that way.

View File

@@ -0,0 +1 @@
Decentralized social network

View File

@@ -0,0 +1 @@
Mastodon

View File

@@ -0,0 +1,16 @@
A Mastodon a legnagyobb decentralizált közösségi hálózat az interneten. Egyetlen weboldal helyett, ez több millió felhasználóból álló, független közösségek hálózata, amelyek egymással kapcsolatba tudnak lépni, zökkenőmentesen. Nem számít, mi a hobbid, a Mastodonon találkozhatsz róla posztoló lelkes emberekkel!
Csatlakozz egy közösséghez és készítsd el a profilodat. Keress és kövess lenyűgöző embereket, és olvasd egy reklámmentes, kronologikus idővonalon a bejegyzéseiket. Fejezd ki magad egyedi hangulatjelekkel, képekkel, GIFekkel, videókkal és hanggal, 500 karakter hosszúságú posztokban. Reply to threads and reblog posts from anyone to share great stuff. Fedezz fel új fiókokat amiket követhetsz és felkapott hashtageket, hogy bővíthesd a kapcsolataidat.
A Mastodon az adatvédelemre és a biztonságra összpontosítva épült. Döntsd el, hogy a posztjaidat csak a követőiddel, csak azokkal akiket megemlítesz, vagy az egész világgal osztod meg. 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.
More features:
• 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
Mastodon is a registered nonprofit and development is supported directly by your donations. Theres no advertising, no monetization, and no venture capital, and we plan to keep it that way.

View File

@@ -0,0 +1 @@
Decentralizált szociális hálózat

View File

@@ -0,0 +1 @@
Mastodon

View File

@@ -5,14 +5,17 @@ plugins {
android {
compileSdk 33
defaultConfig {
archivesBaseName = "megalodon"
applicationId "org.joinmastodon.android.sk"
archivesBaseName = "moshidon"
applicationId "org.joinmastodon.android.moshinda"
minSdk 23
targetSdk 33
versionCode 60
versionName "1.1.5+fork.60"
versionCode 62
versionName "1.1.4+fork.62.moshinda"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
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 {
@@ -77,4 +80,4 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -111,7 +111,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
private void actuallyCheckForUpdates(){
Request req=new Request.Builder()
.url("https://api.github.com/repos/sk22/megalodon/releases/latest")
.url("https://api.github.com/repos/LucasGGamerM/moshidon/releases/latest")
.build();
Call call=MastodonAPIController.getHttpClient().newCall(req);
try(Response resp=call.execute()){
@@ -144,7 +144,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
for(JsonElement el:obj.getAsJsonArray("assets")){
JsonObject asset=el.getAsJsonObject();
if("megalodon.apk".equals(asset.get("name").getAsString()) && "application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
if("moshidon.apk".equals(asset.get("name").getAsString()) && "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();

View File

@@ -14,12 +14,12 @@
<application
android:name=".MastodonApp"
android:allowBackup="true"
android:label="@string/sk_app_name"
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"
android:theme="@style/Theme.Mastodon.AutoLightDark.Original"
android:largeHeap="true">
<activity android:name=".MainActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize" android:launchMode="singleTask">
@@ -33,7 +33,7 @@
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="megalodon-android-auth" android:host="callback"/>
<data android:scheme="moshidon-android-auth" android:host="callback"/>
</intent-filter>
</activity>
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -52,7 +52,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
Intent intent=getIntent();
StringBuilder builder=new StringBuilder();
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) builder.append(intent.getStringExtra(Intent.EXTRA_SUBJECT)).append("\n\n");
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) builder.append(intent.getStringExtra(Intent.EXTRA_SUBJECT)).append("\n");
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
String text=builder.toString();
List<Uri> mediaUris;

View File

@@ -1,20 +1,8 @@
package org.joinmastodon.android;
import static org.joinmastodon.android.api.MastodonAPIController.gson;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GlobalUserPreferences{
public static boolean playGifs;
public static boolean useCustomTabs;
@@ -30,18 +18,10 @@ public class GlobalUserPreferences{
public static ThemePreference theme;
public static ColorPreference color;
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
public static Map<String, List<String>> recentLanguages;
private static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
}
private static <T> T fromJson(String json, Type type, T orElse) {
try { return gson.fromJson(json, type); }
catch (JsonSyntaxException ignored) { return orElse; }
}
public static void load(){
SharedPreferences prefs=getPrefs();
playGifs=prefs.getBoolean("playGifs", true);
@@ -56,14 +36,7 @@ public class GlobalUserPreferences{
disableMarquee=prefs.getBoolean("disableMarquee", false);
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>());
try {
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
} catch (IllegalArgumentException|ClassCastException ignored) {
// invalid color name or color was previously saved as integer
color=ColorPreference.PINK;
}
color=ColorPreference.values()[prefs.getInt("color", 1)];
}
public static void save(){
@@ -79,21 +52,18 @@ public class GlobalUserPreferences{
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
.putBoolean("disableMarquee", disableMarquee)
.putInt("theme", theme.ordinal())
.putString("color", color.name())
.putString("recentLanguages", gson.toJson(recentLanguages))
.putInt("color", color.ordinal())
.apply();
}
public enum ColorPreference{
MATERIAL3,
PINK,
PURPLE,
GREEN,
BLUE,
BROWN,
RED,
ORANGE,
YELLOW
YELLOW,
MATERIAL3
}
public enum ThemePreference{
@@ -102,4 +72,3 @@ public class GlobalUserPreferences{
DARK
}
}

View File

@@ -14,9 +14,9 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.SplashFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
@@ -33,7 +33,7 @@ public class MainActivity extends FragmentStackActivity{
if(savedInstanceState==null){
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
showFragmentClearingBackStack(new SplashFragment());
}else{
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
AccountSession session;

View File

@@ -64,7 +64,7 @@ public class OAuthActivity extends Activity{
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account account){
AccountSessionManager.getInstance().addAccount(instance, token, account, app, null);
AccountSessionManager.getInstance().addAccount(instance, token, account, app, true);
progress.dismiss();
finish();
// not calling restartMainActivity() here on purpose to have it recreated (notice different flags)

View File

@@ -143,7 +143,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
.setShowWhen(true)
.setCategory(Notification.CATEGORY_SOCIAL)
.setAutoCancel(true)
.setColor(context.getColor(R.color.primary_700));
.setColor(context.getColor(R.color.shortcut_icon_background));
if(avatar!=null){
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
}

View File

@@ -370,7 +370,7 @@ public class PushSubscriptionManager{
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
if(session.pushSubscription==null || forceReRegister)
session.getPushSubscriptionManager().registerAccountForPush(session.pushSubscription);
else
else if(session.needUpdatePushSettings)
session.getPushSubscriptionManager().updatePushSettings(session.pushSubscription);
}
}

View File

@@ -11,7 +11,6 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.model.Status;
import java.util.HashMap;
import java.util.function.Consumer;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -26,7 +25,7 @@ public class StatusInteractionController{
this.accountID=accountID;
}
public void setFavorited(Status status, boolean favorited, Consumer<Status> cb){
public void setFavorited(Status status, boolean favorited){
if(!Looper.getMainLooper().isCurrentThread())
throw new IllegalStateException("Can only be called from main thread");
@@ -39,8 +38,6 @@ public class StatusInteractionController{
@Override
public void onSuccess(Status result){
runningFavoriteRequests.remove(status.id);
result.favouritesCount = Math.max(0, status.favouritesCount) + (favorited ? 1 : -1);
cb.accept(result);
E.post(new StatusCountersUpdatedEvent(result));
}
@@ -49,17 +46,24 @@ public class StatusInteractionController{
runningFavoriteRequests.remove(status.id);
error.showToast(MastodonApp.context);
status.favourited=!favorited;
cb.accept(status);
if(favorited)
status.favouritesCount--;
else
status.favouritesCount++;
E.post(new StatusCountersUpdatedEvent(status));
}
})
.exec(accountID);
runningFavoriteRequests.put(status.id, req);
status.favourited=favorited;
if(favorited)
status.favouritesCount++;
else
status.favouritesCount--;
E.post(new StatusCountersUpdatedEvent(status));
}
public void setReblogged(Status status, boolean reblogged, Consumer<Status> cb){
public void setReblogged(Status status, boolean reblogged){
if(!Looper.getMainLooper().isCurrentThread())
throw new IllegalStateException("Can only be called from main thread");
@@ -72,8 +76,6 @@ public class StatusInteractionController{
@Override
public void onSuccess(Status result){
runningReblogRequests.remove(status.id);
result.reblogsCount = Math.max(0, status.reblogsCount) + (reblogged ? 1 : -1);
cb.accept(result);
E.post(new StatusCountersUpdatedEvent(result));
}
@@ -82,21 +84,24 @@ public class StatusInteractionController{
runningReblogRequests.remove(status.id);
error.showToast(MastodonApp.context);
status.reblogged=!reblogged;
cb.accept(status);
if(reblogged)
status.reblogsCount--;
else
status.reblogsCount++;
E.post(new StatusCountersUpdatedEvent(status));
}
})
.exec(accountID);
runningReblogRequests.put(status.id, req);
status.reblogged=reblogged;
if(reblogged)
status.reblogsCount++;
else
status.reblogsCount--;
E.post(new StatusCountersUpdatedEvent(status));
}
public void setBookmarked(Status status, boolean bookmarked){
setBookmarked(status, bookmarked, r->{});
}
public void setBookmarked(Status status, boolean bookmarked, Consumer<Status> cb){
if(!Looper.getMainLooper().isCurrentThread())
throw new IllegalStateException("Can only be called from main thread");
@@ -109,7 +114,6 @@ public class StatusInteractionController{
@Override
public void onSuccess(Status result){
runningBookmarkRequests.remove(status.id);
cb.accept(result);
E.post(new StatusCountersUpdatedEvent(result));
}
@@ -118,7 +122,6 @@ public class StatusInteractionController{
runningBookmarkRequests.remove(status.id);
error.showToast(MastodonApp.context);
status.bookmarked=!bookmarked;
cb.accept(status);
E.post(new StatusCountersUpdatedEvent(status));
}
})

View File

@@ -7,15 +7,4 @@ public class GetInstance extends MastodonAPIRequest<Instance>{
public GetInstance(){
super(HttpMethod.GET, "/instance", Instance.class);
}
public static class V2 extends MastodonAPIRequest<Instance.V2>{
public V2(){
super(HttpMethod.GET, "/instance", Instance.V2.class);
}
@Override
protected String getPathPrefix() {
return "/api/v2";
}
}
}

View File

@@ -11,9 +11,9 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
}
private static class Request{
public String clientName="Megalodon";
public String clientName="Moshidon";
public String redirectUris=AccountSessionManager.REDIRECT_URI;
public String scopes=AccountSessionManager.SCOPE;
public String website="https://sk22.github.io/megalodon";
public String website="https://github.com/LucasGGamerM/moshidon";
}
}

View File

@@ -0,0 +1,20 @@
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;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusTranslation;
import java.util.Locale;
public class GetStatusTranslation extends MastodonAPIRequest<StatusTranslation>{
public GetStatusTranslation(String id){
super(HttpMethod.POST, "/statuses/"+id+"/translate", StatusTranslation.class);
Request r = new Request();
setRequestBody(r);
}
public static class Request{
}
}

View File

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

View File

@@ -1,11 +0,0 @@
package org.joinmastodon.android.api.session;
public class AccountActivationInfo{
public String email;
public long lastEmailConfirmationResend;
public AccountActivationInfo(String email, long lastEmailConfirmationResend){
this.email=email;
this.lastEmailConfirmationResend=lastEmailConfirmationResend;
}
}

View File

@@ -7,7 +7,6 @@ import org.joinmastodon.android.api.StatusInteractionController;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.model.Token;
@@ -29,20 +28,17 @@ public class AccountSession{
public long filtersLastUpdated;
public List<Filter> wordFilters=new ArrayList<>();
public String pushAccountID;
public Preferences preferences;
public AccountActivationInfo activationInfo;
private transient MastodonAPIController apiController;
private transient StatusInteractionController statusInteractionController;
private transient CacheController cacheController;
private transient PushSubscriptionManager pushSubscriptionManager;
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
AccountSession(Token token, Account self, Application app, String domain, boolean activated){
this.token=token;
this.self=self;
this.domain=domain;
this.app=app;
this.activated=activated;
this.activationInfo=activationInfo;
infoLastUpdated=System.currentTimeMillis();
}

View File

@@ -22,7 +22,6 @@ import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
@@ -35,7 +34,6 @@ import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.Token;
import java.io.File;
@@ -63,7 +61,7 @@ import me.grishka.appkit.api.ErrorResponse;
public class AccountSessionManager{
private static final String TAG="AccountSessionManager";
public static final String SCOPE="read write follow push";
public static final String REDIRECT_URI="megalodon-android-auth://callback";
public static final String REDIRECT_URI="moshidon-android-auth://callback";
private static final AccountSessionManager instance=new AccountSessionManager();
@@ -102,9 +100,9 @@ public class AccountSessionManager{
maybeUpdateShortcuts();
}
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
public void addAccount(Instance instance, Token token, Account self, Application app, boolean active){
instances.put(instance.uri, instance);
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
AccountSession session=new AccountSession(token, self, app, instance.uri, active);
sessions.put(session.getID(), session);
lastActiveAccountID=session.getID();
writeAccountsFile();
@@ -213,7 +211,7 @@ public class AccountSessionManager{
.path("/oauth/authorize")
.appendQueryParameter("response_type", "code")
.appendQueryParameter("client_id", result.clientId)
.appendQueryParameter("redirect_uri", "megalodon-android-auth://callback")
.appendQueryParameter("redirect_uri", "moshidon-android-auth://callback")
.appendQueryParameter("scope", SCOPE)
.build();
@@ -250,13 +248,12 @@ public class AccountSessionManager{
HashSet<String> domains=new HashSet<>();
for(AccountSession session:sessions.values()){
domains.add(session.domain.toLowerCase());
// if(now-session.infoLastUpdated>24L*3600_000L){
updateSessionPreferences(session);
updateSessionLocalInfo(session);
// }
// if(now-session.filtersLastUpdated>3600_000L){
updateSessionWordFilters(session);
// }
if(now-session.infoLastUpdated>24L*3600_000L){
updateSessionLocalInfo(session);
}
if(now-session.filtersLastUpdated>3600_000L){
updateSessionWordFilters(session);
}
}
if(loadedInstances){
maybeUpdateCustomEmojis(domains);
@@ -266,10 +263,10 @@ public class AccountSessionManager{
private void maybeUpdateCustomEmojis(Set<String> domains){
long now=System.currentTimeMillis();
for(String domain:domains){
// Long lastUpdated=instancesLastUpdated.get(domain);
// if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
updateInstanceInfo(domain);
// }
Long lastUpdated=instancesLastUpdated.get(domain);
if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
updateInstanceInfo(domain);
}
}
}
@@ -291,18 +288,6 @@ public class AccountSessionManager{
.exec(session.getID());
}
private void updateSessionPreferences(AccountSession session){
new GetPreferences().setCallback(new Callback<>() {
@Override
public void onSuccess(Preferences preferences) {
session.preferences=preferences;
}
@Override
public void onError(ErrorResponse error) {}
}).exec(session.getID());
}
private void updateSessionWordFilters(AccountSession session){
new GetWordFilters()
.setCallback(new Callback<>(){
@@ -328,11 +313,6 @@ public class AccountSessionManager{
public void onSuccess(Instance instance){
instances.put(domain, instance);
updateInstanceEmojis(instance, domain);
try {
if (Integer.parseInt(instance.version.split("\\.")[0]) >= 4) {
updateInstanceInfoV2(domain);
}
} catch (Exception ignored) {}
}
@Override
@@ -343,19 +323,6 @@ public class AccountSessionManager{
.execNoAuth(domain);
}
public void updateInstanceInfoV2(String domain) {
new GetInstance.V2().setCallback(new Callback<>() {
@Override
public void onSuccess(Instance.V2 v2) {
Instance instanceInfo = instances.get(domain);
if (instanceInfo != null) instanceInfo.v2 = v2;
}
@Override
public void onError(ErrorResponse errorResponse) {}
}).execNoAuth(domain);
}
private void updateInstanceEmojis(Instance instance, String domain){
new GetCustomEmojis()
.setCallback(new Callback<>(){
@@ -431,10 +398,6 @@ public class AccountSessionManager{
return instances.get(domain);
}
public Instance getInstanceInfoForAccount(String account) {
return AccountSessionManager.getInstance().getInstanceInfo(instance.getAccount(account).domain);
}
public void updateAccountInfo(String id, Account account){
AccountSession session=getAccount(id);
session.self=account;

View File

@@ -462,6 +462,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
Status status=holder.getItem().status;
revealSpoiler(status, holder.getItemID());
}
public void onRevealTranslationClick(HeaderStatusDisplayItem.Holder holder){
Status status=holder.getItem().status;
revealTranslation(status, holder.getItemID());
}
protected void revealTranslation(Status status, String itemID){
status.wantsTranslation=!status.wantsTranslation;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null)
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null)
header.rebind();
updateImagesSpoilerState(status, itemID);
}
protected void revealSpoiler(Status status, String itemID){
status.spoilerRevealed=true;

View File

@@ -1,9 +1,5 @@
package org.joinmastodon.android.fragments;
import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages;
import static org.joinmastodon.android.utils.MastodonLanguage.allLanguages;
import static org.joinmastodon.android.utils.MastodonLanguage.defaultRecentLanguages;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -33,13 +29,11 @@ import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
@@ -47,7 +41,6 @@ import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
@@ -61,7 +54,6 @@ import android.widget.Toast;
import com.twitter.twittertext.TwitterTextEmojiRegex;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
@@ -102,7 +94,6 @@ import org.joinmastodon.android.ui.views.ComposeEditText;
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
import org.joinmastodon.android.utils.MastodonLanguage;
import org.parceler.Parcel;
import org.parceler.Parcels;
@@ -113,7 +104,6 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -154,8 +144,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private String accountID;
private int charCount, charLimit, trimmedCharCount;
private Button publishButton, languageButton;
private PopupMenu languagePopup;
private Button publishButton;
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn;
private ImageView sensitiveIcon;
private ComposeMediaLayout attachmentsView;
@@ -164,8 +153,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private View pollWrap;
private View addPollOptionBtn;
private View sensitiveItem;
private View pollAllowMultipleItem;
private CheckBox pollAllowMultipleCheckbox;
private TextView pollDurationView;
private ArrayList<DraftPollOption> pollOptions=new ArrayList<>();
@@ -200,9 +187,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private boolean ignoreSelectionChanges=false;
private Runnable updateUploadEtaRunnable;
private String language;
private MastodonLanguage.LanguageResolver languageResolver;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -214,7 +198,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
instanceDomain=session.domain;
customEmojis=AccountSessionManager.getInstance().getCustomEmojis(instanceDomain);
instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain);
languageResolver=new MastodonLanguage.LanguageResolver(instance);
if(getArguments().containsKey("editStatus")){
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
redraftStatus=getArguments().getBoolean("redraftStatus");
@@ -233,6 +216,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
charLimit=instance.configuration.statuses.maxCharacters;
else
charLimit=500;
loadDefaultStatusVisibility(savedInstanceState);
}
@Override
@@ -312,9 +297,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
pollOptionsView=view.findViewById(R.id.poll_options);
pollWrap=view.findViewById(R.id.poll_wrap);
addPollOptionBtn=view.findViewById(R.id.add_poll_option);
pollAllowMultipleItem=view.findViewById(R.id.poll_allow_multiple);
pollAllowMultipleCheckbox=view.findViewById(R.id.poll_allow_multiple_checkbox);
pollAllowMultipleItem.setOnClickListener(v->this.togglePollAllowMultiple());
addPollOptionBtn.setOnClickListener(v->{
createDraftPollOption().edit.requestFocus();
@@ -329,7 +311,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
pollBtn.setSelected(true);
mediaBtn.setEnabled(false);
pollWrap.setVisibility(View.VISIBLE);
updatePollAllowMultiple(savedInstanceState.getBoolean("pollAllowMultiple", false));
for(String oldText:savedInstanceState.getStringArrayList("pollOptions")){
DraftPollOption opt=createDraftPollOption();
opt.edit.setText(oldText);
@@ -340,7 +321,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
pollBtn.setSelected(true);
mediaBtn.setEnabled(false);
pollWrap.setVisibility(View.VISIBLE);
updatePollAllowMultiple(editingStatus.poll.multiple);
for(Poll.Option eopt:editingStatus.poll.options){
DraftPollOption opt=createDraftPollOption();
opt.edit.setText(eopt.title);
@@ -387,7 +367,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
statusVisibility=editingStatus.visibility;
}
loadDefaultStatusVisibility(savedInstanceState);
updateVisibilityIcon();
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
@@ -412,11 +391,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
outState.putStringArrayList("pollOptions", opts);
outState.putInt("pollDuration", pollDuration);
outState.putString("pollDurationStr", pollDurationStr);
outState.putBoolean("pollAllowMultiple", pollAllowMultipleItem.isSelected());
}
outState.putBoolean("sensitive", sensitive);
outState.putBoolean("hasSpoiler", hasSpoiler);
outState.putString("language", language);
if(!attachments.isEmpty()){
ArrayList<Parcelable> serializedAttachments=new ArrayList<>(attachments.size());
for(DraftMediaAttachment att:attachments){
@@ -508,14 +485,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter()));
if(replyTo!=null){
replyText.setText(getString(R.string.in_reply_to, replyTo.account.displayName));
int visibilityNameRes = switch (replyTo.visibility) {
int visibilityNameRes = switch (statusVisibility) {
case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted;
case PRIVATE -> R.string.visibility_followers_only;
case DIRECT -> R.string.visibility_private;
};
replyText.setContentDescription(getString(R.string.in_reply_to, replyTo.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
Drawable visibilityIcon = getActivity().getDrawable(switch(replyTo.visibility){
Drawable visibilityIcon = getActivity().getDrawable(switch(statusVisibility){
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
@@ -556,7 +533,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
spoilerEdit.setText(replyTo.spoilerText);
spoilerBtn.setSelected(true);
}
if (replyTo.language != null && !replyTo.language.isEmpty()) updateLanguage(replyTo.language);
}
}else if (editingStatus==null || editingStatus.inReplyToId==null){
// TODO: remove workaround after https://github.com/mastodon/mastodon-android/issues/341 gets fixed
@@ -569,7 +545,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
ignoreSelectionChanges=true;
mainEditText.setSelection(mainEditText.length());
ignoreSelectionChanges=false;
updateLanguage(editingStatus.language);
if(!editingStatus.mediaAttachments.isEmpty()){
attachmentsView.setVisibility(View.VISIBLE);
for(Attachment att:editingStatus.mediaAttachments){
@@ -632,10 +607,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
sendError.setVisibility(View.GONE);
sendProgress.setVisibility(View.GONE);
LinearLayout.LayoutParams langParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
langParams.setMarginEnd(V.dp(8));
wrap.addView(buildLanguageSelector(), langParams);
wrap.addView(publishButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
wrap.setClipToPadding(false);
@@ -645,59 +616,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
updatePublishButtonState();
}
private void updateLanguage(String lang) {
updateLanguage(languageResolver.from(lang));
}
private void updateLanguage(MastodonLanguage loc) {
language = loc.getLanguage();
languageButton.setText(loc.getLanguageName());
languageButton.setContentDescription(getActivity().getString(R.string.sk_post_language, loc.getDefaultName()));
}
@SuppressLint("ClickableViewAccessibility")
private Button buildLanguageSelector() {
TypedValue typedValue = new TypedValue();
getActivity().getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true);
languageButton=new Button(getActivity());
languageButton.setTextColor(typedValue.data);
languageButton.setBackground(getActivity().getDrawable(R.drawable.bg_text_button));
languageButton.setPadding(V.dp(8), 0, V.dp(8), 0);
languageButton.setCompoundDrawablesRelativeWithIntrinsicBounds(getActivity().getDrawable(R.drawable.ic_fluent_local_language_16_regular), null, null, null);
languageButton.setCompoundDrawableTintList(languageButton.getTextColors());
languageButton.setCompoundDrawablePadding(V.dp(6));
languagePopup=new PopupMenu(getActivity(), languageButton);
languageButton.setOnTouchListener(languagePopup.getDragToOpenListener());
languageButton.setOnClickListener(v->languagePopup.show());
Preferences prefs = AccountSessionManager.getInstance().getAccount(accountID).preferences;
updateLanguage(prefs != null && prefs.postingDefaultLanguage != null && prefs.postingDefaultLanguage.length() > 0
? languageResolver.from(prefs.postingDefaultLanguage)
: languageResolver.getDefault());
Menu languageMenu = languagePopup.getMenu();
for (String recentLanguage : Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages)) {
MastodonLanguage l = languageResolver.from(recentLanguage);
languageMenu.add(0, allLanguages.indexOf(l), Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
}
SubMenu allLanguagesMenu = languageMenu.addSubMenu(R.string.sk_available_languages);
for (int i = 0; i < allLanguages.size(); i++) {
MastodonLanguage l = allLanguages.get(i);
allLanguagesMenu.add(0, i, Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
}
languagePopup.setOnMenuItemClickListener(i->{
if (i.hasSubMenu()) return false;
updateLanguage(allLanguages.get(i.getItemId()));
return true;
});
return languageButton;
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
return true;
@@ -771,7 +689,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.status=text;
req.visibility=statusVisibility;
req.sensitive=sensitive;
req.language=language;
if(!attachments.isEmpty()){
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
}
@@ -781,7 +698,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(!pollOptions.isEmpty()){
req.poll=new CreateStatus.Request.Poll();
req.poll.expiresIn=pollDuration;
req.poll.multiple=pollAllowMultipleItem.isSelected();
for(DraftPollOption opt:pollOptions)
req.poll.options.add(opt.edit.getText().toString());
}
@@ -849,14 +765,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
.setCallback(resCallback)
.exec(accountID);
}
if (replyTo == null) {
List<String> newRecentLanguages = new ArrayList<>(Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages));
newRecentLanguages.remove(language);
newRecentLanguages.add(0, language);
recentLanguages.put(accountID, newRecentLanguages.stream().limit(4).collect(Collectors.toList()));
GlobalUserPreferences.save();
}
}
private boolean hasDraft(){
@@ -1300,11 +1208,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
option.view=LayoutInflater.from(getActivity()).inflate(R.layout.compose_poll_option, pollOptionsView, false);
option.edit=option.view.findViewById(R.id.edit);
option.dragger=option.view.findViewById(R.id.dragger_thingy);
ImageView icon = option.view.findViewById(R.id.icon);
icon.setImageDrawable(getContext().getDrawable(pollAllowMultipleItem.isSelected() ?
R.drawable.ic_poll_checkbox_regular_selector :
R.drawable.ic_poll_option_button
));
option.dragger.setOnLongClickListener(v->{
pollOptionsView.startDragging(option.view);
@@ -1440,23 +1343,34 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
}
Preferences prefs = AccountSessionManager.getInstance().getAccount(accountID).preferences;
if (prefs != null) {
// Only override the reply visibility if our preference is more private
if (prefs.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) {
statusVisibility = switch (prefs.postingDefaultVisibility) {
case PUBLIC -> StatusPrivacy.PUBLIC;
case UNLISTED -> StatusPrivacy.UNLISTED;
case PRIVATE -> StatusPrivacy.PRIVATE;
case DIRECT -> StatusPrivacy.DIRECT;
};
}
new GetPreferences()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Preferences result){
// Only override the reply visibility if our preference is more private
if (result.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) {
statusVisibility = switch (result.postingDefaultVisibility) {
case PUBLIC -> StatusPrivacy.PUBLIC;
case UNLISTED -> StatusPrivacy.UNLISTED;
case PRIVATE -> StatusPrivacy.PRIVATE;
case DIRECT -> StatusPrivacy.DIRECT;
};
}
// A saved privacy setting from a previous compose session wins over all
if(savedInstanceState !=null){
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
}
}
// A saved privacy setting from a previous compose session wins over all
if(savedInstanceState !=null){
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
}
updateVisibilityIcon ();
}
@Override
public void onError(ErrorResponse error){
Log.w(TAG, "Unable to get user preferences to set default post privacy");
}
})
.exec(accountID);
}
private void updateVisibilityIcon(){
@@ -1471,27 +1385,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
});
}
private void togglePollAllowMultiple() {
updatePollAllowMultiple(!pollAllowMultipleItem.isSelected());
}
private void updatePollAllowMultiple(boolean multiple){
pollAllowMultipleItem.setSelected(multiple);
pollAllowMultipleCheckbox.setChecked(multiple);
ImageView btn = addPollOptionBtn.findViewById(R.id.add_poll_option_icon);
btn.setImageDrawable(getContext().getDrawable(multiple ?
R.drawable.ic_fluent_add_square_24_regular :
R.drawable.ic_fluent_add_circle_24_regular
));
for (DraftPollOption opt:pollOptions) {
ImageView icon = opt.view.findViewById(R.id.icon);
icon.setImageDrawable(getContext().getDrawable(multiple ?
R.drawable.ic_poll_checkbox_regular_selector :
R.drawable.ic_poll_option_button
));
}
}
@Override
public void onSelectionChanged(int start, int end){
if(ignoreSelectionChanges)

View File

@@ -302,7 +302,7 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
if (!rel.requested && !rel.followedBy && adapter != null) {
data.remove(item);
adapter.notifyItemRemoved(getLayoutPosition());
adapter.notifyItemRemoved(getBindingAdapterPosition());
} else {
rebind();
}

View File

@@ -321,6 +321,9 @@ public class HomeTimelineFragment extends StatusListFragment{
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
toolbarLogo.setImageResource(R.drawable.logo);
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
// toolbarLogo =new TextView(getActivity());
// toolbarLogo.setText(getString(R.string.app_name).toLowerCase(Locale.getDefault()));
// toolbarLogo.setTextAppearance(R.style.app_title);
toolbarShowNewPostsBtn=new Button(getActivity());
toolbarShowNewPostsBtn.setTextAppearance(R.style.m3_title_medium);
@@ -348,9 +351,7 @@ public class HomeTimelineFragment extends StatusListFragment{
}
FrameLayout logoWrap=new FrameLayout(getActivity());
FrameLayout.LayoutParams logoParams=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
logoParams.setMargins(0, V.dp(2), 0, 0);
logoWrap.addView(toolbarLogo, logoParams);
logoWrap.addView(toolbarLogo, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
logoWrap.addView(toolbarShowNewPostsBtn, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, V.dp(32), Gravity.CENTER));
Toolbar toolbar=getToolbar();

View File

@@ -11,7 +11,6 @@ import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -32,7 +31,6 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
@@ -50,7 +48,6 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import java.util.ArrayList;
import java.util.Objects;
import java.util.function.Consumer;
import androidx.annotation.DrawableRes;
@@ -98,7 +95,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
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));
items.add(new SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
items.add(new SwitchItem(R.string.disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
GlobalUserPreferences.disableMarquee=i.checked;
GlobalUserPreferences.save();
}));
@@ -117,7 +114,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.showInteractionCounts=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
items.add(new SwitchItem(R.string.settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
GlobalUserPreferences.save();
}));
@@ -160,15 +157,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
items.add(checkForUpdateItem);
}
items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sk22/megalodon")));
items.add(new TextItem(R.string.settings_contribute_fork, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sk22/megalodon")));
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
GlobalUserPreferences.recentLanguages.remove(accountID);
GlobalUserPreferences.save();
})));
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
items.add(new FooterItem(getString(R.string.sk_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
items.add(new FooterItem(getString(R.string.settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
}
@Override
@@ -243,25 +236,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
restartActivityToApplyNewTheme();
}
private boolean onColorPreferenceClick(MenuItem item){
ColorPreference pref = null;
int id = item.getItemId();
private void onColorPreferenceClick(GlobalUserPreferences.ColorPreference color){
if (id == R.id.m3_color) pref = ColorPreference.MATERIAL3;
else if (id == R.id.pink_color) pref = ColorPreference.PINK;
else if (id == R.id.purple_color) pref = ColorPreference.PURPLE;
else if (id == R.id.green_color) pref = ColorPreference.GREEN;
else if (id == R.id.blue_color) pref = ColorPreference.BLUE;
else if (id == R.id.brown_color) pref = ColorPreference.BROWN;
else if (id == R.id.red_color) pref = ColorPreference.RED;
else if (id == R.id.yellow_color) pref = ColorPreference.YELLOW;
if (pref == null) return false;
GlobalUserPreferences.color=pref;
GlobalUserPreferences.color=color;
GlobalUserPreferences.save();
restartActivityToApplyNewTheme();
return true;
}
private void onTrueBlackThemeChanged(SwitchItem item){
@@ -699,24 +678,62 @@ public class SettingsFragment extends MastodonToolbarFragment{
button=findViewById(R.id.color_picker_button);
popupMenu=new PopupMenu(getActivity(), button, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.color_picker);
popupMenu.setOnMenuItemClickListener(SettingsFragment.this::onColorPreferenceClick);
popupMenu.setOnMenuItemClickListener(item->{
GlobalUserPreferences.ColorPreference pref;
int id=item.getItemId();
if(id==R.id.pink_color) {
pref = GlobalUserPreferences.ColorPreference.PINK;
onColorPreferenceClick(pref);
}
else if(id==R.id.purple_color) {
pref = GlobalUserPreferences.ColorPreference.PURPLE;
onColorPreferenceClick(pref);
}
else if(id==R.id.green_color) {
pref = GlobalUserPreferences.ColorPreference.GREEN;
onColorPreferenceClick(pref);
}
else if(id==R.id.blue_color) {
pref = GlobalUserPreferences.ColorPreference.BLUE;
onColorPreferenceClick(pref);
}
else if(id==R.id.orange_color) {
pref = GlobalUserPreferences.ColorPreference.ORANGE;
onColorPreferenceClick(pref);
}
else if(id==R.id.yellow_color) {
pref = GlobalUserPreferences.ColorPreference.YELLOW;
onColorPreferenceClick(pref);
}
else if(id==R.id.m3_color) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
pref = GlobalUserPreferences.ColorPreference.MATERIAL3;
onColorPreferenceClick(pref);
}else{
Toast.makeText(getActivity(), R.string.sk_not_supported,
Toast.LENGTH_LONG).show();
}
}
else
return false;
return true;
});
// UiUtils.enablePopupMenuIcons(getActivity(), popupMenu);
button.setOnTouchListener(popupMenu.getDragToOpenListener());
button.setOnClickListener(v->popupMenu.show());
}
@Override
public void onBind(ColorPicker item){
icon.setImageResource(R.drawable.ic_fluent_color_24_regular);
icon.setImageResource(R.drawable.ic_color_theme_preference);
button.setText(switch(GlobalUserPreferences.color){
case MATERIAL3 -> R.string.sk_color_theme_material3;
case PINK -> R.string.sk_color_theme_pink;
case PURPLE -> R.string.sk_color_theme_purple;
case GREEN -> R.string.sk_color_theme_green;
case BLUE -> R.string.sk_color_theme_blue;
case BROWN -> R.string.sk_color_theme_brown;
case RED -> R.string.sk_color_theme_red;
case ORANGE -> R.string.sk_color_theme_brown;
case YELLOW -> R.string.sk_color_theme_yellow;
default -> throw new IllegalStateException("Unexpected value: "+GlobalUserPreferences.color);
case MATERIAL3 -> R.string.sk_color_theme_material_you;
});
}
}
@@ -836,10 +853,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
text.setText(getString(R.string.sk_update_available, info.version));
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.sk_update_ready, info.version));
text.setText(getString(R.string.update_ready, info.version));
button.setText(R.string.install_update);
}
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){

View File

@@ -1,28 +1,20 @@
package org.joinmastodon.android.fragments;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.style.ReplacementSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import me.grishka.appkit.Nav;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.utils.V;
@@ -31,13 +23,12 @@ public class SplashFragment extends AppKitFragment{
private SizeListenerFrameLayout contentView;
private View artContainer, blueFill, greenFill;
private ViewPager2 pager;
private ViewGroup pagerDots;
private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill;
private InterpolatingMotionEffect motionEffect;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
}
@Nullable
@@ -46,44 +37,15 @@ public class SplashFragment extends AppKitFragment{
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
artClouds=contentView.findViewById(R.id.art_clouds);
artPlaneElephant=contentView.findViewById(R.id.art_plane_elephant);
artRightHill=contentView.findViewById(R.id.art_right_hill);
artLeftHill=contentView.findViewById(R.id.art_left_hill);
artCenterHill=contentView.findViewById(R.id.art_center_hill);
pager=contentView.findViewById(R.id.pager);
pagerDots=contentView.findViewById(R.id.pager_dots);
pager.setAdapter(new PagerAdapter());
pager.setOffscreenPageLimit(3);
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
for(int i=0;i<pagerDots.getChildCount();i++){
float alpha;
if(i==position){
alpha=0.3f+0.7f*(1f-positionOffset);
}else if(i==position+1){
alpha=0.3f+0.7f*positionOffset;
}else{
alpha=0.3f;
}
pagerDots.getChildAt(i).setAlpha(alpha);
}
float parallaxProgress=(position+positionOffset)/2f;
artClouds.setTranslationX(V.dp(-27)*(position>=1 ? 1f : positionOffset));
artPlaneElephant.setTranslationX(V.dp(101.55f)*parallaxProgress);
artLeftHill.setTranslationX(V.dp(-88)*parallaxProgress);
artLeftHill.setTranslationY(V.dp(24)*parallaxProgress);
artRightHill.setTranslationX(V.dp(-88)*parallaxProgress);
artRightHill.setTranslationY(V.dp(-24)*parallaxProgress);
artCenterHill.setTranslationX(V.dp(-40)*parallaxProgress);
}
});
artContainer=contentView.findViewById(R.id.art_container);
blueFill=contentView.findViewById(R.id.blue_fill);
greenFill=contentView.findViewById(R.id.green_fill);
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_clouds), V.dp(-5), V.dp(5), V.dp(-5), V.dp(5)));
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_right_hill), V.dp(-15), V.dp(25), V.dp(-10), V.dp(10)));
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_left_hill), V.dp(-25), V.dp(15), V.dp(-15), V.dp(15)));
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_center_hill), V.dp(-14), V.dp(14), V.dp(-5), V.dp(25)));
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_plane_elephant), V.dp(-20), V.dp(12), V.dp(-20), V.dp(12)));
contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){
@Override
@@ -110,10 +72,10 @@ public class SplashFragment extends AppKitFragment{
}
private void updateArtSize(int w, int h){
float scale=w/(float)V.dp(360);
float scale=w/(float)V.dp(412);
artContainer.setScaleX(scale);
artContainer.setScaleY(scale);
blueFill.setScaleY(artContainer.getBottom()-V.dp(90));
blueFill.setScaleY(h/2f);
greenFill.setScaleY(h-artContainer.getBottom()+V.dp(90));
}
@@ -139,91 +101,15 @@ public class SplashFragment extends AppKitFragment{
return true;
}
private class PagerAdapter extends RecyclerView.Adapter<PagerViewHolder>{
@NonNull
@Override
public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new PagerViewHolder(viewType);
}
@Override
public void onBindViewHolder(@NonNull PagerViewHolder holder, int position){}
@Override
public int getItemCount(){
return 3;
}
@Override
public int getItemViewType(int position){
return position;
}
@Override
protected void onShown(){
super.onShown();
motionEffect.activate();
}
private class PagerViewHolder extends RecyclerView.ViewHolder{
public PagerViewHolder(int page){
super(new LinearLayout(getActivity()));
LinearLayout ll=(LinearLayout) itemView;
ll.setOrientation(LinearLayout.VERTICAL);
int pad=V.dp(16);
ll.setPadding(pad, pad, pad, pad);
ll.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
TextView title=new TextView(getActivity());
title.setTextAppearance(R.style.m3_headline_medium);
title.setText(switch(page){
case 0 -> {
String src=getString(R.string.welcome_page1_title);
SpannableString ss=new SpannableString(src);
int start=src.indexOf("{logo}");
if(start!=-1){
LogoSpan span=new LogoSpan(getResources().getDrawable(R.drawable.splash_logo, getActivity().getTheme()));
ss.setSpan(span, start, start+6, 0);
}
yield ss;
}
case 1 -> getString(R.string.welcome_page2_title);
case 2 -> getString(R.string.welcome_page3_title);
default -> throw new IllegalStateException("Unexpected value: "+page);
});
title.setTextColor(0xFF17063B);
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(page==0 ? 46 : 36));
lp.bottomMargin=V.dp(page==0 ? 4 : 14);
ll.addView(title, lp);
TextView text=new TextView(getActivity());
text.setTextAppearance(R.style.m3_body_medium);
text.setText(switch(page){
case 0 -> R.string.welcome_page1_text;
case 1 -> R.string.welcome_page2_text;
case 2 -> R.string.welcome_page3_text;
default -> throw new IllegalStateException("Unexpected value: "+page);
});
text.setTextColor(0xFF17063B);
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
private class LogoSpan extends ReplacementSpan{
private final Drawable drawable;
private LogoSpan(Drawable drawable){
this.drawable=drawable;
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
return drawable.getIntrinsicWidth();
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint){
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
canvas.save();
canvas.translate(x, y-V.dp(20));
drawable.draw(canvas);
canvas.restore();
}
@Override
protected void onHidden(){
super.onHidden();
motionEffect.deactivate();
}
}

View File

@@ -177,7 +177,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
for(Status s:data){
if(s.getContentStatus().id.equals(ev.id)){
s.getContentStatus().update(ev);
s.update(ev);
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==s.getContentStatus()){

View File

@@ -23,7 +23,6 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ListTimelinesFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.model.Account;
@@ -287,7 +286,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getDisplayUsername())).setVisible(relationship.following);
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
if(relationship.following){
@@ -374,12 +372,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
})
.wrapProgress(getActivity(), R.string.loading, false)
.exec(accountID);
}else if(id==R.id.manage_user_lists){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("profileAccount", account.id);
args.putString("profileDisplayUsername", account.getDisplayUsername());
Nav.go(getActivity(), ListTimelinesFragment.class, args);
}
return true;
}

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.fragments.onboarding;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -12,21 +12,19 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.accounts.ResendConfirmationEmail;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.session.AccountActivationInfo;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.SettingsFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.File;
@@ -37,50 +35,40 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.ToolbarFragment;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.utils.V;
public class AccountActivationFragment extends ToolbarFragment{
public class AccountActivationFragment extends AppKitFragment{
private String accountID;
private Button openEmailBtn, resendBtn;
private View contentView;
private Button btn, backBtn;
private View buttonBar;
private Handler uiHandler=new Handler(Looper.getMainLooper());
private Runnable pollRunnable=this::tryGetAccount;
private APIRequest currentRequest;
private Runnable resendTimer=this::updateResendTimer;
private long lastResendTime;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
setTitle(R.string.confirm_email_title);
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
lastResendTime=session.activationInfo!=null ? session.activationInfo.lastEmailConfirmationResend : 0;
}
@Nullable
@Override
public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragment_onboarding_activation, container, false);
openEmailBtn=view.findViewById(R.id.btn_next);
openEmailBtn.setOnClickListener(this::onOpenEmailClick);
openEmailBtn.setOnLongClickListener(v->{
btn=view.findViewById(R.id.btn_next);
btn.setOnClickListener(v->onButtonClick());
btn.setOnLongClickListener(v->{
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), SettingsFragment.class, args);
return true;
});
resendBtn=view.findViewById(R.id.btn_resend);
resendBtn.setOnClickListener(this::onResendClick);
TextView text=view.findViewById(R.id.subtitle);
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
text.setText(getString(R.string.confirm_email_subtitle, session.activationInfo!=null ? session.activationInfo.email : "?"));
updateResendTimer();
buttonBar=view.findViewById(R.id.button_bar);
view.findViewById(R.id.btn_back).setOnClickListener(v->onBackButtonClick());
contentView=view;
return view;
}
@@ -92,32 +80,14 @@ public class AccountActivationFragment extends ToolbarFragment{
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
getToolbar().setBackground(null);
getToolbar().setElevation(0);
}
@Override
protected boolean canGoBack(){
return true;
}
@Override
public void onToolbarNavigationClick(){
new AccountSwitcherSheet(getActivity()).show();
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(Build.VERSION.SDK_INT>=27){
int inset=insets.getSystemWindowInsetBottom();
contentView.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
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()));
@@ -141,7 +111,7 @@ public class AccountActivationFragment extends ToolbarFragment{
}
}
private void onOpenEmailClick(View v){
private void onButtonClick(){
try{
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}catch(ActivityNotFoundException x){
@@ -149,21 +119,12 @@ public class AccountActivationFragment extends ToolbarFragment{
}
}
private void onResendClick(View v){
private void onBackButtonClick(){
new ResendConfirmationEmail(null)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
Toast.makeText(getActivity(), R.string.resent_email, Toast.LENGTH_SHORT).show();
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
if(session.activationInfo==null){
session.activationInfo=new AccountActivationInfo("?", System.currentTimeMillis());
}else{
session.activationInfo.lastEmailConfirmationResend=System.currentTimeMillis();
}
lastResendTime=session.activationInfo.lastEmailConfirmationResend;
AccountSessionManager.getInstance().writeAccountsFile();
updateResendTimer();
}
@Override
@@ -191,7 +152,7 @@ public class AccountActivationFragment extends ToolbarFragment{
AccountSessionManager mgr=AccountSessionManager.getInstance();
AccountSession session=mgr.getAccount(accountID);
mgr.removeAccount(accountID);
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, true);
String newID=mgr.getLastActiveAccountID();
Bundle args=new Bundle();
args.putString("account", newID);
@@ -228,25 +189,4 @@ public class AccountActivationFragment extends ToolbarFragment{
})
.exec(accountID);
}
@SuppressLint("DefaultLocale")
private void updateResendTimer(){
long sinceResend=System.currentTimeMillis()-lastResendTime;
if(sinceResend>59_000L){
resendBtn.setText(R.string.resend);
resendBtn.setEnabled(true);
return;
}
int seconds=(int)((60_000L-sinceResend)/1000L);
resendBtn.setText(String.format("%s (%d)", getString(R.string.resend), seconds));
if(resendBtn.isEnabled())
resendBtn.setEnabled(false);
resendBtn.postDelayed(resendTimer, 500);
}
@Override
public void onDestroyView(){
super.onDestroyView();
resendBtn.removeCallbacks(resendTimer);
}
}

View File

@@ -1,260 +0,0 @@
package org.joinmastodon.android.fragments.onboarding;
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.Space;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.catalog.CatalogInstance;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.Objects;
import me.grishka.appkit.FragmentStackActivity;
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;
public class CustomWelcomeFragment extends InstanceCatalogFragment {
private View headerView;
public CustomWelcomeFragment() {
super(R.layout.fragment_welcome_custom, 1);
}
@Override
public void onAttach(Context context){
super.onAttach(context);
setRefreshEnabled(false);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
dataLoaded();
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
if (!canGoBack()) {
ImageView toolbarLogo=new ImageView(getActivity());
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
toolbarLogo.setImageResource(R.drawable.logo);
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
FrameLayout logoWrap=new FrameLayout(getActivity());
FrameLayout.LayoutParams logoParams=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
logoParams.setMargins(0, V.dp(2), 0, 0);
logoWrap.addView(toolbarLogo, logoParams);
getToolbar().addView(logoWrap, new Toolbar.LayoutParams(Gravity.CENTER));
} else {
setTitle(R.string.add_account);
}
}
@Override
protected void proceedWithAuthOrSignup(Instance instance) {
AccountSessionManager.getInstance().authenticate(getActivity(), instance);
}
@Override
protected void updateFilteredList(){
boolean addFakeInstance = currentSearchQuery.length()>0 && currentSearchQuery.matches("^\\S+\\.[^\\.]+$");
if(addFakeInstance){
fakeInstance.domain=fakeInstance.normalizedDomain=currentSearchQuery;
fakeInstance.description=getString(R.string.loading_instance);
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
ivh.rebind();
}
}
if(filteredData.isEmpty()){
filteredData.add(fakeInstance);
adapter.notifyItemInserted(0);
}
}
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
filteredData.clear();
if(currentSearchQuery.length()>0){
boolean foundExactMatch=false;
for(CatalogInstance inst:data){
if(inst.normalizedDomain.contains(currentSearchQuery)){
filteredData.add(inst);
if(inst.normalizedDomain.equals(currentSearchQuery))
foundExactMatch=true;
}
}
if(!foundExactMatch && addFakeInstance) {
filteredData.add(0, fakeInstance);
adapter.notifyItemChanged(0);
}
}
UiUtils.updateList(prevData, filteredData, list, adapter, Objects::equals);
for(int i=0;i<list.getChildCount();i++){
list.getChildAt(i).invalidateOutline();
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorWindowBackground));
list.setItemAnimator(new BetterItemAnimator());
}
@Override
protected void doLoadData(int offset, int count) {}
@Override
protected RecyclerView.Adapter getAdapter(){
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_welcome_custom, list, false);
searchEdit=headerView.findViewById(R.id.search_edit);
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
headerView.findViewById(R.id.more).setVisibility(View.GONE);
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
headerView.findViewById(R.id.timestamp).setVisibility(View.GONE);
((TextView) headerView.findViewById(R.id.username)).setText(R.string.sk_app_username);
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);
searchEdit.addTextChangedListener(new TextWatcher(){
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count){
nextButton.setEnabled(false);
chosenInstance = null;
searchEdit.removeCallbacks(searchDebouncer);
searchEdit.postDelayed(searchDebouncer, 300);
}
@Override
public void afterTextChanged(Editable s){}
});
mergeAdapter=new MergeRecyclerAdapter();
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
View spacer = new Space(getActivity());
spacer.setMinimumHeight(V.dp(8));
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(spacer));
return mergeAdapter;
}
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder> {
public InstancesAdapter(){
super(imgLoader);
}
@NonNull
@Override
public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new InstanceViewHolder();
}
@Override
public void onBindViewHolder(InstanceViewHolder holder, int position){
holder.bind(filteredData.get(position));
chosenInstance = filteredData.get(position);
if (chosenInstance != fakeInstance) nextButton.setEnabled(true);
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
return filteredData.size();
}
@Override
public int getItemViewType(int position){
return -1;
}
}
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
private final TextView title, description, userCount, lang;
private final RadioButton radioButton;
public InstanceViewHolder(){
super(getActivity(), R.layout.item_instance_custom, list);
// itemView.setPadding(V.dp(16), V.dp(16), V.dp(16), V.dp(16));
// TypedValue value = new TypedValue();
// getActivity().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, value, true);
// itemView.setBackground(getActivity().getTheme().getDrawable(R.drawable.bg_search_field));
title=findViewById(R.id.title);
description=findViewById(R.id.description);
userCount=findViewById(R.id.user_count);
lang=findViewById(R.id.lang);
radioButton=findViewById(R.id.radiobtn);
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
}
}
@Override
public void onBind(CatalogInstance item){
title.setText(item.normalizedDomain);
description.setText(item.description);
if (item == fakeInstance) {
userCount.setVisibility(View.GONE);
lang.setVisibility(View.GONE);
} else {
userCount.setVisibility(View.VISIBLE);
lang.setVisibility(View.VISIBLE);
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
lang.setText(item.language.toUpperCase());
}
radioButton.setChecked(chosenInstance==item);
radioButton.setVisibility(View.GONE);
}
@Override
public void onClick(){
if(chosenInstance!=null){
int idx=filteredData.indexOf(chosenInstance);
if(idx!=-1){
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
if(holder instanceof InstanceViewHolder ivh){
ivh.radioButton.setChecked(false);
}
}
}
radioButton.setChecked(true);
if(chosenInstance==null)
nextButton.setEnabled(true);
chosenInstance=item;
loadInstanceInfo(chosenInstance.domain, false);
onNextClick(null);
}
}
}

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments.onboarding;
import android.app.Activity;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
@@ -16,7 +15,6 @@ 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.DividerItemDecoration;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.jsoup.Jsoup;
@@ -35,7 +33,6 @@ 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.fragments.ToolbarFragment;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
@@ -49,7 +46,7 @@ import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
public class GoogleMadeMeAddThisFragment extends AppKitFragment{
private UsableRecyclerView list;
private MergeRecyclerAdapter adapter;
private Button btn;
@@ -63,7 +60,6 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
setTitle(R.string.privacy_policy_title);
}
@Override
@@ -86,24 +82,37 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
}
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
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_simple, list, false);
TextView text=headerView.findViewById(R.id.text);
text.setText(R.string.privacy_policy_subtitle);
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.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
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;
}
@@ -111,15 +120,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
getToolbar().setBackground(null);
getToolbar().setElevation(0);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
protected void onButtonClick(){
@@ -191,17 +192,24 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
}
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
private final TextView title;
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);
title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
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

View File

@@ -84,7 +84,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
isSignup=getArguments() != null && getArguments().getBoolean("signup");
isSignup=getArguments().getBoolean("signup");
}
protected abstract void proceedWithAuthOrSignup(Instance instance);
@@ -187,8 +187,6 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
}
protected void loadInstanceInfo(String _domain, boolean isFromRedirect){
if(TextUtils.isEmpty(_domain))
return;
String domain=normalizeInstanceDomain(_domain);
Instance cachedInstance=instancesCache.get(domain);
if(cachedInstance!=null){
@@ -224,7 +222,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
}
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
boolean found=false;
for(CatalogInstance ci:filteredData){
for(CatalogInstance ci : filteredData){
if(ci.domain.equals(domain) && ci!=fakeInstance){
found=true;
break;

View File

@@ -1,52 +1,39 @@
package org.joinmastodon.android.fragments.onboarding;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.LocaleList;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.HorizontalScrollView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.catalog.CatalogCategory;
import org.joinmastodon.android.model.catalog.CatalogInstance;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FilterChipView;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Map;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
@@ -55,37 +42,18 @@ import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment implements OnBackPressedListener{
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
private View headerView;
private MastodonAPIRequest<?> getCategoriesRequest;
private TabLayout categoriesList;
private String currentCategory="all";
private List<CatalogCategory> categories=new ArrayList<>();
private View topBar;
private List<String> languages=Collections.emptyList();
private PopupMenu langFilterMenu, speedFilterMenu;
private SignupSpeedFilter currentSignupSpeedFilter=SignupSpeedFilter.INSTANT;
private String currentLanguage=null;
private boolean searchQueryMode;
private LinearLayout filtersWrap;
private HorizontalScrollView filtersScroll;
private ImageButton backBtn, clearSearchBtn;
private View focusThing;
private FilterChipView categoryGeneral, categorySpecialInterests;
private List<FilterChipView> regionalFilters;
private CatalogInstance.Region chosenRegion;
private CategoryChoice categoryChoice;
public InstanceCatalogSignupFragment(){
super(R.layout.fragment_onboarding_common, 10);
@@ -95,12 +63,6 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
public void onAttach(Context context){
super.onAttach(context);
setRefreshEnabled(false);
setRetainInstance(true);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
loadData();
}
@@ -113,25 +75,6 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
if(getActivity()==null)
return;
onDataLoaded(sortInstances(result), false);
if(langFilterMenu!=null){
Menu menu=langFilterMenu.getMenu();
menu.clear();
menu.add(0, 0, 0, R.string.server_filter_any_language);
languages=result.stream().map(i->i.language).distinct().filter(s->s.length()>0).sorted().collect(Collectors.toList());
int i=1;
for(String lang:languages){
Locale locale=Locale.forLanguageTag(lang);
String name=locale.getDisplayLanguage(locale);
if(name.equals(lang))
name=lang.toUpperCase();
else
name=name.substring(0, 1).toUpperCase()+name.substring(1);
menu.add(0, i, 0, name);
i++;
}
}
updateFilteredList();
}
@@ -168,14 +111,14 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
}
private void updateCategories(){
// categoriesList.removeAllTabs();
// for(CatalogCategory cat:categories){
// int titleRes=getTitleForCategory(cat.category);
// TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
// ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
// emoji.setImageResource(getEmojiForCategory(cat.category));
// categoriesList.addTab(tab);
// }
categoriesList.removeAllTabs();
for(CatalogCategory cat:categories){
int titleRes=getTitleForCategory(cat.category);
TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
emoji.setImageResource(getEmojiForCategory(cat.category));
categoriesList.addTab(tab);
}
}
@Override
@@ -187,77 +130,27 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
@Override
protected RecyclerView.Adapter getAdapter(){
View headerView=new View(getActivity());
headerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
mergeAdapter=new MergeRecyclerAdapter();
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
return mergeAdapter;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
backBtn=view.findViewById(R.id.btn_back);
backBtn.setOnClickListener(v->{
if(searchQueryMode){
setSearchQueryMode(false);
}else{
Nav.finish(this);
}
});
clearSearchBtn=view.findViewById(R.id.clear);
clearSearchBtn.setOnClickListener(v->searchEdit.setText(""));
nextButton.setEnabled(true);
list.setItemAnimator(new BetterItemAnimator());
setStatusBarColor(0);
topBar=view.findViewById(R.id.top_bar);
LayerDrawable topBg=(LayerDrawable) topBar.getBackground().mutate();
topBar.setBackground(topBg);
Drawable topOverlay=topBg.findDrawableByLayerId(R.id.color_overlay);
topOverlay.setAlpha(0);
LayerDrawable btmBg=(LayerDrawable) buttonBar.getBackground().mutate();
buttonBar.setBackground(btmBg);
Drawable btmOverlay=btmBg.findDrawableByLayerId(R.id.color_overlay);
btmOverlay.setAlpha(0);
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
private boolean isAtTop=true;
private Animator currentPanelsAnim;
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_instance_catalog, list, false);
searchEdit=headerView.findViewById(R.id.search_edit);
categoriesList=headerView.findViewById(R.id.categories_list);
categoriesList.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
boolean newAtTop=recyclerView.getChildCount()==0 || (recyclerView.getChildAdapterPosition(recyclerView.getChildAt(0))==0 && recyclerView.getChildAt(0).getTop()==recyclerView.getPaddingTop());
if(newAtTop!=isAtTop){
isAtTop=newAtTop;
if(currentPanelsAnim!=null)
currentPanelsAnim.cancel();
public void onTabSelected(TabLayout.Tab tab){
CatalogCategory category=categories.get(tab.getPosition());
currentCategory=category.category;
updateFilteredList();
}
@Override
public void onTabUnselected(TabLayout.Tab tab){
}
@Override
public void onTabReselected(TabLayout.Tab tab){
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofInt(topOverlay, "alpha", isAtTop ? 0 : 20),
ObjectAnimator.ofInt(btmOverlay, "alpha", isAtTop ? 0 : 20),
ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)),
ObjectAnimator.ofFloat(buttonBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3))
);
set.setDuration(150);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
currentPanelsAnim=null;
}
});
set.start();
currentPanelsAnim=set;
}
}
});
searchEdit=view.findViewById(R.id.search_edit);
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
searchEdit.addTextChangedListener(new TextWatcher(){
@Override
@@ -273,159 +166,42 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
@Override
public void afterTextChanged(Editable s){
if((clearSearchBtn.getVisibility()==View.VISIBLE)!=(s.length()>0)){
clearSearchBtn.setVisibility(s.length()>0 ? View.VISIBLE : View.GONE);
}
}
});
searchEdit.setOnFocusChangeListener((v, hasFocus)->{
if(hasFocus && !searchQueryMode){
setSearchQueryMode(true);
}
});
FilterChipView langFilter=new FilterChipView(getActivity());
langFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
if(currentLanguage==null){
langFilter.setText(R.string.server_filter_any_language);
}else{
Locale locale=Locale.forLanguageTag(currentLanguage);
langFilter.setText(locale.getDisplayLanguage(locale));
langFilter.setSelected(true);
}
langFilterMenu=new PopupMenu(getContext(), langFilter);
langFilter.setOnTouchListener(langFilterMenu.getDragToOpenListener());
langFilter.setOnClickListener(v->langFilterMenu.show());
filtersWrap=view.findViewById(R.id.filters_container);
filtersScroll=view.findViewById(R.id.filters_scroll);
filtersWrap.addView(langFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
FilterChipView speedFilter=new FilterChipView(getActivity());
speedFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
speedFilterMenu=new PopupMenu(getContext(), speedFilter);
speedFilterMenu.getMenu().add(0, 0, 0, R.string.server_filter_any_signup_speed);
speedFilterMenu.getMenu().add(0, 1, 0, R.string.server_filter_instant_signup);
speedFilterMenu.getMenu().add(0, 2, 0, R.string.server_filter_manual_review);
speedFilter.setOnTouchListener(speedFilterMenu.getDragToOpenListener());
speedFilter.setOnClickListener(v->speedFilterMenu.show());
speedFilter.setText(switch(currentSignupSpeedFilter){
case ANY -> R.string.server_filter_any_signup_speed;
case INSTANT -> R.string.server_filter_instant_signup;
case REVIEWED -> R.string.server_filter_manual_review;
});
speedFilter.setSelected(currentSignupSpeedFilter!=SignupSpeedFilter.ANY);
filtersWrap.addView(speedFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
speedFilterMenu.setOnMenuItemClickListener(item->{
speedFilter.setText(item.getTitle());
speedFilter.setSelected(item.getItemId()>0);
currentSignupSpeedFilter=SignupSpeedFilter.values()[item.getItemId()];
updateFilteredList();
return true;
});
langFilterMenu.setOnMenuItemClickListener(item->{
langFilter.setText(item.getTitle());
langFilter.setSelected(item.getItemId()>0);
currentLanguage=item.getItemId()==0 ? null : languages.get(item.getItemId()-1);
updateFilteredList();
return true;
});
View divider=new View(getActivity());
divider.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Outline));
filtersWrap.addView(divider, new LinearLayout.LayoutParams(V.dp(.5f), ViewGroup.LayoutParams.MATCH_PARENT));
categoryGeneral=new FilterChipView(getActivity());
categoryGeneral.setText(R.string.category_general);
categoryGeneral.setTag(CategoryChoice.GENERAL);
categoryGeneral.setOnClickListener(this::onCategoryFilterClick);
categoryGeneral.setSelected(categoryChoice==CategoryChoice.GENERAL);
filtersWrap.addView(categoryGeneral, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
categorySpecialInterests=new FilterChipView(getActivity());
categorySpecialInterests.setText(R.string.category_special_interests);
categorySpecialInterests.setTag(CategoryChoice.SPECIAL);
categorySpecialInterests.setOnClickListener(this::onCategoryFilterClick);
categorySpecialInterests.setSelected(categoryChoice==CategoryChoice.SPECIAL);
filtersWrap.addView(categorySpecialInterests, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
regionalFilters=Arrays.stream(CatalogInstance.Region.values()).map(r->{
FilterChipView fv=new FilterChipView(getActivity());
fv.setTag(r);
fv.setText(switch(r){
case EUROPE -> R.string.server_filter_region_europe;
case NORTH_AMERICA -> R.string.server_filter_region_north_america;
case SOUTH_AMERICA -> R.string.server_filter_region_south_america;
case AFRICA -> R.string.server_filter_region_africa;
case ASIA -> R.string.server_filter_region_asia;
case OCEANIA -> R.string.server_filter_region_oceania;
});
fv.setSelected(r==chosenRegion);
fv.setOnClickListener(this::onRegionFilterClick);
filtersWrap.addView(fv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return fv;
}).collect(Collectors.toList());
focusThing=view.findViewById(R.id.focus_thing);
focusThing.requestFocus();
}
private void onRegionFilterClick(View v){
CatalogInstance.Region r=(CatalogInstance.Region) v.getTag();
if(chosenRegion==r){
chosenRegion=null;
v.setSelected(false);
}else{
if(chosenRegion!=null)
filtersWrap.findViewWithTag(chosenRegion).setSelected(false);
chosenRegion=r;
v.setSelected(true);
}
updateFilteredList();
}
private void onCategoryFilterClick(View v){
CategoryChoice c=(CategoryChoice) v.getTag();
if(categoryChoice==c){
categoryChoice=null;
v.setSelected(false);
}else{
if(categoryChoice!=null)
filtersWrap.findViewWithTag(categoryChoice).setSelected(false);
categoryChoice=c;
v.setSelected(true);
}
updateFilteredList();
mergeAdapter=new MergeRecyclerAdapter();
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
return mergeAdapter;
}
@Override
protected void onNextClick(View v){
if(chosenInstance==null){
String lang=Locale.getDefault().getLanguage();
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
if(instances.isEmpty()){
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
}
if(instances.isEmpty()){
return;
}
chosenInstance=instances.get(new Random().nextInt(instances.size()));
}
super.onNextClick(v);
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
list.setItemAnimator(new BetterItemAnimator());
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
@Override
protected void proceedWithAuthOrSignup(Instance instance){
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
if(!instance.registrations){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.instance_signup_closed)
.setPositiveButton(R.string.ok, null)
.show();
return;
if(isSignup){
if(!instance.registrations){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.instance_signup_closed)
.setPositiveButton(R.string.ok, null)
.show();
return;
}
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
Nav.go(getActivity(), InstanceRulesFragment.class, args);
}else{
}
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
Nav.go(getActivity(), InstanceRulesFragment.class, args);
}
// private String getEmojiForCategory(String category){
@@ -489,29 +265,11 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
protected void updateFilteredList(){
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
filteredData.clear();
if(searchQueryMode){
if(!TextUtils.isEmpty(currentSearchQuery)){
for(CatalogInstance instance:data){
if(instance.domain.contains(currentSearchQuery)){
for(CatalogInstance instance:data){
if(currentCategory.equals("all") || instance.categories.contains(currentCategory)){
if(TextUtils.isEmpty(currentSearchQuery) || instance.domain.contains(currentSearchQuery)){
if(instance.domain.equals(currentSearchQuery) || !isSignup || !instance.approvalRequired)
filteredData.add(instance);
}
}
}
}else{
for(CatalogInstance instance:data){
if(categoryChoice==null || categoryChoice.matches(instance.category)){
if(chosenRegion==null || instance.region==chosenRegion){
boolean signupSpeedMatches=switch(currentSignupSpeedFilter){
case ANY -> true;
case INSTANT -> !instance.approvalRequired;
case REVIEWED -> instance.approvalRequired;
};
if(signupSpeedMatches){
if(currentLanguage==null || instance.languages.contains(currentLanguage)){
filteredData.add(instance);
}
}
}
}
}
}
@@ -538,46 +296,8 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
}).dispatchUpdatesTo(adapter);
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
topBar.setPadding(0, insets.getSystemWindowInsetTop(), 0, 0);
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
@Override
public boolean onBackPressed(){
if(searchQueryMode){
setSearchQueryMode(false);
return true;
}
return false;
}
private void setSearchQueryMode(boolean enabled){
searchQueryMode=enabled;
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) searchEdit.getLayoutParams();
if(searchQueryMode){
filtersScroll.setVisibility(View.GONE);
lp.removeRule(RelativeLayout.END_OF);
backBtn.setScaleX(0.83333333f);
backBtn.setScaleY(0.83333333f);
backBtn.setTranslationX(V.dp(8));
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0));
}else{
filtersScroll.setVisibility(View.VISIBLE);
focusThing.requestFocus();
searchEdit.setText("");
lp.addRule(RelativeLayout.END_OF, R.id.btn_back);
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
backBtn.setScaleX(1);
backBtn.setScaleY(1);
backBtn.setTranslationX(0);
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant)));
}
updateFilteredList();
}
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder> implements ImageLoaderRecyclerAdapter{
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder>{
public InstancesAdapter(){
super(imgLoader);
}
@@ -603,53 +323,32 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
public int getItemViewType(int position){
return -1;
}
@Override
public int getImageCountForItem(int position){
return filteredData.get(position).thumbnailRequest!=null ? 1 : 0;
}
@Override
public ImageLoaderRequest getImageRequest(int position, int image){
return filteredData.get(position).thumbnailRequest;
}
}
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.DisableableClickable, ImageLoaderViewHolder{
private final TextView title, description;
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
private final TextView title, description, userCount, lang;
private final RadioButton radioButton;
private final ImageView thumbnail;
private boolean enabled;
public InstanceViewHolder(){
super(getActivity(), R.layout.item_instance_catalog, list);
title=findViewById(R.id.title);
description=findViewById(R.id.description);
userCount=findViewById(R.id.user_count);
lang=findViewById(R.id.lang);
radioButton=findViewById(R.id.radiobtn);
thumbnail=findViewById(R.id.image);
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
}
}
@Override
public void onBind(CatalogInstance item){
title.setText(item.normalizedDomain);
description.setText(item.description);
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
lang.setText(item.language.toUpperCase());
radioButton.setChecked(chosenInstance==item);
if(item.thumbnailRequest==null)
thumbnail.setImageDrawable(null);
Instance realInstance=instancesCache.get(item.normalizedDomain);
float alpha;
if(realInstance!=null && !realInstance.registrations){
alpha=0.38f;
description.setText(R.string.not_accepting_new_members);
enabled=false;
}else{
alpha=1f;
description.setText(item.description);
enabled=true;
}
title.setAlpha(alpha);
description.setAlpha(alpha);
radioButton.setAlpha(alpha);
thumbnail.setAlpha(alpha);
}
@Override
@@ -659,17 +358,10 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
if(chosenInstance!=null){
int idx=filteredData.indexOf(chosenInstance);
if(idx!=-1){
boolean found=false;
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder.getAbsoluteAdapterPosition()==mergeAdapter.getPositionForAdapter(adapter)+idx && holder instanceof InstanceViewHolder ivh){
ivh.radioButton.setChecked(false);
found=true;
break;
}
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
if(holder instanceof InstanceCatalogSignupFragment.InstanceViewHolder ivh){
ivh.radioButton.setChecked(false);
}
if(!found)
adapter.notifyItemChanged(idx);
}
}
radioButton.setChecked(true);
@@ -678,36 +370,5 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
chosenInstance=item;
loadInstanceInfo(chosenInstance.domain, false);
}
@Override
public void setImage(int index, Drawable image){
thumbnail.setImageDrawable(image);
}
@Override
public void clearImage(int index){
setImage(index, null);
}
@Override
public boolean isEnabled(){
return enabled;
}
}
private enum SignupSpeedFilter{
ANY,
INSTANT,
REVIEWED
}
private enum CategoryChoice{
GENERAL,
SPECIAL;
public boolean matches(String category){
boolean isGeneral=(category==null || "general".equals(category));
return (this==GENERAL)==isGeneral;
}
}
}

View File

@@ -106,13 +106,13 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
.execNoAuth("");
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
Toolbar toolbar=getToolbar();
toolbar.setElevation(0);
toolbar.setBackground(null);
}
// @Override
// protected void onUpdateToolbar(){
// super.onUpdateToolbar();
// Toolbar toolbar=getToolbar();
// toolbar.setElevation(0);
// toolbar.setBackground(null);
// }
@Override
protected RecyclerView.Adapter getAdapter(){
@@ -240,17 +240,13 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
if(chosenInstance!=null){
int idx=filteredData.indexOf(chosenInstance);
if(idx!=-1){
boolean found=false;
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder.getAbsoluteAdapterPosition()==mergeAdapter.getPositionForAdapter(adapter)+idx && holder instanceof InstanceViewHolder ivh){
ivh.radioButton.setChecked(false);
found=true;
break;
}
}
if(!found)
adapter.notifyItemChanged(idx);
}
}
radioButton.setChecked(true);

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.fragments.onboarding;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
@@ -23,14 +22,14 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.fragments.ToolbarFragment;
import me.grishka.appkit.fragments.AppKitFragment;
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;
public class InstanceRulesFragment extends ToolbarFragment{
public class InstanceRulesFragment extends AppKitFragment{
private UsableRecyclerView list;
private MergeRecyclerAdapter adapter;
private Button btn;
@@ -48,28 +47,31 @@ public class InstanceRulesFragment extends ToolbarFragment{
super.onAttach(activity);
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
setTitle(R.string.instance_rules_title);
}
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
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_simple, list, false);
TextView text=headerView.findViewById(R.id.text);
text.setText(getString(R.string.instance_rules_subtitle, instance.uri));
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.instance_rules_title);
subtitle.setText(getString(R.string.instance_rules_subtitle, instance.uri));
adapter=new MergeRecyclerAdapter();
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
adapter.addAdapter(new ItemsAdapter());
list.setAdapter(adapter);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
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;
}
@@ -77,15 +79,7 @@ public class InstanceRulesFragment extends ToolbarFragment{
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
getToolbar().setBackground(null);
getToolbar().setElevation(0);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
protected void onButtonClick(){
@@ -125,22 +119,23 @@ public class InstanceRulesFragment extends ToolbarFragment{
}
private class ItemViewHolder extends BindableViewHolder<Instance.Rule>{
private final TextView text, number;
private final TextView title, subtitle;
private final ImageView checkbox;
public ItemViewHolder(){
super(getActivity(), R.layout.item_server_rule, list);
text=findViewById(R.id.text);
number=findViewById(R.id.number);
super(getActivity(), R.layout.item_report_choice, list);
title=findViewById(R.id.title);
subtitle=findViewById(R.id.subtitle);
checkbox=findViewById(R.id.checkbox);
subtitle.setVisibility(View.GONE);
}
@SuppressLint("DefaultLocale")
@Override
public void onBind(Instance.Rule item){
if(item.parsedText==null){
item.parsedText=HtmlParser.parseLinks(item.text);
}
text.setText(item.parsedText);
number.setText(String.format("%d", getAbsoluteAdapterPosition()));
title.setText(item.parsedText);
}
}
}

View File

@@ -25,15 +25,14 @@ import org.joinmastodon.android.api.MastodonDetailedErrorResponse;
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
import org.joinmastodon.android.api.session.AccountActivationInfo;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Token;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
import org.parceler.Parcels;
import java.io.File;
@@ -50,28 +49,30 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.ToolbarFragment;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class SignupFragment extends ToolbarFragment{
public class SignupFragment extends AppKitFragment{
private static final int AVATAR_RESULT=198;
private static final String TAG="SignupFragment";
private Instance instance;
private EditText displayName, username, email, password, passwordConfirm, reason;
private FloatingHintEditTextLayout displayNameWrap, usernameWrap, emailWrap, passwordWrap, passwordConfirmWrap, reasonWrap;
private EditText displayName, username, email, password, reason;
private TextView reasonExplain;
private Button btn;
private View buttonBar;
private TextWatcher buttonStateUpdater=new SimpleTextWatcher(e->updateButtonState());
private ImageView avatar;
private APIRequest currentBackgroundRequest;
private Application apiApplication;
private Token apiToken;
private boolean submitAfterGettingToken;
private ProgressDialog progressDialog;
private Uri avatarUri;
private File avatarFile;
private HashSet<EditText> errorFields=new HashSet<>();
@Override
@@ -80,30 +81,25 @@ public class SignupFragment extends ToolbarFragment{
setRetainInstance(true);
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
createAppAndGetToken();
setTitle(R.string.signup_title);
}
@Nullable
@Override
public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragment_onboarding_signup, container, false);
TextView title=view.findViewById(R.id.title);
TextView domain=view.findViewById(R.id.domain);
displayName=view.findViewById(R.id.display_name);
username=view.findViewById(R.id.username);
email=view.findViewById(R.id.email);
password=view.findViewById(R.id.password);
passwordConfirm=view.findViewById(R.id.password_confirm);
avatar=view.findViewById(R.id.avatar);
reason=view.findViewById(R.id.reason);
reasonExplain=view.findViewById(R.id.reason_explain);
View avaWrap=view.findViewById(R.id.ava_wrap);
displayNameWrap=view.findViewById(R.id.display_name_wrap);
usernameWrap=view.findViewById(R.id.username_wrap);
emailWrap=view.findViewById(R.id.email_wrap);
passwordWrap=view.findViewById(R.id.password_wrap);
passwordConfirmWrap=view.findViewById(R.id.password_confirm_wrap);
reasonWrap=view.findViewById(R.id.reason_wrap);
title.setText(getString(R.string.signup_title, instance.uri));
domain.setText('@'+instance.uri);
username.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@@ -118,20 +114,23 @@ public class SignupFragment extends ToolbarFragment{
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));
updateButtonState();
username.addTextChangedListener(buttonStateUpdater);
email.addTextChangedListener(buttonStateUpdater);
password.addTextChangedListener(buttonStateUpdater);
passwordConfirm.addTextChangedListener(buttonStateUpdater);
reason.addTextChangedListener(buttonStateUpdater);
username.addTextChangedListener(new ErrorClearingListener(username));
email.addTextChangedListener(new ErrorClearingListener(email));
password.addTextChangedListener(new ErrorClearingListener(password));
passwordConfirm.addTextChangedListener(new ErrorClearingListener(passwordConfirm));
reason.addTextChangedListener(new ErrorClearingListener(reason));
avaWrap.setOutlineProvider(OutlineProviders.roundedRect(22));
avaWrap.setClipToOutline(true);
avaWrap.setOnClickListener(v->onAvatarClick());
if(!instance.approvalRequired){
reason.setVisibility(View.GONE);
reasonExplain.setVisibility(View.GONE);
@@ -143,23 +142,10 @@ public class SignupFragment extends ToolbarFragment{
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
getToolbar().setBackground(null);
getToolbar().setElevation(0);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
}
private void onButtonClick(){
if(!password.getText().equals(passwordConfirm.getText())){
passwordConfirm.setError(getString(R.string.signup_passwords_dont_match));
passwordConfirmWrap.setErrorState();
return;
}
showProgressDialog();
if(currentBackgroundRequest!=null){
submitAfterGettingToken=true;
@@ -174,8 +160,32 @@ public class SignupFragment extends ToolbarFragment{
}
}
private void copyAvatar(Runnable onDone){
// Need to copy the avatar from the content provider to somewhere accessible in case the app gets killed between signup and account activation
Activity activity=getActivity();
MastodonAPIController.runInBackground(()->{
String origName=UiUtils.getFileName(avatarUri);
avatarFile=new File(activity.getCacheDir(), System.currentTimeMillis()+origName.substring(origName.lastIndexOf('.')));
try(InputStream in=activity.getContentResolver().openInputStream(avatarUri);
FileOutputStream out=new FileOutputStream(avatarFile)){
byte[] buf=new byte[10240];
int read;
while((read=in.read(buf))>0){
out.write(buf, 0, read);
}
}catch(IOException x){
Log.w(TAG, "copyAvatar: error copying", x);
}
activity.runOnUiThread(onDone);
});
}
private void submit(){
actuallySubmit();
if(avatarUri!=null && (avatarFile==null || !avatarFile.exists())){
copyAvatar(this::actuallySubmit);
}else{
actuallySubmit();
}
}
private void actuallySubmit(){
@@ -194,7 +204,9 @@ public class SignupFragment extends ToolbarFragment{
fakeAccount.acct=fakeAccount.username=username;
fakeAccount.id="tmp"+System.currentTimeMillis();
fakeAccount.displayName=displayName.getText().toString();
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, new AccountActivationInfo(email, System.currentTimeMillis()));
if(avatarFile!=null)
fakeAccount.avatar=avatarFile.getAbsolutePath();
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, false);
Bundle args=new Bundle();
args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID());
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
@@ -213,7 +225,6 @@ public class SignupFragment extends ToolbarFragment{
continue;
}
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
getFieldWrapByName(fieldName).setErrorState();
errorFields.add(field);
if(first){
first=false;
@@ -241,16 +252,6 @@ public class SignupFragment extends ToolbarFragment{
};
}
private FloatingHintEditTextLayout getFieldWrapByName(String name){
return switch(name){
case "email" -> emailWrap;
case "username" -> usernameWrap;
case "password" -> passwordWrap;
case "reason" -> reasonWrap;
default -> null;
};
}
private void showProgressDialog(){
if(progressDialog==null){
progressDialog=new ProgressDialog(getActivity());
@@ -261,7 +262,7 @@ public class SignupFragment extends ToolbarFragment{
}
private void updateButtonState(){
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && passwordConfirm.length()>=8 && (!instance.approvalRequired || reason.length()>0));
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && (!instance.approvalRequired || reason.length()>0));
}
private void createAppAndGetToken(){
@@ -323,6 +324,20 @@ public class SignupFragment extends ToolbarFragment{
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode==AVATAR_RESULT && resultCode==Activity.RESULT_OK){
avatarUri=data.getData();
if(avatarFile!=null && avatarFile.exists())
avatarFile.delete();
ViewImageLoader.load(avatar, getResources().getDrawable(R.drawable.default_avatar), new UrlImageLoaderRequest(avatarUri, V.dp(100), V.dp(100)));
}
}
private void onAvatarClick(){
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*").addCategory(Intent.CATEGORY_OPENABLE), AVATAR_RESULT);
}
private class ErrorClearingListener implements TextWatcher{
public final EditText editText;

View File

@@ -82,8 +82,6 @@ public class Instance extends BaseModel{
// non-standard field in some Mastodon forks
public int maxTootChars;
public V2 v2;
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();
@@ -178,19 +176,4 @@ public class Instance extends BaseModel{
public int minExpiration;
public int maxExpiration;
}
@Parcel
public static class V2 extends BaseModel {
public V2.Configuration configuration;
@Parcel
public static class Configuration {
public TranslationConfiguration translation;
}
@Parcel
public static class TranslationConfiguration{
public boolean enabled;
}
}
}

View File

@@ -40,6 +40,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
public long favouritesCount;
public long repliesCount;
public Instant editedAt;
public boolean wantsTranslation;
public String url;
public String inReplyToId;

View File

@@ -0,0 +1,146 @@
package org.joinmastodon.android.model;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.List;
@Parcel
public class StatusTranslation extends BaseModel implements DisplayItemsParent{
// @RequiredField
public String id;
// @RequiredField
public String uri;
// @RequiredField
public Instant createdAt;
// @RequiredField
public Account account;
// @RequiredField
public String content;
// @RequiredField
public StatusPrivacy visibility;
public boolean sensitive;
// @RequiredField
public String spoilerText;
// @RequiredField
public List<Attachment> mediaAttachments;
public Application application;
// @RequiredField
public List<Mention> mentions;
// @RequiredField
public List<Hashtag> tags;
// @RequiredField
public List<Emoji> emojis;
public long reblogsCount;
public long favouritesCount;
public long repliesCount;
public Instant editedAt;
public String url;
public String inReplyToId;
public String inReplyToAccountId;
public Status reblog;
public Poll poll;
public Card card;
public String language;
public String text;
public boolean favourited;
public boolean reblogged;
public boolean muted;
public boolean bookmarked;
public boolean pinned;
public transient boolean spoilerRevealed;
public transient boolean hasGapAfter;
private transient String strippedText;
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();
// if(application!=null)
// application.postprocess();
// for(Mention m:mentions)
// m.postprocess();
// for(Hashtag t:tags)
// t.postprocess();
// for(Emoji e:emojis)
// e.postprocess();
// for(Attachment a:mediaAttachments)
// a.postprocess();
// account.postprocess();
// if(poll!=null)
// poll.postprocess();
// if(card!=null)
// card.postprocess();
// if(reblog!=null)
// reblog.postprocess();
// spoilerRevealed=GlobalUserPreferences.alwaysExpandContentWarnings || !sensitive;
}
@Override
public String toString(){
return "Status{"+
"id='"+id+'\''+
", uri='"+uri+'\''+
", createdAt="+createdAt+
", account="+account+
", content='"+content+'\''+
", visibility="+visibility+
", sensitive="+sensitive+
", spoilerText='"+spoilerText+'\''+
", mediaAttachments="+mediaAttachments+
", application="+application+
", mentions="+mentions+
", tags="+tags+
", emojis="+emojis+
", reblogsCount="+reblogsCount+
", favouritesCount="+favouritesCount+
", repliesCount="+repliesCount+
", url='"+url+'\''+
", inReplyToId='"+inReplyToId+'\''+
", inReplyToAccountId='"+inReplyToAccountId+'\''+
", reblog="+reblog+
", poll="+poll+
", card="+card+
", language='"+language+'\''+
", text='"+text+'\''+
", favourited="+favourited+
", reblogged="+reblogged+
", muted="+muted+
", bookmarked="+bookmarked+
", pinned="+pinned+
'}';
}
@Override
public String getID(){
return id;
}
public void update(StatusCountersUpdatedEvent ev){
favouritesCount=ev.favorites;
reblogsCount=ev.reblogs;
repliesCount=ev.replies;
favourited=ev.favorited;
reblogged=ev.reblogged;
bookmarked=ev.bookmarked;
pinned=ev.pinned;
}
public StatusTranslation getContentStatus(){
return this;
}
public String getStrippedText(){
if(strippedText==null)
strippedText=HtmlParser.strip(content);
return strippedText;
}
}

View File

@@ -1,7 +0,0 @@
package org.joinmastodon.android.model;
public class TranslatedStatus extends BaseModel {
public String content;
public String detectedSourceLanguage;
public String provider;
}

View File

@@ -1,10 +1,5 @@
package org.joinmastodon.android.model.catalog;
import android.graphics.Region;
import android.text.TextUtils;
import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.api.AllFieldsAreRequired;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.model.BaseModel;
@@ -12,17 +7,13 @@ import org.joinmastodon.android.model.BaseModel;
import java.net.IDN;
import java.util.List;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
@AllFieldsAreRequired
public class CatalogInstance extends BaseModel{
public String domain;
public String version;
public String description;
public List<String> languages;
@SerializedName("region")
private String _region;
public String region;
public List<String> categories;
public String proxiedThumbnail;
public int totalUsers;
@@ -31,9 +22,7 @@ public class CatalogInstance extends BaseModel{
public String language;
public String category;
public transient Region region;
public transient String normalizedDomain;
public transient UrlImageLoaderRequest thumbnailRequest;
@Override
public void postprocess() throws ObjectValidationException{
@@ -42,14 +31,6 @@ public class CatalogInstance extends BaseModel{
normalizedDomain=IDN.toUnicode(domain);
else
normalizedDomain=domain;
if(!TextUtils.isEmpty(_region)){
try{
region=Region.valueOf(_region.toUpperCase());
}catch(IllegalArgumentException ignore){}
}
if(!TextUtils.isEmpty(proxiedThumbnail)){
thumbnailRequest=new UrlImageLoaderRequest(proxiedThumbnail, 0, V.dp(56));
}
}
@Override
@@ -69,13 +50,4 @@ public class CatalogInstance extends BaseModel{
", category='"+category+'\''+
'}';
}
public enum Region{
EUROPE,
NORTH_AMERICA,
SOUTH_AMERICA,
AFRICA,
ASIA,
OCEANIA
}
}

View File

@@ -2,12 +2,14 @@ package org.joinmastodon.android.ui;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -23,7 +25,8 @@ 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.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.fragments.SplashFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.List;
@@ -77,7 +80,7 @@ public class AccountSwitcherSheet extends BottomSheet{
holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, ()->{
Nav.go(activity, CustomWelcomeFragment.class, null);
Nav.go(activity, SplashFragment.class, null);
dismiss();
}));
@@ -240,10 +243,7 @@ public class AccountSwitcherSheet extends BottomSheet{
public WrappedAccount(AccountSession session){
this.session=session;
if(session.self.avatar!=null)
req=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? session.self.avatar : session.self.avatarStatic, V.dp(50), V.dp(50));
else
req=null;
req=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? session.self.avatar : session.self.avatarStatic, V.dp(50), V.dp(50));
}
}
}

View File

@@ -12,8 +12,6 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Account;
@@ -25,9 +23,7 @@ import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
@@ -158,18 +154,10 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
private void onFollowRequestButtonClick(View v) {
itemView.setHasTransientState(true);
UiUtils.handleFollowRequest((Activity) v.getContext(), item.account, item.parentFragment.getAccountID(), null, v == acceptButton, relationship, rel -> {
UiUtils.handleFollowRequest((Activity) v.getContext(), item.account, item.parentFragment.getAccountID(), item.notification.id , v == acceptButton, relationship, rel -> {
itemView.setHasTransientState(false);
item.parentFragment.putRelationship(item.account.id, rel);
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
if (!rel.requested && !rel.followedBy && adapter != null) {
int index = item.parentFragment.getDisplayItems().indexOf(item);
item.parentFragment.getDisplayItems().remove(index);
item.parentFragment.getDisplayItems().remove(index - 1);
adapter.notifyItemRangeRemoved(getLayoutPosition()-1, 2);
} else {
rebind();
}
rebind();
});
}

View File

@@ -4,14 +4,9 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.ScaleAnimation;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@@ -26,8 +21,10 @@ import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.text.DecimalFormat;
import me.grishka.appkit.Nav;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
public class FooterStatusDisplayItem extends StatusDisplayItem{
@@ -49,7 +46,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
private final TextView reply, boost, favorite, bookmark;
private final ImageView share;
private static final AnimationSet scaleDown, scaleUp;
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
@Override
@@ -60,27 +56,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
};
static {
// 20dp to center in middle of icon, because: (icon width = 24dp) / 2 + (paddingStart = 8dp)
Animation scaleDownAnim = new ScaleAnimation(1, 0.85f, 1, 0.85f, Animation.ABSOLUTE, V.dp(20), Animation.RELATIVE_TO_SELF, 0.5f);
Animation scaleUpAnim = new ScaleAnimation(0.85f, 1, 0.85f, 1, Animation.ABSOLUTE, V.dp(20), Animation.RELATIVE_TO_SELF, 0.5f);
Animation opacityOutAnim = new AlphaAnimation(1, 0.75f);
Animation opacityInAnim = new AlphaAnimation(0.75f, 1);
scaleDown = new AnimationSet(true);
scaleDown.setDuration(350);
scaleDown.setInterpolator(CubicBezierInterpolator.DEFAULT);
scaleDown.setFillAfter(true);
scaleDown.addAnimation(scaleDownAnim);
scaleDown.addAnimation(opacityOutAnim);
scaleUp = new AnimationSet(true);
scaleUp.setDuration(100);
scaleUp.setInterpolator(CubicBezierInterpolator.DEFAULT);
scaleUp.addAnimation(scaleUpAnim);
scaleUp.addAnimation(opacityInAnim);
}
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_footer, parent);
reply=findViewById(R.id.reply);
@@ -99,19 +74,14 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
View favorite=findViewById(R.id.favorite_btn);
View share=findViewById(R.id.share_btn);
View bookmark=findViewById(R.id.bookmark_btn);
reply.setOnTouchListener(this::onButtonTouch);
reply.setOnClickListener(this::onReplyClick);
reply.setAccessibilityDelegate(buttonAccessibilityDelegate);
boost.setOnTouchListener(this::onButtonTouch);
boost.setOnClickListener(this::onBoostClick);
boost.setAccessibilityDelegate(buttonAccessibilityDelegate);
favorite.setOnTouchListener(this::onButtonTouch);
favorite.setOnClickListener(this::onFavoriteClick);
favorite.setAccessibilityDelegate(buttonAccessibilityDelegate);
bookmark.setOnTouchListener(this::onButtonTouch);
bookmark.setOnClickListener(this::onBookmarkClick);
bookmark.setAccessibilityDelegate(buttonAccessibilityDelegate);
share.setOnTouchListener(this::onButtonTouch);
share.setOnClickListener(this::onShareClick);
share.setAccessibilityDelegate(buttonAccessibilityDelegate);
}
@@ -139,45 +109,30 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onReplyClick(View v){
v.startAnimation(scaleUp);
Bundle args=new Bundle();
args.putString("account", item.accountID);
args.putParcelable("replyTo", Parcels.wrap(item.status));
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}
private boolean onButtonTouch(View v, MotionEvent event){
if (event.getAction() == MotionEvent.ACTION_UP) v.performClick();
else if (event.getAction() == MotionEvent.ACTION_DOWN) v.startAnimation(scaleDown);
else if (event.getAction() == MotionEvent.ACTION_CANCEL) v.startAnimation(scaleUp);
return true;
}
private void onBoostClick(View v){
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, r->{
v.startAnimation(scaleUp);
boost.setSelected(item.status.reblogged);
bindButton(boost, r.reblogsCount);
});
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged);
boost.setSelected(item.status.reblogged);
bindButton(boost, item.status.reblogsCount);
}
private void onFavoriteClick(View v){
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
v.startAnimation(scaleUp);
favorite.setSelected(r.favourited);
bindButton(favorite, r.favouritesCount);
});
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited);
favorite.setSelected(item.status.favourited);
bindButton(favorite, item.status.favouritesCount);
}
private void onBookmarkClick(View v){
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked, r->{
v.startAnimation(scaleUp);
bookmark.setSelected(item.status.bookmarked);
});
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
bookmark.setSelected(item.status.bookmarked);
}
private void onShareClick(View v){
v.startAnimation(scaleUp);
Intent intent=new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, item.status.url);

View File

@@ -23,6 +23,7 @@ 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.requests.statuses.GetStatusTranslation;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ComposeFragment;
@@ -33,6 +34,7 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusTranslation;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -60,6 +62,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
private SpannableStringBuilder parsedName;
public final Status status;
private boolean hasVisibilityToggle;
private boolean hasTranslateToggle;
boolean needBottomPadding;
private String extraText;
@@ -74,6 +77,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
HtmlParser.parseCustomEmoji(parsedName, user.emojis);
emojiHelper.setText(parsedName);
if(status!=null){
hasTranslateToggle=true;
hasVisibilityToggle=status.sensitive || !TextUtils.isEmpty(status.spoilerText);
if(!hasVisibilityToggle && !status.mediaAttachments.isEmpty()){
for(Attachment att:status.mediaAttachments){
@@ -107,7 +111,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView name, username, timestamp, extraText;
private final ImageView avatar, more, visibility;
private final ImageView avatar, more, visibility, translate;
private final PopupMenu optionsMenu;
private Relationship relationship;
private APIRequest<?> currentRelationshipRequest;
@@ -121,6 +125,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_header, parent);
translate=findViewById(R.id.translate);
name=findViewById(R.id.name);
username=findViewById(R.id.username);
timestamp=findViewById(R.id.timestamp);
@@ -133,6 +138,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
avatar.setClipToOutline(true);
more.setOnClickListener(this::onMoreClick);
visibility.setOnClickListener(v->item.parentFragment.onVisibilityIconClick(this));
translate.setOnClickListener(v->item.parentFragment.onRevealTranslationClick(this));
optionsMenu=new PopupMenu(activity, more);
optionsMenu.inflate(R.menu.post);
@@ -226,6 +232,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
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);
translate.setVisibility(item.hasTranslateToggle ? View.VISIBLE : View.GONE);
if(item.hasVisibilityToggle){
visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility);
visibility.setContentDescription(item.parentFragment.getString(item.status.spoilerRevealed ? R.string.hide_content : R.string.reveal_content));
@@ -233,6 +240,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
visibility.setTooltipText(visibility.getContentDescription());
}
}
if(item.hasTranslateToggle){
translate.setImageResource(item.status.wantsTranslation ? R.drawable.ic_translate_on : R.drawable.ic_translate_off);
}
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
if(TextUtils.isEmpty(item.extraText)){
extraText.setVisibility(View.GONE);

View File

@@ -5,7 +5,6 @@ import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.R;
@@ -61,8 +60,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<PollOptionStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text, percent;
private final View button;
private final ImageView icon;
private final View icon, button;
private final Drawable progressBg;
public Holder(Activity activity, ViewGroup parent){
@@ -78,23 +76,18 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(PollOptionStatusDisplayItem item){
text.setText(item.text);
// icon.setVisibility(item.showResults ? View.GONE : View.VISIBLE);
percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE);
itemView.setClickable(!item.showResults);
icon.setImageDrawable(itemView.getContext().getDrawable(item.poll.multiple ?
item.showResults ? R.drawable.ic_poll_checkbox_regular_selector : R.drawable.ic_poll_checkbox_filled_selector :
item.showResults ? R.drawable.ic_poll_option_button : R.drawable.ic_fluent_radio_button_24_selector
));
if(item.showResults){
icon.setSelected(item.poll.ownVotes.contains(item.poll.options.indexOf(item.option)));
progressBg.setLevel(Math.round(10000f*item.votesFraction));
button.setBackground(progressBg);
itemView.setSelected(item.isMostVoted);
icon.setSelected(item.poll.ownVotes.contains(item.poll.options.indexOf(item.option)));
icon.setVisibility(item.poll.voted && item.poll.ownVotes.isEmpty() ? View.GONE : View.VISIBLE);
percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f)));
}else{
itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option));
button.setBackgroundResource(R.drawable.bg_poll_option_clickable);
icon.setVisibility(View.VISIBLE);
}
}

View File

@@ -8,20 +8,16 @@ import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.requests.statuses.GetStatusTranslation;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusTranslation;
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.TranslatedStatus;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.views.LinkedTextView;
@@ -39,26 +35,18 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence parsedSpoilerText;
public boolean textSelectable;
public final Status status;
public boolean translated = false;
public TranslatedStatus translation = null;
private AccountSession session;
private Instance instanceInfo;
private boolean translateEnabled;
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status){
super(parentID, parentFragment);
this.text=text;
this.status=status;
// this.wantsTranslation=wantsTranslation;
emojiHelper.setText(text);
if(!TextUtils.isEmpty(status.spoilerText)){
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
spoilerEmojiHelper=new CustomEmojiHelper();
spoilerEmojiHelper.setText(parsedSpoilerText);
}
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
translateEnabled = instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null && instanceInfo.v2.configuration.translation.enabled;
}
@Override
@@ -83,10 +71,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
private final LinkedTextView text;
private final LinearLayout spoilerHeader;
private final TextView spoilerTitle, spoilerTitleInline, translateInfo;
private final View spoilerOverlay, borderTop, borderBottom, textWrap, translateWrap, translateProgress;
private final TextView spoilerTitle, spoilerTitleInline;
private final View spoilerOverlay, borderTop, borderBottom;
private final Drawable backgroundColor, borderColor;
private final Button translateButton;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent);
@@ -97,11 +84,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
spoilerOverlay=findViewById(R.id.spoiler_overlay);
borderTop=findViewById(R.id.border_top);
borderBottom=findViewById(R.id.border_bottom);
textWrap=findViewById(R.id.text_wrap);
translateWrap=findViewById(R.id.translate_wrap);
translateButton=findViewById(R.id.translate_btn);
translateInfo=findViewById(R.id.translate_info);
translateProgress=findViewById(R.id.translate_progress);
itemView.setOnClickListener(v->item.parentFragment.onRevealSpoilerClick(this));
TypedValue outValue=new TypedValue();
@@ -115,9 +97,26 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(TextStatusDisplayItem item){
text.setText(item.translated
? HtmlParser.parse(item.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
: item.text);
if(item.status.wantsTranslation){
new GetStatusTranslation(item.status.id)
.setCallback(new Callback<StatusTranslation>(){
@Override
public void onSuccess(StatusTranslation status){
text.setText(status.getStrippedText());
}
@Override
public void onError(ErrorResponse error){
item.status.wantsTranslation=false;
text.setText(item.text);
error.showToast(item.parentFragment.getActivity());
}
})
.wrapProgress(item.parentFragment.getActivity(), R.string.loading, true)
.exec(item.parentFragment.getAccountID());
}else{
text.setText(item.text);
}
text.setTextIsSelectable(item.textSelectable);
spoilerTitleInline.setTextIsSelectable(item.textSelectable);
text.setInvalidateOnEveryFrame(false);
@@ -131,54 +130,20 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
if(item.status.spoilerRevealed){
spoilerOverlay.setVisibility(View.GONE);
spoilerHeader.setVisibility(View.VISIBLE);
textWrap.setVisibility(View.VISIBLE);
text.setVisibility(View.VISIBLE);
itemView.setClickable(false);
}else{
spoilerOverlay.setVisibility(View.VISIBLE);
spoilerHeader.setVisibility(View.GONE);
textWrap.setVisibility(View.GONE);
text.setVisibility(View.GONE);
itemView.setClickable(true);
}
}else{
spoilerOverlay.setVisibility(View.GONE);
spoilerHeader.setVisibility(View.GONE);
textWrap.setVisibility(View.VISIBLE);
text.setVisibility(View.VISIBLE);
itemView.setClickable(false);
}
translateWrap.setVisibility(item.textSelectable && item.translateEnabled &&
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
item.status.language != null &&
(item.session.preferences == null || !item.status.language.equalsIgnoreCase(item.session.preferences.postingDefaultLanguage))
? View.VISIBLE : View.GONE);
translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post);
translateInfo.setText(item.translated ? itemView.getResources().getString(R.string.sk_translated_using, item.translation.provider) : "");
translateButton.setOnClickListener(v->{
if (item.translation == null) {
translateProgress.setVisibility(View.VISIBLE);
translateButton.setClickable(false);
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.translation = translatedStatus;
item.translated = true;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
rebind();
}
@Override
public void onError(ErrorResponse error) {
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
error.showToast(itemView.getContext());
}
}).exec(item.parentFragment.getAccountID());
} else {
item.translated = !item.translated;
rebind();
}
});
}
@Override

View File

@@ -698,24 +698,14 @@ public class UiUtils{
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack_Blue : R.style.Theme_Mastodon_Dark_Blue;
});
break;
case BROWN:
case ORANGE:
context.setTheme(switch(GlobalUserPreferences.theme){
case AUTO ->
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack_Brown : R.style.Theme_Mastodon_AutoLightDark_Brown;
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack_Orange : R.style.Theme_Mastodon_AutoLightDark_Orange;
case LIGHT ->
R.style.Theme_Mastodon_Light_Brown;
R.style.Theme_Mastodon_Light_Orange;
case DARK ->
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack_Brown : R.style.Theme_Mastodon_Dark_Brown;
});
break;
case RED:
context.setTheme(switch(GlobalUserPreferences.theme){
case AUTO ->
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack_Red : R.style.Theme_Mastodon_AutoLightDark_Red;
case LIGHT ->
R.style.Theme_Mastodon_Light_Red;
case DARK ->
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack_Red : R.style.Theme_Mastodon_Dark_Red;
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack_Orange : R.style.Theme_Mastodon_Dark_Orange;
});
break;
case YELLOW:

View File

@@ -1,59 +0,0 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.Button;
import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.utils.UiUtils;
import androidx.annotation.DrawableRes;
import me.grishka.appkit.utils.V;
public class FilterChipView extends Button{
private boolean currentlySelected;
public FilterChipView(Context context){
this(context, null);
}
public FilterChipView(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public FilterChipView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
setCompoundDrawablePadding(V.dp(8));
setBackgroundResource(R.drawable.bg_filter_chip);
setTextAppearance(R.style.m3_label_large);
setTextColor(getResources().getColorStateList(R.color.filter_chip_text, context.getTheme()));
setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurface)));
updatePadding();
}
@Override
protected void drawableStateChanged(){
super.drawableStateChanged();
if(currentlySelected==isSelected())
return;
currentlySelected=isSelected();
Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()) : null;
Drawable end=getCompoundDrawablesRelative()[2];
setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null);
updatePadding();
}
private void updatePadding(){
int vertical=V.dp(6);
Drawable[] drawables=getCompoundDrawablesRelative();
setPaddingRelative(V.dp(drawables[0]==null ? 16 : 8), vertical, V.dp(drawables[2]==null ? 16 : 8), vertical);
}
public void setDrawableEnd(@DrawableRes int drawable){
setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, drawable, 0);
updatePadding();
}
}

View File

@@ -5,34 +5,19 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.text.Editable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
@@ -43,10 +28,6 @@ public class FloatingHintEditTextLayout extends FrameLayout{
private int offsetY;
private boolean hintVisible;
private Animator currentAnim;
private float animProgress;
private RectF tmpRect=new RectF();
private ColorStateList labelColors, origHintColors;
private boolean errorState;
public FloatingHintEditTextLayout(Context context){
this(context, null);
@@ -63,9 +44,7 @@ public class FloatingHintEditTextLayout extends FrameLayout{
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FloatingHintEditTextLayout);
labelTextSize=ta.getDimensionPixelSize(R.styleable.FloatingHintEditTextLayout_android_labelTextSize, V.dp(12));
offsetY=ta.getDimensionPixelOffset(R.styleable.FloatingHintEditTextLayout_editTextOffsetY, 0);
labelColors=ta.getColorStateList(R.styleable.FloatingHintEditTextLayout_labelTextColor);
ta.recycle();
setAddStatesFromChildren(true);
}
@Override
@@ -79,15 +58,13 @@ public class FloatingHintEditTextLayout extends FrameLayout{
label=new TextView(getContext());
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize);
// label.setTextColor(labelColors==null ? edit.getHintTextColors() : labelColors);
origHintColors=edit.getHintTextColors();
label.setTextColor(edit.getHintTextColors());
label.setText(edit.getHint());
label.setSingleLine();
label.setPivotX(0f);
label.setPivotY(0f);
label.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.TOP);
lp.setMarginStart(edit.getPaddingStart()+((LayoutParams)edit.getLayoutParams()).getMarginStart());
lp.setMarginStart(edit.getPaddingStart());
addView(label, lp);
hintVisible=edit.getText().length()==0;
@@ -98,11 +75,6 @@ public class FloatingHintEditTextLayout extends FrameLayout{
}
private void onTextChanged(Editable text){
if(errorState){
errorState=false;
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field));
refreshDrawableState();
}
boolean newHintVisible=text.length()==0;
if(newHintVisible==hintVisible)
return;
@@ -111,210 +83,42 @@ public class FloatingHintEditTextLayout extends FrameLayout{
hintVisible=newHintVisible;
label.setAlpha(1);
edit.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
float scale=edit.getLineHeight()/(float)label.getLineHeight();
float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
AnimatorSet anim=new AnimatorSet();
if(hintVisible){
anim.playTogether(
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0),
ObjectAnimator.ofFloat(label, SCALE_X, scale),
ObjectAnimator.ofFloat(label, SCALE_Y, scale),
ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY)
);
edit.setHintTextColor(0);
}else{
label.setScaleX(scale);
label.setScaleY(scale);
label.setTranslationY(transY);
anim.playTogether(
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY),
ObjectAnimator.ofFloat(label, SCALE_X, 1f),
ObjectAnimator.ofFloat(label, SCALE_Y, 1f),
ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f)
);
}
anim.setDuration(150);
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
anim.start();
anim.addListener(new AnimatorListenerAdapter(){
@Override
public boolean onPreDraw(){
edit.getViewTreeObserver().removeOnPreDrawListener(this);
float scale=edit.getLineHeight()/(float)label.getLineHeight();
float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
AnimatorSet anim=new AnimatorSet();
public void onAnimationEnd(Animator animation){
currentAnim=null;
if(hintVisible){
anim.playTogether(
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0),
ObjectAnimator.ofFloat(label, SCALE_X, scale),
ObjectAnimator.ofFloat(label, SCALE_Y, scale),
ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY),
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 0f)
);
edit.setHintTextColor(0);
}else{
label.setScaleX(scale);
label.setScaleY(scale);
label.setTranslationY(transY);
anim.playTogether(
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY),
ObjectAnimator.ofFloat(label, SCALE_X, 1f),
ObjectAnimator.ofFloat(label, SCALE_Y, 1f),
ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f),
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 1f)
);
label.setAlpha(0);
edit.setHintTextColor(label.getTextColors());
}
anim.setDuration(150);
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
anim.start();
anim.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
currentAnim=null;
if(hintVisible){
label.setAlpha(0);
edit.setHintTextColor(origHintColors);
}
}
});
currentAnim=anim;
return true;
}
});
}
@Keep
public void setAnimProgress(float progress){
animProgress=progress;
invalidate();
}
@Keep
public float getAnimProgress(){
return animProgress;
}
@Override
public void onDrawForeground(Canvas canvas){
if(getForeground()!=null && animProgress>0){
canvas.save();
float width=(label.getWidth()+V.dp(8))*animProgress;
float centerX=label.getLeft()+label.getWidth()/2f;
tmpRect.set(centerX-width/2f, label.getTop(), centerX+width/2f, label.getBottom());
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O)
canvas.clipOutRect(tmpRect);
else
canvas.clipRect(tmpRect, Region.Op.DIFFERENCE);
super.onDrawForeground(canvas);
canvas.restore();
}else{
super.onDrawForeground(canvas);
}
}
@Override
public void setForeground(Drawable foreground){
super.setForeground(new PaddedForegroundDrawable(foreground));
}
@Override
public Drawable getForeground(){
if(super.getForeground() instanceof PaddedForegroundDrawable pfd){
return pfd.wrapped;
}
return null;
}
@Override
protected void drawableStateChanged(){
super.drawableStateChanged();
if(label==null || errorState)
return;
ColorStateList color=labelColors==null ? origHintColors : labelColors;
label.setTextColor(color.getColorForState(getDrawableState(), 0xff00ff00));
}
public void setErrorState(){
if(errorState)
return;
errorState=true;
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field_error, getContext().getTheme()));
label.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3Error));
}
private class PaddedForegroundDrawable extends Drawable{
private final Drawable wrapped;
private PaddedForegroundDrawable(Drawable wrapped){
this.wrapped=wrapped;
wrapped.setCallback(new Callback(){
@Override
public void invalidateDrawable(@NonNull Drawable who){
invalidateSelf();
}
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when){
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what){
unscheduleSelf(what);
}
});
}
@Override
public void draw(@NonNull Canvas canvas){
wrapped.draw(canvas);
}
@Override
public void setAlpha(int alpha){
wrapped.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter){
wrapped.setColorFilter(colorFilter);
}
@Override
public int getOpacity(){
return wrapped.getOpacity();
}
@Override
public boolean setState(@NonNull int[] stateSet){
return wrapped.setState(stateSet);
}
@Override
public int getLayoutDirection(){
return wrapped.getLayoutDirection();
}
@Override
public int getAlpha(){
return wrapped.getAlpha();
}
@Nullable
@Override
public ColorFilter getColorFilter(){
return wrapped.getColorFilter();
}
@Override
public boolean isStateful(){
return wrapped.isStateful();
}
@NonNull
@Override
public int[] getState(){
return wrapped.getState();
}
@NonNull
@Override
public Drawable getCurrent(){
return wrapped.getCurrent();
}
@Override
public void applyTheme(@NonNull Resources.Theme t){
wrapped.applyTheme(t);
}
@Override
public boolean canApplyTheme(){
return wrapped.canApplyTheme();
}
@Override
protected void onBoundsChange(@NonNull Rect bounds){
super.onBoundsChange(bounds);
LayoutParams lp=(LayoutParams) edit.getLayoutParams();
wrapped.setBounds(bounds.left+lp.leftMargin-V.dp(12), bounds.top, bounds.right-lp.rightMargin+V.dp(12), bounds.bottom);
}
currentAnim=anim;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorButtonBackgroundPrimaryDarkOnLight" android:state_enabled="true"/>
<item android:color="?colorButtonBackgroundPrimaryDarkOnLightDisabled"/>
<item android:color="?android:colorPrimary" android:state_enabled="true"/>
<item android:color="?colorPollVoted"/>
</selector>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorButtonBackgroundPrimaryLightOnDark" android:state_enabled="true"/>
<item android:color="?colorButtonBackgroundPrimaryLightOnDarkDisabled"/>
<item android:color="?colorSecondary" android:state_enabled="true"/>
<item android:color="?colorPollVoted"/>
</selector>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorButtonBackgroundSecondaryDarkOnLight" android:state_enabled="true"/>
<item android:color="?colorButtonBackgroundSecondaryDarkOnLightDisabled"/>
<item android:color="?colorBackgroundLightest" android:state_enabled="true"/>
<item android:color="?android:colorBackground"/>
</selector>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorButtonBackgroundSecondaryLightOnDark" android:state_enabled="true"/>
<item android:color="?colorButtonBackgroundSecondaryLightOnDarkDisabled"/>
<item android:color="?colorPollVoted" android:state_enabled="true"/>
<item android:color="?colorSearchHint"/>
</selector>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3Primary" android:state_enabled="true"/>
<item android:color="?colorM3OnSurface" android:alpha="0.38"/>
</selector>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorButtonTextPrimaryDarkOnLight" android:state_enabled="true"/>
<item android:color="?colorButtonTextPrimaryDarkOnLightDisabled"/>
<item android:color="?colorSecondary" android:state_enabled="true"/>
<item android:color="?colorTabInactive"/>
</selector>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorButtonTextPrimaryLightOnDark" android:state_enabled="true"/>
<item android:color="?colorButtonTextPrimaryLightOnDarkDisabled"/>
<item android:color="@color/black" android:state_enabled="true"/>
<item android:color="?colorTabInactive"/>
</selector>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorButtonTextSecondaryDarkOnLight" android:state_enabled="true"/>
<item android:color="?colorButtonTextSecondaryDarkOnLightDisabled"/>
<item android:color="?android:colorPrimary" android:state_enabled="true"/>
<item android:color="?colorTabInactive"/>
</selector>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/gray_50"/>
<item android:color="?colorSecondary"/>
</selector>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3OnSecondaryContainer" android:state_selected="true"/>
<item android:color="?colorM3OnSurface"/>
</selector>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3OnSecondaryContainer" android:alpha="0.12"/>
</selector>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3OnSurfaceVariant" android:alpha="0.12"/>
</selector>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3Primary" android:state_focused="true"/>
<item android:color="?colorM3OnSurfaceVariant"/>
</selector>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3Primary" android:alpha="0.12"/>
</selector>

View File

@@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="0.9699526"
android:scaleY="0.9699526"
android:translateX="0.96"
android:translateY="6.925208">
<path
android:pathData="m3.639,-0c-1.097,0 -1.983,0.387 -2.658,1.141 -0.655,0.754 -0.981,1.771 -0.981,3.053l0,6.27L2.482,10.464L2.482,4.378c0,-1.284 0.539,-1.935 1.618,-1.935 1.192,0 1.791,0.773 1.791,2.3l0,3.331l2.468,0l0,-3.331c0,-1.527 0.598,-2.3 1.791,-2.3 1.078,0 1.618,0.651 1.618,1.935l0,6.085l2.482,0l0,-6.27c0,-1.281 -0.326,-2.299 -0.981,-3.053 -0.676,-0.754 -1.56,-1.141 -2.658,-1.141 -1.27,0 -2.232,0.488 -2.868,1.466L7.125,2.504 6.506,1.466C5.87,0.488 4.909,-0 3.639,-0Z"
android:strokeWidth="0.796"
android:fillColor="#000000"/>
<path
android:pathData="m18.947,10.464q-1.113,0 -1.986,-0.493 -0.873,-0.507 -1.366,-1.366 -0.479,-0.873 -0.479,-1.958 0,-1.07 0.479,-1.944 0.493,-0.873 1.366,-1.366 0.873,-0.507 1.986,-0.507 1.099,0 1.972,0.507 0.873,0.493 1.352,1.366 0.493,0.873 0.493,1.944 0,1.085 -0.493,1.958 -0.479,0.859 -1.352,1.366 -0.873,0.493 -1.972,0.493zM18.947,8.759q0.535,0 0.986,-0.254 0.451,-0.254 0.718,-0.732 0.268,-0.479 0.268,-1.127 0,-0.634 -0.268,-1.113 -0.268,-0.479 -0.718,-0.732 -0.451,-0.254 -0.986,-0.254 -0.535,0 -0.986,0.254 -0.451,0.254 -0.732,0.732 -0.268,0.479 -0.268,1.113 0,0.634 0.268,1.127 0.282,0.479 0.732,0.732 0.451,0.254 0.986,0.254z"
android:strokeWidth="0.687"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple android:color="@color/m3_primary_overlay" xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/mask">
<shape>
<solid android:color="#000"/>
<corners android:radius="20dp"/>
</shape>
</item>
</ripple>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/gray_800"/>
<solid android:color="?colorWindowBackground"/>
<corners android:radius="10dp"/>
<padding android:top="16dp" android:left="16dp" android:right="16dp" android:bottom="16dp"/>
</shape>

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<ripple android:color="@color/m3_on_secondary_container_overlay">
<item>
<shape>
<corners android:radius="8dp"/>
<solid android:color="?colorM3SecondaryContainer"/>
</shape>
</item>
</ripple>
</item>
<item>
<ripple android:color="@color/m3_on_surface_variant_overlay">
<item android:id="@android:id/mask">
<shape>
<corners android:radius="8dp"/>
<solid android:color="#000"/>
</shape>
</item>
<item>
<shape>
<corners android:radius="8dp"/>
<stroke android:color="?colorM3Outline" android:width="1dp"/>
</shape>
</item>
</ripple>
</item>
</selector>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:left="12dp" android:top="12dp" android:right="12dp" android:bottom="12dp">
<selector>
<item android:state_focused="true">
<shape>
<stroke android:color="?colorM3Primary" android:width="2dp"/>
<corners android:radius="4dp"/>
</shape>
</item>
<item>
<shape>
<stroke android:color="?colorM3Outline" android:width="1dp"/>
<corners android:radius="4dp"/>
</shape>
</item>
</selector>
</item>
</layer-list>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:left="12dp" android:top="12dp" android:right="12dp" android:bottom="12dp">
<shape>
<stroke android:color="?colorM3Error" android:width="2dp"/>
<corners android:radius="4dp"/>
</shape>
</item>
</layer-list>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="?colorM3Background"/>
</item>
<item android:id="@+id/color_overlay">
<color android:color="?colorM3Primary"/>
</item>
</layer-list>

View File

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

View File

@@ -1,5 +0,0 @@
<vector android:height="18dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector android:height="18dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@drawable/ic_fluent_arrow_repeat_all_24_very_filled"/>
<item android:state_selected="true" android:drawable="@drawable/ic_fluent_arrow_repeat_all_24_filled"/>
<item android:state_enabled="true" android:drawable="@drawable/ic_fluent_arrow_repeat_all_24_regular"/>
<item android:drawable="@drawable/ic_fluent_arrow_repeat_all_off_24_regular"/>
</selector>

View File

@@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,22C6.49,22 2,17.51 2,12S6.49,2 12,2s10,4.04 10,9c0,3.31 -2.69,6 -6,6h-1.77c-0.28,0 -0.5,0.22 -0.5,0.5 0,0.12 0.05,0.23 0.13,0.33 0.41,0.47 0.64,1.06 0.64,1.67 0,1.38 -1.12,2.5 -2.5,2.5zM12,4c-4.41,0 -8,3.59 -8,8s3.59,8 8,8c0.28,0 0.5,-0.22 0.5,-0.5 0,-0.16 -0.08,-0.28 -0.14,-0.35 -0.41,-0.46 -0.63,-1.05 -0.63,-1.65 0,-1.38 1.12,-2.5 2.5,-2.5L16,15c2.21,0 4,-1.79 4,-4 0,-3.86 -3.59,-7 -8,-7z"/>
<path
android:fillColor="#FF000000"
android:pathData="M6.5,11.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M9.5,7.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M14.5,7.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M17.5,11.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
</vector>

View File

@@ -1,3 +0,0 @@
<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 7c0.414 0 0.75 0.336 0.75 0.75v3.5h3.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75h-3.5v3.5c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75v-3.5h-3.5C7.336 12.75 7 12.414 7 12s0.336-0.75 0.75-0.75h3.5v-3.5C11.25 7.336 11.586 7 12 7zM3 6.25C3 4.455 4.455 3 6.25 3h11.5C19.545 3 21 4.455 21 6.25v11.5c0 1.795-1.455 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V6.25zM6.25 4.5C5.284 4.5 4.5 5.284 4.5 6.25v11.5c0 0.966 0.784 1.75 1.75 1.75h11.5c0.966 0 1.75-0.784 1.75-1.75V6.25c0-0.966-0.784-1.75-1.75-1.75H6.25z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,23 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M9,9.123C7.423,9.123 6.125,10.421 6.125,11.994C6.125,12.523 6.269,13.014 6.514,13.434C6.514,13.435 6.513,13.436 6.514,13.438L6.619,13.607L6.623,13.613C6.666,13.679 6.706,13.747 6.744,13.816L7.787,12.775C9.015,11.549 10.977,11.548 12.205,12.775C12.789,13.359 13.077,14.104 13.105,14.863L15.002,14.863C16.579,14.863 17.875,13.567 17.875,11.992C17.875,11.463 17.731,10.972 17.486,10.553L17.486,10.549L17.381,10.379C17.337,10.311 17.295,10.241 17.256,10.17L16.215,11.211C14.986,12.439 13.025,12.44 11.797,11.213C11.218,10.634 10.931,9.885 10.902,9.123L9,9.123z"
android:strokeWidth="3"
android:fillColor="#212121"/>
<path
android:pathData="M20.787,8.06C20.603,7.828 20.319,7.678 20,7.678c-0.552,0 -1,0.447 -1,0.999 0,0.208 0.064,0.401 0.172,0.561C19.695,10.028 20,10.975 20,11.993c0,2.759 -2.238,4.996 -4.999,4.996l-5.586,-0.001 1.294,-1.291 0.084,-0.095c0.281,-0.362 0.279,-0.872 -0.006,-1.231L10.709,14.284 10.614,14.2C10.252,13.92 9.741,13.922 9.382,14.206L9.294,14.284 6.289,17.287 6.206,17.382c-0.281,0.362 -0.279,0.872 0.006,1.231l0.078,0.087 3.005,3.003 0.094,0.083c0.392,0.305 0.96,0.277 1.32,-0.083 0.363,-0.362 0.389,-0.934 0.078,-1.326l-0.078,-0.087 -1.304,-1.303 5.596,0.001 0.241,-0.004C18.996,18.857 22,15.776 22,11.993 22,10.534 21.552,9.178 20.787,8.057Z"
android:fillColor="#212121"/>
<path
android:pathData="M14.712,2.289 L14.625,2.211C14.233,1.901 13.661,1.926 13.298,2.289L13.22,2.376C12.91,2.768 12.936,3.34 13.298,3.702l1.299,1.297 -5.598,0 -0.241,0.004C5.004,5.13 2,8.211 2,11.993c0,1.445 0.438,2.787 1.189,3.898 0.182,0.251 0.477,0.415 0.811,0.415 0.552,0 1,-0.447 1,-0.999C5,15.091 4.931,14.891 4.815,14.729L4.68,14.511C4.248,13.772 4,12.911 4,11.993 4,9.234 6.238,6.998 8.999,6.998L14.595,6.998 13.298,8.295 13.22,8.382c-0.311,0.392 -0.285,0.964 0.078,1.326 0.391,0.39 1.024,0.39 1.414,0L17.718,6.705 17.795,6.618C18.106,6.226 18.08,5.654 17.718,5.292Z"
android:strokeWidth="3"
android:fillColor="#212121"/>
<path
android:pathData="M20.11,5.619L20.11,5.619A1.393,0.037 0,0 1,21.503 5.656L21.503,5.656A1.393,0.037 0,0 1,20.11 5.693L20.11,5.693A1.393,0.037 0,0 1,18.717 5.656L18.717,5.656A1.393,0.037 0,0 1,20.11 5.619z"
android:strokeWidth="52.513"
android:fillColor="#613583"
android:fillType="evenOdd"
android:fillAlpha="0.0156863"/>
</vector>

View File

@@ -1,3 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M6.25 3C4.455 3 3 4.455 3 6.25v11.5C3 19.545 4.455 21 6.25 21h11.5c1.795 0 3.25-1.455 3.25-3.25V6.25C21 4.455 19.545 3 17.75 3H6.25zm11.03 6.28l-6.754 6.747c-0.293 0.292-0.767 0.292-1.06 0L6.72 13.28c-0.293-0.293-0.293-0.768 0-1.06 0.293-0.293 0.768-0.293 1.06 0l2.217 2.216 6.223-6.217c0.293-0.292 0.768-0.292 1.06 0.001 0.293 0.293 0.293 0.768 0 1.06z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,3 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M6.25 3C4.455 3 3 4.455 3 6.25v11.5C3 19.545 4.455 21 6.25 21h11.5c1.795 0 3.25-1.455 3.25-3.25V6.25C21 4.455 19.545 3 17.75 3H6.25zM4.5 6.25c0-0.966 0.784-1.75 1.75-1.75h11.5c0.966 0 1.75 0.784 1.75 1.75v11.5c0 0.966-0.784 1.75-1.75 1.75H6.25c-0.966 0-1.75-0.784-1.75-1.75V6.25zm12.78 3.03c0.293-0.292 0.293-0.767 0-1.06-0.292-0.293-0.767-0.293-1.06 0l-6.223 6.216L7.78 12.22c-0.293-0.293-0.768-0.293-1.06 0-0.294 0.292-0.294 0.767 0 1.06l2.745 2.746c0.293 0.293 0.767 0.293 1.06 0l6.754-6.745z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,3 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M3 6.25C3 4.455 4.455 3 6.25 3h11.5C19.545 3 21 4.455 21 6.25v11.5c0 1.795-1.455 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V6.25zM6.25 5C5.56 5 5 5.56 5 6.25v11.5C5 18.44 5.56 19 6.25 19h11.5c0.69 0 1.25-0.56 1.25-1.25V6.25C19 5.56 18.44 5 17.75 5H6.25z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,3 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M3 6.25C3 4.455 4.455 3 6.25 3h11.5C19.545 3 21 4.455 21 6.25v11.5c0 1.795-1.455 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V6.25zM6.25 4.5C5.284 4.5 4.5 5.284 4.5 6.25v11.5c0 0.966 0.784 1.75 1.75 1.75h11.5c0.966 0 1.75-0.784 1.75-1.75V6.25c0-0.966-0.784-1.75-1.75-1.75H6.25z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,3 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
<path android:pathData="M10 18c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-15c0.657 0 1.407 0.59 2.022 1.908 0.217 0.466 0.406 1.002 0.559 1.592H7.419c0.153-0.59 0.342-1.126 0.56-1.592C8.592 3.59 9.342 3 10 3zM7.072 4.485C6.796 5.077 6.565 5.757 6.389 6.5H3.936c0.837-1.446 2.176-2.565 3.778-3.118-0.241 0.33-0.456 0.704-0.642 1.103zM6.192 7.5C6.068 8.288 6 9.13 6 10c0 0.87 0.067 1.712 0.193 2.5H3.46C3.163 11.724 3 10.88 3 10c0-0.88 0.163-1.724 0.46-2.5h2.733zm0.197 6c0.176 0.743 0.407 1.422 0.683 2.015 0.186 0.399 0.401 0.773 0.642 1.103-1.602-0.553-2.941-1.672-3.778-3.118H6.39zm1.03 0h5.162c-0.153 0.59-0.342 1.126-0.56 1.592C11.408 16.41 10.658 17 10 17c-0.657 0-1.407-0.59-2.022-1.908C7.761 14.626 7.572 14.09 7.42 13.5zm5.375-1H7.206C7.073 11.725 7 10.883 7 10s0.074-1.725 0.206-2.5h5.588C12.927 8.275 13 9.117 13 10s-0.073 1.725-0.206 2.5zm0.817 1h2.453c-0.837 1.446-2.176 2.565-3.778 3.118 0.241-0.33 0.456-0.704 0.642-1.103 0.276-0.593 0.507-1.272 0.683-2.015zm2.93-1h-2.734C13.933 11.712 14 10.87 14 10c0-0.87-0.067-1.712-0.193-2.5h2.733C16.837 8.276 17 9.12 17 10c0 0.88-0.163 1.724-0.46 2.5zm-4.255-9.118c1.602 0.553 2.941 1.672 3.778 3.118H13.61c-0.176-0.743-0.407-1.423-0.683-2.015-0.186-0.399-0.401-0.773-0.642-1.103z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,3 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12 1.999c5.524 0 10.002 4.478 10.002 10.002 0 5.523-4.478 10.001-10.002 10.001-5.524 0-10.002-4.478-10.002-10.001C1.998 6.477 6.476 1.999 12 1.999zm0 1.5c-4.695 0-8.502 3.806-8.502 8.502 0 4.695 3.807 8.501 8.502 8.501s8.502-3.806 8.502-8.501c0-4.696-3.807-8.502-8.502-8.502zm-0.003 2.5c3.312 0 5.998 2.686 5.998 5.998 0 3.313-2.686 5.998-5.998 5.998-3.313 0-5.999-2.685-5.999-5.998S8.684 6 11.997 6z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@@ -1,8 +0,0 @@
<?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_radio_button_24_filled" android:state_activated="true"/>
<item android:drawable="@drawable/ic_fluent_radio_button_24_filled" android:state_checked="true"/>
<item android:drawable="@drawable/ic_fluent_radio_button_24_filled" android:state_selected="true"/>
<item android:drawable="@drawable/ic_fluent_radio_button_24_regular"/>
</selector>

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="2.5091188"
android:scaleY="2.5091188">
<path
android:pathData="M0,0h43.043v43.043h-43.043z"
android:strokeWidth="1.53886"
android:fillColor="#282c37"/>
<path
android:pathData="m13.779,16.29c-1.097,0 -1.983,0.387 -2.658,1.141 -0.655,0.754 -0.981,1.771 -0.981,3.053l0,6.27L12.622,26.754L12.622,20.668c0,-1.284 0.539,-1.935 1.618,-1.935 1.192,0 1.791,0.773 1.791,2.3l0,3.331l2.468,0l0,-3.331c0,-1.527 0.598,-2.3 1.791,-2.3 1.078,0 1.618,0.651 1.618,1.935l0,6.085l2.482,0l0,-6.27c0,-1.281 -0.326,-2.299 -0.981,-3.053 -0.676,-0.754 -1.56,-1.141 -2.658,-1.141 -1.27,0 -2.232,0.488 -2.868,1.466L17.265,18.794 16.646,17.756C16.01,16.778 15.049,16.29 13.779,16.29Z"
android:strokeWidth="0.796"
android:fillColor="#eed7f4"/>
<path
android:pathData="m29.087,26.754q-1.113,0 -1.986,-0.493 -0.873,-0.507 -1.366,-1.366 -0.479,-0.873 -0.479,-1.958 0,-1.07 0.479,-1.944 0.493,-0.873 1.366,-1.366 0.873,-0.507 1.986,-0.507 1.099,0 1.972,0.507 0.873,0.493 1.352,1.366 0.493,0.873 0.493,1.944 0,1.085 -0.493,1.958 -0.479,0.859 -1.352,1.366 -0.873,0.493 -1.972,0.493zM29.087,25.049q0.535,0 0.986,-0.254 0.451,-0.254 0.718,-0.732 0.268,-0.479 0.268,-1.127 0,-0.634 -0.268,-1.113 -0.268,-0.479 -0.718,-0.732 -0.451,-0.254 -0.986,-0.254 -0.535,0 -0.986,0.254 -0.451,0.254 -0.732,0.732 -0.268,0.479 -0.268,1.113 0,0.634 0.268,1.127 0.282,0.479 0.732,0.732 0.451,0.254 0.986,0.254z"
android:strokeWidth="0.687"
android:fillColor="#eed7f4"
android:strokeColor="#00000000"/>
</group>
</vector>

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