Compare commits

..

60 Commits

Author SHA1 Message Date
LucasGGamerM
be5ed6e224 documentation: add FAQ link in readme 2023-05-05 08:58:56 -03:00
LucasGGamerM
3b32fe3663 documentation: move F.A.Q to separate file 2023-05-05 08:54:28 -03:00
LucasGGamerM
e15b752bad documentation: update links to matrix chatroom
Use floss.social links instead of matrix.org ones
2023-05-05 08:50:15 -03:00
LucasGGamerM
5928cf675a documentation: update features and behavior sections on readme 2023-05-05 08:46:39 -03:00
LucasGGamerM
7655a5a3ae documentation: update release variants section in readme 2023-05-05 08:24:03 -03:00
LucasGGamerM
b5e0fca9c0 documentation: removing another exclamation mark 2023-05-04 07:36:10 -03:00
LucasGGamerM
bac6f58115 documentation: remove unnecessary exclamation marks on README 2023-05-04 07:35:21 -03:00
LucasGGamerM
e2a20f9599 documentation: remove over used bold sub texts 2023-05-03 11:16:58 -03:00
LucasGGamerM
900823e0dd documentation: add reminder to add alt text feature header 2023-05-03 11:13:21 -03:00
LucasGGamerM
918bda54d6 documentation: move F.A.Q to the bottom.
TODO: eventually move the F.A.Q into its own page
2023-05-03 11:06:52 -03:00
LucasGGamerM
cc1d1180e8 documentation: add translate feature header on readme 2023-05-03 11:02:48 -03:00
LucasGGamerM
006c0d00f2 documentation: add nightly to the readme app variants 2023-05-03 10:53:04 -03:00
LucasGGamerM
741a55110a documentation: add remote followers header in readme 2023-05-03 10:39:52 -03:00
LucasGGamerM
f7dcb754ed documentation: remove duplicate material you header
This removed the material you header, and instead move it to the color themes section
2023-05-03 10:25:10 -03:00
LucasGGamerM
f5e50bf668 decumentation: update readme
Updates custom local timelines feature header in readme
2023-05-03 10:21:08 -03:00
LucasGGamerM
4ad895ab71 Revert "fix(custom-local-timelines): fix inconsistent actions"
Reverting this due to realization I did it the other way due to it being better
2023-05-03 08:57:22 -03:00
LucasGGamerM
0fccd0ab37 fix(custom-local-timelines): fix inconsistent actions
This fixes a "bug" where you would favorite a post, then reload the page, and favorite the post again, your favorite would not "count". As in, you would click to favorite, and the favorite would just stay as is
2023-05-03 08:36:16 -03:00
LucasGGamerM
4fc6a8a2a5 Merge pull request #185
fix(translation): use lookUp status id for request
2023-05-01 17:20:32 -03:00
FineFindus
397d768f3a fix(translation): use lookUp status id for request 2023-05-01 17:50:48 +02:00
LucasGGamerM
f76eba894a feat(search): show hashtag subtitles even if item.history is null 2023-04-29 17:12:35 -03:00
LucasGGamerM
42ae74f1a7 fix(search): crash on akkoma where hashtag histories arent present 2023-04-29 16:53:58 -03:00
LucasGGamerM
9d09a904ab build: fixing debug build with wrong resources 2023-04-29 16:38:14 -03:00
LucasGGamerM
5366c92b4d build: fixing wrong drawables for github release 2023-04-28 16:26:17 -03:00
LucasGGamerM
c047c53aac build: bumping version name from 1.2.0 to 1.3.0
I forgot to do it
2023-04-28 16:05:12 -03:00
LucasGGamerM
489a49ca40 build: bump version number 2023-04-28 15:53:46 -03:00
LucasGGamerM
ed44a4dac4 Merge remote-tracking branch 'weblate/master' 2023-04-28 15:53:11 -03:00
nitrogenez47ab3e44720c4675
dc5c2dd907 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (274 of 274 strings)

Translation: Moshidon/values_sk
Translate-URL: https://translate.codeberg.org/projects/moshidon/values_sk/uk/
2023-04-28 18:51:54 +00:00
Eryk Michalak
cd86d04c9f Translated using Weblate (Polish)
Currently translated at 100.0% (274 of 274 strings)

Translation: Moshidon/values_sk
Translate-URL: https://translate.codeberg.org/projects/moshidon/values_sk/pl/
2023-04-28 18:51:54 +00:00
Andrewblasco
d0c64fcdf5 Translated using Weblate (Spanish)
Currently translated at 100.0% (274 of 274 strings)

Translation: Moshidon/values_sk
Translate-URL: https://translate.codeberg.org/projects/moshidon/values_sk/es/
2023-04-28 18:51:54 +00:00
nitrogenez47ab3e44720c4675
dfff3b8bcf Translated using Weblate (Ukrainian)
Currently translated at 100.0% (46 of 46 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2023-04-28 18:51:54 +00:00
LucasGGamerM
4e13f868fd fix(notificationsListFragment): fix crash when notification markers are null
This would happen when an account had 0 notifications and received one. After which, the user would tap on the notification icon on the tab bar and the app would crash.
Merge this @sk22, as this is a thing that might also happen on megalodon
2023-04-28 15:10:14 -03:00
LucasGGamerM
251ffbba8d feat: add follow back notification action 2023-04-28 15:00:34 -03:00
LucasGGamerM
01ae9ba7f5 fix: re-add missing drawable
This is the second commit for that, now I added it to git
2023-04-27 15:40:58 -03:00
LucasGGamerM
df1df28e23 fix: use enableAutoFabHide instead of autoFabHide 2023-04-27 15:35:53 -03:00
LucasGGamerM
23b2603a5f refactor: change withComposeButton to wantsComposeButton 2023-04-27 15:33:46 -03:00
LucasGGamerM
1e6dadd7ab Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
2023-04-27 15:30:58 -03:00
LucasGGamerM
26ab5a7f55 build: re-add missing drawable 2023-04-27 15:14:02 -03:00
Luna!
d7b76ed70a Fixed a typo in a comment in blocks.tsv (#509) 2023-04-27 17:49:15 +02:00
gallegonovato
bc7946dc23 Translated using Weblate (Spanish)
Currently translated at 100.0% (46 of 46 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-04-26 00:11:15 +00:00
LucasGGamerM
3a34599c82 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (46 of 46 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pt_BR/
2023-04-26 00:11:15 +00:00
Eryk Michalak
0a6dd8a754 Translated using Weblate (Polish)
Currently translated at 100.0% (46 of 46 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pl/
2023-04-26 00:11:15 +00:00
MKCOOL142
a61ece13af Translated using Weblate (German)
Currently translated at 100.0% (273 of 273 strings)

Translation: Moshidon/values_sk
Translate-URL: https://translate.codeberg.org/projects/moshidon/values_sk/de/
2023-04-26 00:11:14 +00:00
MKCOOL142
89e58fa947 Translated using Weblate (German)
Currently translated at 100.0% (33 of 33 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/de/
2023-04-26 00:11:14 +00:00
LucasGGamerM
4c47bb0768 Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-04-25 20:57:15 -03:00
LucasGGamerM
82005bf3bd feat: swap correct icon for mention reblogger automatically setting 2023-04-25 20:56:53 -03:00
LucasGGamerM
03d89ae93c docs: add donation link
Why didnt I do this earlier?
2023-04-24 17:19:03 -03:00
sk
2e84faa505 update languages 2023-04-23 17:08:51 +02:00
sk
e7e8d13d9e fix auto hide fab in profile fragment 2023-04-22 22:33:20 +02:00
sk
a683c2cb11 hide fab in notifications 2023-04-22 22:20:40 +02:00
sk
addf7de316 single fab for home tabs
closes sk22#415
2023-04-22 21:25:02 +02:00
sk
44d4eada51 fix "load missing more" being hidden
closes sk22#482
2023-04-22 20:14:07 +02:00
sk
40bfdea5b1 fix pleroma emoji reaction notifications 2023-04-22 19:52:46 +02:00
sk
55138c1e86 fix wrong true black badge border color
closes sk22#485
2023-04-22 19:17:12 +02:00
sk
0aef680572 Merge remote-tracking branch 'weblate/main' 2023-04-22 19:06:03 +02:00
sk22
6dc37d6bde Translated using Weblate (German)
Currently translated at 100.0% (275 of 275 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-04-22 17:05:32 +00:00
sk
60ea7cedf6 support glitch react notification 2023-04-22 19:05:14 +02:00
Espasant3
c986b10e14 Translated using Weblate (Galician)
Currently translated at 99.6% (272 of 273 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-04-22 17:00:55 +00:00
Choukajohn
d52174bd9e Translated using Weblate (French)
Currently translated at 100.0% (273 of 273 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-04-22 17:00:55 +00:00
sk22
c65d138911 Translated using Weblate (German)
Currently translated at 100.0% (273 of 273 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-04-22 17:00:55 +00:00
sk
ad9bb8ad58 support glitch react notification 2023-04-22 19:00:37 +02:00
79 changed files with 732 additions and 1772 deletions

5
FAQ.md Normal file
View File

@@ -0,0 +1,5 @@
## F.A.Q
Q: What are the main differences between Moshidon and Megalodon?
A: There are many, but the most outstanding differences are: the ability to have other server's local timeline inside the app. It can be acessed in the "Add community" option in the top right corner of the Edit timelines screen. Other outstanding features that Moshidon has are some quality of life improvements, such as notification actions and allowing for unlisted replies by default. Most other features are pretty minor, such as profile notes directly available in the person's profile. Other features are quite minor usability and visibility improvements. All of which can be found in the settings page.

View File

@@ -18,27 +18,27 @@
 
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
## Help out the project by donating at: https://github.com/sponsors/LucasGGamerM!
---
## F.A.Q
### Q: What are the main differences between Moshidon and Megalodon?
### A: There are many, but the most outstanding differences are: the ability to have other server's local timeline inside the app. It can be acessed in the "Add community" option in the top right corner of the Edit timelines screen. Other outstanding features that Moshidon has are some quality of life improvements, such as notification actions and allowing for unlisted replies by default. Most other features are pretty minor, such as profile notes directly available in the person's profile. Other features are quite minor usability and visibility improvements. All of which can be found in the settings page.
---
## Key features
### **The ability to add new custom local timelines!**
### **The ability to add other server's local timeline to your timelines**
#### It can be accessed in the "Edit timelines" menu, where you can add a new "Community" to see other server's local posts!
It can be accessed in the "Edit timelines" menu, where you can add a new "Community" to see other server's local posts!
### **Material you theme support on Android 12+ devices!**
### **View remote profiles**
### **Show posts filtered with a warning!**
You can now see all of a profile follows and followers, by directly loading them from the profile's home instance. In case of a failed lookup, the app will automatically fall back to the older method.
**Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:**
### **Translate posts easily**
Allows you to easily translate posts in another language with a translate button! Your instance must support translation, otherwise it will not work.
### **Show posts filtered with a warning**
Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:
Before | After
:-------------------------:|:-------------------------:
@@ -47,7 +47,7 @@ Before | After
### **Color themes**
**Allows you to change theme within the app. Supports Purple, pink, green, blue, red, orange, yellow and Nord!**
**Allows you to change theme within the app. Supports Material You, purple, pink, green, blue, red, orange, yellow and Nord!**
### **Unlisted posting**
@@ -71,6 +71,10 @@ Thats one of the reasons why choosing a small, **well-moderated instance is i
This is important to **ensure the content youre sharing is as accessible as possible** to people who cant see the images and rely on software to read back the provided content descriptions. Thankfully, its quite common for people on the Fediverse to provide such alt texts, and hopefully things stay this way!
### **Reminder to add alt text to attached media**
By default, Moshidon will show a warning to add alt text if your post has any attachments without any alt text. This is for better accessibility, and it can easily be bypassed and disabled in settings.
### **Pinning posts**
**This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in peoples profiles shows all the posts they pinned.**
@@ -95,12 +99,22 @@ Moshidon is also available in [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/
## Release variants
All downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
### Stable variant
All stable version downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
**`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`.
### Nightly variant
All nightly builds can be downloaded at [Nightly Releases](https://github.com/LucasGGamerM/moshidon-nightly/releases) page.
**`moshidon-nightly.apk`**
Unstable variant with an integrated updater. It's for development and testing purposes. If you find any bugs with it, please file a bug report at our [issues](https://github.com/LucasGGamerM/moshidon/issues) page.
---
@@ -108,16 +122,18 @@ Variant with an integrated updater. If you download Moshidon from here (and not
### Features
* [Adding the ability to view other server's local timelines](https://github.com/LucasGGamerM/moshidon/tree/feature/local-timelines)
* [Adding the ability to load followers and following from remote instance](https://github.com/LucasGGamerM/moshidon/tree/feature/remote-followers)
* [Adding the ability to have filtered posts show with a warning](https://github.com/LucasGGamerM/moshidon/tree/feature/filters_again)
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
* Adding a useful private profile note box!*
* Auto hiding the compose button on scroll!*
* Adding the ability to remind yourself to add alt text to images!*
* An indicator for if an image has alt text or not*
* Adding the ability to have drafts!*
* Also adding the ability to view announcements from your instance!*
* Adding the ability to post for local timeline only (Only on instances that support it!)*
* Adding a useful private profile note box
* Auto hiding the compose button on scroll
* Adding the ability to remind yourself to add alt text to images
* An indicator for if an image has alt text or not
* Adding the ability to have drafts
* Also adding the ability to view announcements from your instance
* Adding the ability to post for local timeline only (Only on instances that support it!)
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
@@ -139,6 +155,7 @@ Variant with an integrated updater. If you download Moshidon from here (and not
### Behavior
* Allow for confirmation before reblogging
* Adding a bottom option for the publish button, allowing for easier use on larger screens!
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
@@ -170,6 +187,10 @@ This project is released under the [GPL-3 License](./LICENSE).
## Links
[Official matrix chatroom:](https://matrix.to/#/#moshidon:matrix.org) https://matrix.to/#/#moshidon:matrix.org
[F.A.Q](FAQ.md)
[Official matrix chatroom:](https://matrix.to/#/#moshidon:floss.social) https://matrix.to/#/#moshidon:floss.social
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
---

View File

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

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 988 B

After

Width:  |  Height:  |  Size: 988 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -12,7 +12,7 @@ gab.protohype.net
social.unzensiert.to
freeatlantis.com
# reactionary bigotry and hatespeech against magrinalized groups
# reactionary bigotry and hatespeech against marginalized groups
poa.st
freespeechextremist.com
rdrama.cc
1 # lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
12 # reactionary bigotry and hatespeech against magrinalized groups reactionary bigotry and hatespeech against marginalized groups
13 poa.st
14 freespeechextremist.com
15 rdrama.cc
16 outpoa.st
17 anime.website
18 gameliberty.club

View File

@@ -19,6 +19,7 @@ import android.text.TextUtils;
import android.util.Log;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.notifications.GetNotificationByID;
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
@@ -123,8 +124,16 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if(intent.hasExtra("notification")){
org.joinmastodon.android.model.Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
String statusID=notification.status.id;
if (statusID != null) {
String statusID = null;
String targetAccountID = null;
if(notification.status != null){
statusID = notification.status.id;
}
if(notification.account != null){
targetAccountID = notification.account.id;
}
if (statusID != null || targetAccountID != null) {
AccountSessionManager accountSessionManager = AccountSessionManager.getInstance();
Preferences preferences = accountSessionManager.getAccount(accountID).preferences;
@@ -134,6 +143,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
case BOOST -> new SetStatusReblogged(notification.status.id, true, preferences.postingDefaultVisibility).exec(accountID);
case UNBOOST -> new SetStatusReblogged(notification.status.id, false, preferences.postingDefaultVisibility).exec(accountID);
case REPLY -> handleReplyAction(context, accountID, intent, notification, notificationId, preferences);
case FOLLOW_BACK -> new SetAccountFollowed(notification.account.id, true, true, false).exec(accountID);
default -> Log.w(TAG, "onReceive: Failed to get NotificationAction");
}
}
@@ -241,6 +251,9 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if(notification.status.reblogged)
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.sk_undo_reblog), NotificationAction.UNBOOST));
}
case FOLLOW -> {
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.follow_back), NotificationAction.FOLLOW_BACK));
}
}
}

View File

@@ -49,7 +49,6 @@ import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.MediaGridLayout;
import org.joinmastodon.android.utils.TypedObjectPool;
import java.util.ArrayList;
@@ -90,10 +89,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public BaseStatusListFragment(){
super(20);
if (withComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab);
if (wantsComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab);
}
protected boolean withComposeButton() {
protected boolean wantsComposeButton() {
return false;
}
@@ -109,8 +108,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
setRetainInstance(true);
}
@Override
protected RecyclerView.Adapter getAdapter(){
return adapter=new DisplayItemsAdapter();
@@ -278,6 +275,36 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
});
}
public @Nullable View getFab() {
if (getParentFragment() instanceof HasFab l) return l.getFab();
else return fab;
}
public void animateFab(boolean show) {
View fab = getFab();
if (fab == null) return;
if (show && fab.getVisibility() != View.VISIBLE) {
fab.setVisibility(View.VISIBLE);
TranslateAnimation animate = new TranslateAnimation(
0,
0,
fab.getHeight() * 2,
0);
animate.setDuration(300);
fab.startAnimation(animate);
} else if (!show && fab.getVisibility() == View.VISIBLE) {
TranslateAnimation animate = new TranslateAnimation(
0,
0,
0,
fab.getHeight() * 2);
animate.setDuration(300);
fab.startAnimation(animate);
fab.setVisibility(View.INVISIBLE);
scrollDiff = 0;
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
@@ -289,47 +316,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(currentPhotoViewer!=null)
currentPhotoViewer.offsetView(-dx, -dy);
View fab = getFab();
if (fab!=null && GlobalUserPreferences.enableFabAutoHide) {
// This piece of code should make it so that the fab is always visible if the status list scroll view is at the item at the top
if(list.getChildAt(0).getTop() == 0){
scrollDiff= THRESHOLD +1;
}else{
if(dy > 0){
scrollDiff=0;
}
}
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
TranslateAnimation animate = new TranslateAnimation(
0,
0,
0,
fab.getHeight() * 2);
animate.setDuration(300);
// animate.setFillAfter(true);
fab.startAnimation(animate);
fab.setEnabled(false);
fab.setVisibility(View.INVISIBLE);
scrollDiff = 0;
animateFab(false);
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
if (scrollDiff > THRESHOLD) {
TranslateAnimation animate = new TranslateAnimation(
0,
0,
fab.getHeight() * 2,
0);
animate.setDuration(300);
// animate.setFillAfter(true);
fab.startAnimation(animate);
fab.setEnabled(true);
fab.setVisibility(View.VISIBLE);
if (list.getChildAt(0).getTop() == 0 || scrollDiff > THRESHOLD) {
animateFab(true);
scrollDiff = 0;
} else {
scrollDiff += Math.abs(dy);
}
}
}
}});
}
});
list.addItemDecoration(new StatusListItemDecoration());
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
private Rect tmpRect=new Rect();
@@ -364,11 +365,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true);
updateToolbar();
if (withComposeButton()) {
fab = view.findViewById(R.id.fab);
if (wantsComposeButton() && !getArguments().getBoolean("__disable_fab", false)) {
fab.setVisibility(View.VISIBLE);
fab.setOnClickListener(this::onFabClick);
fab.setOnLongClickListener(this::onFabLongClick);
} else if (fab != null) {
fab.setVisibility(View.GONE);
}
}
@@ -696,13 +698,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
currentPhotoViewer.onPause();
}
protected void onFabClick(View v){
public void onFabClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), ComposeFragment.class, args);
}
protected boolean onFabLongClick(View v) {
public boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID);
}

