Settings and other things
@@ -10,7 +10,7 @@ android {
|
|||||||
applicationId "org.joinmastodon.android"
|
applicationId "org.joinmastodon.android"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 31
|
targetSdk 31
|
||||||
versionCode 20
|
versionCode 21
|
||||||
versionName "0.1"
|
versionName "0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.joinmastodon.android;
|
|||||||
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -14,11 +13,11 @@ 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.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
@@ -26,6 +25,7 @@ import me.grishka.appkit.FragmentStackActivity;
|
|||||||
public class ExternalShareActivity extends FragmentStackActivity{
|
public class ExternalShareActivity extends FragmentStackActivity{
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||||
|
UiUtils.setUserPreferredTheme(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
public class GlobalUserPreferences{
|
||||||
|
public static boolean playGifs;
|
||||||
|
public static boolean useCustomTabs;
|
||||||
|
public static boolean trueBlackTheme;
|
||||||
|
public static ThemePreference theme;
|
||||||
|
|
||||||
|
private static SharedPreferences getPrefs(){
|
||||||
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load(){
|
||||||
|
SharedPreferences prefs=getPrefs();
|
||||||
|
playGifs=prefs.getBoolean("playGifs", true);
|
||||||
|
useCustomTabs=prefs.getBoolean("useCustomTabs", true);
|
||||||
|
trueBlackTheme=prefs.getBoolean("trueBlackTheme", false);
|
||||||
|
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save(){
|
||||||
|
getPrefs().edit()
|
||||||
|
.putBoolean("playGifs", playGifs)
|
||||||
|
.putBoolean("useCustomTabs", useCustomTabs)
|
||||||
|
.putBoolean("trueBlackTheme", trueBlackTheme)
|
||||||
|
.putInt("theme", theme.ordinal())
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ThemePreference{
|
||||||
|
AUTO,
|
||||||
|
LIGHT,
|
||||||
|
DARK
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import org.joinmastodon.android.fragments.SplashFragment;
|
|||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@@ -25,6 +26,7 @@ import me.grishka.appkit.FragmentStackActivity;
|
|||||||
public class MainActivity extends FragmentStackActivity{
|
public class MainActivity extends FragmentStackActivity{
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||||
|
UiUtils.setUserPreferredTheme(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
|
|||||||
@@ -27,5 +27,6 @@ public class MastodonApp extends Application{
|
|||||||
context=getApplicationContext();
|
context=getApplicationContext();
|
||||||
|
|
||||||
PushSubscriptionManager.tryRegisterFCM();
|
PushSubscriptionManager.tryRegisterFCM();
|
||||||
|
GlobalUserPreferences.load();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.joinmastodon.android.model.Account;
|
|||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -24,6 +25,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
public class OAuthActivity extends Activity{
|
public class OAuthActivity extends Activity{
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||||
|
UiUtils.setUserPreferredTheme(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Uri uri=getIntent().getData();
|
Uri uri=getIntent().getData();
|
||||||
if(uri==null || isTaskRoot()){
|
if(uri==null || isTaskRoot()){
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.joinmastodon.android.api;
|
package org.joinmastodon.android.api;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
@@ -14,8 +13,6 @@ import org.joinmastodon.android.BuildConfig;
|
|||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.SearchResult;
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
@@ -26,7 +23,6 @@ import java.util.EnumSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.utils.WorkerThread;
|
import me.grishka.appkit.utils.WorkerThread;
|
||||||
@@ -87,7 +83,7 @@ public class CacheController{
|
|||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}catch(SQLiteException x){
|
}catch(SQLiteException x){
|
||||||
Log.w(TAG, x);
|
Log.w(TAG, x);
|
||||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage())));
|
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500)));
|
||||||
}finally{
|
}finally{
|
||||||
closeDelayed();
|
closeDelayed();
|
||||||
}
|
}
|
||||||
@@ -145,7 +141,7 @@ public class CacheController{
|
|||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}catch(SQLiteException x){
|
}catch(SQLiteException x){
|
||||||
Log.w(TAG, x);
|
Log.w(TAG, x);
|
||||||
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage())));
|
uiHandler.post(()->callback.onError(new MastodonErrorResponse(x.getLocalizedMessage(), 500)));
|
||||||
}finally{
|
}finally{
|
||||||
closeDelayed();
|
closeDelayed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class MastodonAPIController{
|
|||||||
synchronized(req){
|
synchronized(req){
|
||||||
req.okhttpCall=null;
|
req.okhttpCall=null;
|
||||||
}
|
}
|
||||||
req.onError(e.getLocalizedMessage());
|
req.onError(e.getLocalizedMessage(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -136,7 +136,7 @@ public class MastodonAPIController{
|
|||||||
}catch(JsonIOException|JsonSyntaxException x){
|
}catch(JsonIOException|JsonSyntaxException x){
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
|
||||||
req.onError(x.getLocalizedMessage());
|
req.onError(x.getLocalizedMessage(), response.code());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ public class MastodonAPIController{
|
|||||||
}catch(IOException x){
|
}catch(IOException x){
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error post-processing or validating response", x);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error post-processing or validating response", x);
|
||||||
req.onError(x.getLocalizedMessage());
|
req.onError(x.getLocalizedMessage(), response.code());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,11 +157,11 @@ public class MastodonAPIController{
|
|||||||
try{
|
try{
|
||||||
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
|
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
|
||||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error);
|
||||||
req.onError(error.get("error").getAsString());
|
req.onError(error.get("error").getAsString(), response.code());
|
||||||
}catch(JsonIOException|JsonSyntaxException x){
|
}catch(JsonIOException|JsonSyntaxException x){
|
||||||
req.onError(response.code()+" "+response.message());
|
req.onError(response.code()+" "+response.message(), response.code());
|
||||||
}catch(IllegalStateException x){
|
}catch(IllegalStateException x){
|
||||||
req.onError("Error parsing an API error");
|
req.onError("Error parsing an API error", response.code());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ public class MastodonAPIController{
|
|||||||
}catch(Exception x){
|
}catch(Exception x){
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] error creating and sending http request", x);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] error creating and sending http request", x);
|
||||||
req.onError(x.getLocalizedMessage());
|
req.onError(x.getLocalizedMessage(), 0);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package org.joinmastodon.android.api;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.BuildConfig;
|
||||||
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.model.BaseModel;
|
import org.joinmastodon.android.model.BaseModel;
|
||||||
@@ -60,6 +60,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void cancel(){
|
public synchronized void cancel(){
|
||||||
|
if(BuildConfig.DEBUG)
|
||||||
|
Log.d(TAG, "canceling request "+this);
|
||||||
canceled=true;
|
canceled=true;
|
||||||
if(okhttpCall!=null){
|
if(okhttpCall!=null){
|
||||||
okhttpCall.cancel();
|
okhttpCall.cancel();
|
||||||
@@ -181,8 +183,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onError(String msg){
|
void onError(String msg, int httpStatus){
|
||||||
invokeErrorCallback(new MastodonErrorResponse(msg));
|
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSuccess(T resp){
|
void onSuccess(T resp){
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
|
|
||||||
public class MastodonErrorResponse extends ErrorResponse{
|
public class MastodonErrorResponse extends ErrorResponse{
|
||||||
public final String error;
|
public final String error;
|
||||||
|
public final int httpStatus;
|
||||||
|
|
||||||
public MastodonErrorResponse(String error){
|
public MastodonErrorResponse(String error, int httpStatus){
|
||||||
this.error=error;
|
this.error=error;
|
||||||
|
this.httpStatus=httpStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import android.util.Log;
|
|||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.api.requests.notifications.RegisterForPushNotifications;
|
import org.joinmastodon.android.api.requests.notifications.RegisterForPushNotifications;
|
||||||
|
import org.joinmastodon.android.api.requests.notifications.UpdatePushSettings;
|
||||||
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.model.PushNotification;
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
@@ -119,6 +120,10 @@ public class PushSubscriptionManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void registerAccountForPush(){
|
public void registerAccountForPush(){
|
||||||
|
registerAccountForPush(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerAccountForPush(PushSubscription subscription){
|
||||||
if(TextUtils.isEmpty(deviceToken))
|
if(TextUtils.isEmpty(deviceToken))
|
||||||
throw new IllegalStateException("No device push token available");
|
throw new IllegalStateException("No device push token available");
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
@@ -143,19 +148,22 @@ public class PushSubscriptionManager{
|
|||||||
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
|
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new RegisterForPushNotifications(deviceToken, encodedPublicKey, encodedAuthKey, PushSubscription.Alerts.ofAll(), accountID)
|
new RegisterForPushNotifications(deviceToken,
|
||||||
|
encodedPublicKey,
|
||||||
|
encodedAuthKey,
|
||||||
|
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
|
||||||
|
subscription==null ? PushSubscription.Policy.ALL : subscription.policy,
|
||||||
|
accountID)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(PushSubscription result){
|
public void onSuccess(PushSubscription result){
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
||||||
|
|
||||||
if(serverKey!=null){
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
session.pushServerKey=Base64.encodeToString(serverKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
|
session.pushSubscription=result;
|
||||||
AccountSessionManager.getInstance().writeAccountsFile();
|
AccountSessionManager.getInstance().writeAccountsFile();
|
||||||
Log.d(TAG, "Successfully registered "+accountID+" for push notifications");
|
Log.d(TAG, "Successfully registered "+accountID+" for push notifications");
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +176,34 @@ public class PushSubscriptionManager{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updatePushSettings(PushSubscription subscription){
|
||||||
|
new UpdatePushSettings(subscription.alerts, subscription.policy)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(PushSubscription result){
|
||||||
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
if(result.policy!=subscription.policy)
|
||||||
|
result.policy=subscription.policy;
|
||||||
|
session.pushSubscription=result;
|
||||||
|
session.needUpdatePushSettings=false;
|
||||||
|
AccountSessionManager.getInstance().writeAccountsFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
if(((MastodonErrorResponse)error).httpStatus==404){ // Not registered for push, register now
|
||||||
|
registerAccountForPush(subscription);
|
||||||
|
}else{
|
||||||
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
session.needUpdatePushSettings=true;
|
||||||
|
session.pushSubscription=subscription;
|
||||||
|
AccountSessionManager.getInstance().writeAccountsFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
private PublicKey deserializeRawPublicKey(byte[] rawBytes){
|
private PublicKey deserializeRawPublicKey(byte[] rawBytes){
|
||||||
if(rawBytes.length!=65 && rawBytes.length!=64)
|
if(rawBytes.length!=65 && rawBytes.length!=64)
|
||||||
return null;
|
return null;
|
||||||
@@ -320,8 +356,10 @@ public class PushSubscriptionManager{
|
|||||||
|
|
||||||
private static void registerAllAccountsForPush(){
|
private static void registerAllAccountsForPush(){
|
||||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||||
if(TextUtils.isEmpty(session.pushServerKey))
|
if(session.pushSubscription==null)
|
||||||
session.getPushSubscriptionManager().registerAccountForPush();
|
session.getPushSubscriptionManager().registerAccountForPush();
|
||||||
|
else if(session.needUpdatePushSettings)
|
||||||
|
session.getPushSubscriptionManager().updatePushSettings(session.pushSubscription);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
|||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
|
|
||||||
public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscription>{
|
public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscription>{
|
||||||
public RegisterForPushNotifications(String deviceToken, String encryptionKey, String authKey, PushSubscription.Alerts alerts, String accountID){
|
public RegisterForPushNotifications(String deviceToken, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy, String accountID){
|
||||||
super(HttpMethod.POST, "/push/subscription", PushSubscription.class);
|
super(HttpMethod.POST, "/push/subscription", PushSubscription.class);
|
||||||
Request r=new Request();
|
Request r=new Request();
|
||||||
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
||||||
r.data.alerts=alerts;
|
r.data.alerts=alerts;
|
||||||
|
r.data.policy=policy;
|
||||||
r.subscription.keys.p256dh=encryptionKey;
|
r.subscription.keys.p256dh=encryptionKey;
|
||||||
r.subscription.keys.auth=authKey;
|
r.subscription.keys.auth=authKey;
|
||||||
setRequestBody(r);
|
setRequestBody(r);
|
||||||
@@ -30,6 +31,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
|
|
||||||
private static class Data{
|
private static class Data{
|
||||||
public PushSubscription.Alerts alerts;
|
public PushSubscription.Alerts alerts;
|
||||||
|
public PushSubscription.Policy policy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.notifications;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
|
|
||||||
|
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
|
||||||
|
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||||
|
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
|
||||||
|
setRequestBody(new Request(alerts, policy));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Request{
|
||||||
|
public Data data=new Data();
|
||||||
|
|
||||||
|
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||||
|
this.data.alerts=alerts;
|
||||||
|
this.data.policy=policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Data{
|
||||||
|
public PushSubscription.Alerts alerts;
|
||||||
|
public PushSubscription.Policy policy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import org.joinmastodon.android.api.PushSubscriptionManager;
|
|||||||
import org.joinmastodon.android.api.StatusInteractionController;
|
import org.joinmastodon.android.api.StatusInteractionController;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
|
||||||
public class AccountSession{
|
public class AccountSession{
|
||||||
@@ -19,7 +19,8 @@ public class AccountSession{
|
|||||||
public String pushPrivateKey;
|
public String pushPrivateKey;
|
||||||
public String pushPublicKey;
|
public String pushPublicKey;
|
||||||
public String pushAuthKey;
|
public String pushAuthKey;
|
||||||
public String pushServerKey;
|
public PushSubscription pushSubscription;
|
||||||
|
public boolean needUpdatePushSettings;
|
||||||
private transient MastodonAPIController apiController;
|
private transient MastodonAPIController apiController;
|
||||||
private transient StatusInteractionController statusInteractionController;
|
private transient StatusInteractionController statusInteractionController;
|
||||||
private transient CacheController cacheController;
|
private transient CacheController cacheController;
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
@@ -592,6 +594,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
return list.getWidth();
|
return list.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean wantsOverlaySystemNavigation(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onSetFabBottomInset(int inset){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
|
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0 && wantsOverlaySystemNavigation()){
|
||||||
|
list.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||||
|
onSetFabBottomInset(insets.getSystemWindowInsetBottom());
|
||||||
|
insets=insets.inset(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||||
|
}else{
|
||||||
|
onSetFabBottomInset(0);
|
||||||
|
}
|
||||||
|
super.onApplyWindowInsets(insets);
|
||||||
|
}
|
||||||
|
|
||||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
public DisplayItemsAdapter(){
|
public DisplayItemsAdapter(){
|
||||||
|
|||||||
@@ -993,32 +993,7 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||||||
PopupMenu menu=new PopupMenu(getActivity(), v);
|
PopupMenu menu=new PopupMenu(getActivity(), v);
|
||||||
menu.inflate(R.menu.compose_visibility);
|
menu.inflate(R.menu.compose_visibility);
|
||||||
Menu m=menu.getMenu();
|
Menu m=menu.getMenu();
|
||||||
if(Build.VERSION.SDK_INT>=29){
|
UiUtils.enablePopupMenuIcons(getActivity(), menu);
|
||||||
menu.setForceShowIcon(true);
|
|
||||||
}else{
|
|
||||||
try{
|
|
||||||
Method setOptionalIconsVisible=m.getClass().getDeclaredMethod("setOptionalIconsVisible", boolean.class);
|
|
||||||
setOptionalIconsVisible.setAccessible(true);
|
|
||||||
setOptionalIconsVisible.invoke(m, true);
|
|
||||||
}catch(Exception ignore){}
|
|
||||||
}
|
|
||||||
ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary));
|
|
||||||
for(int i=0;i<m.size();i++){
|
|
||||||
MenuItem item=m.getItem(i);
|
|
||||||
Drawable icon=item.getIcon().mutate();
|
|
||||||
if(Build.VERSION.SDK_INT>=26){
|
|
||||||
item.setIconTintList(iconTint);
|
|
||||||
}else{
|
|
||||||
icon.setTintList(iconTint);
|
|
||||||
}
|
|
||||||
icon=new InsetDrawable(icon, V.dp(8), 0, 0, 0);
|
|
||||||
item.setIcon(icon);
|
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(item.getTitle());
|
|
||||||
ssb.insert(0, " ");
|
|
||||||
ssb.setSpan(new SpacerSpan(V.dp(24), 1), 0, 1, 0);
|
|
||||||
ssb.append(" ", new SpacerSpan(V.dp(8), 1), 0);
|
|
||||||
item.setTitle(ssb);
|
|
||||||
}
|
|
||||||
m.setGroupCheckable(0, true, true);
|
m.setGroupCheckable(0, true, true);
|
||||||
m.findItem(switch(statusVisibility){
|
m.findItem(switch(statusVisibility){
|
||||||
case PUBLIC, UNLISTED -> R.id.vis_public;
|
case PUBLIC, UNLISTED -> R.id.vis_public;
|
||||||
@@ -1113,6 +1088,16 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||||||
finishAutocomplete();
|
finishAutocomplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wantsLightStatusBar(){
|
||||||
|
return !UiUtils.isDarkTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wantsLightNavigationBar(){
|
||||||
|
return !UiUtils.isDarkTheme();
|
||||||
|
}
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
static class DraftMediaAttachment{
|
static class DraftMediaAttachment{
|
||||||
public Attachment serverAttachment;
|
public Attachment serverAttachment;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
@@ -13,6 +14,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HashtagTimelineFragment extends StatusListFragment{
|
public class HashtagTimelineFragment extends StatusListFragment{
|
||||||
private String hashtag;
|
private String hashtag;
|
||||||
@@ -61,4 +63,9 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
args.putString("prefilledText", '#'+hashtag+' ');
|
args.putString("prefilledText", '#'+hashtag+' ');
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSetFabBottomInset(int inset){
|
||||||
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -16,18 +18,28 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.MainActivity;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.PushNotificationReceiver;
|
import org.joinmastodon.android.PushNotificationReceiver;
|
||||||
import org.joinmastodon.android.R;
|
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.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||||
|
import org.joinmastodon.android.fragments.discover.SearchFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.TabBar;
|
import org.joinmastodon.android.ui.views.TabBar;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.fragments.LoaderFragment;
|
import me.grishka.appkit.fragments.LoaderFragment;
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
@@ -58,6 +70,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
|
||||||
|
if(savedInstanceState==null){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
homeTimelineFragment=new HomeTimelineFragment();
|
homeTimelineFragment=new HomeTimelineFragment();
|
||||||
@@ -73,6 +86,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
args.putBoolean("noAutoLoad", true);
|
args.putBoolean("noAutoLoad", true);
|
||||||
profileFragment=new ProfileFragment();
|
profileFragment=new ProfileFragment();
|
||||||
profileFragment.setArguments(args);
|
profileFragment.setArguments(args);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +102,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
|
|
||||||
inflater.inflate(R.layout.tab_bar, content);
|
inflater.inflate(R.layout.tab_bar, content);
|
||||||
tabBar=content.findViewById(R.id.tabbar);
|
tabBar=content.findViewById(R.id.tabbar);
|
||||||
tabBar.setListener(this::onTabSelected);
|
tabBar.setListeners(this::onTabSelected, this::onTabLongClick);
|
||||||
tabBarWrap=content.findViewById(R.id.tabbar_wrap);
|
tabBarWrap=content.findViewById(R.id.tabbar_wrap);
|
||||||
|
|
||||||
tabBarAvatar=tabBar.findViewById(R.id.tab_profile_ava);
|
tabBarAvatar=tabBar.findViewById(R.id.tab_profile_ava);
|
||||||
@@ -123,12 +137,32 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
tabBar.selectTab(currentTab);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewStateRestored(Bundle savedInstanceState){
|
||||||
|
super.onViewStateRestored(savedInstanceState);
|
||||||
|
if(savedInstanceState==null || homeTimelineFragment!=null)
|
||||||
|
return;
|
||||||
|
homeTimelineFragment=(HomeTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTimelineFragment");
|
||||||
|
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
||||||
|
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
||||||
|
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
||||||
|
currentTab=savedInstanceState.getInt("selectedTab");
|
||||||
|
Fragment current=fragmentForTab(currentTab);
|
||||||
|
getChildFragmentManager().beginTransaction()
|
||||||
|
.hide(homeTimelineFragment)
|
||||||
|
.hide(searchFragment)
|
||||||
|
.hide(notificationsFragment)
|
||||||
|
.hide(profileFragment)
|
||||||
|
.show(current)
|
||||||
|
.commit();
|
||||||
|
maybeTriggerLoading(current);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHiddenChanged(boolean hidden){
|
public void onHiddenChanged(boolean hidden){
|
||||||
super.onHiddenChanged(hidden);
|
super.onHiddenChanged(hidden);
|
||||||
@@ -137,12 +171,12 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean wantsLightStatusBar(){
|
public boolean wantsLightStatusBar(){
|
||||||
return currentTab!=R.id.tab_profile && (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)!=Configuration.UI_MODE_NIGHT_YES;
|
return currentTab!=R.id.tab_profile && !UiUtils.isDarkTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean wantsLightNavigationBar(){
|
public boolean wantsLightNavigationBar(){
|
||||||
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)!=Configuration.UI_MODE_NIGHT_YES;
|
return !UiUtils.isDarkTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -182,6 +216,12 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
||||||
|
maybeTriggerLoading(newFragment);
|
||||||
|
currentTab=tab;
|
||||||
|
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeTriggerLoading(Fragment newFragment){
|
||||||
if(newFragment instanceof LoaderFragment){
|
if(newFragment instanceof LoaderFragment){
|
||||||
LoaderFragment lf=(LoaderFragment) newFragment;
|
LoaderFragment lf=(LoaderFragment) newFragment;
|
||||||
if(!lf.loaded && !lf.dataLoading)
|
if(!lf.loaded && !lf.dataLoading)
|
||||||
@@ -194,8 +234,28 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
NotificationManager nm=getActivity().getSystemService(NotificationManager.class);
|
NotificationManager nm=getActivity().getSystemService(NotificationManager.class);
|
||||||
nm.cancel(accountID, PushNotificationReceiver.NOTIFICATION_ID);
|
nm.cancel(accountID, PushNotificationReceiver.NOTIFICATION_ID);
|
||||||
}
|
}
|
||||||
currentTab=tab;
|
}
|
||||||
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
|
||||||
|
private boolean onTabLongClick(@IdRes int tab){
|
||||||
|
if(tab==R.id.tab_profile){
|
||||||
|
ArrayList<String> options=new ArrayList<>();
|
||||||
|
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||||
|
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
||||||
|
}
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setItems(options.toArray(new String[0]), (dialog, which)->{
|
||||||
|
AccountSession session=AccountSessionManager.getInstance().getLoggedInAccounts().get(which);
|
||||||
|
AccountSessionManager.getInstance().setLastActiveAccountID(session.getID());
|
||||||
|
getActivity().finish();
|
||||||
|
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.add_account, (dialog, which)->{
|
||||||
|
Nav.go(getActivity(), SplashFragment.class, null);
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -206,4 +266,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
return searchFragment.onBackPressed();
|
return searchFragment.onBackPressed();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState){
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putInt("selectedTab", currentTab);
|
||||||
|
getChildFragmentManager().putFragment(outState, "homeTimelineFragment", homeTimelineFragment);
|
||||||
|
getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
||||||
|
getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||||
|
getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,42 +83,9 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
ArrayList<String> options=new ArrayList<>();
|
Bundle args=new Bundle();
|
||||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
args.putString("account", accountID);
|
||||||
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||||
}
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setItems(options.toArray(new String[0]), (dialog, which)->{
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().getLoggedInAccounts().get(which);
|
|
||||||
AccountSessionManager.getInstance().setLastActiveAccountID(session.getID());
|
|
||||||
getActivity().finish();
|
|
||||||
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
|
|
||||||
})
|
|
||||||
.setPositiveButton(R.string.log_out, (dialog, which)->{
|
|
||||||
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){
|
|
||||||
AccountSessionManager.getInstance().removeAccount(session.getID());
|
|
||||||
getActivity().finish();
|
|
||||||
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
AccountSessionManager.getInstance().removeAccount(session.getID());
|
|
||||||
getActivity().finish();
|
|
||||||
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, false)
|
|
||||||
.execNoAuth(session.domain);
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.add_account, (dialog, which)->{
|
|
||||||
Nav.go(getActivity(), SplashFragment.class, null);
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import android.graphics.Paint;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
import android.graphics.drawable.shapes.RoundRectShape;
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewOutlineProvider;
|
import android.view.ViewOutlineProvider;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.view.WindowInsets;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.fragments.WindowInsetsAwareFragment;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||||
@@ -44,7 +47,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 ProfileAboutFragment extends Fragment{
|
public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareFragment{
|
||||||
private static final int MAX_FIELDS=4;
|
private static final int MAX_FIELDS=4;
|
||||||
|
|
||||||
public UsableRecyclerView list;
|
public UsableRecyclerView list;
|
||||||
@@ -111,6 +114,23 @@ public class ProfileAboutFragment extends Fragment{
|
|||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wantsLightStatusBar(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wantsLightNavigationBar(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private class AboutAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> implements ImageLoaderRecyclerAdapter{
|
private class AboutAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
public AboutAdapter(){
|
public AboutAdapter(){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import android.widget.RelativeLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
@@ -115,6 +116,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private String profileAccountID;
|
private String profileAccountID;
|
||||||
private boolean refreshing;
|
private boolean refreshing;
|
||||||
private View fab;
|
private View fab;
|
||||||
|
private WindowInsets childInsets;
|
||||||
|
|
||||||
public ProfileFragment(){
|
public ProfileFragment(){
|
||||||
super(R.layout.loader_fragment_overlay_toolbar);
|
super(R.layout.loader_fragment_overlay_toolbar);
|
||||||
@@ -365,16 +367,35 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@Override
|
@Override
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
statusBarHeight=insets.getSystemWindowInsetTop();
|
statusBarHeight=insets.getSystemWindowInsetTop();
|
||||||
((ViewGroup.MarginLayoutParams)getToolbar().getLayoutParams()).topMargin=statusBarHeight;
|
if(contentView!=null){
|
||||||
|
((ViewGroup.MarginLayoutParams) getToolbar().getLayoutParams()).topMargin=statusBarHeight;
|
||||||
refreshLayout.setProgressViewEndTarget(true, statusBarHeight+refreshLayout.getProgressCircleDiameter()+V.dp(24));
|
refreshLayout.setProgressViewEndTarget(true, statusBarHeight+refreshLayout.getProgressCircleDiameter()+V.dp(24));
|
||||||
|
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||||
|
int insetBottom=insets.getSystemWindowInsetBottom();
|
||||||
|
childInsets=insets.inset(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0);
|
||||||
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+insetBottom;
|
||||||
|
applyChildWindowInsets();
|
||||||
|
insets=insets.inset(0, 0, 0, insetBottom);
|
||||||
|
}else{
|
||||||
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24);
|
||||||
|
}
|
||||||
|
}
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyChildWindowInsets(){
|
||||||
|
if(postsFragment!=null && postsFragment.isAdded() && childInsets!=null){
|
||||||
|
postsFragment.onApplyWindowInsets(childInsets);
|
||||||
|
postsWithRepliesFragment.onApplyWindowInsets(childInsets);
|
||||||
|
mediaFragment.onApplyWindowInsets(childInsets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void bindHeaderView(){
|
private void bindHeaderView(){
|
||||||
setTitle(account.displayName);
|
setTitle(account.displayName);
|
||||||
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, account.statusesCount, account.statusesCount));
|
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, account.statusesCount, account.statusesCount));
|
||||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(account.avatar, V.dp(100), V.dp(100)));
|
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
||||||
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(account.header, 1000, 1000));
|
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
||||||
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
||||||
name.setText(ssb);
|
name.setText(ssb);
|
||||||
@@ -790,8 +811,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||||
Fragment fragment=getFragmentForPage(position);
|
Fragment fragment=getFragmentForPage(position);
|
||||||
if(!fragment.isAdded())
|
if(!fragment.isAdded()){
|
||||||
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), getFragmentForPage(position)).commit();
|
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
|
||||||
|
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw(){
|
||||||
|
if(fragment.isAdded()){
|
||||||
|
holder.itemView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
|
applyChildWindowInsets();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,612 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.BuildConfig;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.MainActivity;
|
||||||
|
import org.joinmastodon.android.MastodonApp;
|
||||||
|
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.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
|
import me.grishka.appkit.imageloader.ImageCache;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class SettingsFragment extends ToolbarFragment{
|
||||||
|
private UsableRecyclerView list;
|
||||||
|
private ArrayList<Item> items=new ArrayList<>();
|
||||||
|
private ThemeItem themeItem;
|
||||||
|
private NotificationPolicyItem notificationPolicyItem;
|
||||||
|
private String accountID;
|
||||||
|
private boolean needUpdateNotificationSettings;
|
||||||
|
private PushSubscription pushSubscription;
|
||||||
|
|
||||||
|
private ImageView themeTransitionWindowView;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
items.add(new HeaderItem(R.string.settings_theme));
|
||||||
|
items.add(themeItem=new ThemeItem());
|
||||||
|
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
|
||||||
|
|
||||||
|
items.add(new 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 HeaderItem(R.string.settings_notifications));
|
||||||
|
items.add(notificationPolicyItem=new NotificationPolicyItem());
|
||||||
|
PushSubscription pushSubscription=getPushSubscription();
|
||||||
|
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)));
|
||||||
|
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)));
|
||||||
|
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)));
|
||||||
|
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_at_symbol, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked)));
|
||||||
|
|
||||||
|
items.add(new HeaderItem(R.string.settings_boring));
|
||||||
|
items.add(new TextItem(R.string.settings_account, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit")));
|
||||||
|
items.add(new TextItem(R.string.settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/mastodon/mastodon-android")));
|
||||||
|
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
|
||||||
|
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
|
||||||
|
|
||||||
|
items.add(new RedHeaderItem(R.string.settings_spicy));
|
||||||
|
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
||||||
|
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
||||||
|
|
||||||
|
items.add(new FooterItem(getString(R.string.settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity){
|
||||||
|
super.onAttach(activity);
|
||||||
|
if(themeTransitionWindowView!=null){
|
||||||
|
// Activity has finished recreating. Remove the overlay.
|
||||||
|
MastodonApp.context.getSystemService(WindowManager.class).removeView(themeTransitionWindowView);
|
||||||
|
themeTransitionWindowView=null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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()>0)
|
||||||
|
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 onDestroy(){
|
||||||
|
super.onDestroy();
|
||||||
|
if(needUpdateNotificationSettings){
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onThemePreferenceClick(GlobalUserPreferences.ThemePreference theme){
|
||||||
|
GlobalUserPreferences.theme=theme;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
restartActivityToApplyNewTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTrueBlackThemeChanged(SwitchItem item){
|
||||||
|
GlobalUserPreferences.trueBlackTheme=item.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
|
||||||
|
RecyclerView.ViewHolder themeHolder=list.findViewHolderForAdapterPosition(items.indexOf(themeItem));
|
||||||
|
if(themeHolder!=null){
|
||||||
|
((ThemeViewHolder)themeHolder).bindSubitems();
|
||||||
|
}else{
|
||||||
|
list.getAdapter().notifyItemChanged(items.indexOf(themeItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(UiUtils.isDarkTheme()){
|
||||||
|
restartActivityToApplyNewTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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_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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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 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=subscription.alerts.poll=enabled;
|
||||||
|
}
|
||||||
|
needUpdateNotificationSettings=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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){
|
||||||
|
((NotificationPolicyViewHolder)policyHolder).rebind();
|
||||||
|
}else{
|
||||||
|
list.getAdapter().notifyItemChanged(index);
|
||||||
|
}
|
||||||
|
if((prevPolicy==PushSubscription.Policy.NONE)!=(policy==PushSubscription.Policy.NONE)){
|
||||||
|
index++;
|
||||||
|
while(items.get(index) instanceof SwitchItem){
|
||||||
|
SwitchItem si=(SwitchItem) items.get(index);
|
||||||
|
si.enabled=si.checked=policy!=PushSubscription.Policy.NONE;
|
||||||
|
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(index);
|
||||||
|
if(holder!=null)
|
||||||
|
((BindableViewHolder<?>)holder).rebind();
|
||||||
|
else
|
||||||
|
list.getAdapter().notifyItemChanged(index);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
needUpdateNotificationSettings=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(){
|
||||||
|
AccountSessionManager.getInstance().removeAccount(accountID);
|
||||||
|
Intent intent=new Intent(getActivity(), MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearImageCache(){
|
||||||
|
MastodonAPIController.runInBackground(()->{
|
||||||
|
Activity activity=getActivity();
|
||||||
|
ImageCache.getInstance(getActivity()).clear();
|
||||||
|
Toast.makeText(activity, R.string.media_cache_cleared, Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class Item{
|
||||||
|
public abstract int getViewType();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeaderItem extends Item{
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public HeaderItem(@StringRes int text){
|
||||||
|
this.text=getString(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewType(){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SwitchItem extends Item{
|
||||||
|
private String text;
|
||||||
|
private int icon;
|
||||||
|
private 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, 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 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ThemeItem extends Item{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewType(){
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NotificationPolicyItem extends Item{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewType(){
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TextItem extends Item{
|
||||||
|
private String text;
|
||||||
|
private Runnable onClick;
|
||||||
|
|
||||||
|
public TextItem(@StringRes int text, Runnable onClick){
|
||||||
|
this.text=getString(text);
|
||||||
|
this.onClick=onClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewType(){
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RedHeaderItem extends HeaderItem{
|
||||||
|
|
||||||
|
public RedHeaderItem(int text){
|
||||||
|
super(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewType(){
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FooterItem extends Item{
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public FooterItem(String text){
|
||||||
|
this.text=text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewType(){
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(viewType){
|
||||||
|
case 0 -> new HeaderViewHolder(false);
|
||||||
|
case 1 -> new SwitchViewHolder();
|
||||||
|
case 2 -> new ThemeViewHolder();
|
||||||
|
case 3 -> new NotificationPolicyViewHolder();
|
||||||
|
case 4 -> new TextViewHolder();
|
||||||
|
case 5 -> new HeaderViewHolder(true);
|
||||||
|
case 6 -> new FooterViewHolder();
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: "+viewType);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@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(boolean red){
|
||||||
|
super(getActivity(), R.layout.item_settings_header, list);
|
||||||
|
text=(TextView) itemView;
|
||||||
|
if(red)
|
||||||
|
text.setTextColor(getResources().getColor(R.color.error_700));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(HeaderItem item){
|
||||||
|
text.setText(item.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ThemeViewHolder extends BindableViewHolder<ThemeItem>{
|
||||||
|
private SubitemHolder autoHolder, lightHolder, darkHolder;
|
||||||
|
|
||||||
|
public ThemeViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_settings_theme, list);
|
||||||
|
autoHolder=new SubitemHolder(findViewById(R.id.theme_auto));
|
||||||
|
lightHolder=new SubitemHolder(findViewById(R.id.theme_light));
|
||||||
|
darkHolder=new SubitemHolder(findViewById(R.id.theme_dark));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(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, 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.setSelected(checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChecked(boolean checked){
|
||||||
|
checkbox.setSelected(checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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 class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
||||||
|
private final TextView text;
|
||||||
|
public TextViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_settings_text, list);
|
||||||
|
text=(TextView) itemView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(TextItem item){
|
||||||
|
text.setText(item.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(){
|
||||||
|
item.onClick.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FooterViewHolder extends BindableViewHolder<FooterItem>{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ public class AccountActivationFragment extends AppKitFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean wantsLightStatusBar(){
|
public boolean wantsLightStatusBar(){
|
||||||
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)!=Configuration.UI_MODE_NIGHT_YES;
|
return !UiUtils.isDarkTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -281,4 +281,9 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
if(ev.reportAccountID.equals(reportAccount.id))
|
if(ev.reportAccountID.equals(reportAccount.id))
|
||||||
Nav.finish(this);
|
Nav.finish(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean wantsOverlaySystemNavigation(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
package org.joinmastodon.android.model;
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
@AllFieldsAreRequired
|
@AllFieldsAreRequired
|
||||||
public class PushSubscription extends BaseModel{
|
public class PushSubscription extends BaseModel implements Cloneable{
|
||||||
public int id;
|
public int id;
|
||||||
public String endpoint;
|
public String endpoint;
|
||||||
public Alerts alerts;
|
public Alerts alerts;
|
||||||
public String serverKey;
|
public String serverKey;
|
||||||
|
public Policy policy=Policy.ALL;
|
||||||
|
|
||||||
|
public PushSubscription(){}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
@@ -19,7 +26,18 @@ public class PushSubscription extends BaseModel{
|
|||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Alerts{
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public PushSubscription clone(){
|
||||||
|
PushSubscription copy=null;
|
||||||
|
try{
|
||||||
|
copy=(PushSubscription) super.clone();
|
||||||
|
}catch(CloneNotSupportedException ignore){}
|
||||||
|
copy.alerts=alerts.clone();
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Alerts implements Cloneable{
|
||||||
public boolean follow;
|
public boolean follow;
|
||||||
public boolean favourite;
|
public boolean favourite;
|
||||||
public boolean reblog;
|
public boolean reblog;
|
||||||
@@ -42,5 +60,26 @@ public class PushSubscription extends BaseModel{
|
|||||||
", poll="+poll+
|
", poll="+poll+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Alerts clone(){
|
||||||
|
try{
|
||||||
|
return (Alerts) super.clone();
|
||||||
|
}catch(CloneNotSupportedException e){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Policy{
|
||||||
|
@SerializedName("all")
|
||||||
|
ALL,
|
||||||
|
@SerializedName("followed")
|
||||||
|
FOLLOWED,
|
||||||
|
@SerializedName("follower")
|
||||||
|
FOLLOWER,
|
||||||
|
@SerializedName("none")
|
||||||
|
NONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
@@ -52,7 +53,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
this.user=user;
|
this.user=user;
|
||||||
this.createdAt=createdAt;
|
this.createdAt=createdAt;
|
||||||
avaRequest=new UrlImageLoaderRequest(user.avatar, V.dp(50), V.dp(50));
|
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic, V.dp(50), V.dp(50));
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
parsedName=new SpannableStringBuilder(user.displayName);
|
parsedName=new SpannableStringBuilder(user.displayName);
|
||||||
this.status=status;
|
this.status=status;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.graphics.Rect;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.style.ReplacementSpan;
|
import android.text.style.ReplacementSpan;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -53,6 +54,6 @@ public class CustomEmojiSpan extends ReplacementSpan{
|
|||||||
|
|
||||||
public UrlImageLoaderRequest createImageLoaderRequest(){
|
public UrlImageLoaderRequest createImageLoaderRequest(){
|
||||||
int size=V.dp(20);
|
int size=V.dp(20);
|
||||||
return new UrlImageLoaderRequest(emoji.url, size, size);
|
return new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? emoji.url : emoji.staticUrl, size, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,34 @@ package org.joinmastodon.android.ui.utils;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.InsetDrawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||||
@@ -36,8 +46,10 @@ import org.joinmastodon.android.model.Relationship;
|
|||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||||
|
import org.joinmastodon.android.ui.text.SpacerSpan;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
@@ -69,11 +81,14 @@ public class UiUtils{
|
|||||||
private UiUtils(){}
|
private UiUtils(){}
|
||||||
|
|
||||||
public static void launchWebBrowser(Context context, String url){
|
public static void launchWebBrowser(Context context, String url){
|
||||||
// TODO setting for custom tabs
|
if(GlobalUserPreferences.useCustomTabs){
|
||||||
new CustomTabsIntent.Builder()
|
new CustomTabsIntent.Builder()
|
||||||
.setShowTitle(true)
|
.setShowTitle(true)
|
||||||
.build()
|
.build()
|
||||||
.launchUrl(context, Uri.parse(url));
|
.launchUrl(context, Uri.parse(url));
|
||||||
|
}else{
|
||||||
|
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatRelativeTimestamp(Context context, Instant instant){
|
public static String formatRelativeTimestamp(Context context, Instant instant){
|
||||||
@@ -374,4 +389,48 @@ public class UiUtils{
|
|||||||
d.draw(new Canvas(bitmap));
|
d.draw(new Canvas(bitmap));
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void enablePopupMenuIcons(Context context, PopupMenu menu){
|
||||||
|
Menu m=menu.getMenu();
|
||||||
|
if(Build.VERSION.SDK_INT>=29){
|
||||||
|
menu.setForceShowIcon(true);
|
||||||
|
}else{
|
||||||
|
try{
|
||||||
|
Method setOptionalIconsVisible=m.getClass().getDeclaredMethod("setOptionalIconsVisible", boolean.class);
|
||||||
|
setOptionalIconsVisible.setAccessible(true);
|
||||||
|
setOptionalIconsVisible.invoke(m, true);
|
||||||
|
}catch(Exception ignore){}
|
||||||
|
}
|
||||||
|
ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
||||||
|
for(int i=0;i<m.size();i++){
|
||||||
|
MenuItem item=m.getItem(i);
|
||||||
|
Drawable icon=item.getIcon().mutate();
|
||||||
|
if(Build.VERSION.SDK_INT>=26){
|
||||||
|
item.setIconTintList(iconTint);
|
||||||
|
}else{
|
||||||
|
icon.setTintList(iconTint);
|
||||||
|
}
|
||||||
|
icon=new InsetDrawable(icon, V.dp(8), 0, 0, 0);
|
||||||
|
item.setIcon(icon);
|
||||||
|
SpannableStringBuilder ssb=new SpannableStringBuilder(item.getTitle());
|
||||||
|
ssb.insert(0, " ");
|
||||||
|
ssb.setSpan(new SpacerSpan(V.dp(24), 1), 0, 1, 0);
|
||||||
|
ssb.append(" ", new SpacerSpan(V.dp(8), 1), 0);
|
||||||
|
item.setTitle(ssb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUserPreferredTheme(Context context){
|
||||||
|
context.setTheme(switch(GlobalUserPreferences.theme){
|
||||||
|
case AUTO -> GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack : R.style.Theme_Mastodon_AutoLightDark;
|
||||||
|
case LIGHT -> R.style.Theme_Mastodon_Light;
|
||||||
|
case DARK -> GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack : R.style.Theme_Mastodon_Dark;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDarkTheme(){
|
||||||
|
if(GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO)
|
||||||
|
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)==Configuration.UI_MODE_NIGHT_YES;
|
||||||
|
return GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.widget.LinearLayout;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
|
||||||
import java.util.function.IntConsumer;
|
import java.util.function.IntConsumer;
|
||||||
|
import java.util.function.IntPredicate;
|
||||||
|
|
||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -17,6 +18,7 @@ public class TabBar extends LinearLayout{
|
|||||||
@IdRes
|
@IdRes
|
||||||
private int selectedTabID;
|
private int selectedTabID;
|
||||||
private IntConsumer listener;
|
private IntConsumer listener;
|
||||||
|
private IntPredicate longClickListener;
|
||||||
|
|
||||||
public TabBar(Context context){
|
public TabBar(Context context){
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@@ -39,6 +41,7 @@ public class TabBar extends LinearLayout{
|
|||||||
child.setSelected(true);
|
child.setSelected(true);
|
||||||
}
|
}
|
||||||
child.setOnClickListener(this::onChildClick);
|
child.setOnClickListener(this::onChildClick);
|
||||||
|
child.setOnLongClickListener(this::onChildLongClick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,8 +54,13 @@ public class TabBar extends LinearLayout{
|
|||||||
selectedTabID=v.getId();
|
selectedTabID=v.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setListener(IntConsumer listener){
|
private boolean onChildLongClick(View v){
|
||||||
|
return longClickListener.test(v.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListeners(IntConsumer listener, IntPredicate longClickListener){
|
||||||
this.listener=listener;
|
this.listener=listener;
|
||||||
|
this.longClickListener=longClickListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectTab(int id){
|
public void selectTab(int id){
|
||||||
|
|||||||
6
mastodon/src/main/res/anim/fade_out_fast.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="200"
|
||||||
|
android:interpolator="@android:anim/decelerate_interpolator"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0"/>
|
||||||
BIN
mastodon/src/main/res/drawable-mdpi/theme_auto.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
mastodon/src/main/res/drawable-mdpi/theme_auto_trueblack.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
mastodon/src/main/res/drawable-mdpi/theme_dark.webp
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
mastodon/src/main/res/drawable-mdpi/theme_dark_trueblack.webp
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
mastodon/src/main/res/drawable-mdpi/theme_light.webp
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
mastodon/src/main/res/drawable-xhdpi/theme_auto.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
mastodon/src/main/res/drawable-xhdpi/theme_auto_trueblack.webp
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
mastodon/src/main/res/drawable-xhdpi/theme_dark.webp
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
mastodon/src/main/res/drawable-xhdpi/theme_dark_trueblack.webp
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
mastodon/src/main/res/drawable-xhdpi/theme_light.webp
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
mastodon/src/main/res/drawable-xxhdpi/theme_auto.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
mastodon/src/main/res/drawable-xxhdpi/theme_auto_trueblack.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
mastodon/src/main/res/drawable-xxhdpi/theme_dark.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
mastodon/src/main/res/drawable-xxhdpi/theme_dark_trueblack.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
mastodon/src/main/res/drawable-xxhdpi/theme_light.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
9
mastodon/src/main/res/drawable/bg_inline_button.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<solid android:color="?colorPollVoted"/>
|
||||||
|
<corners android:radius="4dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zm0-1.5v-17c4.694 0 8.5 3.806 8.5 8.5s-3.806 8.5-8.5 8.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M18.75 3.501c1.795 0 3.25 1.455 3.25 3.25v10.503c0 1.794-1.455 3.25-3.25 3.25H5.25c-1.795 0-3.25-1.456-3.25-3.25V6.75C2 4.955 3.455 3.5 5.25 3.5h13.5zm0 1.5H5.25c-0.966 0-1.75 0.784-1.75 1.75v10.503c0 0.966 0.784 1.75 1.75 1.75h13.5c0.966 0 1.75-0.784 1.75-1.75V6.75C20.5 5.784 19.716 5 18.75 5zM8.015 8.872c0.596 0 1.018 0.082 1.502 0.314 0.31 0.15 0.442 0.523 0.293 0.834-0.15 0.31-0.523 0.442-0.834 0.293-0.3-0.144-0.54-0.19-0.961-0.19-0.867 0-1.504 0.796-1.504 1.872 0 1.077 0.638 1.876 1.504 1.876 0.428 0 0.791-0.18 0.98-0.501L9 13.355v-0.734H8.625c-0.314 0-0.573-0.23-0.618-0.532L8 11.997c0-0.314 0.232-0.574 0.533-0.619l0.092-0.006h1.002c0.314 0 0.573 0.23 0.618 0.532l0.007 0.093-0.002 1.547-0.006 0.056-0.021 0.09-0.02 0.055c-0.377 0.89-1.241 1.376-2.188 1.376-1.626 0-2.754-1.413-2.754-3.126 0-1.713 1.127-3.123 2.754-3.123zm4.614 0.122c0.314 0 0.574 0.232 0.618 0.533l0.007 0.092v4.763c0 0.345-0.28 0.625-0.625 0.625-0.314 0-0.574-0.232-0.618-0.533l-0.007-0.092V9.619c0-0.345 0.28-0.625 0.625-0.625zm2.996 0l1.997 0.007c0.345 0.002 0.624 0.282 0.623 0.627-0.001 0.314-0.233 0.573-0.535 0.617l-0.092 0.006-1.371-0.005V12h1.123c0.314 0 0.574 0.232 0.618 0.534l0.007 0.092c0 0.314-0.231 0.573-0.533 0.618l-0.092 0.007-1.123-0.001v1.115c0 0.314-0.23 0.574-0.532 0.619l-0.092 0.006c-0.314 0-0.574-0.23-0.619-0.532l-0.006-0.093V9.617c0-0.314 0.233-0.573 0.534-0.616l0.093-0.007z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M9.25 7C9.664 7 10 7.336 10 7.75c0 0.377-0.277 0.688-0.64 0.742L9.25 8.5H7c-1.933 0-3.5 1.567-3.5 3.5 0 1.864 1.457 3.388 3.294 3.494L7 15.5h2.25c0.414 0 0.75 0.336 0.75 0.75 0 0.377-0.277 0.688-0.64 0.742L9.25 17H7c-2.761 0-5-2.239-5-5 0-2.678 2.105-4.864 4.75-4.994L7 7h2.25zM17 7c2.761 0 5 2.239 5 5 0 2.678-2.105 4.864-4.75 4.994L17 17h-2.25C14.336 17 14 16.664 14 16.25c0-0.377 0.277-0.688 0.64-0.742l0.11-0.008H17c1.933 0 3.5-1.567 3.5-3.5 0-1.864-1.457-3.388-3.294-3.494L17 8.5h-2.25C14.336 8.5 14 8.164 14 7.75c0-0.377 0.277-0.688 0.64-0.742L14.75 7H17zM7 11.25h10c0.414 0 0.75 0.336 0.75 0.75 0 0.38-0.282 0.694-0.648 0.743L17 12.75H7c-0.414 0-0.75-0.336-0.75-0.75 0-0.38 0.282-0.694 0.648-0.743L7 11.25h10H7z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="25dp" android:height="24dp" android:viewportWidth="25" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M17.916 12c3.038 0 5.5 2.463 5.5 5.5 0 3.038-2.462 5.5-5.5 5.5-3.037 0-5.5-2.462-5.5-5.5 0-3.037 2.463-5.5 5.5-5.5zm-5.477 2c-0.297 0.463-0.537 0.966-0.709 1.5H4.669c-0.414 0-0.75 0.335-0.75 0.75v0.577c0 0.535 0.192 1.053 0.54 1.46 1.253 1.469 3.22 2.214 5.957 2.214 0.597 0 1.157-0.035 1.68-0.106 0.246 0.495 0.553 0.954 0.912 1.367-0.795 0.16-1.66 0.24-2.592 0.24-3.146 0-5.532-0.906-7.098-2.74-0.58-0.679-0.898-1.543-0.898-2.435v-0.578C2.42 15.007 3.427 14 4.669 14h7.77zm5.477 0l-0.09 0.008c-0.204 0.037-0.364 0.198-0.402 0.402l-0.008 0.09V17H14.92l-0.09 0.008c-0.204 0.037-0.365 0.198-0.402 0.402L14.42 17.5l0.008 0.09c0.037 0.204 0.198 0.365 0.402 0.402L14.92 18h2.495l0.001 2.5 0.008 0.09c0.038 0.204 0.198 0.365 0.402 0.402L17.916 21l0.09-0.008c0.204-0.037 0.365-0.198 0.402-0.402l0.008-0.09V18h2.504l0.09-0.008c0.204-0.037 0.365-0.198 0.402-0.402l0.008-0.09-0.008-0.09c-0.037-0.204-0.198-0.365-0.402-0.402L20.92 17h-2.505l0.001-2.5-0.008-0.09c-0.037-0.204-0.198-0.365-0.402-0.402L17.916 14zm-7.5-11.995c2.762 0 5 2.239 5 5s-2.238 5-5 5c-2.761 0-5-2.239-5-5s2.239-5 5-5zm0 1.5c-1.933 0-3.5 1.567-3.5 3.5s1.567 3.5 3.5 3.5 3.5-1.567 3.5-3.5-1.567-3.5-3.5-3.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="25dp" android:height="24dp" android:viewportWidth="25" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M12.416 2c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10zm6.517 4.543L6.96 18.517c1.477 1.238 3.38 1.983 5.457 1.983 4.694 0 8.5-3.806 8.5-8.5 0-2.077-0.745-3.98-1.983-5.457zM12.416 3.5c-4.694 0-8.5 3.806-8.5 8.5 0 2.077 0.745 3.98 1.983 5.457L17.873 5.483C16.396 4.245 14.493 3.5 12.416 3.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -5,6 +5,6 @@
|
|||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:pathData="M16.7096,17.7682C19.4819,17.4391 21.8955,15.7408 22.199,14.1888C22.6769,11.7442 22.6376,8.2231 22.6376,8.2231C22.6376,3.4504 19.4929,2.0516 19.4929,2.0516C17.9073,1.3274 15.1846,1.023 12.356,1H12.2865C9.4579,1.023 6.7369,1.3274 5.1513,2.0516C5.1513,2.0516 2.0066,3.4504 2.0066,8.2231C2.0066,8.5125 2.0051,8.8169 2.0035,9.1339C1.9991,10.0135 1.9943,10.9896 2.0199,12.0083C2.1341,16.6755 2.8805,21.2752 7.2202,22.4175C9.2213,22.944 10.9392,23.0542 12.323,22.9785C14.832,22.8403 16.2406,22.0883 16.2406,22.0883L16.1577,20.2779C16.1577,20.2779 14.3648,20.8402 12.3511,20.7717C10.356,20.7037 8.2496,20.5577 7.9269,18.1221C7.8972,17.9082 7.8823,17.6794 7.8823,17.4391C7.8823,17.4391 9.8408,17.9152 12.323,18.0283C13.8407,18.0974 15.2639,17.9399 16.7096,17.7682ZM18.8747,14.3719V8.5932C18.8747,7.4121 18.5723,6.4736 17.9648,5.7792C17.3382,5.0849 16.518,4.729 15.4997,4.729C14.3212,4.729 13.4291,5.1792 12.8392,6.0799L12.2657,7.0359L11.692,6.0799C11.1023,5.1792 10.21,4.729 9.0316,4.729C8.0134,4.729 7.193,5.0849 6.5664,5.7792C5.9589,6.4736 5.6565,7.4121 5.6565,8.5932V14.3719H7.959V8.763C7.959,7.5805 8.4594,6.9806 9.4602,6.9806C10.5665,6.9806 11.1211,7.6925 11.1211,9.1001V12.1701H13.4101V9.1001C13.4101,7.6925 13.9647,6.9806 15.071,6.9806C16.0718,6.9806 16.5722,7.5805 16.5722,8.763V14.3719H18.8747Z"
|
android:pathData="M16.7096,17.7682C19.4819,17.4391 21.8955,15.7408 22.199,14.1888C22.6769,11.7442 22.6376,8.2231 22.6376,8.2231C22.6376,3.4504 19.4929,2.0516 19.4929,2.0516C17.9073,1.3274 15.1846,1.023 12.356,1H12.2865C9.4579,1.023 6.7369,1.3274 5.1513,2.0516C5.1513,2.0516 2.0066,3.4504 2.0066,8.2231C2.0066,8.5125 2.0051,8.8169 2.0035,9.1339C1.9991,10.0135 1.9943,10.9896 2.0199,12.0083C2.1341,16.6755 2.8805,21.2752 7.2202,22.4175C9.2213,22.944 10.9392,23.0542 12.323,22.9785C14.832,22.8403 16.2406,22.0883 16.2406,22.0883L16.1577,20.2779C16.1577,20.2779 14.3648,20.8402 12.3511,20.7717C10.356,20.7037 8.2496,20.5577 7.9269,18.1221C7.8972,17.9082 7.8823,17.6794 7.8823,17.4391C7.8823,17.4391 9.8408,17.9152 12.323,18.0283C13.8407,18.0974 15.2639,17.9399 16.7096,17.7682ZM18.8747,14.3719V8.5932C18.8747,7.4121 18.5723,6.4736 17.9648,5.7792C17.3382,5.0849 16.518,4.729 15.4997,4.729C14.3212,4.729 13.4291,5.1792 12.8392,6.0799L12.2657,7.0359L11.692,6.0799C11.1023,5.1792 10.21,4.729 9.0316,4.729C8.0134,4.729 7.193,5.0849 6.5664,5.7792C5.9589,6.4736 5.6565,7.4121 5.6565,8.5932V14.3719H7.959V8.763C7.959,7.5805 8.4594,6.9806 9.4602,6.9806C10.5665,6.9806 11.1211,7.6925 11.1211,9.1001V12.1701H13.4101V9.1001C13.4101,7.6925 13.9647,6.9806 15.071,6.9806C16.0718,6.9806 16.5722,7.5805 16.5722,8.763V14.3719H18.8747Z"
|
||||||
android:fillColor="#000000"
|
android:fillColor="#fff"
|
||||||
android:fillType="evenOdd"/>
|
android:fillType="evenOdd"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
9
mastodon/src/main/res/layout/item_settings_footer.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:gravity="center"
|
||||||
|
tools:text="Visual appearance"/>
|
||||||
10
mastodon/src/main/res/layout/item_settings_header.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:textColor="?android:colorAccent"
|
||||||
|
android:padding="16dp"
|
||||||
|
tools:text="Visual appearance"/>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?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="32dp"
|
||||||
|
android:layoutDirection="locale"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textSize="20dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:text="@string/notify_me_when"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:background="@drawable/bg_inline_button"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textSize="20dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:paddingLeft="4dp"
|
||||||
|
android:paddingRight="4dp"
|
||||||
|
android:stateListAnimator="@null"
|
||||||
|
android:elevation="0dp"
|
||||||
|
tools:text="@string/notify_followed"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
39
mastodon/src/main/res/layout/item_settings_switch.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?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="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_star_24_regular"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="@string/theme_true_black"/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:clickable="false"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
13
mastodon/src/main/res/layout/item_settings_text.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textSize="16dp"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="daffdsa"/>
|
||||||
28
mastodon/src/main/res/layout/item_settings_theme.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
<include layout="@layout/item_settings_theme_subitem" android:id="@+id/theme_auto"/>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<include layout="@layout/item_settings_theme_subitem" android:id="@+id/theme_light"/>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<include layout="@layout/item_settings_theme_subitem" android:id="@+id/theme_dark"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
36
mastodon/src/main/res/layout/item_settings_theme_subitem.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?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="vertical"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="121dp"
|
||||||
|
android:elevation="1dp"
|
||||||
|
android:foreground="?android:selectableItemBackground"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
tools:src="@drawable/theme_auto"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:textAppearance="@style/m3_body_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="@string/theme_auto"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/checkbox"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:src="@drawable/ic_round_checkbox"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
7
mastodon/src/main/res/menu/notification_policy.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@+id/notify_anyone" android:title="@string/notify_anyone" android:icon="@drawable/ic_fluent_earth_24_filled"/>
|
||||||
|
<item android:id="@+id/notify_follower" android:title="@string/notify_follower" android:icon="@drawable/ic_fluent_people_checkmark_24_regular"/>
|
||||||
|
<item android:id="@+id/notify_followed" android:title="@string/notify_followed" android:icon="@drawable/ic_fluent_people_checkmark_24_regular"/>
|
||||||
|
<item android:id="@+id/notify_none" android:title="@string/notify_none" android:icon="@drawable/ic_fluent_prohibited_24_regular"/>
|
||||||
|
</menu>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="Theme.Mastodon.AutoLightDark" parent="Theme.Mastodon.Dark"/>
|
<style name="Theme.Mastodon.AutoLightDark" parent="Theme.Mastodon.Dark"/>
|
||||||
|
<style name="Theme.Mastodon.AutoLightDark.TrueBlack" parent="Theme.Mastodon.Dark.TrueBlack"/>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
<string name="notification_channel_audio_player">Audio playback</string>
|
<string name="notification_channel_audio_player">Audio playback</string>
|
||||||
<string name="play">Play</string>
|
<string name="play">Play</string>
|
||||||
<string name="pause">Pause</string>
|
<string name="pause">Pause</string>
|
||||||
<string name="log_out">Log out</string>
|
<string name="log_out">Sign out</string>
|
||||||
<string name="add_account">Add account</string>
|
<string name="add_account">Add account</string>
|
||||||
<string name="search_hint">Search</string>
|
<string name="search_hint">Search</string>
|
||||||
<string name="hashtags">Hashtags</string>
|
<string name="hashtags">Hashtags</string>
|
||||||
@@ -236,4 +236,32 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="media_attachment_unsupported_type">File %s is of an unsupported type</string>
|
<string name="media_attachment_unsupported_type">File %s is of an unsupported type</string>
|
||||||
<string name="media_attachment_too_big">File %1$s exceeds the size limit of %2$s MB</string>
|
<string name="media_attachment_too_big">File %1$s exceeds the size limit of %2$s MB</string>
|
||||||
|
<string name="settings_theme">Visual appearance</string>
|
||||||
|
<string name="theme_auto">Automatic</string>
|
||||||
|
<string name="theme_light">Light</string>
|
||||||
|
<string name="theme_dark">Dark</string>
|
||||||
|
<string name="theme_true_black">True black mode</string>
|
||||||
|
<string name="settings_behavior">Behavior</string>
|
||||||
|
<string name="settings_gif">Play animated avatars and emoji</string>
|
||||||
|
<string name="settings_custom_tabs">Use in-app browser</string>
|
||||||
|
<string name="settings_notifications">Notifications</string>
|
||||||
|
<string name="notify_me_when">Notify me when</string>
|
||||||
|
<string name="notify_anyone">anyone</string>
|
||||||
|
<string name="notify_follower">a follower</string>
|
||||||
|
<string name="notify_followed">someone I follow</string>
|
||||||
|
<string name="notify_none">no one</string>
|
||||||
|
<string name="notify_favorites">Favorites my post</string>
|
||||||
|
<string name="notify_follow">Follows me</string>
|
||||||
|
<string name="notify_reblog">Reblogs my post</string>
|
||||||
|
<string name="notify_mention">Mentions me</string>
|
||||||
|
<string name="settings_boring">The boring zone</string>
|
||||||
|
<string name="settings_account">Account settings</string>
|
||||||
|
<string name="settings_contribute">Contribute to Mastodon</string>
|
||||||
|
<string name="settings_tos">Terms of service</string>
|
||||||
|
<string name="settings_privacy_policy">Privacy policy</string>
|
||||||
|
<string name="settings_spicy">The spicy zone</string>
|
||||||
|
<string name="settings_clear_cache">Clear media cache</string>
|
||||||
|
<string name="settings_app_version">Mastodon for Android v%1$s (%2$d)</string>
|
||||||
|
<string name="media_cache_cleared">Media cache cleared</string>
|
||||||
|
<string name="confirm_log_out">Are you sure you want to sign out?</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -92,7 +92,12 @@
|
|||||||
<item name="android:actionOverflowMenuStyle">@style/Widget.Mastodon.PopupMenu</item>
|
<item name="android:actionOverflowMenuStyle">@style/Widget.Mastodon.PopupMenu</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Mastodon.Dark.TrueBlack">
|
||||||
|
<item name="colorWindowBackground">#000</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Mastodon.AutoLightDark" parent="Theme.Mastodon.Light"/>
|
<style name="Theme.Mastodon.AutoLightDark" parent="Theme.Mastodon.Light"/>
|
||||||
|
<style name="Theme.Mastodon.AutoLightDark.TrueBlack" parent="Theme.Mastodon.Light"/>
|
||||||
|
|
||||||
<style name="Theme.Mastodon.Toolbar" parent="android:ThemeOverlay.Material.ActionBar">
|
<style name="Theme.Mastodon.Toolbar" parent="android:ThemeOverlay.Material.ActionBar">
|
||||||
<item name="android:colorPrimary">@color/gray_50</item>
|
<item name="android:colorPrimary">@color/gray_50</item>
|
||||||
@@ -282,4 +287,8 @@
|
|||||||
<item name="android:textColor">?android:textColorPrimary</item>
|
<item name="android:textColor">?android:textColorPrimary</item>
|
||||||
<item name="android:lineSpacingExtra">3dp</item>
|
<item name="android:lineSpacingExtra">3dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="window_fade_out">
|
||||||
|
<item name="android:windowExitAnimation">@anim/fade_out_fast</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||