Compare commits

..

18 Commits

Author SHA1 Message Date
LucasGGamerM
cd749e9c9c feat: rework SettingsCategoryItem, and add visual appearance settings 2023-04-23 12:47:49 -03:00
LucasGGamerM
1d25d80186 Merge remote branch 'FineFindus/feat/settings-redesign' 2023-04-23 12:09:49 -03:00
FineFindus
cdeeb24eac refactor(settings): add red header item 2023-04-23 15:20:57 +02:00
FineFindus
5a5181fde8 refactor(settings): move about to about page 2023-04-23 15:10:47 +02:00
FineFindus
69420b2399 refactor: move account and instance setttings to account page 2023-04-23 15:02:26 +02:00
FineFindus
aaa80f80f1 refactor: move miscellanious settings 2023-04-23 14:50:00 +02:00
FineFindus
9b11fbbbfe refactor: move notifications to NotificationsFragment 2023-04-23 14:45:57 +02:00
FineFindus
57f513048a refactor: move timelines to TimelineFragment 2023-04-23 12:44:40 +02:00
FineFindus
c5e35e550c refactor: add compose behaviour to behaviour page 2023-04-23 12:38:51 +02:00
FineFindus
2751b804fe refactor: move behaviour settings to behaviour page 2023-04-23 12:33:03 +02:00
FineFindus
d310673f92 refactor: use SettingsCategory to move between pages 2023-04-23 12:24:06 +02:00
FineFindus
afca57501f fix: move method to fix compiler error 2023-04-23 12:10:43 +02:00
LucasGGamerM
34443726e2 feat: trying to add the theme settings. Still missing some things
What is an enclosed class? What am I missing?
2023-04-22 23:01:31 -03:00
LucasGGamerM
fc9ffc9aef feat: add settings category view holder
Also adds a small proof of concept
2023-04-22 22:35:39 -03:00
LucasGGamerM
44b1bc70af feat: add all previously missing view holders
This includes: Updater View holder, theme view holder and notification policy view holder
2023-04-22 18:02:54 -03:00
LucasGGamerM
45796000c4 Merge https://github.com/LucasGGamerM/moshidon into HEAD 2023-04-22 17:22:46 -03:00
FineFindus
f2c13ed379 refactor: add abstract settingsbase fragment 2023-04-21 21:17:20 +02:00
FineFindus
fd21b9e568 feat: move Settingsfragment to settings folder 2023-04-21 18:34:00 +02:00
79 changed files with 1772 additions and 732 deletions

5
FAQ.md
View File

@@ -1,5 +0,0 @@
## 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> <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 ## Key features
### **The ability to add other server's local timeline to your timelines** ### **The ability to add new custom local 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!
### **View remote profiles** ### **Material you theme support on Android 12+ devices!**
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. ### **Show posts filtered with a warning!**
### **Translate posts easily** **Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:**
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 Before | After
:-------------------------:|:-------------------------: :-------------------------:|:-------------------------:
@@ -47,7 +47,7 @@ Before | After
### **Color themes** ### **Color themes**
**Allows you to change theme within the app. Supports Material You, purple, pink, green, blue, red, orange, yellow and Nord!** **Allows you to change theme within the app. Supports Purple, pink, green, blue, red, orange, yellow and Nord!**
### **Unlisted posting** ### **Unlisted posting**
@@ -71,10 +71,6 @@ 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! 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** ### **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.** **This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in peoples profiles shows all the posts they pinned.**
@@ -99,22 +95,12 @@ Moshidon is also available in [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/
## Release variants ## Release variants
### Stable variant All downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
All stable version downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
**`moshidon.apk`** **`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`. 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.
--- ---
@@ -122,18 +108,16 @@ Unstable variant with an integrated updater. It's for development and testing pu
### Features ### 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) * [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) * [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)) ([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
* Adding a useful private profile note box * Adding a useful private profile note box!*
* Auto hiding the compose button on scroll * Auto hiding the compose button on scroll!*
* Adding the ability to remind yourself to add alt text to images * Adding the ability to remind yourself to add alt text to images!*
* An indicator for if an image has alt text or not * An indicator for if an image has alt text or not*
* Adding the ability to have drafts * Adding the ability to have drafts!*
* Also adding the ability to view announcements from your instance * 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 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)) * [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 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)) * [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))
@@ -155,7 +139,6 @@ Unstable variant with an integrated updater. It's for development and testing pu
### Behavior ### Behavior
* Allow for confirmation before reblogging
* Adding a bottom option for the publish button, allowing for easier use on larger screens! * 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)) * [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)) * [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))
@@ -187,10 +170,6 @@ This project is released under the [GPL-3 License](./LICENSE).
## Links ## Links
[F.A.Q](FAQ.md) [Official matrix chatroom:](https://matrix.to/#/#moshidon:matrix.org) https://matrix.to/#/#moshidon:matrix.org
[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> <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" applicationId "org.joinmastodon.android.moshinda"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 33
versionCode 100 versionCode 99
versionName "1.3.0+fork.100.moshinda" versionName "1.2.0+fork.99.moshinda"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 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'] 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" setRoot "src/github"
} }
debug { debug {
setRoot "src/debug" setRoot "src/github"
} }
} }
namespace 'org.joinmastodon.android' namespace 'org.joinmastodon.android'