View File

@@ -20,7 +20,7 @@ public class CustomLocalTimelineFragment extends StatusListFragment {
private String maxID;
@Override
protected boolean withComposeButton() {
protected boolean wantsComposeButton() {
return false;
}

View File

@@ -0,0 +1,7 @@
package org.joinmastodon.android.fragments;
import android.view.View;
public interface HasFab {
View getFab();
}

View File

@@ -40,7 +40,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
private MenuItem followButton;
@Override
protected boolean withComposeButton() {
protected boolean wantsComposeButton() {
return true;
}
@@ -146,12 +146,12 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
}
@Override
protected boolean onFabLongClick(View v) {
public boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
}
@Override
protected void onFabClick(View v){
public void onFabClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("prefilledText", '#'+hashtag+' ');

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import static org.joinmastodon.android.GlobalUserPreferences.reduceMotion;
import static org.joinmastodon.android.GlobalUserPreferences.showNewPostsButton;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -24,6 +25,7 @@ import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
@@ -46,8 +48,6 @@ import org.joinmastodon.android.events.HashtagUpdatedEvent;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.fragments.settings.SettingsFragment;
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
import org.joinmastodon.android.model.Announcement;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.HeaderPaginationList;
@@ -73,7 +73,7 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay {
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay, HasFab {
private static final int ANNOUNCEMENTS_RESULT = 654;
private String accountID;
@@ -101,6 +101,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
private PopupMenu overflowPopup;
private View overflowActionView = null;
private boolean announcementsBadged, settingsBadged;
private ImageButton fab;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -129,6 +130,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
FrameLayout view = new FrameLayout(getContext());
inflater.inflate(R.layout.compose_fab, view);
fab = view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
fab.setOnLongClickListener(this::onFabLongClick);
pager = new ViewPager2(getContext());
toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false);
@@ -136,6 +141,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
Bundle args = new Bundle();
args.putString("account", accountID);
args.putBoolean("__is_tab", true);
args.putBoolean("__disable_fab", true);
args.putBoolean("onlyPosts", true);
for (int i = 0; i < timelineDefinitions.size(); i++) {
@@ -299,6 +305,20 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
return DomainDisplay.super.getDomain();
}
private void onFabClick(View v){
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) {
l.onFabClick(v);
}
}
private boolean onFabLongClick(View v) {
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) {
return l.onFabLongClick(v);
} else {
return false;
}
}
private void addListsToOverflowMenu() {
Context ctx = getContext();
listsMenu.clear();
@@ -449,6 +469,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
private void updateSwitcherIcon(int i) {
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
timelineTitle.setText(timelines[i].getTitle(getContext()));
if (fragments[i] instanceof BaseStatusListFragment<?> l) l.animateFab(true);
}
@Override
@@ -463,7 +484,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
getToolbar().post(() -> overflowPopup.show());
return true;
} else if (id == R.id.settings || id == R.id.settings_action) {
Nav.go(getActivity(), SettingsMainFragment.class, args);
Nav.go(getActivity(), SettingsFragment.class, args);
} else if (id == R.id.announcements || id == R.id.announcements_action) {
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
} else if (id == R.id.edit_timelines) {
@@ -687,6 +708,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
return hashtagsItems.values();
}
public ImageButton getFab() {
return fab;
}
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
@NonNull
@Override

View File

@@ -38,7 +38,7 @@ public class HomeTimelineFragment extends StatusListFragment {
private String lastSavedMarkerID;
@Override
protected boolean withComposeButton() {
protected boolean wantsComposeButton() {
return true;
}

View File

@@ -44,7 +44,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
private ListTimeline.RepliesPolicy repliesPolicy;
@Override
protected boolean withComposeButton() {
protected boolean wantsComposeButton() {
return true;
}
@@ -152,7 +152,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
}
@Override
protected void onFabClick(View v){
public void onFabClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), ComposeFragment.class, args);

View File

@@ -19,7 +19,6 @@ import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
@@ -50,8 +49,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
@Override
protected boolean withComposeButton() {
return true;
protected boolean wantsComposeButton() {
return false;
}
@Override
@@ -107,7 +106,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
case UPDATE -> getString(R.string.sk_post_edited);
case SIGN_UP -> getString(R.string.sk_signed_up);
case REPORT -> getString(R.string.sk_reported);
case EMOJI_REACTION -> getString(R.string.sk_reacted, n.emoji);
case REACTION, PLEROMA_EMOJI_REACTION ->
n.emoji != null ? getString(R.string.sk_reacted_with, n.emoji) : getString(R.string.sk_reacted);
};
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, n.emojiUrl!=null ? HtmlParser.parseCustomEmoji(extraText, Collections.singletonList(emoji)) : extraText, n, null) : null;
if(n.status!=null){
@@ -156,7 +156,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
loadRelationships(needRelationships);
maxID=result.maxID;
if(offset==0 && !result.items.isEmpty() && !result.isFromCache()){
if(offset==0 && !result.items.isEmpty() && !result.isFromCache() && AccountSessionManager.getInstance().getAccount(accountID).markers.notifications != null){
E.post(new AllNotificationsSeenEvent());
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
AccountSessionManager.getInstance().getAccount(accountID).markers
@@ -214,7 +214,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
if (getParentFragment() instanceof NotificationsFragment) fab.setVisibility(View.GONE);
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
}

View File

@@ -115,7 +115,7 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab{
private static final int AVATAR_RESULT=722;
private static final int COVER_RESULT=343;
@@ -158,7 +158,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private WindowInsets childInsets;
private PhotoViewer currentPhotoViewer;
private boolean editModeLoading;
protected int scrollDiff = 0;
private static final int MAX_FIELDS=4;
@@ -232,7 +231,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followingCount=content.findViewById(R.id.following_count);
followingLabel=content.findViewById(R.id.following_label);
followingBtn=content.findViewById(R.id.following_btn);
postsCount=content.findViewById(R.id.posts_count);
postsLabel=content.findViewById(R.id.posts_label);
postsBtn=content.findViewById(R.id.posts_btn);
@@ -594,7 +592,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
if(account.locked){
ssb=new SpannableStringBuilder("@");
ssb.append(account.acct);
@@ -902,36 +899,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(currentPhotoViewer!=null){
currentPhotoViewer.offsetView(0, oldScrollY-scrollY);
}
if(GlobalUserPreferences.enableFabAutoHide){
int dy = scrollY - oldScrollY;
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
TranslateAnimation animate = new TranslateAnimation(
0,
0,
0,
fab.getHeight() * 2);
animate.setDuration(300);
fab.startAnimation(animate);
fab.setVisibility(View.INVISIBLE);
scrollDiff = 0;
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
if (scrollDiff > 400) {
fab.setVisibility(View.VISIBLE);
TranslateAnimation animate = new TranslateAnimation(
0,
0,
fab.getHeight() * 2,
0);
animate.setDuration(300);
fab.startAnimation(animate);
scrollDiff = 0;
} else {
scrollDiff += Math.abs(dy);
}
}
}
}
private Fragment getFragmentForPage(int page){

View File

@@ -32,7 +32,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
@Override
protected boolean withComposeButton() {
protected boolean wantsComposeButton() {
return true;
}
@@ -57,7 +57,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
}
@Override
protected void onFabClick(View v) {
public void onFabClick(View v) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
@@ -65,7 +65,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
}
@Override
protected boolean onFabLongClick(View v) {
public boolean onFabLongClick(View v) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.fragments.settings;
package org.joinmastodon.android.fragments;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
@@ -12,6 +12,7 @@ import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
import android.util.TypedValue;
import android.view.Gravity;
@@ -44,6 +45,7 @@ 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.PushNotificationReceiver;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager;
@@ -52,7 +54,6 @@ 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.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.fragments.MastodonToolbarFragment;
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
@@ -65,6 +66,7 @@ import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Map;
import java.util.function.Consumer;
import androidx.annotation.DrawableRes;
@@ -80,7 +82,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class SettingsFragment extends MastodonToolbarFragment {
public class SettingsFragment extends MastodonToolbarFragment{
private View view;
private UsableRecyclerView list;
private ArrayList<Item> items=new ArrayList<>();
@@ -247,8 +249,7 @@ public class SettingsFragment extends MastodonToolbarFragment {
GlobalUserPreferences.defaultToUnlistedReplies=i.checked;
GlobalUserPreferences.save();
}));
// TODO find a good icon for this setting
items.add(new SwitchItem(R.string.mo_mention_reblogger_automatically, R.drawable.ic_fluent_balloon_24_regular, GlobalUserPreferences.mentionRebloggerAutomatically, i -> {
items.add(new SwitchItem(R.string.mo_mention_reblogger_automatically, R.drawable.ic_fluent_comment_mention_24_regular, GlobalUserPreferences.mentionRebloggerAutomatically, i -> {
GlobalUserPreferences.mentionRebloggerAutomatically=i.checked;
GlobalUserPreferences.save();
}));

View File

@@ -21,7 +21,7 @@ public class FederatedTimelineFragment extends StatusListFragment {
private String maxID;
@Override
protected boolean withComposeButton() {
protected boolean wantsComposeButton() {
return true;
}

View File

@@ -20,7 +20,7 @@ public class LocalTimelineFragment extends StatusListFragment {
private String maxID;
@Override
protected boolean withComposeButton() {
protected boolean wantsComposeButton() {
return true;
}

View File

@@ -23,7 +23,8 @@ 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.settings.SettingsFragment;
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;

View File

@@ -1,122 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.LruCache;
import android.widget.Toast;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
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.onboarding.AccountActivationFragment;
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.parceler.Parcels;
import java.util.ArrayList;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ImageCache;
public class AboutFragment extends SettingsBaseFragment{
private TextItem checkForUpdateItem, clearImageCacheItem;
private ImageCache imageCache;
@Override
public void addItems(ArrayList<Item> items) {
items.add(new HeaderItem(R.string.sk_settings_about));
items.add(new TextItem(R.string.mo_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular));
if (GithubSelfUpdater.needSelfUpdating()) {
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
items.add(checkForUpdateItem);
items.add(new SwitchItem(R.string.sk_updater_enable_pre_releases, 0, GlobalUserPreferences.enablePreReleases, i->{
GlobalUserPreferences.enablePreReleases=i.checked;
GlobalUserPreferences.save();
}));
}
LruCache<?, ?> cache = imageCache == null ? null : imageCache.getLruCache();
clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), cache != null ? cache.size() : 0, true), this::clearImageCache, 0);
items.add(clearImageCacheItem);
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.mo_clear_recent_emoji, ()-> {
GlobalUserPreferences.recentEmojis.clear();
GlobalUserPreferences.save();
}));
if(BuildConfig.DEBUG){
items.add(new RedHeaderItem("Debug options"));
items.add(new TextItem("Test E-Mail confirmation flow", ()->{
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
sess.activated=false;
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
Bundle args=new Bundle();
args.putString("account", accountID);
args.putBoolean("debug", true);
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
}));
items.add(new TextItem("Copy preferences", ()->{
StringBuilder prefBuilder = new StringBuilder();
GlobalUserPreferences.load();
GlobalUserPreferences.getPrefs().getAll().forEach((key, value) -> prefBuilder.append(key).append(": ").append(value).append('\n'));
UiUtils.copyText(view, prefBuilder.toString());
}));
items.add(new TextItem("Reset preferences", ()->{
GlobalUserPreferences.load();
GlobalUserPreferences.getPrefs().edit().clear().commit();
UiUtils.restartApp();
}, R.drawable.ic_fluent_warning_24_regular));
items.add(new TextItem("Open App Info", () ->
getContext().startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", getContext().getPackageName(), null))),
R.drawable.ic_fluent_open_24_regular
)
);
items.add(new TextItem("Open developer settings",
()-> getContext().startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)),
R.drawable.ic_fluent_open_24_regular)
);
}
String version = getContext().getString(R.string.mo_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE);
items.add(new FooterItem(version, () -> UiUtils.copyText(view, version)));
}
private void clearImageCache(){
MastodonAPIController.runInBackground(()->{
Activity activity=getActivity();
imageCache.clear();
Toast.makeText(activity, R.string.media_cache_cleared, Toast.LENGTH_SHORT).show();
});
if (list.findViewHolderForAdapterPosition(items.indexOf(clearImageCacheItem)) instanceof TextViewHolder tvh) {
clearImageCacheItem.secondaryText = UiUtils.formatFileSize(getContext(), 0, true);
tvh.rebind();
}
}
}

View File

@@ -1,122 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.Toast;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
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.InstanceRulesFragment;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.util.ArrayList;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.utils.V;
public class AccountFragment extends SettingsBaseFragment{
private SwitchItem glitchModeItem;
@Override
public void addItems(ArrayList<Item> items) {
items.add(new HeaderItem(R.string.settings_account));
items.add(new TextItem(R.string.sk_settings_profile, ()-> UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.sk_settings_posting, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/preferences/other"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.sk_settings_filters, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/filters"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.sk_settings_auth, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit"), R.drawable.ic_fluent_open_24_regular));
items.add(new HeaderItem(getInstanceName()));
items.add(new TextItem(R.string.sk_settings_rules, ()->{
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(getInstance()));
Nav.go(getActivity(), InstanceRulesFragment.class, args);
}, R.drawable.ic_fluent_task_list_ltr_24_regular));
items.add(new TextItem(R.string.sk_settings_about_instance , ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/about"), R.drawable.ic_fluent_info_24_regular));
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
if (!TextUtils.isEmpty(getInstance().version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, getInstance().version)));
items.add(new HeaderItem(R.string.sk_instance_features));
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
glitchModeItem.enabled = i.checked;
if (i.checked) {
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
if (getInstance().pleroma == null) GlobalUserPreferences.accountsInGlitchMode.add(accountID);
} else {
GlobalUserPreferences.accountsWithLocalOnlySupport.remove(accountID);
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
}
glitchModeItem.checked = GlobalUserPreferences.accountsInGlitchMode.contains(accountID);
if (list.findViewHolderForAdapterPosition(items.indexOf(glitchModeItem)) instanceof SwitchViewHolder svh) svh.rebind();
GlobalUserPreferences.save();
}));
items.add(new SmallTextItem(getString(R.string.sk_settings_local_only_explanation)));
items.add(glitchModeItem = new SwitchItem(R.string.sk_settings_glitch_instance, 0, GlobalUserPreferences.accountsInGlitchMode.contains(accountID), i->{
if (i.checked) {
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
} else {
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
}
GlobalUserPreferences.save();
}));
glitchModeItem.enabled = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
items.add(new SmallTextItem(getString(R.string.sk_settings_glitch_mode_explanation)));
boolean translationAvailable = getInstance().v2 != null && getInstance().v2.configuration.translation != null && getInstance().v2.configuration.translation.enabled;
items.add(new SmallTextItem(getString(translationAvailable ?
R.string.sk_settings_translation_availability_note_available :
R.string.sk_settings_translation_availability_note_unavailable, getInstance().title)));
}
private void confirmLogOut(){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.log_out)
.setMessage(R.string.confirm_log_out)
.setPositiveButton(R.string.log_out, (dialog, which) -> logOut())
.setNegativeButton(R.string.cancel, null)
.show();
}
private void logOut(){
AccountSession session= AccountSessionManager.getInstance().getAccount(accountID);
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
onLoggedOut();
}
@Override
public void onError(ErrorResponse error){
onLoggedOut();
}
})
.wrapProgress(getActivity(), R.string.loading, false)
.exec(accountID);
}
private void onLoggedOut(){
if (getActivity() == null) return;
AccountSessionManager.getInstance().removeAccount(accountID);
getActivity().finish();
Intent intent=new Intent(getActivity(), MainActivity.class);
startActivity(intent);
}
}

View File

@@ -1,129 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.Toast;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import java.util.ArrayList;
import me.grishka.appkit.utils.V;
public class BehaviourFragment extends SettingsBaseFragment{
@Override
public void addItems(ArrayList<Item> items) {
items.add(new HeaderItem(R.string.settings_behavior));
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
GlobalUserPreferences.playGifs=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.settings_custom_tabs, R.drawable.ic_fluent_link_24_regular, GlobalUserPreferences.useCustomTabs, i->{
GlobalUserPreferences.useCustomTabs=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.mo_hide_compose_button_while_scrolling_setting, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.enableFabAutoHide, i->{
GlobalUserPreferences.enableFabAutoHide =i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.mo_load_remote_followers, R.drawable.ic_fluent_people_24_regular, GlobalUserPreferences.loadRemoteAccountFollowers, i -> {
GlobalUserPreferences.loadRemoteAccountFollowers=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
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->{
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
GlobalUserPreferences.save();
}));
// items.add(new SwitchItem(R.string.sk_settings_show_differentiated_notification_icons, R.drawable.ic_ntf_logo, GlobalUserPreferences.showUniformPushNoticationIcons, this::onNotificationStyleChanged));
items.add(new SwitchItem(R.string.sk_tabs_disable_swipe, R.drawable.ic_fluent_swipe_right_24_regular, GlobalUserPreferences.disableSwipe, i->{
GlobalUserPreferences.disableSwipe=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.mo_disable_double_tap_to_swipe_between_tabs, R.drawable.ic_fluent_double_tap_swipe_right_24_regular, GlobalUserPreferences.disableDoubleTapToSwipe, i->{
GlobalUserPreferences.disableDoubleTapToSwipe=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_settings_confirm_before_reblog, R.drawable.ic_fluent_checkmark_circle_24_regular, GlobalUserPreferences.confirmBeforeReblog, i->{
GlobalUserPreferences.confirmBeforeReblog=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.mo_swap_bookmark_with_reblog, R.drawable.ic_boost, GlobalUserPreferences.swapBookmarkWithBoostAction, i -> {
GlobalUserPreferences.swapBookmarkWithBoostAction=i.checked;
GlobalUserPreferences.save();
}));
items.add(new HeaderItem(R.string.mo_composer_behavior));
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> {
updatePublishText(b);
b.setOnClickListener(l -> {
if(!GlobalUserPreferences.relocatePublishButton) {
FrameLayout inputWrap = new FrameLayout(getContext());
EditText input = new EditText(getContext());
input.setHint(R.string.publish);
input.setText(GlobalUserPreferences.publishButtonText.trim());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
input.setLayoutParams(params);
inputWrap.addView(input);
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(inputWrap)
.setPositiveButton(R.string.save, (d, which) -> {
GlobalUserPreferences.publishButtonText = input.getText().toString().trim();
GlobalUserPreferences.save();
updatePublishText(b);
})
.setNeutralButton(R.string.clear, (d, which) -> {
GlobalUserPreferences.publishButtonText = "";
GlobalUserPreferences.save();
updatePublishText(b);
})
.setNegativeButton(R.string.cancel, (d, which) -> {
})
.show();
} else {
Toast.makeText(getActivity(), R.string.mo_disable_relocate_publish_button_to_enable_customization,
Toast.LENGTH_LONG).show();
}
});
}));
items.add(new SwitchItem(R.string.mo_relocate_publish_button, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
GlobalUserPreferences.relocatePublishButton=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.mo_change_default_reply_visibility_to_unlisted, R.drawable.ic_fluent_lock_open_24_regular, GlobalUserPreferences.defaultToUnlistedReplies, i->{
GlobalUserPreferences.defaultToUnlistedReplies=i.checked;
GlobalUserPreferences.save();
}));
// TODO find a good icon for this setting
items.add(new SwitchItem(R.string.mo_mention_reblogger_automatically, R.drawable.ic_fluent_balloon_24_regular, GlobalUserPreferences.mentionRebloggerAutomatically, i -> {
GlobalUserPreferences.mentionRebloggerAutomatically=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.mo_disable_reminder_to_add_alt_text, R.drawable.ic_fluent_image_alt_text_24_regular, GlobalUserPreferences.disableAltTextReminder, i->{
GlobalUserPreferences.disableAltTextReminder=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
GlobalUserPreferences.save();
}));
}
private void updatePublishText(Button btn) {
if (GlobalUserPreferences.publishButtonText.isBlank()) btn.setText(R.string.publish);
else btn.setText(GlobalUserPreferences.publishButtonText);
}
}

View File

@@ -1,38 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.PushSubscription;
import java.util.ArrayList;
public class NotificationsFragment extends SettingsBaseFragment {
@Override
public void addItems(ArrayList<Item> items) {
items.add(notificationPolicyItem = new NotificationPolicyItem());
PushSubscription pushSubscription = getPushSubscription();
boolean switchEnabled = pushSubscription.policy != PushSubscription.Policy.NONE;
items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i -> onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked), switchEnabled));
items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i -> onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked), switchEnabled));
items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i -> onNotificationsChanged(PushNotification.Type.REBLOG, i.checked), switchEnabled));
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i -> onNotificationsChanged(PushNotification.Type.MENTION, i.checked), switchEnabled));
items.add(new SwitchItem(R.string.sk_notify_posts, R.drawable.ic_fluent_chat_24_regular, pushSubscription.alerts.status, i -> onNotificationsChanged(PushNotification.Type.STATUS, i.checked), switchEnabled));
items.add(new SwitchItem(R.string.sk_notify_update, R.drawable.ic_fluent_history_24_regular, pushSubscription.alerts.update, i -> onNotificationsChanged(PushNotification.Type.UPDATE, i.checked), switchEnabled));
items.add(new SwitchItem(R.string.sk_notify_poll_results, R.drawable.ic_fluent_poll_24_regular, pushSubscription.alerts.poll, i -> onNotificationsChanged(PushNotification.Type.POLL, i.checked), switchEnabled));
items.add(new HeaderItem(R.string.mo_miscellaneous_settings));
items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
GlobalUserPreferences.enableDeleteNotifications=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
GlobalUserPreferences.save();
}));
}
}

View File

@@ -1,84 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Build;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.PopupMenu;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
public class SettingsAppearanceFragment extends SettingsBaseFragment {
@Override
public void addItems(ArrayList<Item> items) {
items.add(themeItem = new SettingsBaseFragment.ThemeItem());
items.add(new SettingsBaseFragment.ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b -> {
PopupMenu popupMenu = new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.color_palettes);
popupMenu.getMenu().findItem(R.id.m3_color).setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S);
popupMenu.setOnMenuItemClickListener(this::onColorPreferenceClick);
b.setOnTouchListener(popupMenu.getDragToOpenListener());
b.setOnClickListener(v -> popupMenu.show());
b.setText(switch (GlobalUserPreferences.color) {
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
case NORD -> R.string.mo_color_palette_nord;
});
}));
items.add(new SettingsBaseFragment.SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
items.add(new SettingsBaseFragment.SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i -> {
GlobalUserPreferences.disableMarquee = i.checked;
GlobalUserPreferences.save();
needAppRestart = true;
}));
items.add(new SettingsBaseFragment.SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i -> {
GlobalUserPreferences.uniformNotificationIcon = i.checked;
GlobalUserPreferences.save();
}));
items.add(new SettingsBaseFragment.SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i -> {
GlobalUserPreferences.reduceMotion = i.checked;
GlobalUserPreferences.save();
needAppRestart = true;
}));
}
protected boolean onColorPreferenceClick(MenuItem item){
GlobalUserPreferences.ColorPreference pref = null;
int id = item.getItemId();
if (id == R.id.m3_color) pref = GlobalUserPreferences.ColorPreference.MATERIAL3;
else if (id == R.id.pink_color) pref = GlobalUserPreferences.ColorPreference.PINK;
else if (id == R.id.purple_color) pref = GlobalUserPreferences.ColorPreference.PURPLE;
else if (id == R.id.green_color) pref = GlobalUserPreferences.ColorPreference.GREEN;
else if (id == R.id.blue_color) pref = GlobalUserPreferences.ColorPreference.BLUE;
else if (id == R.id.brown_color) pref = GlobalUserPreferences.ColorPreference.BROWN;
else if (id == R.id.red_color) pref = GlobalUserPreferences.ColorPreference.RED;
else if (id == R.id.yellow_color) pref = GlobalUserPreferences.ColorPreference.YELLOW;
else if (id == R.id.nord_color) pref = GlobalUserPreferences.ColorPreference.NORD;
if (pref == null) return false;
GlobalUserPreferences.color=pref;
GlobalUserPreferences.save();
restartActivityToApplyNewTheme();
return true;
}
}