View File

@@ -1,369 +0,0 @@
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

@@ -1,62 +0,0 @@
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());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

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

View File

@@ -12,7 +12,7 @@ gab.protohype.net
social.unzensiert.to social.unzensiert.to
freeatlantis.com freeatlantis.com
# reactionary bigotry and hatespeech against marginalized groups # reactionary bigotry and hatespeech against magrinalized groups
poa.st poa.st
freespeechextremist.com freespeechextremist.com
rdrama.cc 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 marginalized groups reactionary bigotry and hatespeech against magrinalized groups
13 poa.st
14 freespeechextremist.com
15 rdrama.cc
16 outpoa.st
17 anime.website
18 gameliberty.club

View File

@@ -19,7 +19,6 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import org.joinmastodon.android.api.MastodonAPIController; 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.notifications.GetNotificationByID;
import org.joinmastodon.android.api.requests.statuses.CreateStatus; import org.joinmastodon.android.api.requests.statuses.CreateStatus;
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked; import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
@@ -124,16 +123,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if(intent.hasExtra("notification")){ if(intent.hasExtra("notification")){
org.joinmastodon.android.model.Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification")); org.joinmastodon.android.model.Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
String statusID = null; String statusID=notification.status.id;
String targetAccountID = null; if (statusID != 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(); AccountSessionManager accountSessionManager = AccountSessionManager.getInstance();
Preferences preferences = accountSessionManager.getAccount(accountID).preferences; Preferences preferences = accountSessionManager.getAccount(accountID).preferences;
@@ -143,7 +134,6 @@ public class PushNotificationReceiver extends BroadcastReceiver{
case BOOST -> new SetStatusReblogged(notification.status.id, true, preferences.postingDefaultVisibility).exec(accountID); 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 UNBOOST -> new SetStatusReblogged(notification.status.id, false, preferences.postingDefaultVisibility).exec(accountID);
case REPLY -> handleReplyAction(context, accountID, intent, notification, notificationId, preferences); 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"); default -> Log.w(TAG, "onReceive: Failed to get NotificationAction");
} }
} }
@@ -251,9 +241,6 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if(notification.status.reblogged) if(notification.status.reblogged)
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.sk_undo_reblog), NotificationAction.UNBOOST)); 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,6 +49,7 @@ import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController; import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.MediaGridLayout;
import org.joinmastodon.android.utils.TypedObjectPool; import org.joinmastodon.android.utils.TypedObjectPool;
import java.util.ArrayList; import java.util.ArrayList;
@@ -89,10 +90,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public BaseStatusListFragment(){ public BaseStatusListFragment(){
super(20); super(20);
if (wantsComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab); if (withComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab);
} }
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return false; return false;
} }
@@ -108,6 +109,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
setRetainInstance(true); setRetainInstance(true);
} }
@Override @Override
protected RecyclerView.Adapter getAdapter(){ protected RecyclerView.Adapter getAdapter(){
return adapter=new DisplayItemsAdapter(); return adapter=new DisplayItemsAdapter();
@@ -275,36 +278,6 @@ 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 @Override
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
@@ -316,21 +289,47 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(currentPhotoViewer!=null) if(currentPhotoViewer!=null)
currentPhotoViewer.offsetView(-dx, -dy); currentPhotoViewer.offsetView(-dx, -dy);
View fab = getFab();
if (fab!=null && GlobalUserPreferences.enableFabAutoHide) { 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) { if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
animateFab(false); 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;
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) { } else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
if (list.getChildAt(0).getTop() == 0 || scrollDiff > THRESHOLD) { if (scrollDiff > THRESHOLD) {
animateFab(true); 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);
scrollDiff = 0; scrollDiff = 0;
} else { } else {
scrollDiff += Math.abs(dy); scrollDiff += Math.abs(dy);
} }
} }
} }
} }});
});
list.addItemDecoration(new StatusListItemDecoration()); list.addItemDecoration(new StatusListItemDecoration());
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){ ((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
private Rect tmpRect=new Rect(); private Rect tmpRect=new Rect();
@@ -365,12 +364,11 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true); ((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true);
updateToolbar(); updateToolbar();
if (wantsComposeButton() && !getArguments().getBoolean("__disable_fab", false)) { if (withComposeButton()) {
fab = view.findViewById(R.id.fab);
fab.setVisibility(View.VISIBLE); fab.setVisibility(View.VISIBLE);
fab.setOnClickListener(this::onFabClick); fab.setOnClickListener(this::onFabClick);
fab.setOnLongClickListener(this::onFabLongClick); fab.setOnLongClickListener(this::onFabLongClick);
} else if (fab != null) {
fab.setVisibility(View.GONE);
} }
} }
@@ -698,13 +696,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
currentPhotoViewer.onPause(); currentPhotoViewer.onPause();
} }
public void onFabClick(View v){ protected void onFabClick(View v){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
Nav.go(getActivity(), ComposeFragment.class, args); Nav.go(getActivity(), ComposeFragment.class, args);
} }
public boolean onFabLongClick(View v) { protected boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID); return UiUtils.pickAccountForCompose(getActivity(), accountID);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
@@ -49,8 +50,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS); private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
@Override @Override
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return false; return true;
} }
@Override @Override
@@ -106,8 +107,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
case UPDATE -> getString(R.string.sk_post_edited); case UPDATE -> getString(R.string.sk_post_edited);
case SIGN_UP -> getString(R.string.sk_signed_up); case SIGN_UP -> getString(R.string.sk_signed_up);
case REPORT -> getString(R.string.sk_reported); case REPORT -> getString(R.string.sk_reported);
case REACTION, PLEROMA_EMOJI_REACTION -> case EMOJI_REACTION -> getString(R.string.sk_reacted, n.emoji);
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; 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){ if(n.status!=null){
@@ -156,7 +156,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
loadRelationships(needRelationships); loadRelationships(needRelationships);
maxID=result.maxID; maxID=result.maxID;
if(offset==0 && !result.items.isEmpty() && !result.isFromCache() && AccountSessionManager.getInstance().getAccount(accountID).markers.notifications != null){ if(offset==0 && !result.items.isEmpty() && !result.isFromCache()){
E.post(new AllNotificationsSeenEvent()); E.post(new AllNotificationsSeenEvent());
new SaveMarkers(null, result.items.get(0).id).exec(accountID); new SaveMarkers(null, result.items.get(0).id).exec(accountID);
AccountSessionManager.getInstance().getAccount(accountID).markers AccountSessionManager.getInstance().getAccount(accountID).markers
@@ -214,6 +214,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this)); list.addItemDecoration(new InsetStatusItemDecoration(this));
if (getParentFragment() instanceof NotificationsFragment) fab.setVisibility(View.GONE);
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap); 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.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab{ public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{
private static final int AVATAR_RESULT=722; private static final int AVATAR_RESULT=722;
private static final int COVER_RESULT=343; private static final int COVER_RESULT=343;
@@ -158,6 +158,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private WindowInsets childInsets; private WindowInsets childInsets;
private PhotoViewer currentPhotoViewer; private PhotoViewer currentPhotoViewer;
private boolean editModeLoading; private boolean editModeLoading;
protected int scrollDiff = 0;
private static final int MAX_FIELDS=4; private static final int MAX_FIELDS=4;
@@ -231,6 +232,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followingCount=content.findViewById(R.id.following_count); followingCount=content.findViewById(R.id.following_count);
followingLabel=content.findViewById(R.id.following_label); followingLabel=content.findViewById(R.id.following_label);
followingBtn=content.findViewById(R.id.following_btn); followingBtn=content.findViewById(R.id.following_btn);
postsCount=content.findViewById(R.id.posts_count); postsCount=content.findViewById(R.id.posts_count);
postsLabel=content.findViewById(R.id.posts_label); postsLabel=content.findViewById(R.id.posts_label);
postsBtn=content.findViewById(R.id.posts_btn); postsBtn=content.findViewById(R.id.posts_btn);
@@ -592,6 +594,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account); boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
if(account.locked){ if(account.locked){
ssb=new SpannableStringBuilder("@"); ssb=new SpannableStringBuilder("@");
ssb.append(account.acct); ssb.append(account.acct);
@@ -899,6 +902,36 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(currentPhotoViewer!=null){ if(currentPhotoViewer!=null){
currentPhotoViewer.offsetView(0, oldScrollY-scrollY); 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){ 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; private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
@Override @Override
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return true; return true;
} }
@@ -57,7 +57,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
} }
@Override @Override
public void onFabClick(View v) { protected void onFabClick(View v) {
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant()); args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
@@ -65,7 +65,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
} }
@Override @Override
public boolean onFabLongClick(View v) { protected boolean onFabLongClick(View v) {
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant()); args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());

View File

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

View File

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

View File

@@ -23,8 +23,7 @@ import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.session.AccountActivationInfo; import org.joinmastodon.android.api.session.AccountActivationInfo;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HomeFragment; import org.joinmastodon.android.fragments.settings.SettingsFragment;
import org.joinmastodon.android.fragments.SettingsFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.AccountSwitcherSheet; import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;

View File

@@ -0,0 +1,122 @@
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

@@ -0,0 +1,122 @@
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

@@ -0,0 +1,129 @@
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

@@ -0,0 +1,38 @@
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

@@ -0,0 +1,84 @@
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

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

View File

@@ -0,0 +1,20 @@
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

@@ -0,0 +1,128 @@
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,14 +53,12 @@ public class Notification extends BaseModel implements DisplayItemsParent{
STATUS, STATUS,
@SerializedName("update") @SerializedName("update")
UPDATE, UPDATE,
@SerializedName("reaction")
REACTION,
@SerializedName("pleroma:emoji_reaction")
PLEROMA_EMOJI_REACTION,
@SerializedName("admin.sign_up") @SerializedName("admin.sign_up")
SIGN_UP, SIGN_UP,
@SerializedName("admin.report") @SerializedName("admin.report")
REPORT REPORT,
@SerializedName("pleroma:emoji_reaction")
EMOJI_REACTION
} }
@Parcel @Parcel