View File

@@ -1,863 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RadioButton;
import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.DomainDisplay;
import org.joinmastodon.android.fragments.MastodonToolbarFragment;
import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import java.util.ArrayList;
import java.util.function.Consumer;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public abstract class SettingsBaseFragment extends MastodonToolbarFragment implements DomainDisplay {
protected View view;
protected UsableRecyclerView list;
protected ImageView themeTransitionWindowView;
protected ThemeItem themeItem;
protected boolean needAppRestart;
protected SettingsBaseFragment.NotificationPolicyItem notificationPolicyItem;
protected PushSubscription pushSubscription;
protected ArrayList<Item> items=new ArrayList<>();
protected String accountID;
protected boolean needUpdateNotificationSettings;
public abstract void addItems(ArrayList<Item> items);
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
setTitle(R.string.settings);
accountID=getArguments().getString("account");
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
DomainManager.getInstance().setCurrentDomain(session.domain + "/settings");
addItems(items);
}
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
list=new UsableRecyclerView(getActivity());
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(new SettingsAdapter());
list.setBackgroundColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorBackground));
list.setPadding(0, V.dp(16), 0, V.dp(12));
list.setClipToPadding(false);
list.addItemDecoration(new RecyclerView.ItemDecoration(){
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
// Add 32dp gaps between sections
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if((holder instanceof HeaderViewHolder || holder instanceof FooterViewHolder) && holder.getAbsoluteAdapterPosition()>1)
outRect.top=V.dp(32);
}
});
return list;
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
list.setPadding(0, V.dp(16), 0, V.dp(12)+insets.getSystemWindowInsetBottom());
insets=insets.inset(0, 0, 0, insets.getSystemWindowInsetBottom());
}
super.onApplyWindowInsets(insets);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
this.view = view;
}
static abstract class Item{
public abstract int getViewType();
}
protected class HeaderItem extends Item{
private String text;
public HeaderItem(@StringRes int text){
this.text=getString(text);
}
public HeaderItem(String text){
this.text=text;
}
@Override
public int getViewType(){
return Type.HEADER.ordinal();
}
}
protected class SwitchItem extends Item{
private String text;
private int icon;
boolean checked;
private Consumer<SwitchItem> onChanged;
private boolean enabled=true;
public SwitchItem(@StringRes int text, @DrawableRes int icon, boolean checked, Consumer<SwitchItem> onChanged){
this.text=getString(text);
this.icon=icon;
this.checked=checked;
this.onChanged=onChanged;
}
public SwitchItem(@StringRes int text, @DrawableRes int icon, boolean checked, Consumer<SwitchItem> onChanged, boolean enabled){
this.text=getString(text);
this.icon=icon;
this.checked=checked;
this.onChanged=onChanged;
this.enabled=enabled;
}
@Override
public int getViewType(){
return Type.SWITCH.ordinal();
}
}
protected class UpdateItem extends SettingsBaseFragment.Item {
@Override
public int getViewType(){
return Type.UPDATER.ordinal();
}
}
protected static class ThemeItem extends SettingsBaseFragment.Item {
@Override
public int getViewType(){
return Type.THEME.ordinal();
}
}
protected static class NotificationPolicyItem extends SettingsBaseFragment.Item {
@Override
public int getViewType(){
return Type.NOTIFICATION_POLICY.ordinal();
}
}
protected class ButtonItem extends Item{
private int text;
private int icon;
private Consumer<Button> buttonConsumer;
public ButtonItem(@StringRes int text, @DrawableRes int icon, Consumer<Button> buttonConsumer) {
this.text = text;
this.icon = icon;
this.buttonConsumer = buttonConsumer;
}
@Override
public int getViewType(){
return Type.BUTTON.ordinal();
}
}
protected class SmallTextItem extends Item {
private String text;
public SmallTextItem(String text) {
this.text = text;
}
@Override
public int getViewType() {
return Type.SMALL_TEXT.ordinal();
}
}
protected class TextItem extends Item{
private String text;
private String secondaryText;
private Runnable onClick;
private boolean loading;
private int icon;
public TextItem(@StringRes int text, Runnable onClick) {
this(text, null, onClick, false, 0);
}
public TextItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
this(text, null, onClick, false, icon);
}
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, @DrawableRes int icon) {
this(text, secondaryText, onClick, false, icon);
}
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, boolean loading, @DrawableRes int icon){
this.text=getString(text);
this.onClick=onClick;
this.loading=loading;
this.icon=icon;
this.secondaryText = secondaryText;
}
public TextItem(String text, Runnable onClick){
this.text=text;
this.onClick=onClick;
}
public TextItem(String text, Runnable onClick, @DrawableRes int icon){
this.text=text;
this.onClick=onClick;
this.icon=icon;
}
@Override
public int getViewType(){
return Type.TEXT.ordinal();
}
}
protected class SettingsCategoryItem extends Item{
private String text;
private String secondaryText;
private Runnable onClick;
private boolean loading;
private int icon;
public SettingsCategoryItem(@StringRes int text, Runnable onClick) {
this(text, null, onClick, false, 0);
}
public SettingsCategoryItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
this(text, null, onClick, false, icon);
}
public SettingsCategoryItem(@StringRes int text, String secondaryText, Runnable onClick, @DrawableRes int icon) {
this(text, secondaryText, onClick, false, icon);
}
public SettingsCategoryItem(@StringRes int text, String secondaryText, Runnable onClick, boolean loading, @DrawableRes int icon){
this.text=getString(text);
this.onClick=onClick;
this.loading=loading;
this.icon=icon;
this.secondaryText = secondaryText;
}
public SettingsCategoryItem(String text, Runnable onClick){
this.text=text;
this.onClick=onClick;
}
public SettingsCategoryItem(String text, Runnable onClick, @DrawableRes int icon){
this.text=text;
this.onClick=onClick;
this.icon=icon;
}
@Override
public int getViewType(){
return Type.SETTINGS_CATEGORY.ordinal();
}
}
protected class FooterItem extends Item{
private String text;
private Runnable onClick;
public FooterItem(String text, Runnable onClick){
this.text=text;
this.onClick=onClick;
}
@Override
public int getViewType(){
return Type.FOOTER.ordinal();
}
}
public enum Type{
HEADER,
SWITCH,
THEME,
TEXT,
NOTIFICATION_POLICY,
FOOTER,
BUTTON,
SMALL_TEXT,
UPDATER,
SETTINGS_CATEGORY
}
private class SettingsAdapter extends RecyclerView.Adapter<BindableViewHolder<Item>>{
@NonNull
@Override
public BindableViewHolder<Item> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
//noinspection unchecked
return (BindableViewHolder<Item>) switch(Type.values()[viewType]){
case HEADER -> new HeaderViewHolder();
case SWITCH -> new SwitchViewHolder();
case THEME -> new ThemeViewHolder();
case TEXT -> new TextViewHolder();
case NOTIFICATION_POLICY -> new NotificationPolicyViewHolder();
case FOOTER -> new FooterViewHolder();
case BUTTON -> new ButtonViewHolder();
case SMALL_TEXT -> new SmallTextViewHolder();
case UPDATER -> new UpdateViewHolder();
case SETTINGS_CATEGORY -> new SettingsCategoryViewHolder();
};
}
@Override
public void onBindViewHolder(@NonNull BindableViewHolder<Item> holder, int position){
holder.bind(items.get(position));
}
@Override
public int getItemCount(){
return items.size();
}
@Override
public int getItemViewType(int position){
return items.get(position).getViewType();
}
}
private class HeaderViewHolder extends BindableViewHolder<HeaderItem>{
private final TextView text;
public HeaderViewHolder(){
super(getActivity(), R.layout.item_settings_header, list);
text=(TextView) itemView;
}
@Override
public void onBind(HeaderItem item){
text.setText(item.text);
}
}
protected boolean onColorPreferenceClick(MenuItem item){
GlobalUserPreferences.ColorPreference pref = null;
int id = item.getItemId();
if (id == R.id.m3_color) pref = GlobalUserPreferences.ColorPreference.MATERIAL3;
else if (id == R.id.pink_color) pref = GlobalUserPreferences.ColorPreference.PINK;
else if (id == R.id.purple_color) pref = GlobalUserPreferences.ColorPreference.PURPLE;
else if (id == R.id.green_color) pref = GlobalUserPreferences.ColorPreference.GREEN;
else if (id == R.id.blue_color) pref = GlobalUserPreferences.ColorPreference.BLUE;
else if (id == R.id.brown_color) pref = GlobalUserPreferences.ColorPreference.BROWN;
else if (id == R.id.red_color) pref = GlobalUserPreferences.ColorPreference.RED;
else if (id == R.id.yellow_color) pref = GlobalUserPreferences.ColorPreference.YELLOW;
else if (id == R.id.nord_color) pref = GlobalUserPreferences.ColorPreference.NORD;
if (pref == null) return false;
GlobalUserPreferences.color=pref;
GlobalUserPreferences.save();
restartActivityToApplyNewTheme();
return true;
}
protected void onThemePreferenceClick(GlobalUserPreferences.ThemePreference theme){
GlobalUserPreferences.theme=theme;
GlobalUserPreferences.save();
restartActivityToApplyNewTheme();
}
protected void restartActivityToApplyNewTheme(){
// Calling activity.recreate() causes a black screen for like half a second.
// So, let's take a screenshot and overlay it on top to create the illusion of a smoother transition.
// As a bonus, we can fade it out to make it even smoother.
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
View activityDecorView=getActivity().getWindow().getDecorView();
Bitmap bitmap=Bitmap.createBitmap(activityDecorView.getWidth(), activityDecorView.getHeight(), Bitmap.Config.ARGB_8888);
activityDecorView.draw(new Canvas(bitmap));
themeTransitionWindowView=new ImageView(MastodonApp.context);
themeTransitionWindowView.setImageBitmap(bitmap);
WindowManager.LayoutParams lp=new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION);
lp.flags=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
lp.systemUiVisibility=View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
lp.systemUiVisibility|=(activityDecorView.getWindowSystemUiVisibility() & (View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR));
lp.width=lp.height=WindowManager.LayoutParams.MATCH_PARENT;
lp.token=getActivity().getWindow().getAttributes().token;
lp.windowAnimations=R.style.window_fade_out;
MastodonApp.context.getSystemService(WindowManager.class).addView(themeTransitionWindowView, lp);
}
getActivity().recreate();
}
@Override
public void onDestroy(){
super.onDestroy();
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
}
if(needAppRestart) UiUtils.restartApp();
}
protected void onTrueBlackThemeChanged(SettingsBaseFragment.SwitchItem item){
GlobalUserPreferences.trueBlackTheme=item.checked;
GlobalUserPreferences.save();
RecyclerView.ViewHolder themeHolder=list.findViewHolderForAdapterPosition(items.indexOf(themeItem));
if(themeHolder!=null){
((SettingsBaseFragment.ThemeViewHolder)themeHolder).bindSubitems();
}else{
list.getAdapter().notifyItemChanged(items.indexOf(themeItem));
}
if(UiUtils.isDarkTheme()){
restartActivityToApplyNewTheme();
}
}
private class NotificationPolicyViewHolder extends BindableViewHolder<NotificationPolicyItem>{
private final Button button;
private final PopupMenu popupMenu;
@SuppressLint("ClickableViewAccessibility")
public NotificationPolicyViewHolder(){
super(getActivity(), R.layout.item_settings_notification_policy, list);
button=findViewById(R.id.button);
popupMenu=new PopupMenu(getActivity(), button, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.notification_policy);
popupMenu.setOnMenuItemClickListener(item->{
PushSubscription.Policy policy;
int id=item.getItemId();
if(id==R.id.notify_anyone)
policy=PushSubscription.Policy.ALL;
else if(id==R.id.notify_followed)
policy=PushSubscription.Policy.FOLLOWED;
else if(id==R.id.notify_follower)
policy=PushSubscription.Policy.FOLLOWER;
else if(id==R.id.notify_none)
policy=PushSubscription.Policy.NONE;
else
return false;
onNotificationsPolicyChanged(policy);
return true;
});
UiUtils.enablePopupMenuIcons(getActivity(), popupMenu);
button.setOnTouchListener(popupMenu.getDragToOpenListener());
button.setOnClickListener(v->popupMenu.show());
}
@Override
public void onBind(SettingsBaseFragment.NotificationPolicyItem item){
button.setText(switch(getPushSubscription().policy){
case ALL -> R.string.notify_anyone;
case FOLLOWED -> R.string.notify_followed;
case FOLLOWER -> R.string.notify_follower;
case NONE -> R.string.notify_none;
});
}
}
private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
PushSubscription subscription=getPushSubscription();
PushSubscription.Policy prevPolicy=subscription.policy;
if(prevPolicy==policy)
return;
subscription.policy=policy;
int index=items.indexOf(notificationPolicyItem);
RecyclerView.ViewHolder policyHolder=list.findViewHolderForAdapterPosition(index);
if(policyHolder!=null){
((SettingsBaseFragment.NotificationPolicyViewHolder)policyHolder).rebind();
}else{
list.getAdapter().notifyItemChanged(index);
}
if((prevPolicy==PushSubscription.Policy.NONE)!=(policy==PushSubscription.Policy.NONE)){
boolean newState=policy!=PushSubscription.Policy.NONE;
for(PushNotification.Type value : PushNotification.Type.values()){
onNotificationsChanged(value, newState);
}
index++;
while(items.get(index) instanceof SettingsBaseFragment.SwitchItem si){
si.enabled=si.checked=newState;
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(index);
if(holder!=null)
((BindableViewHolder<?>)holder).rebind();
else
list.getAdapter().notifyItemChanged(index);
index++;
}
}
needUpdateNotificationSettings=true;
}
protected void onNotificationsChanged(PushNotification.Type type, boolean enabled){
PushSubscription subscription=getPushSubscription();
switch(type){
case FAVORITE -> subscription.alerts.favourite=enabled;
case FOLLOW -> subscription.alerts.follow=enabled;
case REBLOG -> subscription.alerts.reblog=enabled;
case MENTION -> subscription.alerts.mention=enabled;
case POLL -> subscription.alerts.poll=enabled;
case STATUS -> subscription.alerts.status=enabled;
case UPDATE -> subscription.alerts.update=enabled;
}
needUpdateNotificationSettings=true;
}
protected PushSubscription getPushSubscription(){
if(pushSubscription!=null)
return pushSubscription;
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
if(session.pushSubscription==null){
pushSubscription=new PushSubscription();
pushSubscription.alerts=PushSubscription.Alerts.ofAll();
}else{
pushSubscription=session.pushSubscription.clone();
}
return pushSubscription;
}
private class SwitchViewHolder extends BindableViewHolder<SwitchItem> implements UsableRecyclerView.DisableableClickable{
private final TextView text;
private final ImageView icon;
private final Switch checkbox;
public SwitchViewHolder(){
super(getActivity(), R.layout.item_settings_switch, list);
text=findViewById(R.id.text);
icon=findViewById(R.id.icon);
checkbox=findViewById(R.id.checkbox);
}
@Override
public void onBind(SwitchItem item){
text.setText(item.text);
if (item.icon == 0) {
icon.setVisibility(View.GONE);
} else {
icon.setVisibility(View.VISIBLE);
icon.setImageResource(item.icon);
}
checkbox.setChecked(item.checked && item.enabled);
checkbox.setEnabled(item.enabled);
}
@Override
public void onClick(){
item.checked=!item.checked;
checkbox.setChecked(item.checked);
item.onChanged.accept(item);
}
@Override
public boolean isEnabled(){
return item.enabled;
}
}
class ThemeViewHolder extends BindableViewHolder<ThemeItem>{
private ThemeViewHolder.SubitemHolder autoHolder, lightHolder, darkHolder;
public ThemeViewHolder(){
super(getActivity(), R.layout.item_settings_theme, list);
autoHolder=new ThemeViewHolder.SubitemHolder(findViewById(R.id.theme_auto));
lightHolder=new ThemeViewHolder.SubitemHolder(findViewById(R.id.theme_light));
darkHolder=new ThemeViewHolder.SubitemHolder(findViewById(R.id.theme_dark));
}
@Override
public void onBind(SettingsBaseFragment.ThemeItem item){
bindSubitems();
}
public void bindSubitems(){
autoHolder.bind(R.string.theme_auto, GlobalUserPreferences.trueBlackTheme ? R.drawable.theme_auto_trueblack : R.drawable.theme_auto, GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO);
lightHolder.bind(R.string.theme_light, R.drawable.theme_light, GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.LIGHT);
darkHolder.bind(R.string.theme_dark, GlobalUserPreferences.trueBlackTheme ? R.drawable.theme_dark_trueblack : R.drawable.theme_dark, GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK);
}
private void onSubitemClick(View v){
GlobalUserPreferences.ThemePreference pref;
if(v.getId()==R.id.theme_auto)
pref=GlobalUserPreferences.ThemePreference.AUTO;
else if(v.getId()==R.id.theme_light)
pref=GlobalUserPreferences.ThemePreference.LIGHT;
else if(v.getId()==R.id.theme_dark)
pref=GlobalUserPreferences.ThemePreference.DARK;
else
return;
onThemePreferenceClick(pref);
}
private class SubitemHolder{
public TextView text;
public ImageView icon;
public RadioButton checkbox;
public SubitemHolder(View view){
text=view.findViewById(R.id.text);
icon=view.findViewById(R.id.icon);
checkbox=view.findViewById(R.id.checkbox);
view.setOnClickListener(ThemeViewHolder.this::onSubitemClick);
icon.setClipToOutline(true);
icon.setOutlineProvider(OutlineProviders.roundedRect(4));
}
public void bind(int text, int icon, boolean checked){
this.text.setText(text);
this.icon.setImageResource(icon);
checkbox.setChecked(checked);
}
public void setChecked(boolean checked){
checkbox.setChecked(checked);
}
}
}
private class SettingsCategoryViewHolder extends BindableViewHolder<SettingsCategoryItem> implements UsableRecyclerView.DisableableClickable{
private final ImageView icon;
private final TextView text;
@SuppressLint("ClickableViewAccessibility")
public SettingsCategoryViewHolder(){
super(getActivity(), R.layout.item_settings_category, list);
text=findViewById(R.id.text);
icon=findViewById(R.id.icon);
}
@Override
public void onBind(SettingsCategoryItem item){
text.setText(item.text);
icon.setImageResource(item.icon);
}
@Override
public void onClick() {
item.onClick.run();
}
@Override
public boolean isEnabled(){
return true;
}
}
protected class ButtonViewHolder extends BindableViewHolder<ButtonItem>{
private final Button button;
private final ImageView icon;
private final TextView text;
@SuppressLint("ClickableViewAccessibility")
public ButtonViewHolder(){
super(getActivity(), R.layout.item_settings_button, list);
text=findViewById(R.id.text);
icon=findViewById(R.id.icon);
button=findViewById(R.id.button);
}
@Override
public void onBind(ButtonItem item){
text.setText(item.text);
icon.setImageResource(item.icon);
item.buttonConsumer.accept(button);
}
}
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
private final TextView text, secondaryText;
private final ProgressBar progress;
private final ImageView icon;
public TextViewHolder(){
super(getActivity(), R.layout.item_settings_text, list);
text = itemView.findViewById(R.id.text);
secondaryText = itemView.findViewById(R.id.secondary_text);
progress = itemView.findViewById(R.id.progress);
icon = itemView.findViewById(R.id.icon);
}
@Override
public void onBind(TextItem item){
icon.setVisibility(item.icon != 0 ? View.VISIBLE : View.GONE);
secondaryText.setVisibility(item.secondaryText != null ? View.VISIBLE : View.GONE);
text.setText(item.text);
progress.animate().alpha(item.loading ? 1 : 0);
icon.setImageResource(item.icon);
secondaryText.setText(item.secondaryText);
}
@Override
public void onClick(){
item.onClick.run();
}
}
private class SmallTextViewHolder extends BindableViewHolder<SmallTextItem> {
private final TextView text;
public SmallTextViewHolder(){
super(getActivity(), R.layout.item_settings_text, list);
text = itemView.findViewById(R.id.text);
text.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary));
text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
text.setPaddingRelative(text.getPaddingStart(), 0, text.getPaddingEnd(), text.getPaddingBottom());
}
@Override
public void onBind(SmallTextItem item){
text.setText(item.text);
}
}
private class FooterViewHolder extends BindableViewHolder<FooterItem> implements UsableRecyclerView.Clickable{
private final TextView text;
public FooterViewHolder(){
super(getActivity(), R.layout.item_settings_footer, list);
text=(TextView) itemView;
}
@Override
public void onBind(FooterItem item){
text.setText(item.text);
}
@Override
public void onClick(){
item.onClick.run();
}
}
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
private final TextView text, changelog;
private final Button button;
private final ImageButton cancelBtn;
private final ProgressBar progress;
private ObjectAnimator rotationAnimator;
private Runnable progressUpdater=this::updateProgress;
public UpdateViewHolder(){
super(getActivity(), R.layout.item_settings_update, list);
text=findViewById(R.id.text);
changelog=findViewById(R.id.changelog);
button=findViewById(R.id.button);
cancelBtn=findViewById(R.id.cancel_btn);
progress=findViewById(R.id.progress);
button.setOnClickListener(v->{
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
switch(updater.getState()){
case UPDATE_AVAILABLE -> updater.downloadUpdate();
case DOWNLOADED -> updater.installUpdate(getActivity());
}
});
cancelBtn.setOnClickListener(v->GithubSelfUpdater.getInstance().cancelDownload());
rotationAnimator=ObjectAnimator.ofFloat(progress, View.ROTATION, 0f, 360f);
rotationAnimator.setInterpolator(new LinearInterpolator());
rotationAnimator.setDuration(1500);
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
}
@Override
public void onBind(SettingsBaseFragment.UpdateItem item){
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
GithubSelfUpdater.UpdateState state=updater.getState();
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
text.setText(getString(R.string.mo_update_available, info.version));
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
}else{
text.setText(getString(R.string.mo_update_ready, info.version));
button.setText(R.string.install_update);
}
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
rotationAnimator.start();
button.setVisibility(View.INVISIBLE);
cancelBtn.setVisibility(View.VISIBLE);
progress.setVisibility(View.VISIBLE);
updateProgress();
}else{
rotationAnimator.cancel();
button.setVisibility(View.VISIBLE);
cancelBtn.setVisibility(View.GONE);
progress.setVisibility(View.GONE);
progress.removeCallbacks(progressUpdater);
}
changelog.setText(info.changelog);
// changelog.setText(getString(R.string.sk_changelog, info.changelog));
}
private void updateProgress(){
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
if(updater.getState()!=GithubSelfUpdater.UpdateState.DOWNLOADING)
return;
int value=Math.round(progress.getMax()*updater.getDownloadProgress());
if(Build.VERSION.SDK_INT>=24)
progress.setProgress(value, true);
else
progress.setProgress(value);
progress.postDelayed(progressUpdater, 1000);
}
}
}