View File

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

View File

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

View File

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

View File

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

View File

@@ -208,13 +208,49 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start(); translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
if(item.status.reloadWhenClicked){ if(item.status.reloadWhenClicked){
UiUtils.lookupStatus(item.parentFragment.getContext(), UiUtils.lookupStatus(item.parentFragment.getContext(), item.status, item.parentFragment.getAccountID(), null, status1 -> {
item.status, new TranslateStatus(item.status.id).setCallback(new Callback<>() {
item.parentFragment.getAccountID(), @Override
null, public void onSuccess(TranslatedStatus translatedStatus) {
reloadedStatus -> loadTranslation(reloadedStatus.id)); 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());
});
} else { } else {
loadTranslation(item.status.id); 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());
} }
} else { } else {
item.setTranslationShown(!item.translationShown); item.setTranslationShown(!item.translationShown);
@@ -267,29 +303,5 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private CustomEmojiHelper getEmojiHelper(){ private CustomEmojiHelper getEmojiHelper(){
return item.spoilerEmojiHelper!=null && !item.status.spoilerRevealed ? item.spoilerEmojiHelper : item.emojiHelper; 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

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="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

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="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

@@ -0,0 +1,32 @@
<?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_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_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_loading_fediverse_resource_title">Suche im Fediverse</string>
<string name="sk_undo_reblog">Teilen rückgängig machen</string> <string name="sk_undo_reblog">Reblog rückgängig machen</string>
<string name="sk_reblog_with_visibility">Teilen mit Sichtbarkeit</string> <string name="sk_reblog_with_visibility">Rebloggen mit Sichtbarkeit</string>
<string name="sk_quote_post">Drüberkommentieren</string> <string name="sk_quote_post">Drüberkommentieren</string>
<string name="sk_hashtags_you_follow">Hashtags, denen du folgst</string> <string name="sk_hashtags_you_follow">Hashtags, denen du folgst</string>
<string name="sk_copy_link_to_post">Link zum Beitrag kopieren</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_favorite_as">Favorit mit anderem Konto</string>
<string name="sk_favorited_as">Favorisiert als %s</string> <string name="sk_favorited_as">Favorisiert als %s</string>
<string name="sk_already_favorited">Bereits favorisiert</string> <string name="sk_already_favorited">Bereits favorisiert</string>
<string name="sk_reblog_as">Mit einem anderen Konto teilen</string> <string name="sk_reblog_as">Mit einem anderen Konto boosten</string>
<string name="sk_reblogged_as">Geteilt als %s</string> <string name="sk_reblogged_as">Geboostet als %s</string>
<string name="sk_already_reblogged">Bereits geteilt</string> <string name="sk_already_reblogged">Bereits geboostet</string>
<string name="sk_reply_as">Antworten mit anderem Konto</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_settings_uniform_icon_for_notifications">Einheitliches Icon für alle Benachrichtigungen</string>
<string name="sk_forward_report_to">Weiterleiten zu %s</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_location">Standort</string>
<string name="sk_icon_microphone">Mikrophon</string> <string name="sk_icon_microphone">Mikrophon</string>
<string name="sk_icon_microscope">Mikroskop</string> <string name="sk_icon_microscope">Mikroskop</string>
<string name="sk_icon_keyboard">Tastatur</string> <string name="sk_icon_keyboard">Keyboard</string>
<string name="sk_icon_coffee">Kaffee</string> <string name="sk_icon_coffee">Kaffee</string>
<string name="sk_icon_laugh">Lachen</string> <string name="sk_icon_laugh">Lachen</string>
<string name="sk_icon_news">Nachrichten</string> <string name="sk_icon_news">Nachrichten</string>
@@ -269,9 +269,7 @@
<string name="sk_quoting_user">Zitiere %s</string> <string name="sk_quoting_user">Zitiere %s</string>
<string name="sk_notification_action_replied">Antwort an %s gesendet</string> <string name="sk_notification_action_replied">Antwort an %s gesendet</string>
<string name="sk_show_thread">Thread öffnen</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_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_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> </resources>

View File

@@ -40,5 +40,4 @@
<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_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_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_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> </resources>

View File

@@ -14,7 +14,7 @@
<string name="sk_app_name">Megalodon</string> <string name="sk_app_name">Megalodon</string>
<string name="sk_unpinning">Desanclando publicación…</string> <string name="sk_unpinning">Desanclando publicación…</string>
<string name="sk_image_description">Descripción de la imagen</string> <string name="sk_image_description">Descripción de la imagen</string>
<string name="sk_visibility_unlisted">No listado</string> <string name="sk_visibility_unlisted">No listada</string>
<string name="sk_settings_show_replies">Mostrar respuestas</string> <string name="sk_settings_show_replies">Mostrar respuestas</string>
<string name="sk_settings_show_boosts">Mostrar impulsos</string> <string name="sk_settings_show_boosts">Mostrar impulsos</string>
<string name="sk_settings_load_new_posts">Cargar publicaciones nuevas automáticamente</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_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_loading_fediverse_resource_title">Buscándolo en el Fediverso</string>
<string name="sk_quote_post">Publicar sobre esto</string> <string name="sk_quote_post">Publicar sobre esto</string>
<string name="sk_undo_reblog">Deshacer impulso</string> <string name="sk_undo_reblog">Deshacer reblogueo</string>
<string name="sk_reblog_with_visibility">Impulsar con visibilidad</string> <string name="sk_reblog_with_visibility">Rebloguea con visibilidad</string>
<string name="sk_hashtags_you_follow">Etiquetas que sigues</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_copy_link_to_post">Copiar enlace de la publicación</string>
<string name="sk_open_with_account">Abrir con otra cuenta</string> <string name="sk_open_with_account">Abrir con otra cuenta</string>
@@ -269,10 +269,8 @@
<string name="sk_settings_reply_visibility_following">Responde a mis seguidores</string> <string name="sk_settings_reply_visibility_following">Responde a mis seguidores</string>
<string name="sk_settings_reply_visibility_self">Respondeme</string> <string name="sk_settings_reply_visibility_self">Respondeme</string>
<string name="sk_notification_action_replied">Respuesta enviada a %s</string> <string name="sk_notification_action_replied">Respuesta enviada a %s</string>
<string name="sk_reply_line_above_avatar">Linea \"En respuesta a\" sobre el avatar</string> <string name="sk_reply_line_above_avatar">\"En respuesta a\" línea sobre el avatar</string>
<string name="sk_show_thread">Mostrar hilo</string> <string name="sk_show_thread">Mostrar hilo</string>
<string name="sk_compact_reblog_reply_line">Línea compacta de impulso/respuesta</string> <string name="sk_compact_reblog_reply_line">Línea compacta de reblog/respuesta</string>
<string name="sk_settings_confirm_before_reblog">Confirmación antes de impulsar</string> <string name="sk_settings_confirm_before_reblog">Confirmar antes de volver a publicar</string>
<string name="sk_settings_show_new_posts_button">Botón \"Ver nuevas publicaciones\"</string>
<string name="sk_reacted">reaccionó con %s</string>
</resources> </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_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_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_loading_fediverse_resource_title">Rechercher sur le Fediverse</string>
<string name="sk_reblog_with_visibility">Booster avec la visibilité</string> <string name="sk_reblog_with_visibility">Reposter avec la visibilité</string>
<string name="sk_undo_reblog">Annuler le boost</string> <string name="sk_undo_reblog">Annuler le repost</string>
<string name="sk_quote_post">Poster à ce sujet</string> <string name="sk_quote_post">Poster à ce sujet</string>
<string name="sk_hashtags_you_follow">Hashtags que vous suivez</string> <string name="sk_hashtags_you_follow">Hashtags que vous suivez</string>
<string name="sk_open_in_account">Ouvrir dans un autre compte</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_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_bookmarked">Déjà mis en signet</string>
<string name="sk_already_favorited">Déjà mis en favori</string> <string name="sk_already_favorited">Déjà mis en favori</string>
<string name="sk_reblogged_as">Boosté en tant que %s</string> <string name="sk_reblogged_as">Reposté en tant que %s</string>
<string name="sk_already_reblogged">Déjà boosté</string> <string name="sk_already_reblogged">Déjà reposté</string>
<string name="sk_bookmarked_as">Ajouté aux signets en tant que %s</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_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_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_bookmark_as">Mettre en signet avec un autre compte</string>
<string name="sk_reblog_as">Booster avec un autre compte</string> <string name="sk_reblog_as">Reposter avec un autre compte</string>
<string name="sk_settings_uniform_icon_for_notifications">Icône uniforme pour toutes les notifications</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_forward_report_to">Transférer à %s</string>
<string name="sk_unsent_posts">Messages non envoyé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_alt_button">ALT</string>
<string name="sk_post_edited">édité</string> <string name="sk_post_edited">édité</string>
<string name="sk_edit_timeline">Modifier la timeline</string> <string name="sk_edit_timeline">Modifier la timeline</string>
<string name="sk_notify_update">Modifier un article boosté</string> <string name="sk_notify_update">Modifie un article reposté</string>
<string name="sk_settings_disable_alt_text_reminder">Désactiver le rappel pour ajouter du texte alternatif</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_notification_type_update">Messages modifiés</string>
<string name="sk_icon_code">Code</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_settings_reply_visibility_self">Me répond</string>
<string name="sk_notification_action_replied">Réponse envoyée à %s</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_show_thread">Afficher le fil</string>
<string name="sk_compact_reblog_reply_line">Ligne boost/réponse compacte</string> <string name="sk_compact_reblog_reply_line">Ligne de repost/réponse compacte</string>
<string name="sk_reply_line_above_avatar">Ligne \"En réponse à\" au-dessus de l\'avatar</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 booster</string> <string name="sk_settings_confirm_before_reblog">Confirmer avant de reposter</string>
</resources> </resources>

View File

@@ -79,8 +79,8 @@
<string name="sk_favorite_as">Favorecer con outra conta</string> <string name="sk_favorite_as">Favorecer con outra conta</string>
<string name="sk_favorited_as">Favorita con %s</string> <string name="sk_favorited_as">Favorita con %s</string>
<string name="sk_already_favorited">Xa foi favorecida</string> <string name="sk_already_favorited">Xa foi favorecida</string>
<string name="sk_reblogged_as">Impulsado coma %s</string> <string name="sk_reblogged_as">Promovida por %s</string>
<string name="sk_already_reblogged">Xa foi impulsado</string> <string name="sk_already_reblogged">Xa foi promovida</string>
<string name="sk_reply_as">Responder con outra conta</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_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> <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_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_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_loading_fediverse_resource_title">Buscando no Fediverso</string>
<string name="sk_reblog_with_visibility">Impulsar con visibilidade</string> <string name="sk_reblog_with_visibility">Promover con visibilidade</string>
<string name="sk_quote_post">Publicar acerca disto</string> <string name="sk_quote_post">Publicar acerca disto</string>
<string name="sk_undo_reblog">Desfacer o impulso</string> <string name="sk_undo_reblog">Retirar a promoción</string>
<string name="sk_copy_link_to_post">Copiar ligazón á publicación</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_loading_resource_on_instance_title">Buscando en %s</string>
<string name="sk_open_with_account">Abrir con outra conta</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_schedule_or_draft">Programar ou borrador</string>
<string name="sk_compose_no_schedule">Non programar</string> <string name="sk_compose_no_schedule">Non programar</string>
<string name="sk_compose_no_draft">Non facer borrador</string> <string name="sk_compose_no_draft">Non facer borrador</string>
<string name="sk_reblog_as">Impulsar con outra conta</string> <string name="sk_reblog_as">Promover con outra conta</string>
<string name="sk_settings_reduce_motion">Reducir movemento nas animacións</string> <string name="sk_settings_reduce_motion">Reducir movemento nas animacións</string>
<string name="sk_announcements">Anuncios</string> <string name="sk_announcements">Anuncios</string>
<string name="sk_mark_as_read">Marcar como lido</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_gavel">Mazo</string>
<string name="sk_icon_gauge">Indicador</string> <string name="sk_icon_gauge">Indicador</string>
<string name="sk_icon_math_formula">Fórmula matemática</string> <string name="sk_icon_math_formula">Fórmula matemática</string>
<string name="sk_notify_update">Edita unha publicación impulsada</string> <string name="sk_notify_update">Edita unha publicación promovida</string>
<string name="sk_no_results">Sen resultados</string> <string name="sk_no_results">Sen resultados</string>
<string name="sk_save_draft">Gardar borrador\?</string> <string name="sk_save_draft">Gardar borrador\?</string>
<string name="sk_no_alt_text">Sen texto descriptivo</string> <string name="sk_no_alt_text">Sen texto descriptivo</string>
@@ -271,6 +271,5 @@
<string name="sk_notification_action_replied">Resposta enviada a %s</string> <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_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_show_thread">Mostrar chío</string>
<string name="sk_compact_reblog_reply_line">Compactar liña de impulso/resposta</string> <string name="sk_compact_reblog_reply_line">Compactar liña de promoción/resposta</string>
<string name="sk_settings_confirm_before_reblog">Confirma antes de impulsar</string>
</resources> </resources>

View File

@@ -40,5 +40,4 @@
<string name="mo_swap_bookmark_with_reblog">Użyj akcji reblogowania zamiast akcji zakładki w powiadomieniach</string> <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_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_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> </resources>

View File

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

View File

@@ -40,5 +40,4 @@
<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_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_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_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> </resources>

View File

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

View File

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

View File

@@ -252,8 +252,7 @@
<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_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_signed_up">signed up</string>
<string name="sk_reported">reported</string> <string name="sk_reported">reported</string>
<string name="sk_reacted_with">reacted with %s</string> <string name="sk_reacted">reacted with %s</string>
<string name="sk_reacted">reacted</string>
<string name="sk_sign_ups">Users signing up</string> <string name="sk_sign_ups">Users signing up</string>
<string name="sk_new_reports">New reports</string> <string name="sk_new_reports">New reports</string>
<string name="sk_settings_server_version">Server version: %s</string> <string name="sk_settings_server_version">Server version: %s</string>

View File

@@ -222,7 +222,6 @@
<item name="android:actionBarTheme">@style/Theme.Mastodon.Toolbar.Dark.TrueBlack</item> <item name="android:actionBarTheme">@style/Theme.Mastodon.Toolbar.Dark.TrueBlack</item>
<item name="colorBackgroundLight">@color/black</item> <item name="colorBackgroundLight">@color/black</item>
<item name="colorButtonText">@color/black</item> <item name="colorButtonText">@color/black</item>
<item name="toolbarBackground">@color/black</item>
<item name="colorPollVoted">?colorGray700</item> <item name="colorPollVoted">?colorGray700</item>
<item name="colorSearchField">?colorGray900</item> <item name="colorSearchField">?colorGray900</item>
<item name="colorBackgroundLightest">@color/black</item> <item name="colorBackgroundLightest">@color/black</item>
@@ -258,7 +257,7 @@
</style> </style>
<style name="Theme.Mastodon.Toolbar.Dark.TrueBlack" parent="android:ThemeOverlay.Material.Dark.ActionBar"> <style name="Theme.Mastodon.Toolbar.Dark.TrueBlack" parent="android:ThemeOverlay.Material.Dark.ActionBar">
<item name="android:colorPrimary">?toolbarBackground</item> <item name="android:colorPrimary">@color/black</item>
<item name="android:toolbarStyle">@style/Widget.Mastodon.Toolbar</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 --> <!-- 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> <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 fehlen, 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 feheln, wie z. B. eine Föderierte Timeline, Ungelistetes Veröffentlichen und die Möglichkeit Bildbeschreibungen zu sehen.
<b>Wichtigste Funktionen</b> <b>Wichtigste Features</b>
- <b>große Farbauswahl</b>: Material You und viele weitere Farbschemen! - <b>große Farbauswahl</b>: Material You und viele weitere Farbschemen!
- <b>Übersetzungsfunktion</b>: Eine Schaltfläche, um Beiträge zu übersetzen! - <b>Übersetzungsfunktion</b>: Eine Schaltfläche, um Übersetzungen durchzuführen!
- <b>Beitrags-Sprachauswahl</b>: Eine Schaltfläche, um die Sprache deines Beitrags auszuwählen! - <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 Beiträge in Trends, Hashtags oder auf öffentlichen Timelines erscheinen. - <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 Beiträge von allen Fediverse-Servern, mit denen deine Instanz verbunden ist. - <b>Föderierte Timeline</b>: Sieh alle öffentlichen Posts 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>Bildbeschreibungen</b>: Sieh auf einen Blick, ob ein Bild oder ein Video einen Alternativtext hat.
- <b>Posts anpinnen</b>: Pinne deine wichtigsten Beiträge auf der Profilseite an und finde Pins von anderen Nutzern im "Angepinnt"-Tab. - <b>Posts anpinnen</b>: Pinne deine wichtigsten Posts auf deiner 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>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>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 das Bearbeiten von Beiträgen ohne eigentliche Bearbeitungsfunktion möglich gemacht hat. - <b>Löschen und Neuverfassen</b>: Die beliebte Funktion, die Bearbeiten von Posts 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! - <b>Extras</b>: Viele Anpassungen der Nutzeroberfläche, wie z. B. Interaktionssymbole in Benachrichtigungen und die Entfernung von vielen Unstimmigkeiten beim ursprünglichen Design!