View File

@@ -1,20 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import android.os.Bundle;
import org.joinmastodon.android.R;
import java.util.ArrayList;
import me.grishka.appkit.Nav;
public class SettingsMainFragment extends SettingsBaseFragment{
@Override
public void addItems(ArrayList<SettingsBaseFragment.Item> items) {
items.add(new SettingsBaseFragment.SettingsCategoryItem(R.string.settings_theme, () -> {
Bundle args = new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), SettingsAppearanceFragment.class, args);
}, R.drawable.ic_fluent_color_24_regular));
}
}

View File

@@ -1,128 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.PopupMenu;
import android.widget.Toast;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import java.util.ArrayList;
import me.grishka.appkit.utils.V;
public class TimeLineFragment extends SettingsBaseFragment{
private SwitchItem showNewPostsButtonItem, compactReblogReplyLineItem;
@Override
public void addItems(ArrayList<Item> items) {
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
GlobalUserPreferences.showReplies=i.checked;
GlobalUserPreferences.save();
}));
if (getInstance().pleroma != null) {
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.reply_visibility);
popupMenu.setOnMenuItemClickListener(item -> this.onReplyVisibilityChanged(item, b));
b.setOnTouchListener(popupMenu.getDragToOpenListener());
b.setOnClickListener(v->popupMenu.show());
b.setText(GlobalUserPreferences.replyVisibility == null ?
R.string.sk_settings_reply_visibility_all :
switch(GlobalUserPreferences.replyVisibility){
case "following" -> R.string.sk_settings_reply_visibility_following;
case "self" -> R.string.sk_settings_reply_visibility_self;
default -> R.string.sk_settings_reply_visibility_all;
});
}));
}
items.add(new SwitchItem(R.string.sk_settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
GlobalUserPreferences.showBoosts=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{
GlobalUserPreferences.loadNewPosts=i.checked;
showNewPostsButtonItem.enabled = i.checked;
if (!i.checked) {
GlobalUserPreferences.showNewPostsButton = false;
showNewPostsButtonItem.checked = false;
}
if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsButtonItem)) instanceof SwitchViewHolder svh) svh.rebind();
GlobalUserPreferences.save();
}));
items.add(showNewPostsButtonItem = new SwitchItem(R.string.sk_settings_show_new_posts_button, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.showNewPostsButton, i->{
GlobalUserPreferences.showNewPostsButton=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_show_alt_indicator, R.drawable.ic_fluent_scan_text_24_regular, GlobalUserPreferences.showAltIndicator, i->{
GlobalUserPreferences.showAltIndicator=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_settings_show_no_alt_indicator, R.drawable.ic_fluent_important_24_regular, GlobalUserPreferences.showNoAltIndicator, i->{
GlobalUserPreferences.showNoAltIndicator=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_settings_collapse_long_posts, R.drawable.ic_fluent_chevron_down_24_regular, GlobalUserPreferences.collapseLongPosts, i->{
GlobalUserPreferences.collapseLongPosts=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_hide_fab, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.autoHideFab, i->{
GlobalUserPreferences.autoHideFab=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_reply_line_above_avatar, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.replyLineAboveHeader, i->{
GlobalUserPreferences.replyLineAboveHeader=i.checked;
GlobalUserPreferences.compactReblogReplyLine=i.checked;
compactReblogReplyLineItem.enabled=i.checked;
compactReblogReplyLineItem.checked= GlobalUserPreferences.replyLineAboveHeader;
if (list.findViewHolderForAdapterPosition(items.indexOf(compactReblogReplyLineItem)) instanceof SwitchViewHolder svh) svh.rebind();
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(compactReblogReplyLineItem=new SwitchItem(R.string.sk_compact_reblog_reply_line, R.drawable.ic_fluent_re_order_24_regular, GlobalUserPreferences.compactReblogReplyLine, i->{
GlobalUserPreferences.compactReblogReplyLine=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
compactReblogReplyLineItem.enabled=GlobalUserPreferences.replyLineAboveHeader;
items.add(new SwitchItem(R.string.sk_settings_hide_interaction, R.drawable.ic_fluent_eye_24_regular, GlobalUserPreferences.spectatorMode, i->{
GlobalUserPreferences.spectatorMode=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.mo_disable_dividers, R.drawable.ic_fluent_timeline_24_regular, GlobalUserPreferences.disableDividers, i->{
GlobalUserPreferences.disableDividers=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
}
private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
String pref = null;
int id = item.getItemId();
if (id == R.id.reply_visibility_following) pref = "following";
else if (id == R.id.reply_visibility_self) pref = "self";
GlobalUserPreferences.replyVisibility=pref;
GlobalUserPreferences.save();
btn.setText(GlobalUserPreferences.replyVisibility == null ?
R.string.sk_settings_reply_visibility_all :
switch(GlobalUserPreferences.replyVisibility){
case "following" -> R.string.sk_settings_reply_visibility_following;
case "self" -> R.string.sk_settings_reply_visibility_self;
default -> R.string.sk_settings_reply_visibility_all;
});
return true;
}
}

View File

@@ -53,12 +53,14 @@ public class Notification extends BaseModel implements DisplayItemsParent{
STATUS,
@SerializedName("update")
UPDATE,
@SerializedName("reaction")
REACTION,
@SerializedName("pleroma:emoji_reaction")
PLEROMA_EMOJI_REACTION,
@SerializedName("admin.sign_up")
SIGN_UP,
@SerializedName("admin.report")
REPORT,
@SerializedName("pleroma:emoji_reaction")
EMOJI_REACTION
REPORT
}
@Parcel

View File

@@ -6,4 +6,5 @@ public enum NotificationAction {
UNBOOST,
BOOKMARK,
REPLY,
FOLLOW_BACK
}

View File

@@ -450,4 +450,4 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
return 0;
}
}
}
}

View File

@@ -37,12 +37,14 @@ public class HashtagStatusDisplayItem extends StatusDisplayItem{
public void onBind(HashtagStatusDisplayItem _item){
Hashtag item=_item.tag;
title.setText('#'+item.name);
int numPeople=item.history.get(0).accounts;
if(item.history.size()>1)
numPeople+=item.history.get(1).accounts;
int numPeople = 0;
if(item.history != null){
numPeople=item.history.get(0).accounts;
if(item.history.size()>1)
numPeople+=item.history.get(1).accounts;
chart.setData(item.history);
}
subtitle.setText(_item.parentFragment.getResources().getQuantityString(R.plurals.x_people_talking, numPeople, numPeople));
chart.setData(item.history);
}
}
}

View File

@@ -207,7 +207,6 @@ public abstract class StatusDisplayItem{
items.add(new GapStatusDisplayItem(parentID, fragment));
}
}
int i=1;
for(StatusDisplayItem item:items){
item.inset=inset;

View File

@@ -208,49 +208,13 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
if(item.status.reloadWhenClicked){
UiUtils.lookupStatus(item.parentFragment.getContext(), item.status, item.parentFragment.getAccountID(), null, status1 -> {
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
rebind();
}
@Override
public void onError(ErrorResponse error) {
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
error.showToast(itemView.getContext());
}
}).exec(item.parentFragment.getAccountID());
});
UiUtils.lookupStatus(item.parentFragment.getContext(),
item.status,
item.parentFragment.getAccountID(),
null,
reloadedStatus -> loadTranslation(reloadedStatus.id));
} else {
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
rebind();
}
@Override
public void onError(ErrorResponse error) {
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
error.showToast(itemView.getContext());
}
}).exec(item.parentFragment.getAccountID());
loadTranslation(item.status.id);
}
} else {
item.setTranslationShown(!item.translationShown);
@@ -303,5 +267,29 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private CustomEmojiHelper getEmojiHelper(){
return item.spoilerEmojiHelper!=null && !item.status.spoilerRevealed ? item.spoilerEmojiHelper : item.emojiHelper;
}
private void loadTranslation(String statusId) {
new TranslateStatus(statusId).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
rebind();
}
@Override
public void onError(ErrorResponse error) {
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
error.showToast(itemView.getContext());
}
}).exec(item.parentFragment.getAccountID());
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
<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="M22,12c0,-5.523 -4.477,-10 -10,-10S2,6.477 2,12a9.96,9.96 0,0 0,1.115 4.592l-1.068,3.823a1.25,1.25 0,0 0,1.54 1.54l3.826,-1.067a9.96,9.96 0,0 0,5.368 1.082,6.518 6.518,0 0,1 -1.051,-1.474 8.449,8.449 0,0 1,-3.863 -1.066l-0.27,-0.15 -3.986,1.111 1.113,-3.984 -0.151,-0.27A8.458,8.458 0,0 1,3.5 12a8.5,8.5 0,0 1,16.996 -0.27c0.54,0.281 1.036,0.636 1.474,1.05 0.02,-0.257 0.03,-0.517 0.03,-0.78ZM12.837,16.472a2,2 0,0 0,1.441 -2.496l-0.198,-0.687a5.28,5.28 0,0 1,1.483 -0.912l0.499,0.524a2,2 0,0 0,2.899 0.001l0.493,-0.518a5.28,5.28 0,0 1,1.484 0.921l-0.186,0.631a2,2 0,0 0,1.45 2.51l0.539,0.13a5.732,5.732 0,0 1,0.006 1.808l-0.584,0.144a2,2 0,0 0,-1.44 2.496l0.197,0.686c-0.439,0.383 -0.939,0.693 -1.483,0.913l-0.498,-0.525a2,2 0,0 0,-2.9 0l-0.493,0.519a5.28,5.28 0,0 1,-1.484 -0.922l0.187,-0.631a2,2 0,0 0,-1.45 -2.51l-0.54,-0.13a5.718,5.718 0,0 1,-0.006 -1.808l0.584,-0.144ZM18.95,17.5c0,-0.828 -0.65,-1.5 -1.45,-1.5 -0.8,0 -1.45,0.672 -1.45,1.5S16.7,19 17.5,19c0.8,0 1.45,-0.672 1.45,-1.5Z"
android:fillColor="#212121"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M18,2C15.791,2 14,3.791 14,6.001C14,8.21 15.791,10.001 18,10.001C18.826,10.001 19.588,9.76 20.222,9.335C20.451,9.181 20.762,9.243 20.915,9.472C21.069,9.702 21.008,10.012 20.778,10.166C19.98,10.701 19.025,11.001 18,11.001C15.238,11.001 13,8.762 13,6.001C13,3.239 15.238,1 18,1C20.762,1 23.001,3.239 23.001,6.001L23,6.01V6.751C23,7.717 22.217,8.501 21.25,8.501C20.648,8.501 20.117,8.196 19.802,7.733C19.348,8.206 18.708,8.501 18,8.501C16.62,8.501 15.5,7.381 15.5,6.001C15.5,4.62 16.62,3.501 18,3.501C18.563,3.501 19.083,3.687 19.5,4C19.501,3.724 19.724,3.501 20,3.501C20.277,3.501 20.5,3.724 20.5,4.001V6.751C20.5,7.165 20.836,7.501 21.25,7.501C21.665,7.501 22,7.165 22,6.751V5.995L22.001,5.987C21.993,3.784 20.205,2 18,2ZM16.5,6.001C16.5,6.829 17.172,7.501 18,7.501C18.829,7.501 19.5,6.829 19.5,6.001C19.5,5.172 18.829,4.501 18,4.501C17.172,4.501 16.5,5.172 16.5,6.001ZM22,14.75V10.473C21.555,10.871 21.05,11.204 20.5,11.457V14.75C20.5,15.717 19.716,16.5 18.75,16.5H12.514L7.5,20.251L7.499,16.5H5.25C4.284,16.5 3.5,15.717 3.5,14.75V6.251C3.5,5.284 4.284,4.501 5.25,4.501H12.189C12.326,3.968 12.534,3.465 12.803,3.001H5.25C3.455,3.001 2,4.456 2,6.251V14.75C2,16.545 3.455,18 5.25,18H5.999L6,20.75C6,21.02 6.087,21.283 6.249,21.499C6.662,22.052 7.446,22.165 7.999,21.751L13.012,18H18.75C20.545,18 22,16.545 22,14.75Z"
android:fillColor="#212121"/>
</vector>

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:gravity="center_vertical"
android:layoutDirection="locale">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:importantForAccessibility="no"
android:tint="?android:textColorPrimary"
tools:src="@drawable/ic_fluent_color_24_regular"/>
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:paddingVertical="8dp"
android:gravity="center_vertical"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</LinearLayout>

View File

@@ -82,8 +82,8 @@
<string name="sk_settings_translation_availability_note_available">%s unterstützt Übersetzung!</string>
<string name="sk_settings_translation_availability_note_unavailable">%s scheint keine Übersetzung zu unterstützen.</string>
<string name="sk_loading_fediverse_resource_title">Suche im Fediverse</string>
<string name="sk_undo_reblog">Reblog rückgängig machen</string>
<string name="sk_reblog_with_visibility">Rebloggen mit Sichtbarkeit</string>
<string name="sk_undo_reblog">Teilen rückgängig machen</string>
<string name="sk_reblog_with_visibility">Teilen mit Sichtbarkeit</string>
<string name="sk_quote_post">Drüberkommentieren</string>
<string name="sk_hashtags_you_follow">Hashtags, denen du folgst</string>
<string name="sk_copy_link_to_post">Link zum Beitrag kopieren</string>
@@ -96,9 +96,9 @@
<string name="sk_favorite_as">Favorit mit anderem Konto</string>
<string name="sk_favorited_as">Favorisiert als %s</string>
<string name="sk_already_favorited">Bereits favorisiert</string>
<string name="sk_reblog_as">Mit einem anderen Konto boosten</string>
<string name="sk_reblogged_as">Geboostet als %s</string>
<string name="sk_already_reblogged">Bereits geboostet</string>
<string name="sk_reblog_as">Mit einem anderen Konto teilen</string>
<string name="sk_reblogged_as">Geteilt als %s</string>
<string name="sk_already_reblogged">Bereits geteilt</string>
<string name="sk_reply_as">Antworten mit anderem Konto</string>
<string name="sk_settings_uniform_icon_for_notifications">Einheitliches Icon für alle Benachrichtigungen</string>
<string name="sk_forward_report_to">Weiterleiten zu %s</string>
@@ -186,7 +186,7 @@
<string name="sk_icon_location">Standort</string>
<string name="sk_icon_microphone">Mikrophon</string>
<string name="sk_icon_microscope">Mikroskop</string>
<string name="sk_icon_keyboard">Keyboard</string>
<string name="sk_icon_keyboard">Tastatur</string>
<string name="sk_icon_coffee">Kaffee</string>
<string name="sk_icon_laugh">Lachen</string>
<string name="sk_icon_news">Nachrichten</string>
@@ -269,7 +269,9 @@
<string name="sk_quoting_user">Zitiere %s</string>
<string name="sk_notification_action_replied">Antwort an %s gesendet</string>
<string name="sk_show_thread">Thread öffnen</string>
<string name="sk_compact_reblog_reply_line">Kompakte Geteilt/Geantwortet-Zeile</string>
<string name="sk_compact_reblog_reply_line">Kompakte Geteilt-/Geantwortet-Zeile</string>
<string name="sk_reply_line_above_avatar">“Als Antwort auf”-Zeile über Profilbild</string>
<string name="sk_settings_confirm_before_reblog">Vor dem Teilen bestätigen</string>
<string name="sk_reacted">hat reagiert</string>
<string name="sk_reacted_with">hat mit %s reagiert</string>
</resources>

View File

@@ -40,4 +40,5 @@
<string name="mo_swap_bookmark_with_reblog">Usa la acción de rebloguear en vez de la de marcador en las notificaciones</string>
<string name="mo_download_latest_nightly_release">Descargar la última versión beta</string>
<string name="mo_load_remote_followers">Cargar remotamente seguidores y seguidos del perfil</string>
<string name="mo_mention_reblogger_automatically">Mencionar automáticamente la cuenta que reblogueó el post en las respuestas</string>
</resources>

View File

@@ -14,7 +14,7 @@
<string name="sk_app_name">Megalodon</string>
<string name="sk_unpinning">Desanclando publicación…</string>
<string name="sk_image_description">Descripción de la imagen</string>
<string name="sk_visibility_unlisted">No listada</string>
<string name="sk_visibility_unlisted">No listado</string>
<string name="sk_settings_show_replies">Mostrar respuestas</string>
<string name="sk_settings_show_boosts">Mostrar impulsos</string>
<string name="sk_settings_load_new_posts">Cargar publicaciones nuevas automáticamente</string>
@@ -83,8 +83,8 @@
<string name="sk_settings_translation_availability_note_unavailable">%s no parece admitir la traducción.</string>
<string name="sk_loading_fediverse_resource_title">Buscándolo en el Fediverso</string>
<string name="sk_quote_post">Publicar sobre esto</string>
<string name="sk_undo_reblog">Deshacer reblogueo</string>
<string name="sk_reblog_with_visibility">Rebloguea con visibilidad</string>
<string name="sk_undo_reblog">Deshacer impulso</string>
<string name="sk_reblog_with_visibility">Impulsar con visibilidad</string>
<string name="sk_hashtags_you_follow">Etiquetas que sigues</string>
<string name="sk_copy_link_to_post">Copiar enlace de la publicación</string>
<string name="sk_open_with_account">Abrir con otra cuenta</string>
@@ -269,8 +269,10 @@
<string name="sk_settings_reply_visibility_following">Responde a mis seguidores</string>
<string name="sk_settings_reply_visibility_self">Respondeme</string>
<string name="sk_notification_action_replied">Respuesta enviada a %s</string>
<string name="sk_reply_line_above_avatar">\"En respuesta a\" línea sobre el avatar</string>
<string name="sk_reply_line_above_avatar">Linea \"En respuesta a\" sobre el avatar</string>
<string name="sk_show_thread">Mostrar hilo</string>
<string name="sk_compact_reblog_reply_line">Línea compacta de reblog/respuesta</string>
<string name="sk_settings_confirm_before_reblog">Confirmar antes de volver a publicar</string>
<string name="sk_compact_reblog_reply_line">Línea compacta de impulso/respuesta</string>
<string name="sk_settings_confirm_before_reblog">Confirmación antes de impulsar</string>
<string name="sk_settings_show_new_posts_button">Botón \"Ver nuevas publicaciones\"</string>
<string name="sk_reacted">reaccionó con %s</string>
</resources>

View File

@@ -82,8 +82,8 @@
<string name="sk_settings_translation_availability_note_unavailable">%s ne semble pas prendre en charge la traduction.</string>
<string name="sk_clear_all_notifications_confirm">Voulez-vous vraiment supprimer toutes les notifications \?</string>
<string name="sk_loading_fediverse_resource_title">Rechercher sur le Fediverse</string>
<string name="sk_reblog_with_visibility">Reposter avec la visibilité</string>
<string name="sk_undo_reblog">Annuler le repost</string>
<string name="sk_reblog_with_visibility">Booster avec la visibilité</string>
<string name="sk_undo_reblog">Annuler le boost</string>
<string name="sk_quote_post">Poster à ce sujet</string>
<string name="sk_hashtags_you_follow">Hashtags que vous suivez</string>
<string name="sk_open_in_account">Ouvrir dans un autre compte</string>
@@ -94,13 +94,13 @@
<string name="sk_favorite_as">Mettre en favoris avec un autre compte</string>
<string name="sk_already_bookmarked">Déjà mis en signet</string>
<string name="sk_already_favorited">Déjà mis en favori</string>
<string name="sk_reblogged_as">Reposté en tant que %s</string>
<string name="sk_already_reblogged">Déjà reposté</string>
<string name="sk_reblogged_as">Boosté en tant que %s</string>
<string name="sk_already_reblogged">Déjà boosté</string>
<string name="sk_bookmarked_as">Ajouté aux signets en tant que %s</string>
<string name="sk_favorited_as">Ajouté aux favoris en tant que %s</string>
<string name="sk_reply_as">Répondre avec un autre compte</string>
<string name="sk_bookmark_as">Mettre en signet avec un autre compte</string>
<string name="sk_reblog_as">Reposter avec un autre compte</string>
<string name="sk_reblog_as">Booster avec un autre compte</string>
<string name="sk_settings_uniform_icon_for_notifications">Icône uniforme pour toutes les notifications</string>
<string name="sk_forward_report_to">Transférer à %s</string>
<string name="sk_unsent_posts">Messages non envoyés</string>
@@ -171,7 +171,7 @@
<string name="sk_alt_button">ALT</string>
<string name="sk_post_edited">édité</string>
<string name="sk_edit_timeline">Modifier la timeline</string>
<string name="sk_notify_update">Modifie un article reposté</string>
<string name="sk_notify_update">Modifier un article boosté</string>
<string name="sk_settings_disable_alt_text_reminder">Désactiver le rappel pour ajouter du texte alternatif</string>
<string name="sk_notification_type_update">Messages modifiés</string>
<string name="sk_icon_code">Code</string>
@@ -271,7 +271,7 @@
<string name="sk_settings_reply_visibility_self">Me répond</string>
<string name="sk_notification_action_replied">Réponse envoyée à %s</string>
<string name="sk_show_thread">Afficher le fil</string>
<string name="sk_compact_reblog_reply_line">Ligne de repost/réponse compacte</string>
<string name="sk_compact_reblog_reply_line">Ligne boost/réponse compacte</string>
<string name="sk_reply_line_above_avatar">Ligne \"En réponse à\" au-dessus de l\'avatar</string>
<string name="sk_settings_confirm_before_reblog">Confirmer avant de reposter</string>
<string name="sk_settings_confirm_before_reblog">Confirmer avant de booster</string>
</resources>

View File

@@ -79,8 +79,8 @@
<string name="sk_favorite_as">Favorecer con outra conta</string>
<string name="sk_favorited_as">Favorita con %s</string>
<string name="sk_already_favorited">Xa foi favorecida</string>
<string name="sk_reblogged_as">Promovida por %s</string>
<string name="sk_already_reblogged">Xa foi promovida</string>
<string name="sk_reblogged_as">Impulsado coma %s</string>
<string name="sk_already_reblogged">Xa foi impulsado</string>
<string name="sk_reply_as">Responder con outra conta</string>
<string name="sk_settings_uniform_icon_for_notifications">Icona uniforme para tódalas notificacións</string>
<string name="sk_enable_delete_notifications">Activar a eliminación de notificacións</string>
@@ -88,9 +88,9 @@
<string name="sk_settings_publish_button_text_title">Personalizar o texto do botón de publicar</string>
<string name="sk_settings_translation_availability_note_unavailable">%s non semella ter soporte para tradución.</string>
<string name="sk_loading_fediverse_resource_title">Buscando no Fediverso</string>
<string name="sk_reblog_with_visibility">Promover con visibilidade</string>
<string name="sk_reblog_with_visibility">Impulsar con visibilidade</string>
<string name="sk_quote_post">Publicar acerca disto</string>
<string name="sk_undo_reblog">Retirar a promoción</string>
<string name="sk_undo_reblog">Desfacer o impulso</string>
<string name="sk_copy_link_to_post">Copiar ligazón á publicación</string>
<string name="sk_loading_resource_on_instance_title">Buscando en %s</string>
<string name="sk_open_with_account">Abrir con outra conta</string>
@@ -113,7 +113,7 @@
<string name="sk_schedule_or_draft">Programar ou borrador</string>
<string name="sk_compose_no_schedule">Non programar</string>
<string name="sk_compose_no_draft">Non facer borrador</string>
<string name="sk_reblog_as">Promover con outra conta</string>
<string name="sk_reblog_as">Impulsar con outra conta</string>
<string name="sk_settings_reduce_motion">Reducir movemento nas animacións</string>
<string name="sk_announcements">Anuncios</string>
<string name="sk_mark_as_read">Marcar como lido</string>
@@ -225,7 +225,7 @@
<string name="sk_icon_gavel">Mazo</string>
<string name="sk_icon_gauge">Indicador</string>
<string name="sk_icon_math_formula">Fórmula matemática</string>
<string name="sk_notify_update">Edita unha publicación promovida</string>
<string name="sk_notify_update">Edita unha publicación impulsada</string>
<string name="sk_no_results">Sen resultados</string>
<string name="sk_save_draft">Gardar borrador\?</string>
<string name="sk_no_alt_text">Sen texto descriptivo</string>
@@ -271,5 +271,6 @@
<string name="sk_notification_action_replied">Resposta enviada a %s</string>
<string name="sk_reply_line_above_avatar">Liña \"en resposta a\" sobre o avatar</string>
<string name="sk_show_thread">Mostrar chío</string>
<string name="sk_compact_reblog_reply_line">Compactar liña de promoción/resposta</string>
<string name="sk_compact_reblog_reply_line">Compactar liña de impulso/resposta</string>
<string name="sk_settings_confirm_before_reblog">Confirma antes de impulsar</string>
</resources>

View File

@@ -40,4 +40,5 @@
<string name="mo_swap_bookmark_with_reblog">Użyj akcji reblogowania zamiast akcji zakładki w powiadomieniach</string>
<string name="mo_download_latest_nightly_release">Pobierz najnowsze nocne wydanie</string>
<string name="mo_load_remote_followers">Wczytaj listę obserwujących i obserwowanych profilu zdalnego</string>
<string name="mo_mention_reblogger_automatically">Automatycznie oznaczaj konto, które zreblogowało wpis w odpowiedziach</string>
</resources>

View File

@@ -69,8 +69,8 @@
<string name="sk_already_bookmarked">Zakładka została już zapisana</string>
<string name="sk_favorited_as">Polubiono jako %s</string>
<string name="sk_already_favorited">Już polubiono</string>
<string name="sk_reblogged_as">Zrebloguj jako %s</string>
<string name="sk_already_reblogged">Już zreblogowano</string>
<string name="sk_reblogged_as">Podbij jako %s</string>
<string name="sk_already_reblogged">Już podbito</string>
<string name="sk_reply_as">Odpowiedz innym kontem</string>
<string name="sk_settings_uniform_icon_for_notifications">Identyczna ikona dla wszystkich notyfikacji</string>
<string name="sk_settings_translate_only_opened">Tłumacz tylko otwarte wpisy</string>
@@ -96,8 +96,8 @@
<string name="sk_clear_all_notifications_confirm_action">Usuń wszystkie</string>
<string name="sk_clear_all_notifications_confirm">Czy jesteś pewien że chcesz usunąć wszystkie powiadomienia\?</string>
<string name="sk_loading_fediverse_resource_title">Wyszukiwanie na Fediwersum</string>
<string name="sk_undo_reblog">Cofnij reblog</string>
<string name="sk_reblog_with_visibility">Reblog z widocznością</string>
<string name="sk_undo_reblog">Cofnij podbicie</string>
<string name="sk_reblog_with_visibility">Podbicie z widocznością</string>
<string name="sk_quote_post">Wpis o tym</string>
<string name="sk_hashtags_you_follow">Hashtagi które obserwujesz</string>
<string name="sk_copy_link_to_post">Kopiuj link do wpisu</string>
@@ -127,7 +127,7 @@
<string name="sk_compose_no_draft">Nie twórz wersji roboczej</string>
<string name="sk_schedule_or_draft">Zaplanowany wpis lub kopia robocza</string>
<string name="sk_favorite_as">Polub innym kontem</string>
<string name="sk_reblog_as">Już zreblogowano</string>
<string name="sk_reblog_as">Już podbito</string>
<string name="sk_settings_reduce_motion">Zmniejsz ruch animacji</string>
<string name="sk_mark_as_read">Oznacz jako przeczytane</string>
<string name="sk_settings_about_instance">O instancji</string>
@@ -220,7 +220,7 @@
<string name="sk_icon_headphones">Słuchawki</string>
<string name="sk_icon_human">Człowiek</string>
<string name="sk_icon_globe">Glob</string>
<string name="sk_notify_update">Edytuje reblogowany wpis</string>
<string name="sk_notify_update">Edytuje podbity wpis</string>
<string name="sk_icon_pin">Pinezka</string>
<string name="sk_remove_follower">Usuń obserwującego</string>
<string name="sk_remove_follower_confirm">Usunąć %s z obserwatorów, poprzez zablokowanie i natychmiastowe odblokowanie ich\?</string>
@@ -270,6 +270,8 @@
<string name="sk_notification_action_replied">Wysłano odpowiedź do %s</string>
<string name="sk_reply_line_above_avatar">Tekst \"W odpowiedzi na\" nad avatarem</string>
<string name="sk_show_thread">Pokaż wątek</string>
<string name="sk_compact_reblog_reply_line">Zmniejsz linię reblogu/odpowiedzi</string>
<string name="sk_settings_confirm_before_reblog">Potwierdź przed reblogowaniem</string>
<string name="sk_compact_reblog_reply_line">Zmniejsz linię podbicia/odpowiedzi</string>
<string name="sk_settings_confirm_before_reblog">Potwierdź przed podbiciem</string>
<string name="sk_settings_show_new_posts_button">Przycisk pokazujący nowe wpisy</string>
<string name="sk_reacted">zareagowano z %s</string>
</resources>

View File

@@ -40,4 +40,5 @@
<string name="mo_swap_bookmark_with_reblog">Usar ação de reblogar ao invés da ação salvar nas notificações</string>
<string name="mo_download_latest_nightly_release">Baixar mais novo lançamento noturno</string>
<string name="mo_load_remote_followers">Carregar seguidores e seguindo de perfil remoto</string>
<string name="mo_mention_reblogger_automatically">Automaticamente mencionar conta que reblogou a postagem nas respostas</string>
</resources>

View File

@@ -39,4 +39,6 @@
<string name="mo_duration_minutes_5">5 хвилин</string>
<string name="mo_disable_double_tap_to_swipe_between_tabs">Вимкнути подвійне тицяння для перемикання між вкладками</string>
<string name="mo_swap_bookmark_with_reblog">Використовувати дію реблог замість дії закладок на сповіщеннях</string>
<string name="mo_load_remote_followers">Завантажити підписки та підписників з віддаленного профілю</string>
<string name="mo_mention_reblogger_automatically">Автоматично згадувати акаунт що реблогнув публікацію у відповідях</string>
</resources>

View File

@@ -95,7 +95,7 @@
<string name="sk_favorited_as">Уподобано як %s</string>
<string name="sk_already_favorited">Уже вподобано</string>
<string name="sk_reblog_as">Поширити в іншому обліковому записі</string>
<string name="sk_already_reblogged">Уже поширено</string>
<string name="sk_already_reblogged">Вже поширено</string>
<string name="sk_settings_uniform_icon_for_notifications">Єдина піктограма для всіх сповіщень</string>
<string name="sk_bookmark_as">Додати до закладок іншого облікового запису</string>
<string name="sk_favorite_as">Уподобане іншим обліковим записом</string>
@@ -172,7 +172,7 @@
<string name="sk_edit_timeline">Редагувати стрічку</string>
<string name="sk_edit_timelines">Редагувати стрічки</string>
<string name="sk_notification_type_update">Змінені дописи</string>
<string name="sk_notify_update">Зміни й поширені дописи</string>
<string name="sk_notify_update">Змінює поширену публікацію</string>
<string name="sk_icon_city">Місто</string>
<string name="sk_icon_cat">Кіт</string>
<string name="sk_icon_dog">Собака</string>
@@ -271,6 +271,8 @@
<string name="sk_notification_action_replied">Надіслано відповідь на %s</string>
<string name="sk_reply_line_above_avatar">Рядок «У відповідь» над аватаром</string>
<string name="sk_show_thread">Показати потік</string>
<string name="sk_compact_reblog_reply_line">Компактний рядок для поширеного допису/відповіді</string>
<string name="sk_compact_reblog_reply_line">Компактний рядок для поширеної публікації/відповіді</string>
<string name="sk_settings_confirm_before_reblog">Підтверджувати поширення</string>
<string name="sk_settings_show_new_posts_button">Кнопка \"Показати нові публікації\"</string>
<string name="sk_reacted">відреагував із %s</string>
</resources>

View File

@@ -252,7 +252,8 @@
<string name="sk_settings_glitch_mode_explanation">Enable this if your home instance runs on Glitch. Not needed for Hometown or Akkoma.</string>
<string name="sk_signed_up">signed up</string>
<string name="sk_reported">reported</string>
<string name="sk_reacted">reacted with %s</string>
<string name="sk_reacted_with">reacted with %s</string>
<string name="sk_reacted">reacted</string>
<string name="sk_sign_ups">Users signing up</string>
<string name="sk_new_reports">New reports</string>
<string name="sk_settings_server_version">Server version: %s</string>

View File

@@ -222,6 +222,7 @@
<item name="android:actionBarTheme">@style/Theme.Mastodon.Toolbar.Dark.TrueBlack</item>
<item name="colorBackgroundLight">@color/black</item>
<item name="colorButtonText">@color/black</item>
<item name="toolbarBackground">@color/black</item>
<item name="colorPollVoted">?colorGray700</item>
<item name="colorSearchField">?colorGray900</item>
<item name="colorBackgroundLightest">@color/black</item>
@@ -257,7 +258,7 @@
</style>
<style name="Theme.Mastodon.Toolbar.Dark.TrueBlack" parent="android:ThemeOverlay.Material.Dark.ActionBar">
<item name="android:colorPrimary">@color/black</item>
<item name="android:colorPrimary">?toolbarBackground</item>
<item name="android:toolbarStyle">@style/Widget.Mastodon.Toolbar</item>
<!-- Why must we re-add this? So that the text color isn't wrong. I don't know why it doesn't work, but we shall do this so it looks right -->
<item name="android:textColorPrimary">?colorGray50</item>

View File

@@ -1,15 +1,15 @@
Moshidon ist eine veränderte Version der <a href="https://github.com/mastodon/mastodon-android">offiziellen Mastodon Android-App</a> , die wichtige Funktionen ergänzt, die in der offiziellen App feheln, wie z. B. eine Föderierte Timeline, Ungelistetes Veröffentlichen und die Möglichkeit Bildbeschreibungen zu sehen.
Moshidon ist eine veränderte Version der <a href="https://github.com/mastodon/mastodon-android">offiziellen Mastodon Android-App</a> , die wichtige Funktionen ergänzt, die in der offiziellen App fehlen, wie z. B. eine föderierte Timeline, ungelistetes Veröffentlichen und die Möglichkeit Bildbeschreibungen zu sehen.
<b>Wichtigste Features</b>
<b>Wichtigste Funktionen</b>
- <b>große Farbauswahl</b>: Material You und viele weitere Farbschemen!
- <b>Übersetzungsfunktion</b>: Eine Schaltfläche, um Übersetzungen durchzuführen!
- <b>Tröt-Sprachauswahl</b>: Eine Schaltfläche, um die Sprache des Posts auszuwählen!
- <b>Ungelistetes Veröffentlichen</b>: Poste öffentlich, ohne dass deine Posts in Trends, Hashtags oder auf öffentlichen Timelines erscheinen.
- <b>Föderierte Timeline</b>: Sieh alle öffentlichen Posts von allen Fediverse-Servern, mit denen deine Instanz verbunden ist.
- <b>Übersetzungsfunktion</b>: Eine Schaltfläche, um Beiträge zu übersetzen!
- <b>Beitrags-Sprachauswahl</b>: Eine Schaltfläche, um die Sprache deines Beitrags auszuwählen!
- <b>Ungelistetes Veröffentlichen</b>: Poste öffentlich, ohne dass deine Beiträge in Trends, Hashtags oder auf öffentlichen Timelines erscheinen.
- <b>Föderierte Timeline</b>: Sieh alle öffentlichen Beiträge von allen Fediverse-Servern, mit denen deine Instanz verbunden ist.
- <b>Bildbeschreibungen</b>: Sieh auf einen Blick, ob ein Bild oder ein Video einen Alternativtext hat.
- <b>Posts anpinnen</b>: Pinne deine wichtigsten Posts auf deiner Profilseite an und finde Pins von anderen Nutzern im "Angepinnt"-Tab.
- <b>Posts anpinnen</b>: Pinne deine wichtigsten Beiträge auf der Profilseite an und finde Pins von anderen Nutzern im "Angepinnt"-Tab.
- <b>Hashtags folgen</b>: Sieh Posts mit bestimmten Hashtags direkt auf deiner Startseite, indem ihnen einfach folgst.
- <b>Follower-Anfragen beantworten</b>: Bestätige oder lehne Follower-Anfragen direkt in Benachrichtigungen oder auf der separaten Follower-Anfrageliste ab.
- <b>Löschen und Neuverfassen</b>: Die beliebte Funktion, die Bearbeiten von Posts ohne eigentliche Bearbeitungsfunktion möglich gemacht hat.
- <b>Löschen und Neuverfassen</b>: Die beliebte Funktion, die das Bearbeiten von Beiträgen ohne eigentliche Bearbeitungsfunktion möglich gemacht hat.
- <b>Extras</b>: Viele Anpassungen der Nutzeroberfläche, wie z. B. Interaktionssymbole in Benachrichtigungen und die Entfernung von vielen Unstimmigkeiten beim ursprünglichen Design!