Merge branch 'donations'
This commit is contained in:
commit
ff7b6e4564
|
@ -8,11 +8,11 @@ android {
|
|||
generateLocaleConfig = true
|
||||
}
|
||||
|
||||
compileSdk 33
|
||||
compileSdk 34
|
||||
defaultConfig {
|
||||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
targetSdk 34
|
||||
versionCode 108
|
||||
versionName "2.5.6"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
@ -101,7 +101,7 @@ dependencies {
|
|||
annotationProcessor 'org.parceler:parceler:1.1.12'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||
|
||||
def appCenterSdkVersion = "4.4.2"
|
||||
def appCenterSdkVersion = "5.0.4"
|
||||
appcenterPrivateBetaImplementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
|
||||
appcenterPrivateBetaImplementation "com.microsoft.appcenter:appcenter-distribute:${appCenterSdkVersion}"
|
||||
appcenterPublicBetaImplementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||
|
@ -79,6 +80,7 @@
|
|||
<data android:mimeType="*/*"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".DonationFragmentActivity" android:exported="false" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize"/>
|
||||
|
||||
<service android:name=".AudioPlayerService" android:foregroundServiceType="mediaPlayback"/>
|
||||
<service android:name=".NotificationActionHandlerService" android:exported="false"/>
|
||||
|
|
|
@ -88,8 +88,13 @@ public class AudioPlayerService extends Service{
|
|||
nm=getSystemService(NotificationManager.class);
|
||||
// registerReceiver(receiver, new IntentFilter(Intent.ACTION_MEDIA_BUTTON));
|
||||
registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_PLAY_PAUSE));
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_STOP));
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_PLAY_PAUSE), RECEIVER_EXPORTED);
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_STOP), RECEIVER_EXPORTED);
|
||||
}else{
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_PLAY_PAUSE));
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_STOP));
|
||||
}
|
||||
instance=this;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package org.joinmastodon.android;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.fragments.DonationWebViewFragment;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
|
||||
// This exists because our designer wanted to avoid extra sheet showing/hiding animations.
|
||||
// This is the only way to show a fragment on top of a sheet without having to rewrite way too many things.
|
||||
public class DonationFragmentActivity extends FragmentStackActivity{
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
if(savedInstanceState==null){
|
||||
DonationWebViewFragment fragment=new DonationWebViewFragment();
|
||||
fragment.setArguments(getIntent().getBundleExtra("fragmentArgs"));
|
||||
showFragment(fragment);
|
||||
overridePendingTransition(R.anim.fragment_enter, R.anim.no_op_300ms);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(){
|
||||
super.finish();
|
||||
overridePendingTransition(0, R.anim.fragment_exit);
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ import org.joinmastodon.android.model.Notification;
|
|||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -45,8 +44,8 @@ import me.grishka.appkit.utils.WorkerThread;
|
|||
public class CacheController{
|
||||
private static final String TAG="CacheController";
|
||||
private static final int DB_VERSION=3;
|
||||
private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
||||
private static final Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||
public static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
||||
public static final Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||
|
||||
private final String accountID;
|
||||
private DatabaseHelper db;
|
||||
|
@ -467,9 +466,4 @@ public class CacheController{
|
|||
db.execSQL("ALTER TABLE `notifications_mentions` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface DatabaseRunnable{
|
||||
void run(SQLiteDatabase db) throws IOException;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package org.joinmastodon.android.api;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DatabaseRunnable{
|
||||
void run(SQLiteDatabase db) throws IOException;
|
||||
}
|
|
@ -12,10 +12,12 @@ import com.google.gson.JsonParser;
|
|||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.time.Instant;
|
||||
|
@ -29,6 +31,8 @@ import java.util.concurrent.TimeUnit;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.utils.WorkerThread;
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
@ -49,8 +53,11 @@ public class MastodonAPIController{
|
|||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.cache(new Cache(new File(MastodonApp.context.getCacheDir(), "http"), 10*1024*1024))
|
||||
.build();
|
||||
|
||||
private static final CacheControl NO_CACHE_WHATSOEVER=new CacheControl.Builder().noCache().noStore().build();
|
||||
|
||||
private AccountSession session;
|
||||
|
||||
static{
|
||||
|
@ -80,6 +87,9 @@ public class MastodonAPIController{
|
|||
if(token!=null)
|
||||
builder.header("Authorization", "Bearer "+token);
|
||||
|
||||
if(!req.cacheable)
|
||||
builder.cacheControl(NO_CACHE_WHATSOEVER);
|
||||
|
||||
if(req.headers!=null){
|
||||
for(Map.Entry<String, String> header:req.headers.entrySet()){
|
||||
builder.header(header.getKey(), header.getValue());
|
||||
|
|
|
@ -46,6 +46,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||
boolean canceled;
|
||||
Map<String, String> headers;
|
||||
long timeout;
|
||||
boolean cacheable;
|
||||
private ProgressDialog progressDialog;
|
||||
protected boolean removeUnsupportedItems;
|
||||
|
||||
|
@ -132,6 +133,10 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||
this.timeout=timeout;
|
||||
}
|
||||
|
||||
protected void setCacheable(){
|
||||
cacheable=true;
|
||||
}
|
||||
|
||||
protected String getPathPrefix(){
|
||||
return "/api/v1";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package org.joinmastodon.android.api.requests.catalog;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.donations.DonationCampaign;
|
||||
|
||||
public class GetDonationCampaigns extends MastodonAPIRequest<DonationCampaign>{
|
||||
private final String locale, seed, source;
|
||||
private boolean staging;
|
||||
|
||||
public GetDonationCampaigns(String locale, String seed, String source){
|
||||
super(HttpMethod.GET, null, DonationCampaign.class);
|
||||
this.locale=locale;
|
||||
this.seed=seed;
|
||||
this.source=source;
|
||||
setCacheable();
|
||||
}
|
||||
|
||||
public void setStaging(boolean staging){
|
||||
this.staging=staging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getURL(){
|
||||
Uri.Builder builder=new Uri.Builder()
|
||||
.scheme("https")
|
||||
.authority("api.joinmastodon.org")
|
||||
.path("/v1/donations/campaigns/active")
|
||||
.appendQueryParameter("platform", "android")
|
||||
.appendQueryParameter("locale", locale)
|
||||
.appendQueryParameter("seed", seed);
|
||||
if(staging)
|
||||
builder.appendQueryParameter("environment", "staging");
|
||||
if(!TextUtils.isEmpty(source))
|
||||
builder.appendQueryParameter("source", source);
|
||||
return builder.build();
|
||||
}
|
||||
}
|
|
@ -33,7 +33,8 @@ import org.joinmastodon.android.model.TimelineMarkers;
|
|||
import org.joinmastodon.android.model.Token;
|
||||
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -44,6 +45,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||
|
||||
public class AccountSession{
|
||||
private static final String TAG="AccountSession";
|
||||
private static final int MIN_DAYS_ACCOUNT_AGE_FOR_DONATIONS=28;
|
||||
|
||||
public Token token;
|
||||
public Account self;
|
||||
|
@ -276,4 +278,12 @@ public class AccountSession{
|
|||
public void setNotificationsMentionsOnly(boolean mentionsOnly){
|
||||
getRawLocalPreferences().edit().putBoolean("notificationsMentionsOnly", mentionsOnly).apply();
|
||||
}
|
||||
|
||||
public boolean isEligibleForDonations(){
|
||||
return ("mastodon.social".equalsIgnoreCase(domain) || "mastodon.online".equalsIgnoreCase(domain)) && self.createdAt.isBefore(Instant.now().minus(MIN_DAYS_ACCOUNT_AGE_FOR_DONATIONS, ChronoUnit.DAYS));
|
||||
}
|
||||
|
||||
public int getDonationSeed(){
|
||||
return Math.abs(getFullUsername().hashCode())%100;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,16 @@ package org.joinmastodon.android.api.session;
|
|||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
@ -18,11 +23,13 @@ import org.joinmastodon.android.E;
|
|||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.CacheController;
|
||||
import org.joinmastodon.android.api.DatabaseRunnable;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.filters.GetLegacyFilters;
|
||||
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
||||
|
@ -30,9 +37,10 @@ import org.joinmastodon.android.model.Account;
|
|||
import org.joinmastodon.android.model.Application;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.EmojiCategory;
|
||||
import org.joinmastodon.android.model.LegacyFilter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.LegacyFilter;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -60,6 +68,7 @@ public class AccountSessionManager{
|
|||
private static final String TAG="AccountSessionManager";
|
||||
public static final String SCOPE="read write follow push";
|
||||
public static final String REDIRECT_URI="mastodon-android-auth://callback";
|
||||
private static final int DB_VERSION=1;
|
||||
|
||||
private static final AccountSessionManager instance=new AccountSessionManager();
|
||||
|
||||
|
@ -73,6 +82,8 @@ public class AccountSessionManager{
|
|||
private String lastActiveAccountID;
|
||||
private SharedPreferences prefs;
|
||||
private boolean loadedInstances;
|
||||
private DatabaseHelper db;
|
||||
private final Runnable databaseCloseRunnable=this::closeDatabase;
|
||||
|
||||
public static AccountSessionManager getInstance(){
|
||||
return instance;
|
||||
|
@ -450,6 +461,68 @@ public class AccountSessionManager{
|
|||
}
|
||||
}
|
||||
|
||||
private void closeDelayed(){
|
||||
CacheController.databaseThread.postRunnable(databaseCloseRunnable, 10_000);
|
||||
}
|
||||
|
||||
public void closeDatabase(){
|
||||
if(db!=null){
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.d(TAG, "closeDatabase");
|
||||
db.close();
|
||||
db=null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelDelayedClose(){
|
||||
if(db!=null){
|
||||
CacheController.databaseThread.handler.removeCallbacks(databaseCloseRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
private SQLiteDatabase getOrOpenDatabase(){
|
||||
if(db==null)
|
||||
db=new DatabaseHelper();
|
||||
return db.getWritableDatabase();
|
||||
}
|
||||
|
||||
private void runOnDbThread(DatabaseRunnable r){
|
||||
cancelDelayedClose();
|
||||
CacheController.databaseThread.postRunnable(()->{
|
||||
try{
|
||||
SQLiteDatabase db=getOrOpenDatabase();
|
||||
r.run(db);
|
||||
}catch(SQLiteException|IOException x){
|
||||
Log.w(TAG, x);
|
||||
}finally{
|
||||
closeDelayed();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public void runIfDonationCampaignNotDismissed(String id, Runnable action){
|
||||
runOnDbThread(db->{
|
||||
try(Cursor cursor=db.query("dismissed_donation_campaigns", null, "id=?", new String[]{id}, null, null, null)){
|
||||
if(!cursor.moveToFirst()){
|
||||
UiUtils.runOnUiThread(action);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void markDonationCampaignAsDismissed(String id){
|
||||
runOnDbThread(db->{
|
||||
ContentValues values=new ContentValues();
|
||||
values.put("id", id);
|
||||
values.put("dismissed_at", System.currentTimeMillis());
|
||||
db.insert("dismissed_donation_campaigns", null, values);
|
||||
});
|
||||
}
|
||||
|
||||
public void clearDismissedDonationCampaigns(){
|
||||
runOnDbThread(db->db.delete("dismissed_donation_campaigns", null, null));
|
||||
}
|
||||
|
||||
private static class SessionsStorageWrapper{
|
||||
public List<AccountSession> accounts;
|
||||
}
|
||||
|
@ -459,4 +532,24 @@ public class AccountSessionManager{
|
|||
public List<Emoji> emojis;
|
||||
public long lastUpdated;
|
||||
}
|
||||
|
||||
private static class DatabaseHelper extends SQLiteOpenHelper{
|
||||
public DatabaseHelper(){
|
||||
super(MastodonApp.context, "accounts.db", null, DB_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db){
|
||||
db.execSQL("""
|
||||
CREATE TABLE `dismissed_donation_campaigns` (
|
||||
`id` text PRIMARY KEY,
|
||||
`dismissed_at` bigint
|
||||
)""");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package org.joinmastodon.android.events;
|
||||
|
||||
public class DismissDonationCampaignBannerEvent{
|
||||
public final String campaignID;
|
||||
|
||||
public DismissDonationCampaignBannerEvent(String campaignID){
|
||||
this.campaignID=campaignID;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.webkit.WebResourceRequest;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.DismissDonationCampaignBannerEvent;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
|
||||
public class DonationWebViewFragment extends WebViewFragment{
|
||||
public static final String SUCCESS_URL="https://sponsor.joinmastodon.org/donate/success";
|
||||
public static final String FAILURE_URL="https://sponsor.joinmastodon.org/donate/failure";
|
||||
public static final String CANCEL_URL="https://sponsor.joinmastodon.org/donate/cancel";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
if(BuildConfig.DEBUG){
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
webView.loadUrl(Objects.requireNonNull(getArguments().getString("url")));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldOverrideUrlLoading(WebResourceRequest req){
|
||||
String url=req.getUrl().buildUpon().clearQuery().fragment(null).build().toString();
|
||||
if(url.equalsIgnoreCase(SUCCESS_URL)){
|
||||
onSuccess();
|
||||
return true;
|
||||
}else if(url.equalsIgnoreCase(FAILURE_URL)){
|
||||
onFailure();
|
||||
return true;
|
||||
}else if(url.equalsIgnoreCase(CANCEL_URL)){
|
||||
onCancel();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
if(BuildConfig.DEBUG){
|
||||
menu.add(0, 0, 0, "Simulate success");
|
||||
menu.add(0, 1, 0, "Simulate failure");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
if(item.getItemId()==0)
|
||||
onSuccess();
|
||||
else if(item.getItemId()==1)
|
||||
onFailure();
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void onFailure(){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.donation_server_error)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setOnDismissListener(dlg->Nav.finish(this))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void onSuccess(){
|
||||
String campaignID=getArguments().getString("campaignID");
|
||||
AccountSessionManager.getInstance().markDonationCampaignAsDismissed(campaignID);
|
||||
E.post(new DismissDonationCampaignBannerEvent(campaignID));
|
||||
getActivity().setResult(Activity.RESULT_OK, new Intent().putExtra("postText", getArguments().getString("successPostText")));
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
private void onCancel(){
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
|
@ -5,15 +5,23 @@ import android.animation.AnimatorListenerAdapter;
|
|||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.Button;
|
||||
|
@ -26,14 +34,17 @@ import android.widget.Toolbar;
|
|||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetDonationCampaigns;
|
||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.DismissDonationCampaignBannerEvent;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
|
@ -41,8 +52,11 @@ import org.joinmastodon.android.model.FilterContext;
|
|||
import org.joinmastodon.android.model.FollowList;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.TimelineMarkers;
|
||||
import org.joinmastodon.android.model.donations.DonationCampaign;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.sheets.DonationSheet;
|
||||
import org.joinmastodon.android.ui.sheets.DonationSuccessfulSheet;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.viewcontrollers.HomeTimelineMenuController;
|
||||
import org.joinmastodon.android.ui.viewcontrollers.ToolbarDropdownMenuController;
|
||||
|
@ -53,6 +67,7 @@ import org.parceler.Parcels;
|
|||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -64,8 +79,11 @@ import me.grishka.appkit.api.SimpleCallback;
|
|||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
public class HomeTimelineFragment extends StatusListFragment implements ToolbarDropdownMenuController.HostFragment{
|
||||
private static final int DONATION_RESULT=211;
|
||||
|
||||
private ImageButton fab;
|
||||
private LinearLayout listsDropdown;
|
||||
private FixedAspectRatioImageView listsDropdownArrow;
|
||||
|
@ -81,9 +99,13 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
private FollowList currentList;
|
||||
private MergeRecyclerAdapter mergeAdapter;
|
||||
private DiscoverInfoBannerHelper localTimelineBannerHelper;
|
||||
private View donationBanner;
|
||||
private boolean donationBannerDismissing;
|
||||
|
||||
private String maxID;
|
||||
private String lastSavedMarkerID;
|
||||
private DonationCampaign currentDonationCampaign;
|
||||
private BottomSheet donationSheet;
|
||||
|
||||
public HomeTimelineFragment(){
|
||||
setListLayoutId(R.layout.fragment_timeline);
|
||||
|
@ -93,6 +115,32 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
localTimelineBannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE, accountID);
|
||||
|
||||
if(AccountSessionManager.get(accountID).isEligibleForDonations()){
|
||||
GetDonationCampaigns req=new GetDonationCampaigns(Locale.getDefault().toLanguageTag().replace('-', '_'), String.valueOf(AccountSessionManager.get(accountID).getDonationSeed()), null);
|
||||
if(getActivity().getSharedPreferences("debug", Context.MODE_PRIVATE).getBoolean("donationsStaging", false)){
|
||||
req.setStaging(true);
|
||||
}
|
||||
req.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(DonationCampaign result){
|
||||
if(result==null)
|
||||
return;
|
||||
AccountSessionManager.getInstance().runIfDonationCampaignNotDismissed(result.id, ()->showDonationBanner(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){}
|
||||
})
|
||||
.execNoAuth("");
|
||||
}
|
||||
E.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
E.unregister(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -233,6 +281,8 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
E.register(this);
|
||||
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
||||
}
|
||||
if(currentDonationCampaign!=null)
|
||||
showDonationBanner(currentDonationCampaign);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -587,6 +637,8 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
E.unregister(this);
|
||||
}
|
||||
donationBanner=null;
|
||||
donationBannerDismissing=false;
|
||||
}
|
||||
|
||||
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
|
||||
|
@ -599,6 +651,13 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
updateUpdateState(ev.state);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDismissDonationCampaignBanner(DismissDonationCampaignBannerEvent ev){
|
||||
if(currentDonationCampaign!=null && ev.campaignID.equals(currentDonationCampaign.id)){
|
||||
dismissDonationBanner();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||
return true;
|
||||
|
@ -653,6 +712,17 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
super.onDataLoaded(d, more);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(requestCode==DONATION_RESULT){
|
||||
if(donationSheet!=null)
|
||||
donationSheet.dismissWithoutAnimation();
|
||||
if(resultCode==Activity.RESULT_OK){
|
||||
new DonationSuccessfulSheet(getActivity(), accountID, data.getStringExtra("postText")).showWithoutAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getCurrentListTitle(){
|
||||
return switch(listMode){
|
||||
case FOLLOWING -> getString(R.string.timeline_following);
|
||||
|
@ -661,6 +731,77 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
|||
};
|
||||
}
|
||||
|
||||
private void showDonationBanner(DonationCampaign campaign){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
currentDonationCampaign=campaign;
|
||||
if(donationBanner==null){
|
||||
ViewStub stub=contentView.findViewById(R.id.donation_banner);
|
||||
donationBanner=stub.inflate();
|
||||
donationBanner.findViewById(R.id.banner_dismiss).setOnClickListener(v->{
|
||||
AccountSessionManager.getInstance().markDonationCampaignAsDismissed(currentDonationCampaign.id);
|
||||
dismissDonationBanner();
|
||||
});
|
||||
donationBanner.setOnClickListener(v->openDonationSheet());
|
||||
}else{
|
||||
donationBanner.setVisibility(View.VISIBLE);
|
||||
}
|
||||
TextView text=donationBanner.findViewById(R.id.banner_text);
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(campaign.bannerMessage);
|
||||
ssb.append(' ');
|
||||
int start=ssb.length();
|
||||
ssb.append(campaign.bannerButtonText);
|
||||
ssb.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.masterialDark_colorGoldenrodContainer, getActivity().getTheme())), start, ssb.length(), 0);
|
||||
ssb.setSpan(new UnderlineSpan(), start, ssb.length(), 0);
|
||||
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), start, ssb.length(), 0);
|
||||
text.setText(ssb);
|
||||
donationBanner.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
donationBanner.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(donationBanner, View.TRANSLATION_Y, donationBanner.getHeight(), 0),
|
||||
ObjectAnimator.ofFloat(fab, View.TRANSLATION_Y, -donationBanner.getHeight())
|
||||
);
|
||||
set.setDuration(250);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void dismissDonationBanner(){
|
||||
if(donationBanner==null || donationBannerDismissing)
|
||||
return;
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofFloat(donationBanner, View.TRANSLATION_Y, donationBanner.getHeight()),
|
||||
ObjectAnimator.ofFloat(fab, View.TRANSLATION_Y, 0)
|
||||
);
|
||||
set.setDuration(250);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
donationBanner.setVisibility(View.GONE);
|
||||
donationBannerDismissing=false;
|
||||
}
|
||||
});
|
||||
donationBannerDismissing=true;
|
||||
set.start();
|
||||
currentDonationCampaign=null;
|
||||
}
|
||||
|
||||
private void openDonationSheet(){
|
||||
donationSheet=new DonationSheet(getActivity(), currentDonationCampaign, accountID, intent->startActivityForResult(intent, DONATION_RESULT));
|
||||
donationSheet.setOnDismissListener(dialog->donationSheet=null);
|
||||
donationSheet.show();
|
||||
}
|
||||
|
||||
private enum ListMode{
|
||||
FOLLOWING,
|
||||
LOCAL,
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebResourceError;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.LoaderFragment;
|
||||
|
||||
public abstract class WebViewFragment extends LoaderFragment{
|
||||
private static final String TAG="WebViewFragment";
|
||||
|
||||
protected WebView webView;
|
||||
private Runnable backCallback=this::onGoBack;
|
||||
private boolean backCallbackSet;
|
||||
|
||||
@Override
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
webView=new WebView(getActivity());
|
||||
webView.setWebChromeClient(new WebChromeClient(){
|
||||
@Override
|
||||
public void onReceivedTitle(WebView view, String title){
|
||||
setTitle(title);
|
||||
}
|
||||
});
|
||||
webView.setWebViewClient(new WebViewClient(){
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url){
|
||||
if(BuildConfig.DEBUG){
|
||||
Log.d(TAG, "onPageFinished: "+url);
|
||||
}
|
||||
dataLoaded();
|
||||
updateBackCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error){
|
||||
onError(new MastodonErrorResponse(error.getDescription().toString(), -1, null));
|
||||
updateBackCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request){
|
||||
return WebViewFragment.this.shouldOverrideUrlLoading(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload){
|
||||
updateBackCallback();
|
||||
}
|
||||
});
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
return webView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh(){
|
||||
webView.reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolbarNavigationClick(){
|
||||
Nav.finish(this);
|
||||
}
|
||||
|
||||
private void updateBackCallback(){
|
||||
boolean canGoBack=webView.canGoBack();
|
||||
if(canGoBack!=backCallbackSet){
|
||||
if(canGoBack){
|
||||
addBackCallback(backCallback);
|
||||
backCallbackSet=true;
|
||||
}else{
|
||||
removeBackCallback(backCallback);
|
||||
backCallbackSet=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onGoBack(){
|
||||
if(webView.canGoBack())
|
||||
webView.goBack();
|
||||
}
|
||||
|
||||
protected abstract boolean shouldOverrideUrlLoading(WebResourceRequest req);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -9,6 +11,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
|||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
|
@ -18,6 +21,8 @@ import java.util.List;
|
|||
import me.grishka.appkit.Nav;
|
||||
|
||||
public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
||||
private CheckableListItem<Void> donationsStagingItem;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -28,7 +33,9 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
|||
selfUpdateItem=new ListItem<>("Force self-update", null, this::onForceSelfUpdateClick),
|
||||
resetUpdateItem=new ListItem<>("Reset self-updater", null, this::onResetUpdaterClick),
|
||||
new ListItem<>("Reset search info banners", null, this::onResetDiscoverBannersClick),
|
||||
new ListItem<>("Reset pre-reply sheets", null, this::onResetPreReplySheetsClick)
|
||||
new ListItem<>("Reset pre-reply sheets", null, this::onResetPreReplySheetsClick),
|
||||
new ListItem<>("Clear dismissed donation campaigns", null, this::onClearDismissedCampaignsClick),
|
||||
donationsStagingItem=new CheckableListItem<>("Use staging environment for donations", null, CheckableListItem.Style.SWITCH, getPrefs().getBoolean("donationsStaging", false), this::toggleCheckableItem)
|
||||
));
|
||||
if(!GithubSelfUpdater.needSelfUpdating()){
|
||||
resetUpdateItem.isEnabled=selfUpdateItem.isEnabled=false;
|
||||
|
@ -39,6 +46,12 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
|||
@Override
|
||||
protected void doLoadData(int offset, int count){}
|
||||
|
||||
@Override
|
||||
public void onStop(){
|
||||
super.onStop();
|
||||
getPrefs().edit().putBoolean("donationsStaging", donationsStagingItem.checked).apply();
|
||||
}
|
||||
|
||||
private void onTestEmailConfirmClick(ListItem<?> item){
|
||||
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
sess.activated=false;
|
||||
|
@ -70,9 +83,18 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
|||
Toast.makeText(getActivity(), "Pre-reply sheets were reset", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void onClearDismissedCampaignsClick(ListItem<?> item){
|
||||
AccountSessionManager.getInstance().clearDismissedDonationCampaigns();
|
||||
Toast.makeText(getActivity(), "Dismissed campaigns cleared. Restart app to see your current campaign, if any", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void restartUI(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
}
|
||||
|
||||
private SharedPreferences getPrefs(){
|
||||
return getActivity().getSharedPreferences("debug", Context.MODE_PRIVATE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
|
@ -12,27 +16,38 @@ import org.joinmastodon.android.BuildConfig;
|
|||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetDonationCampaigns;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.model.donations.DonationCampaign;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.sheets.DonationSheet;
|
||||
import org.joinmastodon.android.ui.sheets.DonationSuccessfulSheet;
|
||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
|
||||
public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
||||
private static final int DONATION_RESULT=433;
|
||||
|
||||
private boolean loggedOut;
|
||||
private HideableSingleViewRecyclerAdapter bannerAdapter;
|
||||
private Button updateButton1, updateButton2;
|
||||
private TextView updateText;
|
||||
private DonationSheet donationSheet;
|
||||
private Runnable updateDownloadProgressUpdater=new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
|
@ -49,21 +64,26 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||
super.onCreate(savedInstanceState);
|
||||
setTitle(R.string.settings);
|
||||
setSubtitle(AccountSessionManager.get(accountID).getFullUsername());
|
||||
onDataLoaded(List.of(
|
||||
ArrayList<ListItem<Void>> items=new ArrayList<>();
|
||||
if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.equals("appcenterPrivateBeta")){
|
||||
items.add(new ListItem<>("Debug settings", null, R.drawable.ic_settings_24px, i->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
|
||||
}
|
||||
items.addAll(List.of(
|
||||
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_settings_24px, this::onBehaviorClick),
|
||||
new ListItem<>(R.string.settings_display, 0, R.drawable.ic_style_24px, this::onDisplayClick),
|
||||
new ListItem<>(R.string.settings_privacy, 0, R.drawable.ic_privacy_tip_24px, this::onPrivacyClick),
|
||||
new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_filter_alt_24px, this::onFiltersClick),
|
||||
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_notifications_24px, this::onNotificationsClick),
|
||||
new ListItem<>(AccountSessionManager.get(accountID).domain, getString(R.string.settings_server_explanation), R.drawable.ic_dns_24px, this::onServerClick),
|
||||
new ListItem<>(getString(R.string.about_app, getString(R.string.app_name)), null, R.drawable.ic_info_24px, this::onAboutClick, null, 0, true),
|
||||
new ListItem<>(R.string.manage_accounts, 0, R.drawable.ic_switch_account_24px, this::onManageAccountsClick),
|
||||
new ListItem<>(R.string.log_out, 0, R.drawable.ic_logout_24px, this::onLogOutClick, R.attr.colorM3Error, false)
|
||||
new ListItem<>(getString(R.string.about_app, getString(R.string.app_name)), null, R.drawable.ic_info_24px, this::onAboutClick, null, 0, true)
|
||||
));
|
||||
|
||||
if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.equals("appcenterPrivateBeta")){
|
||||
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_settings_24px, i->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
|
||||
if(AccountSessionManager.get(accountID).isEligibleForDonations()){
|
||||
items.add(new ListItem<>(R.string.settings_donate, 0, R.drawable.ic_volunteer_activism_24px, this::onDonateClick));
|
||||
items.add(new ListItem<>(R.string.settings_manage_donations, 0, R.drawable.ic_settings_heart_24px, this::onManageDonationClick, 0, true));
|
||||
}
|
||||
items.add(new ListItem<>(R.string.manage_accounts, 0, R.drawable.ic_switch_account_24px, this::onManageAccountsClick));
|
||||
items.add(new ListItem<>(R.string.log_out, 0, R.drawable.ic_logout_24px, this::onLogOutClick, R.attr.colorM3Error, false));
|
||||
onDataLoaded(items);
|
||||
|
||||
AccountSession session=AccountSessionManager.get(accountID);
|
||||
session.reloadPreferences(null);
|
||||
|
@ -117,6 +137,17 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(requestCode==DONATION_RESULT){
|
||||
if(donationSheet!=null)
|
||||
donationSheet.dismissWithoutAnimation();
|
||||
if(resultCode==Activity.RESULT_OK){
|
||||
new DonationSuccessfulSheet(getActivity(), accountID, data.getStringExtra("postText")).showWithoutAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Bundle makeFragmentArgs(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
|
@ -167,6 +198,39 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||
.show();
|
||||
}
|
||||
|
||||
private void onDonateClick(ListItem<?> item){
|
||||
GetDonationCampaigns req=new GetDonationCampaigns(Locale.getDefault().toLanguageTag().replace('-', '_'), String.valueOf(AccountSessionManager.get(accountID).getDonationSeed()), null);
|
||||
if(getActivity().getSharedPreferences("debug", Context.MODE_PRIVATE).getBoolean("donationsStaging", false)){
|
||||
req.setStaging(true);
|
||||
}
|
||||
req.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(DonationCampaign result){
|
||||
Activity activity=getActivity();
|
||||
if(activity==null)
|
||||
return;
|
||||
if(result==null){
|
||||
Toast.makeText(activity, "No campaign available (server misconfiguration?)", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
donationSheet=new DonationSheet(getActivity(), result, accountID, intent->startActivityForResult(intent, DONATION_RESULT));
|
||||
donationSheet.setOnDismissListener(dialog->donationSheet=null);
|
||||
donationSheet.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, true)
|
||||
.execNoAuth("");
|
||||
}
|
||||
|
||||
private void onManageDonationClick(ListItem<?> item){
|
||||
UiUtils.launchWebBrowser(getActivity(), "https://sponsor.staging.joinmastodon.org/donate/manage");
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
||||
updateUpdateBanner();
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package org.joinmastodon.android.model.donations;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.model.BaseModel;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public class DonationCampaign extends BaseModel{
|
||||
public String id;
|
||||
public String bannerMessage;
|
||||
public String bannerButtonText;
|
||||
public String donationMessage;
|
||||
public String donationButtonText;
|
||||
public Amounts amounts;
|
||||
public String defaultCurrency;
|
||||
public String donationUrl;
|
||||
public String donationSuccessPost;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
amounts.postprocess();
|
||||
}
|
||||
|
||||
public static class Amounts extends BaseModel{
|
||||
public Map<String, long[]> oneTime;
|
||||
@RequiredField
|
||||
public Map<String, long[]> monthly;
|
||||
public Map<String, long[]> yearly;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,353 @@
|
|||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import org.joinmastodon.android.DonationFragmentActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.DonationWebViewFragment;
|
||||
import org.joinmastodon.android.model.donations.DonationCampaign;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.CurrencyAmountInput;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Currency;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.utils.CustomViewHelper;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
public class DonationSheet extends BottomSheet{
|
||||
private final DonationCampaign campaign;
|
||||
private final String accountID;
|
||||
private final Consumer<Intent> startCallback;
|
||||
private DonationFrequency frequency=DonationFrequency.MONTHLY;
|
||||
|
||||
private View onceTab, monthlyTab, yearlyTab;
|
||||
private int currentTab;
|
||||
private CurrencyAmountInput amountField;
|
||||
private ToggleButton[] suggestedAmountButtons=new ToggleButton[6];
|
||||
private View button;
|
||||
private TextView buttonText;
|
||||
private Activity activity;
|
||||
|
||||
public DonationSheet(@NonNull Activity activity, DonationCampaign campaign, String accountID, Consumer<Intent> startCallback){
|
||||
super(activity);
|
||||
this.campaign=campaign;
|
||||
this.accountID=accountID;
|
||||
this.activity=activity;
|
||||
this.startCallback=startCallback;
|
||||
Context context=activity;
|
||||
|
||||
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_donation, null);
|
||||
setContentView(content);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
|
||||
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||
|
||||
TextView text=findViewById(R.id.text);
|
||||
text.setText(campaign.donationMessage);
|
||||
|
||||
onceTab=findViewById(R.id.once);
|
||||
monthlyTab=findViewById(R.id.monthly);
|
||||
yearlyTab=findViewById(R.id.yearly);
|
||||
onceTab.setOnClickListener(this::onTabClick);
|
||||
monthlyTab.setOnClickListener(this::onTabClick);
|
||||
yearlyTab.setOnClickListener(this::onTabClick);
|
||||
|
||||
if(campaign.amounts.yearly==null)
|
||||
yearlyTab.setVisibility(View.GONE);
|
||||
if(campaign.amounts.oneTime==null)
|
||||
onceTab.setVisibility(View.GONE);
|
||||
if(campaign.amounts.monthly==null){
|
||||
monthlyTab.setVisibility(View.GONE);
|
||||
if(campaign.amounts.oneTime!=null){
|
||||
onceTab.setSelected(true);
|
||||
currentTab=R.id.once;
|
||||
frequency=DonationFrequency.ONCE;
|
||||
}else if(campaign.amounts.yearly!=null){
|
||||
yearlyTab.setSelected(true);
|
||||
currentTab=R.id.yearly;
|
||||
frequency=DonationFrequency.YEARLY;
|
||||
}else{
|
||||
Toast.makeText(context, "Amounts object is empty", Toast.LENGTH_SHORT).show();
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
monthlyTab.setSelected(true);
|
||||
currentTab=R.id.monthly;
|
||||
}
|
||||
|
||||
|
||||
View tabBarItself=findViewById(R.id.tabbar_inner);
|
||||
tabBarItself.setOutlineProvider(OutlineProviders.roundedRect(20));
|
||||
tabBarItself.setClipToOutline(true);
|
||||
|
||||
amountField=findViewById(R.id.amount);
|
||||
List<String> availableCurrencies=campaign.amounts.monthly.keySet().stream().sorted().collect(Collectors.toList());
|
||||
amountField.setCurrencies(availableCurrencies);
|
||||
try{
|
||||
amountField.setSelectedCurrency(campaign.defaultCurrency);
|
||||
}catch(IllegalArgumentException x){
|
||||
new M3AlertDialogBuilder(context)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage("Default currency "+campaign.defaultCurrency+" not in list of available currencies "+availableCurrencies)
|
||||
.show();
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
amountField.setChangeListener(new CurrencyAmountInput.ChangeListener(){
|
||||
@Override
|
||||
public void onCurrencyChanged(String code){
|
||||
updateSuggestedAmounts(code);
|
||||
button.setEnabled(amountField.getAmount()>=getMinimumChargeAmount(code));
|
||||
updateSuggestedButtonsState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAmountChanged(long amount){
|
||||
button.setEnabled(amount>=getMinimumChargeAmount(amountField.getCurrency()));
|
||||
updateSuggestedButtonsState();
|
||||
}
|
||||
});
|
||||
button=findViewById(R.id.button);
|
||||
buttonText=findViewById(R.id.button_text);
|
||||
|
||||
ViewGroup suggestedAmounts=findViewById(R.id.suggested_amounts);
|
||||
for(int i=0;i<suggestedAmountButtons.length;i++){
|
||||
ToggleButton btn=new ToggleButton(context);
|
||||
btn.setBackgroundResource(R.drawable.bg_filter_chip);
|
||||
btn.setTextAppearance(R.style.m3_label_large);
|
||||
btn.setTextColor(context.getResources().getColorStateList(R.color.filter_chip_text, context.getTheme()));
|
||||
btn.setMinWidth(V.dp(64));
|
||||
btn.setMinimumWidth(0);
|
||||
int pad=V.dp(16);
|
||||
btn.setPadding(pad, 0, pad, 0);
|
||||
btn.setStateListAnimator(null);
|
||||
btn.setTextOff(null);
|
||||
btn.setTextOn(null);
|
||||
btn.setOnClickListener(this::onSuggestedAmountClick);
|
||||
btn.setTag(i);
|
||||
suggestedAmountButtons[i]=btn;
|
||||
suggestedAmounts.addView(btn);
|
||||
}
|
||||
updateSuggestedAmounts(campaign.defaultCurrency);
|
||||
button.setEnabled(false);
|
||||
buttonText.setText(campaign.donationButtonText);
|
||||
button.setOnClickListener(v->openWebView());
|
||||
|
||||
Arrays.stream(getCurrentSuggestedAmounts(campaign.defaultCurrency)).min().ifPresent(amountField::setAmount);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
Window window=getWindow();
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
}
|
||||
|
||||
private void onTabClick(View v){
|
||||
if(v.getId()==currentTab)
|
||||
return;
|
||||
findViewById(currentTab).setSelected(false);
|
||||
v.setSelected(true);
|
||||
currentTab=v.getId();
|
||||
if(currentTab==R.id.once)
|
||||
frequency=DonationFrequency.ONCE;
|
||||
else if(currentTab==R.id.monthly)
|
||||
frequency=DonationFrequency.MONTHLY;
|
||||
else if(currentTab==R.id.yearly)
|
||||
frequency=DonationFrequency.YEARLY;
|
||||
updateSuggestedAmounts(amountField.getCurrency());
|
||||
}
|
||||
|
||||
private long[] getCurrentSuggestedAmounts(String currency){
|
||||
long[] amounts=(switch(frequency){
|
||||
case ONCE -> campaign.amounts.oneTime;
|
||||
case MONTHLY -> campaign.amounts.monthly;
|
||||
case YEARLY -> campaign.amounts.yearly;
|
||||
}).get(currency);
|
||||
if(amounts==null){
|
||||
amounts=new long[0];
|
||||
}
|
||||
return amounts;
|
||||
}
|
||||
|
||||
private void updateSuggestedAmounts(String currency){
|
||||
NumberFormat format=NumberFormat.getCurrencyInstance();
|
||||
try{
|
||||
format.setCurrency(Currency.getInstance(currency));
|
||||
}catch(IllegalArgumentException ignore){}
|
||||
int defaultFractionDigits=format.getMinimumFractionDigits();
|
||||
long[] amounts=getCurrentSuggestedAmounts(currency);
|
||||
for(int i=0;i<suggestedAmountButtons.length;i++){
|
||||
ToggleButton btn=suggestedAmountButtons[i];
|
||||
if(i>=amounts.length){
|
||||
btn.setVisibility(View.GONE);
|
||||
continue;
|
||||
}
|
||||
btn.setVisibility(View.VISIBLE);
|
||||
long amount=amounts[i];
|
||||
format.setMinimumFractionDigits(amount%100==0 ? 0 : defaultFractionDigits);
|
||||
btn.setText(format.format(amount/100.0));
|
||||
}
|
||||
updateSuggestedButtonsState();
|
||||
}
|
||||
|
||||
private void onSuggestedAmountClick(View v){
|
||||
int index=(int) v.getTag();
|
||||
long[] amounts=getCurrentSuggestedAmounts(amountField.getCurrency());
|
||||
amountField.setAmount(amounts[index]);
|
||||
}
|
||||
|
||||
private void updateSuggestedButtonsState(){
|
||||
long amount=amountField.getAmount();
|
||||
long[] amounts=getCurrentSuggestedAmounts(amountField.getCurrency());
|
||||
for(int i=0;i<Math.min(amounts.length, suggestedAmountButtons.length);i++){
|
||||
ToggleButton btn=suggestedAmountButtons[i];
|
||||
btn.setChecked(amounts[i]==amount);
|
||||
}
|
||||
}
|
||||
|
||||
private void openWebView(){
|
||||
Uri.Builder builder=Uri.parse(campaign.donationUrl).buildUpon();
|
||||
builder.appendQueryParameter("locale", Locale.getDefault().toLanguageTag().replace('-', '_'))
|
||||
.appendQueryParameter("platform", "android")
|
||||
.appendQueryParameter("currency", amountField.getCurrency())
|
||||
.appendQueryParameter("amount", String.valueOf(amountField.getAmount()))
|
||||
.appendQueryParameter("source", "campaign")
|
||||
.appendQueryParameter("campaign_id", campaign.id)
|
||||
.appendQueryParameter("frequency", switch(frequency){
|
||||
case ONCE -> "one_time";
|
||||
case MONTHLY -> "monthly";
|
||||
case YEARLY -> "yearly";
|
||||
})
|
||||
.appendQueryParameter("success_callback_url", DonationWebViewFragment.SUCCESS_URL)
|
||||
.appendQueryParameter("cancel_callback_url", DonationWebViewFragment.CANCEL_URL)
|
||||
.appendQueryParameter("failure_callback_url", DonationWebViewFragment.FAILURE_URL);
|
||||
Bundle args=new Bundle();
|
||||
args.putString("url", builder.build().toString());
|
||||
args.putString("account", accountID);
|
||||
args.putString("campaignID", campaign.id);
|
||||
args.putString("successPostText", campaign.donationSuccessPost);
|
||||
args.putBoolean("_can_go_back", true);
|
||||
startCallback.accept(new Intent(activity, DonationFragmentActivity.class).putExtra("fragmentArgs", args));
|
||||
}
|
||||
|
||||
private static long getMinimumChargeAmount(String currency){
|
||||
// https://docs.stripe.com/currencies#minimum-and-maximum-charge-amounts
|
||||
// values are in cents
|
||||
return switch(currency){
|
||||
case "USD" -> 50;
|
||||
case "AED" -> 2_00;
|
||||
case "AUD" -> 50;
|
||||
case "BGN" -> 1_00;
|
||||
case "BRL" -> 50;
|
||||
case "CAD" -> 50;
|
||||
case "CHF" -> 50;
|
||||
case "CZK" -> 15_00;
|
||||
case "DKK" -> 2_50;
|
||||
case "EUR" -> 50;
|
||||
case "GBP" -> 30;
|
||||
case "HKD" -> 4_00;
|
||||
case "HUF" -> 175_00;
|
||||
case "INR" -> 50;
|
||||
case "JPY" -> 50_00;
|
||||
case "MXN" -> 10_00;
|
||||
case "MYR" -> 2_00;
|
||||
case "NOK" -> 3_00;
|
||||
case "NZD" -> 50;
|
||||
case "PLN" -> 2_00;
|
||||
case "RON" -> 2_00;
|
||||
case "SEK" -> 3_00;
|
||||
case "SGD" -> 50;
|
||||
case "THB" -> 10_00;
|
||||
|
||||
default -> 50;
|
||||
};
|
||||
}
|
||||
|
||||
private enum DonationFrequency{
|
||||
ONCE,
|
||||
MONTHLY,
|
||||
YEARLY
|
||||
}
|
||||
|
||||
public static class SuggestedAmountsLayout extends ViewGroup implements CustomViewHelper{
|
||||
private int visibleChildCount;
|
||||
private static final int H_GAP=24;
|
||||
private static final int V_GAP=8;
|
||||
private static final int ROW_HEIGHT=32;
|
||||
|
||||
public SuggestedAmountsLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SuggestedAmountsLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public SuggestedAmountsLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
visibleChildCount=0;
|
||||
for(int i=0;i<getChildCount();i++){
|
||||
View child=getChildAt(i);
|
||||
if(child.getVisibility()==GONE)
|
||||
continue;
|
||||
visibleChildCount++;
|
||||
}
|
||||
int width=MeasureSpec.getSize(widthMeasureSpec);
|
||||
setMeasuredDimension(width, visibleChildCount>4 ? dp(ROW_HEIGHT*2+V_GAP) : dp(ROW_HEIGHT));
|
||||
int buttonsPerRow=visibleChildCount>4 ? 3 : visibleChildCount;
|
||||
int buttonWidth=(width-dp(H_GAP)*(buttonsPerRow-1))/buttonsPerRow;
|
||||
for(int i=0;i<getChildCount();i++){
|
||||
View child=getChildAt(i);
|
||||
if(child.getVisibility()==GONE)
|
||||
continue;
|
||||
child.measure(buttonWidth | MeasureSpec.EXACTLY, dp(ROW_HEIGHT) | MeasureSpec.EXACTLY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b){
|
||||
int width=r-l;
|
||||
int buttonsPerRow=visibleChildCount>4 ? 3 : visibleChildCount;
|
||||
int buttonWidth=(width-dp(H_GAP)*(buttonsPerRow-1))/buttonsPerRow;
|
||||
for(int i=0;i<getChildCount();i++){
|
||||
View child=getChildAt(i);
|
||||
if(child.getVisibility()==GONE)
|
||||
continue;
|
||||
int column=i%buttonsPerRow;
|
||||
int row=i/buttonsPerRow;
|
||||
int left=(buttonWidth+dp(H_GAP))*column;
|
||||
int top=dp(ROW_HEIGHT+V_GAP)*row;
|
||||
child.layout(left, top, left+buttonWidth, top+dp(ROW_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
public class DonationSuccessfulSheet extends BottomSheet{
|
||||
|
||||
public DonationSuccessfulSheet(@NonNull Context context, @NonNull String accountID, String postText){
|
||||
super(context);
|
||||
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_donation_success, null);
|
||||
setContentView(content);
|
||||
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
|
||||
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||
|
||||
content.findViewById(R.id.btn_done).setOnClickListener(v->dismiss());
|
||||
View shareButton=content.findViewById(R.id.btn_share);
|
||||
if(postText==null){
|
||||
shareButton.setEnabled(false);
|
||||
}
|
||||
shareButton.setOnClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("prefilledText", postText);
|
||||
Nav.go((Activity) context, ComposeFragment.class, args);
|
||||
dismiss();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,340 @@
|
|||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.DigitsKeyListener;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.Currency;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.utils.CustomViewHelper;
|
||||
|
||||
public class CurrencyAmountInput extends LinearLayout implements CustomViewHelper{
|
||||
private ActualEditText edit;
|
||||
private Button currencyBtn;
|
||||
private List<CurrencyInfo> currencies;
|
||||
private CurrencyInfo currentCurrency;
|
||||
private boolean spanAdded;
|
||||
private CurrencySymbolSpan symbolSpan;
|
||||
private boolean symbolBeforeAmount;
|
||||
private ChangeListener changeListener;
|
||||
private long lastAmount=0;
|
||||
private NumberFormat numberFormat=NumberFormat.getNumberInstance();
|
||||
private boolean allowSymbolToBeDeleted;
|
||||
|
||||
public CurrencyAmountInput(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CurrencyAmountInput(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CurrencyAmountInput(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
setForeground(getResources().getDrawable(R.drawable.fg_currency_input, context.getTheme()));
|
||||
setAddStatesFromChildren(true);
|
||||
|
||||
if(!isInEditMode())
|
||||
setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
setClipToOutline(true);
|
||||
|
||||
currencyBtn=new Button(context);
|
||||
currencyBtn.setTextAppearance(R.style.m3_label_large);
|
||||
currencyBtn.setSingleLine();
|
||||
currencyBtn.setTextColor(UiUtils.getThemeColor(context, R.attr.colorM3OnSurfaceVariant));
|
||||
int pad=dp(12);
|
||||
currencyBtn.setPadding(pad, 0, pad, 0);
|
||||
currencyBtn.setBackgroundColor(UiUtils.getThemeColor(context, R.attr.colorM3SurfaceVariant));
|
||||
currencyBtn.setMinimumWidth(0);
|
||||
currencyBtn.setMinWidth(0);
|
||||
currencyBtn.setOnClickListener(v->showCurrencySelector());
|
||||
currencyBtn.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_unfold_more_wght600_15pt_8x20px, 0, 0, 0);
|
||||
currencyBtn.setCompoundDrawableTintList(currencyBtn.getTextColors());
|
||||
currencyBtn.setCompoundDrawablePadding(dp(4));
|
||||
addView(currencyBtn, new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
edit=new ActualEditText(context);
|
||||
edit.setBackgroundColor(UiUtils.getThemeColor(context, R.attr.colorM3Surface));
|
||||
pad=dp(16);
|
||||
edit.setPadding(pad, 0, pad, 0);
|
||||
edit.setSingleLine();
|
||||
edit.setTextAppearance(R.style.m3_title_large);
|
||||
edit.setTextColor(UiUtils.getThemeColor(context, R.attr.colorM3OnSurface));
|
||||
edit.setGravity(Gravity.END |Gravity.CENTER_VERTICAL);
|
||||
edit.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
InputFilter[] filters=edit.getText().getFilters();
|
||||
for(int i=0;i<filters.length;i++){
|
||||
if(filters[i] instanceof DigitsKeyListener){
|
||||
filters[i]=new FormattingFriendlyDigitsKeyListener();
|
||||
edit.getText().setFilters(filters);
|
||||
break;
|
||||
}
|
||||
}
|
||||
addView(edit, new LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
||||
symbolSpan=new CurrencySymbolSpan(edit.getPaint());
|
||||
|
||||
NumberFormat format=NumberFormat.getInstance();
|
||||
String one=format.format(1);
|
||||
format=NumberFormat.getCurrencyInstance();
|
||||
format.setCurrency(Currency.getInstance("USD"));
|
||||
symbolBeforeAmount=format.format(1).indexOf(one)>0;
|
||||
|
||||
edit.addTextChangedListener(new TextWatcher(){
|
||||
private boolean ignore;
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable e){
|
||||
if(ignore)
|
||||
return;
|
||||
ignore=true;
|
||||
if(e.length()>0 && !spanAdded){
|
||||
SpannableString ss=new SpannableString(" ");
|
||||
ss.setSpan(symbolSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
if(symbolBeforeAmount)
|
||||
e.insert(0, ss);
|
||||
else
|
||||
e.append(ss);
|
||||
spanAdded=true;
|
||||
}else if(spanAdded && e.length()<=1){
|
||||
spanAdded=false;
|
||||
if(e.length()>0){
|
||||
allowSymbolToBeDeleted=true;
|
||||
e.clear();
|
||||
allowSymbolToBeDeleted=false;
|
||||
}
|
||||
}
|
||||
ignore=false;
|
||||
|
||||
updateAmount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setCurrencies(List<String> currencies){
|
||||
this.currencies=currencies.stream().map(CurrencyInfo::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void setSelectedCurrency(String code){
|
||||
CurrencyInfo info=null;
|
||||
for(CurrencyInfo c:currencies){
|
||||
if(c.code.equals(code)){
|
||||
info=c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(info==null)
|
||||
throw new IllegalArgumentException();
|
||||
setCurrency(info);
|
||||
}
|
||||
|
||||
private void setCurrency(CurrencyInfo info){
|
||||
currencyBtn.setText(info.code);
|
||||
currentCurrency=info;
|
||||
edit.invalidate();
|
||||
if(changeListener!=null)
|
||||
changeListener.onCurrencyChanged(info.code);
|
||||
}
|
||||
|
||||
private void showCurrencySelector(){
|
||||
ArrayAdapter<CurrencyInfo> adapter=new ArrayAdapter<>(getContext(), R.layout.item_alert_single_choice_2lines_but_different, R.id.text, currencies){
|
||||
@Override
|
||||
public boolean hasStableIds(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent){
|
||||
View view=super.getView(position, convertView, parent);
|
||||
TextView subtitle=view.findViewById(R.id.subtitle);
|
||||
CurrencyInfo item=getItem(position);
|
||||
if(item.jCurrency==null || item.jCurrency.getDisplayName().equals(item.code)){
|
||||
subtitle.setVisibility(View.GONE);
|
||||
}else{
|
||||
subtitle.setVisibility(View.VISIBLE);
|
||||
subtitle.setText(item.jCurrency.getDisplayName());
|
||||
}
|
||||
return view;
|
||||
}
|
||||
};
|
||||
new M3AlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.currency)
|
||||
.setSingleChoiceItems(adapter, currencies.indexOf(currentCurrency), (dlg, item)->{
|
||||
setCurrency(currencies.get(item));
|
||||
dlg.dismiss();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
public void setChangeListener(ChangeListener changeListener){
|
||||
this.changeListener=changeListener;
|
||||
}
|
||||
|
||||
private void updateAmount(){
|
||||
long newAmount;
|
||||
try{
|
||||
Number n=numberFormat.parse(edit.getText().toString().trim());
|
||||
if(n instanceof Long l){
|
||||
newAmount=l*100L;
|
||||
}else if(n instanceof Double d){
|
||||
newAmount=(long)(d*100);
|
||||
}else{
|
||||
newAmount=0;
|
||||
}
|
||||
}catch(ParseException x){
|
||||
newAmount=0;
|
||||
}
|
||||
if(newAmount!=lastAmount){
|
||||
lastAmount=newAmount;
|
||||
if(changeListener!=null)
|
||||
changeListener.onAmountChanged(lastAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public long getAmount(){
|
||||
return lastAmount;
|
||||
}
|
||||
|
||||
public String getCurrency(){
|
||||
return currentCurrency.code;
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public void setAmount(long amount){
|
||||
String value;
|
||||
if(amount%100==0)
|
||||
value=String.valueOf(amount/100);
|
||||
else
|
||||
value=String.format("%.2f", amount/100.0);
|
||||
int start=spanAdded ? 1 : 0;
|
||||
edit.getText().replace(start, edit.length(), value);
|
||||
}
|
||||
|
||||
private class ActualEditText extends EditText{
|
||||
public ActualEditText(Context context){
|
||||
super(context);
|
||||
setClipToPadding(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSelectionChanged(int selStart, int selEnd){
|
||||
super.onSelectionChanged(selStart, selEnd);
|
||||
// Adjust the selection to prevent the symbol span being selected
|
||||
if(spanAdded){
|
||||
int newSelStart=symbolBeforeAmount ? Math.max(selStart, 1) : Math.min(selStart, length()-1);
|
||||
int newSelEnd=symbolBeforeAmount ? Math.max(selEnd, 1) : Math.min(selEnd, length()-1);
|
||||
if(newSelStart!=selStart || newSelEnd!=selEnd){
|
||||
setSelection(newSelStart, newSelEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class CurrencyInfo{
|
||||
public String code;
|
||||
public String symbol;
|
||||
public Currency jCurrency;
|
||||
|
||||
public CurrencyInfo(String code){
|
||||
this.code=code;
|
||||
try{
|
||||
jCurrency=Currency.getInstance(code);
|
||||
symbol=jCurrency.getSymbol();
|
||||
}catch(IllegalArgumentException x){
|
||||
symbol=code;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString(){
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
private class CurrencySymbolSpan extends ReplacementSpan{
|
||||
private Paint paint;
|
||||
public CurrencySymbolSpan(Paint paint){
|
||||
this.paint=new Paint(paint);
|
||||
this.paint.setTextSize(paint.getTextSize()*0.66f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
|
||||
return Math.round(this.paint.measureText(currentCurrency.symbol))+dp(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint){
|
||||
this.paint.setColor(paint.getColor());
|
||||
this.paint.setAlpha(77);
|
||||
if(!symbolBeforeAmount)
|
||||
x+=dp(2);
|
||||
canvas.drawText(currentCurrency.symbol, x, top+dp(1.5f)-this.paint.ascent(), this.paint);
|
||||
}
|
||||
}
|
||||
|
||||
private class FormattingFriendlyDigitsKeyListener extends DigitsKeyListener{
|
||||
public FormattingFriendlyDigitsKeyListener(){
|
||||
super(false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend){
|
||||
// Allow the currency symbol to be inserted (always done as a separate insertion operation)
|
||||
if(source instanceof Spannable s && s.getSpans(start, end, CurrencySymbolSpan.class).length>0){
|
||||
return source;
|
||||
}
|
||||
// Don't allow the currency symbol to be deleted
|
||||
if(!allowSymbolToBeDeleted && end-start<dend-dstart && dest.getSpans(dstart, dend, CurrencySymbolSpan.class).length>0){
|
||||
return dest.subSequence(dstart, dend);
|
||||
}
|
||||
return super.filter(source, start, end, dest, dstart, dend);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ChangeListener{
|
||||
void onCurrencyChanged(String code);
|
||||
void onAmountChanged(long amount);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300"
|
||||
android:interpolator="@interpolator/cubic_bezier_default"
|
||||
android:shareInterpolator="true"
|
||||
>
|
||||
<alpha
|
||||
android:fromAlpha="0"
|
||||
android:toAlpha="1" />
|
||||
<translate
|
||||
android:fromXDelta="@integer/hundred_dp"
|
||||
android:toXDelta="0"/>
|
||||
</set>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="200"
|
||||
android:interpolator="@interpolator/cubic_bezier_default"
|
||||
android:shareInterpolator="true"
|
||||
>
|
||||
<alpha
|
||||
android:fromAlpha="1"
|
||||
android:toAlpha="0" />
|
||||
<translate
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="@integer/hundred_dp"/>
|
||||
</set>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" android:duration="300">
|
||||
|
||||
</set>
|
Binary file not shown.
After Width: | Height: | Size: 188 KiB |
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<color android:color="@color/masterialDark_secondaryContainer"/>
|
||||
</item>
|
||||
<item android:height="0dp">
|
||||
<layer-list>
|
||||
<item android:gravity="center_vertical|end">
|
||||
<bitmap android:src="@drawable/scribble" android:alpha="0.08" android:autoMirrored="true"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -1,5 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true">
|
||||
<ripple android:color="@color/m3_on_secondary_container_overlay">
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:radius="8dp"/>
|
||||
<solid android:color="?colorM3SecondaryContainer"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
</item>
|
||||
<item android:state_selected="true">
|
||||
<ripple android:color="@color/m3_on_secondary_container_overlay">
|
||||
<item>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<corners android:radius="8dp"/>
|
||||
<stroke android:color="?colorM3Primary" android:width="1dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:radius="8dp"/>
|
||||
<stroke android:color="?colorM3OutlineVariant" android:width="1dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15,10.875V9.125H18.333V10.875ZM16,16.667 L13.333,14.667 14.375,13.25 17.042,15.25ZM14.396,6.729 L13.333,5.333 16,3.333 17.062,4.729ZM4.167,15.833V12.5H3.417Q2.729,12.5 2.198,12.052Q1.667,11.604 1.667,10.917V9.083Q1.667,8.396 2.198,7.948Q2.729,7.5 3.417,7.5H6.667L10.833,5V15L6.667,12.5H5.917V15.833ZM11.708,12.792V7.208Q12.271,7.708 12.615,8.427Q12.958,9.146 12.958,10Q12.958,10.854 12.615,11.573Q12.271,12.292 11.708,12.792ZM3.417,9.25Q3.417,9.25 3.417,9.25Q3.417,9.25 3.417,9.25V10.75Q3.417,10.75 3.417,10.75Q3.417,10.75 3.417,10.75H7.208L9.083,11.917V8.083L7.208,9.25ZM6.25,10Q6.25,10 6.25,10Q6.25,10 6.25,10Q6.25,10 6.25,10Q6.25,10 6.25,10Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true" android:drawable="@drawable/ic_baseline_check_18"/>
|
||||
<item android:drawable="@drawable/ic_favorite_18px"/>
|
||||
</selector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="18dp"
|
||||
android:height="18dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10,17 L8.958,16.062Q6.875,14.208 5.521,12.885Q4.167,11.562 3.385,10.531Q2.604,9.5 2.302,8.646Q2,7.792 2,6.896Q2,5.042 3.271,3.771Q4.542,2.5 6.396,2.5Q7.417,2.5 8.375,2.938Q9.333,3.375 10,4.167Q10.667,3.375 11.625,2.938Q12.583,2.5 13.604,2.5Q15.458,2.5 16.729,3.771Q18,5.042 18,6.896Q18,7.792 17.708,8.625Q17.417,9.458 16.635,10.479Q15.854,11.5 14.49,12.844Q13.125,14.188 11,16.104ZM10,14.979Q11.938,13.25 13.188,12.031Q14.438,10.812 15.177,9.906Q15.917,9 16.208,8.292Q16.5,7.583 16.5,6.896Q16.5,5.667 15.667,4.833Q14.833,4 13.604,4Q12.875,4 12.24,4.302Q11.604,4.604 11.146,5.146L10.417,6H9.583L8.854,5.146Q8.396,4.604 7.74,4.302Q7.083,4 6.396,4Q5.167,4 4.333,4.833Q3.5,5.667 3.5,6.896Q3.5,7.583 3.771,8.26Q4.042,8.938 4.76,9.833Q5.479,10.729 6.74,11.958Q8,13.188 10,14.979ZM10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Q10,9.479 10,9.479Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M482,640L622,500Q639,483 644,458.5Q649,434 639,411Q629,388 609,374Q589,360 564,360Q539,360 519,375.5Q499,391 482,408Q464,391 444.5,375.5Q425,360 400,360Q375,360 354.5,373.5Q334,387 324,410Q314,433 319.5,457.5Q325,482 342,500L482,640ZM370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="8dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="8"
|
||||
android:viewportHeight="20">
|
||||
<group android:translateX="-6" android:scaleX="0.75" android:scaleY="0.75" android:pivotX="10" android:pivotY="10">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10,17.375 L6.208,13.583 7.646,12.167 10,14.5 12.354,12.167 13.792,13.583ZM7.646,7.812 L6.208,6.375 10,2.583 13.792,6.375 12.354,7.812 10,5.458Z" />
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M13.292,10.917Q11.354,9.146 9.812,7.521Q8.271,5.896 8.271,4.458Q8.271,3.292 9.083,2.479Q9.896,1.667 11.062,1.667Q11.729,1.667 12.312,1.938Q12.896,2.208 13.292,2.667Q13.688,2.208 14.271,1.938Q14.854,1.667 15.521,1.667Q16.688,1.667 17.5,2.479Q18.312,3.292 18.312,4.458Q18.312,5.896 16.771,7.521Q15.229,9.146 13.292,10.917ZM13.292,8.542Q14.479,7.396 15.521,6.26Q16.562,5.125 16.562,4.458Q16.562,4.021 16.26,3.719Q15.958,3.417 15.521,3.417Q15.229,3.417 15,3.531Q14.771,3.646 14.625,3.812L13.312,5.375L12,3.812Q11.833,3.646 11.594,3.531Q11.354,3.417 11.062,3.417Q10.625,3.417 10.323,3.719Q10.021,4.021 10.021,4.458Q10.021,5.125 11.062,6.26Q12.104,7.396 13.292,8.542ZM13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979Q13.292,5.979 13.292,5.979ZM11.667,18.75 L5.979,17.167V18.396H0.833V9.042H7.542L12.646,10.938Q13.354,11.208 13.781,11.854Q14.208,12.5 14.208,13.229H15.729Q16.812,13.229 17.573,13.958Q18.333,14.688 18.333,15.771V16.667ZM2.583,16.646H4.229V10.792H2.583ZM11.625,16.938 L16.5,15.396Q16.396,15.208 16.188,15.094Q15.979,14.979 15.729,14.979H11.75Q11.125,14.979 10.646,14.906Q10.167,14.833 9.812,14.708L8.104,14.125L8.646,12.438L10.438,13.042Q10.771,13.146 11.167,13.188Q11.562,13.229 12.479,13.229Q12.479,13 12.344,12.802Q12.208,12.604 12.021,12.542L7.229,10.792H5.979V15.396ZM4.229,13.708ZM12.479,13.229Q12.479,13.229 12.479,13.229Q12.479,13.229 12.479,13.229Q12.479,13.229 12.479,13.229Q12.479,13.229 12.479,13.229Q12.479,13.229 12.479,13.229Q12.479,13.229 12.479,13.229Q12.479,13.229 12.479,13.229Q12.479,13.229 12.479,13.229ZM4.229,13.708ZM5.979,13.708Q5.979,13.708 5.979,13.708Q5.979,13.708 5.979,13.708Q5.979,13.708 5.979,13.708Q5.979,13.708 5.979,13.708Q5.979,13.708 5.979,13.708Q5.979,13.708 5.979,13.708Q5.979,13.708 5.979,13.708Q5.979,13.708 5.979,13.708Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M640,520L474,358Q443,328 421.5,291.5Q400,255 400,212Q400,157 438.5,118.5Q477,80 532,80Q564,80 592,93.5Q620,107 640,130Q660,107 688,93.5Q716,80 748,80Q803,80 841.5,118.5Q880,157 880,212Q880,255 859,291.5Q838,328 807,358L640,520ZM640,408L749,301Q768,282 784,260.5Q800,239 800,212Q800,190 785,175Q770,160 748,160Q734,160 721.5,165.5Q709,171 700,182L640,254L580,182Q571,171 558.5,165.5Q546,160 532,160Q510,160 495,175Q480,190 480,212Q480,239 496,260.5Q512,282 531,301L640,408ZM280,740L558,816L796,742Q791,733 781.5,726.5Q772,720 760,720L558,720Q531,720 515,718Q499,716 482,710L389,679L411,601L492,628Q509,633 532,636Q555,639 600,640L600,640Q600,640 600,640Q600,640 600,640Q600,629 593.5,619Q587,609 578,606L344,520Q344,520 344,520Q344,520 344,520L280,520L280,740ZM40,880L40,440L344,440Q351,440 358,441.5Q365,443 371,445L606,532Q639,544 659.5,574Q680,604 680,640L760,640Q810,640 845,673Q880,706 880,760L880,800L560,900L280,822L280,822L280,880L40,880ZM120,800L200,800L200,520L120,520L120,800ZM640,254L640,254Q640,254 640,254Q640,254 640,254Q640,254 640,254Q640,254 640,254Q640,254 640,254Q640,254 640,254L640,254L640,254Q640,254 640,254Q640,254 640,254Q640,254 640,254Q640,254 640,254Q640,254 640,254Q640,254 640,254L640,254Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:controlX1="0.25" android:controlY1="0.1" android:controlX2="0.25" android:controlY2="1"/>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="12dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@drawable/bg_donation_banner">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/banner_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="@color/masterialDark_onSecondaryContainer"
|
||||
tools:text="Donation banner text goes here"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/banner_dismiss"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:tint="@color/masterialDark_onSecondaryContainer"
|
||||
android:backgroundTint="@color/masterialDark_onSecondaryContainer"
|
||||
android:src="@drawable/ic_baseline_close_24"
|
||||
android:contentDescription="@string/dismiss"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -58,5 +58,12 @@
|
|||
android:text="@string/see_new_posts"/>
|
||||
</FrameLayout>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/donation_banner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout="@layout/donation_banner"/>
|
||||
|
||||
</FrameLayout>
|
||||
</me.grishka.appkit.views.RecursiveSwipeRefreshLayout>
|
|
@ -0,0 +1,114 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.joinmastodon.android.ui.views.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bg_bottom_sheet"
|
||||
android:outlineProvider="background"
|
||||
android:elevation="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/bg_bottom_sheet_handle"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
tools:text="By supporting Mastodon, you help sustain a global network that values people over profit. Will you join us today?"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/tabbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tabbar_inner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center"
|
||||
style="@style/Widget.Mastodon.M3.SegmentedButtonContainer">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/once"
|
||||
style="@style/Widget.Mastodon.M3.SegmentedButton">
|
||||
<org.joinmastodon.android.ui.views.CheckIconSelectableTextView
|
||||
style="@style/Widget.Mastodon.M3.SegmentedButtonText"
|
||||
android:text="@string/donation_once"/>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/monthly"
|
||||
style="@style/Widget.Mastodon.M3.SegmentedButton">
|
||||
<TextView
|
||||
style="@style/Widget.Mastodon.M3.SegmentedButtonText"
|
||||
android:drawableStart="@drawable/ic_donation_monthly"
|
||||
android:drawableTint="?colorM3OnSurface"
|
||||
android:text="@string/donation_monthly"/>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/yearly"
|
||||
style="@style/Widget.Mastodon.M3.SegmentedButton">
|
||||
<org.joinmastodon.android.ui.views.CheckIconSelectableTextView
|
||||
style="@style/Widget.Mastodon.M3.SegmentedButtonText"
|
||||
android:text="@string/donation_yearly"/>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.CurrencyAmountInput
|
||||
android:id="@+id/amount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="16dp"/>
|
||||
|
||||
<view class="org.joinmastodon.android.ui.sheets.DonationSheet$SuggestedAmountsLayout"
|
||||
android:id="@+id/suggested_amounts"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginTop="8dp"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="28dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled">
|
||||
<TextView
|
||||
android:id="@+id/button_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:drawableStart="@drawable/ic_volunteer_activism_20px"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:background="@null"
|
||||
android:padding="0dp"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="@color/button_text_m3_filled"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:duplicateParentState="true"
|
||||
android:minWidth="0dp"
|
||||
tools:text="Donate"/>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.CustomScrollView>
|
|
@ -0,0 +1,84 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.joinmastodon.android.ui.views.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@drawable/bg_bottom_sheet"
|
||||
android:outlineProvider="background"
|
||||
android:elevation="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/bg_bottom_sheet_handle"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_headline_medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:gravity="center"
|
||||
android:text="@string/donation_success_title"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:gravity="center"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:text="@string/donation_success_subtitle"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.FixedAspectRatioImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:src="@drawable/donation_successful_art"
|
||||
app:aspectRatio="1.777777"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btn_share"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled">
|
||||
<TextView
|
||||
android:id="@+id/button_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:drawableStart="@drawable/ic_campaign_20px"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:background="@null"
|
||||
android:padding="0dp"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="@color/button_text_m3_filled"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:duplicateParentState="true"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/donation_success_share"/>
|
||||
</FrameLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_done"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Outlined"
|
||||
android:text="@string/done"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.joinmastodon.android.ui.views.CustomScrollView>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="hundred_dp">150</integer>
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="hundred_dp">133</integer>
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="hundred_dp">200</integer>
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="hundred_dp">300</integer>
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="hundred_dp">400</integer>
|
||||
</resources>
|
|
@ -0,0 +1,336 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- light -->
|
||||
<color name="masterialLight_primary">#4000DD</color>
|
||||
<color name="masterialLight_onPrimary">#FFFFFF</color>
|
||||
<color name="masterialLight_primaryContainer">#6648FF</color>
|
||||
<color name="masterialLight_onPrimaryContainer">#FFFFFF</color>
|
||||
<color name="masterialLight_secondary">#5D51AF</color>
|
||||
<color name="masterialLight_onSecondary">#FFFFFF</color>
|
||||
<color name="masterialLight_secondaryContainer">#B0A5FF</color>
|
||||
<color name="masterialLight_onSecondaryContainer">#220C73</color>
|
||||
<color name="masterialLight_tertiary">#810082</color>
|
||||
<color name="masterialLight_onTertiary">#FFFFFF</color>
|
||||
<color name="masterialLight_tertiaryContainer">#B722B7</color>
|
||||
<color name="masterialLight_onTertiaryContainer">#FFFFFF</color>
|
||||
<color name="masterialLight_error">#BA1A1A</color>
|
||||
<color name="masterialLight_onError">#FFFFFF</color>
|
||||
<color name="masterialLight_errorContainer">#FFDAD6</color>
|
||||
<color name="masterialLight_onErrorContainer">#410002</color>
|
||||
<color name="masterialLight_background">#FCF8FF</color>
|
||||
<color name="masterialLight_onBackground">#1C1A25</color>
|
||||
<color name="masterialLight_surface">#FCF8FF</color>
|
||||
<color name="masterialLight_onSurface">#1C1A25</color>
|
||||
<color name="masterialLight_surfaceVariant">#E5DFF6</color>
|
||||
<color name="masterialLight_onSurfaceVariant">#474557</color>
|
||||
<color name="masterialLight_outline">#787588</color>
|
||||
<color name="masterialLight_outlineVariant">#C9C4DA</color>
|
||||
<color name="masterialLight_scrim">#000000</color>
|
||||
<color name="masterialLight_inverseSurface">#312F3B</color>
|
||||
<color name="masterialLight_inverseOnSurface">#F3EEFE</color>
|
||||
<color name="masterialLight_inversePrimary">#C7BFFF</color>
|
||||
<color name="masterialLight_primaryFixed">#E5DEFF</color>
|
||||
<color name="masterialLight_onPrimaryFixed">#180065</color>
|
||||
<color name="masterialLight_primaryFixedDim">#C7BFFF</color>
|
||||
<color name="masterialLight_onPrimaryFixedVariant">#4000DC</color>
|
||||
<color name="masterialLight_secondaryFixed">#E5DEFF</color>
|
||||
<color name="masterialLight_onSecondaryFixed">#180065</color>
|
||||
<color name="masterialLight_secondaryFixedDim">#C7BFFF</color>
|
||||
<color name="masterialLight_onSecondaryFixedVariant">#453895</color>
|
||||
<color name="masterialLight_tertiaryFixed">#FFD7F6</color>
|
||||
<color name="masterialLight_onTertiaryFixed">#380039</color>
|
||||
<color name="masterialLight_tertiaryFixedDim">#FFAAF5</color>
|
||||
<color name="masterialLight_onTertiaryFixedVariant">#800082</color>
|
||||
<color name="masterialLight_surfaceDim">#DCD8E7</color>
|
||||
<color name="masterialLight_surfaceBright">#FCF8FF</color>
|
||||
<color name="masterialLight_surfaceContainerLowest">#FFFFFF</color>
|
||||
<color name="masterialLight_surfaceContainerLow">#F6F1FF</color>
|
||||
<color name="masterialLight_surfaceContainer">#F1EBFB</color>
|
||||
<color name="masterialLight_surfaceContainerHigh">#EBE6F6</color>
|
||||
<color name="masterialLight_surfaceContainerHighest">#E5E0F0</color>
|
||||
<color name="masterialLight_primary_mediumContrast">#3C00D1</color>
|
||||
<color name="masterialLight_onPrimary_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_primaryContainer_mediumContrast">#6648FF</color>
|
||||
<color name="masterialLight_onPrimaryContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_secondary_mediumContrast">#413391</color>
|
||||
<color name="masterialLight_onSecondary_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_secondaryContainer_mediumContrast">#7368C7</color>
|
||||
<color name="masterialLight_onSecondaryContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_tertiary_mediumContrast">#7A007B</color>
|
||||
<color name="masterialLight_onTertiary_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_tertiaryContainer_mediumContrast">#B722B7</color>
|
||||
<color name="masterialLight_onTertiaryContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_error_mediumContrast">#8C0009</color>
|
||||
<color name="masterialLight_onError_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_errorContainer_mediumContrast">#DA342E</color>
|
||||
<color name="masterialLight_onErrorContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_background_mediumContrast">#FCF8FF</color>
|
||||
<color name="masterialLight_onBackground_mediumContrast">#1C1A25</color>
|
||||
<color name="masterialLight_surface_mediumContrast">#FCF8FF</color>
|
||||
<color name="masterialLight_onSurface_mediumContrast">#1C1A25</color>
|
||||
<color name="masterialLight_surfaceVariant_mediumContrast">#E5DFF6</color>
|
||||
<color name="masterialLight_onSurfaceVariant_mediumContrast">#434153</color>
|
||||
<color name="masterialLight_outline_mediumContrast">#605D70</color>
|
||||
<color name="masterialLight_outlineVariant_mediumContrast">#7C788C</color>
|
||||
<color name="masterialLight_scrim_mediumContrast">#000000</color>
|
||||
<color name="masterialLight_inverseSurface_mediumContrast">#312F3B</color>
|
||||
<color name="masterialLight_inverseOnSurface_mediumContrast">#F3EEFE</color>
|
||||
<color name="masterialLight_inversePrimary_mediumContrast">#C7BFFF</color>
|
||||
<color name="masterialLight_primaryFixed_mediumContrast">#7058FF</color>
|
||||
<color name="masterialLight_onPrimaryFixed_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_primaryFixedDim_mediumContrast">#552BFB</color>
|
||||
<color name="masterialLight_onPrimaryFixedVariant_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_secondaryFixed_mediumContrast">#7368C7</color>
|
||||
<color name="masterialLight_onSecondaryFixed_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_secondaryFixedDim_mediumContrast">#5A4EAC</color>
|
||||
<color name="masterialLight_onSecondaryFixedVariant_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_tertiaryFixed_mediumContrast">#C330C2</color>
|
||||
<color name="masterialLight_onTertiaryFixed_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_tertiaryFixedDim_mediumContrast">#A400A6</color>
|
||||
<color name="masterialLight_onTertiaryFixedVariant_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_surfaceDim_mediumContrast">#DCD8E7</color>
|
||||
<color name="masterialLight_surfaceBright_mediumContrast">#FCF8FF</color>
|
||||
<color name="masterialLight_surfaceContainerLowest_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_surfaceContainerLow_mediumContrast">#F6F1FF</color>
|
||||
<color name="masterialLight_surfaceContainer_mediumContrast">#F1EBFB</color>
|
||||
<color name="masterialLight_surfaceContainerHigh_mediumContrast">#EBE6F6</color>
|
||||
<color name="masterialLight_surfaceContainerHighest_mediumContrast">#E5E0F0</color>
|
||||
<color name="masterialLight_primary_highContrast">#1E0077</color>
|
||||
<color name="masterialLight_onPrimary_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_primaryContainer_highContrast">#3C00D1</color>
|
||||
<color name="masterialLight_onPrimaryContainer_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_secondary_highContrast">#1F0671</color>
|
||||
<color name="masterialLight_onSecondary_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_secondaryContainer_highContrast">#413391</color>
|
||||
<color name="masterialLight_onSecondaryContainer_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_tertiary_highContrast">#430044</color>
|
||||
<color name="masterialLight_onTertiary_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_tertiaryContainer_highContrast">#7A007B</color>
|
||||
<color name="masterialLight_onTertiaryContainer_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_error_highContrast">#4E0002</color>
|
||||
<color name="masterialLight_onError_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_errorContainer_highContrast">#8C0009</color>
|
||||
<color name="masterialLight_onErrorContainer_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_background_highContrast">#FCF8FF</color>
|
||||
<color name="masterialLight_onBackground_highContrast">#1C1A25</color>
|
||||
<color name="masterialLight_surface_highContrast">#FCF8FF</color>
|
||||
<color name="masterialLight_onSurface_highContrast">#000000</color>
|
||||
<color name="masterialLight_surfaceVariant_highContrast">#E5DFF6</color>
|
||||
<color name="masterialLight_onSurfaceVariant_highContrast">#242232</color>
|
||||
<color name="masterialLight_outline_highContrast">#434153</color>
|
||||
<color name="masterialLight_outlineVariant_highContrast">#434153</color>
|
||||
<color name="masterialLight_scrim_highContrast">#000000</color>
|
||||
<color name="masterialLight_inverseSurface_highContrast">#312F3B</color>
|
||||
<color name="masterialLight_inverseOnSurface_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_inversePrimary_highContrast">#EFE9FF</color>
|
||||
<color name="masterialLight_primaryFixed_highContrast">#3C00D1</color>
|
||||
<color name="masterialLight_onPrimaryFixed_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_primaryFixedDim_highContrast">#280094</color>
|
||||
<color name="masterialLight_onPrimaryFixedVariant_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_secondaryFixed_highContrast">#413391</color>
|
||||
<color name="masterialLight_onSecondaryFixed_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_secondaryFixedDim_highContrast">#2A197A</color>
|
||||
<color name="masterialLight_onSecondaryFixedVariant_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_tertiaryFixed_highContrast">#7A007B</color>
|
||||
<color name="masterialLight_onTertiaryFixed_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_tertiaryFixedDim_highContrast">#550056</color>
|
||||
<color name="masterialLight_onTertiaryFixedVariant_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_surfaceDim_highContrast">#DCD8E7</color>
|
||||
<color name="masterialLight_surfaceBright_highContrast">#FCF8FF</color>
|
||||
<color name="masterialLight_surfaceContainerLowest_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_surfaceContainerLow_highContrast">#F6F1FF</color>
|
||||
<color name="masterialLight_surfaceContainer_highContrast">#F1EBFB</color>
|
||||
<color name="masterialLight_surfaceContainerHigh_highContrast">#EBE6F6</color>
|
||||
<color name="masterialLight_surfaceContainerHighest_highContrast">#E5E0F0</color>
|
||||
<color name="masterialLight_colorGoldenrod">#7B5800</color>
|
||||
<color name="masterialLight_colorOnGoldenrod">#FFFFFF</color>
|
||||
<color name="masterialLight_colorGoldenrodContainer">#FFC758</color>
|
||||
<color name="masterialLight_colorOnGoldenrodContainer">#503800</color>
|
||||
<color name="masterialLight_colorLime">#476800</color>
|
||||
<color name="masterialLight_colorOnLime">#FFFFFF</color>
|
||||
<color name="masterialLight_colorLimeContainer">#CAFF71</color>
|
||||
<color name="masterialLight_colorOnLimeContainer">#3A5700</color>
|
||||
<color name="masterialLight_colorGoldenrod_mediumContrast">#583E00</color>
|
||||
<color name="masterialLight_colorOnGoldenrod_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_colorGoldenrodContainer_mediumContrast">#986D00</color>
|
||||
<color name="masterialLight_colorOnGoldenrodContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_colorLime_mediumContrast">#314A00</color>
|
||||
<color name="masterialLight_colorOnLime_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_colorLimeContainer_mediumContrast">#588000</color>
|
||||
<color name="masterialLight_colorOnLimeContainer_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_colorGoldenrod_highContrast">#2F1F00</color>
|
||||
<color name="masterialLight_colorOnGoldenrod_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_colorGoldenrodContainer_highContrast">#583E00</color>
|
||||
<color name="masterialLight_colorOnGoldenrodContainer_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_colorLime_highContrast">#182600</color>
|
||||
<color name="masterialLight_colorOnLime_highContrast">#FFFFFF</color>
|
||||
<color name="masterialLight_colorLimeContainer_highContrast">#314A00</color>
|
||||
<color name="masterialLight_colorOnLimeContainer_highContrast">#FFFFFF</color>
|
||||
|
||||
<!-- dark -->
|
||||
<color name="masterialDark_primary">#C7BFFF</color>
|
||||
<color name="masterialDark_onPrimary">#2B009E</color>
|
||||
<color name="masterialDark_primaryContainer">#4D1AF4</color>
|
||||
<color name="masterialDark_onPrimaryContainer">#FAF5FF</color>
|
||||
<color name="masterialDark_secondary">#C7BFFF</color>
|
||||
<color name="masterialDark_onSecondary">#2E1E7E</color>
|
||||
<color name="masterialDark_secondaryContainer">#3D308E</color>
|
||||
<color name="masterialDark_onSecondaryContainer">#D6CFFF</color>
|
||||
<color name="masterialDark_tertiary">#FFAAF5</color>
|
||||
<color name="masterialDark_onTertiary">#5B005C</color>
|
||||
<color name="masterialDark_tertiaryContainer">#970099</color>
|
||||
<color name="masterialDark_onTertiaryContainer">#FFF5F9</color>
|
||||
<color name="masterialDark_error">#FFB4AB</color>
|
||||
<color name="masterialDark_onError">#690005</color>
|
||||
<color name="masterialDark_errorContainer">#93000A</color>
|
||||
<color name="masterialDark_onErrorContainer">#FFDAD6</color>
|
||||
<color name="masterialDark_background">#13121D</color>
|
||||
<color name="masterialDark_onBackground">#E5E0F0</color>
|
||||
<color name="masterialDark_surface">#13121D</color>
|
||||
<color name="masterialDark_onSurface">#E5E0F0</color>
|
||||
<color name="masterialDark_surfaceVariant">#474557</color>
|
||||
<color name="masterialDark_onSurfaceVariant">#C9C4DA</color>
|
||||
<color name="masterialDark_outline">#928EA3</color>
|
||||
<color name="masterialDark_outlineVariant">#474557</color>
|
||||
<color name="masterialDark_scrim">#000000</color>
|
||||
<color name="masterialDark_inverseSurface">#E5E0F0</color>
|
||||
<color name="masterialDark_inverseOnSurface">#312F3B</color>
|
||||
<color name="masterialDark_inversePrimary">#582FFE</color>
|
||||
<color name="masterialDark_primaryFixed">#E5DEFF</color>
|
||||
<color name="masterialDark_onPrimaryFixed">#180065</color>
|
||||
<color name="masterialDark_primaryFixedDim">#C7BFFF</color>
|
||||
<color name="masterialDark_onPrimaryFixedVariant">#4000DC</color>
|
||||
<color name="masterialDark_secondaryFixed">#E5DEFF</color>
|
||||
<color name="masterialDark_onSecondaryFixed">#180065</color>
|
||||
<color name="masterialDark_secondaryFixedDim">#C7BFFF</color>
|
||||
<color name="masterialDark_onSecondaryFixedVariant">#453895</color>
|
||||
<color name="masterialDark_tertiaryFixed">#FFD7F6</color>
|
||||
<color name="masterialDark_onTertiaryFixed">#380039</color>
|
||||
<color name="masterialDark_tertiaryFixedDim">#FFAAF5</color>
|
||||
<color name="masterialDark_onTertiaryFixedVariant">#800082</color>
|
||||
<color name="masterialDark_surfaceDim">#13121D</color>
|
||||
<color name="masterialDark_surfaceBright">#3A3844</color>
|
||||
<color name="masterialDark_surfaceContainerLowest">#0E0D17</color>
|
||||
<color name="masterialDark_surfaceContainerLow">#1C1A25</color>
|
||||
<color name="masterialDark_surfaceContainer">#201E29</color>
|
||||
<color name="masterialDark_surfaceContainerHigh">#2A2934</color>
|
||||
<color name="masterialDark_surfaceContainerHighest">#35333F</color>
|
||||
<color name="masterialDark_primary_mediumContrast">#CCC4FF</color>
|
||||
<color name="masterialDark_onPrimary_mediumContrast">#130056</color>
|
||||
<color name="masterialDark_primaryContainer_mediumContrast">#8F7FFF</color>
|
||||
<color name="masterialDark_onPrimaryContainer_mediumContrast">#000000</color>
|
||||
<color name="masterialDark_secondary_mediumContrast">#CCC4FF</color>
|
||||
<color name="masterialDark_onSecondary_mediumContrast">#130056</color>
|
||||
<color name="masterialDark_secondaryContainer_mediumContrast">#9084E6</color>
|
||||
<color name="masterialDark_onSecondaryContainer_mediumContrast">#000000</color>
|
||||
<color name="masterialDark_tertiary_mediumContrast">#FFB1F5</color>
|
||||
<color name="masterialDark_onTertiary_mediumContrast">#2F0030</color>
|
||||
<color name="masterialDark_tertiaryContainer_mediumContrast">#E552E1</color>
|
||||
<color name="masterialDark_onTertiaryContainer_mediumContrast">#000000</color>
|
||||
<color name="masterialDark_error_mediumContrast">#FFBAB1</color>
|
||||
<color name="masterialDark_onError_mediumContrast">#370001</color>
|
||||
<color name="masterialDark_errorContainer_mediumContrast">#FF5449</color>
|
||||
<color name="masterialDark_onErrorContainer_mediumContrast">#000000</color>
|
||||
<color name="masterialDark_background_mediumContrast">#13121D</color>
|
||||
<color name="masterialDark_onBackground_mediumContrast">#E5E0F0</color>
|
||||
<color name="masterialDark_surface_mediumContrast">#13121D</color>
|
||||
<color name="masterialDark_onSurface_mediumContrast">#FEF9FF</color>
|
||||
<color name="masterialDark_surfaceVariant_mediumContrast">#474557</color>
|
||||
<color name="masterialDark_onSurfaceVariant_mediumContrast">#CDC8DE</color>
|
||||
<color name="masterialDark_outline_mediumContrast">#A4A0B5</color>
|
||||
<color name="masterialDark_outlineVariant_mediumContrast">#848195</color>
|
||||
<color name="masterialDark_scrim_mediumContrast">#000000</color>
|
||||
<color name="masterialDark_inverseSurface_mediumContrast">#E5E0F0</color>
|
||||
<color name="masterialDark_inverseOnSurface_mediumContrast">#2A2934</color>
|
||||
<color name="masterialDark_inversePrimary_mediumContrast">#4100DF</color>
|
||||
<color name="masterialDark_primaryFixed_mediumContrast">#E5DEFF</color>
|
||||
<color name="masterialDark_onPrimaryFixed_mediumContrast">#0F0048</color>
|
||||
<color name="masterialDark_primaryFixedDim_mediumContrast">#C7BFFF</color>
|
||||
<color name="masterialDark_onPrimaryFixedVariant_mediumContrast">#3000AE</color>
|
||||
<color name="masterialDark_secondaryFixed_mediumContrast">#E5DEFF</color>
|
||||
<color name="masterialDark_onSecondaryFixed_mediumContrast">#0F0048</color>
|
||||
<color name="masterialDark_secondaryFixedDim_mediumContrast">#C7BFFF</color>
|
||||
<color name="masterialDark_onSecondaryFixedVariant_mediumContrast">#342584</color>
|
||||
<color name="masterialDark_tertiaryFixed_mediumContrast">#FFD7F6</color>
|
||||
<color name="masterialDark_onTertiaryFixed_mediumContrast">#260027</color>
|
||||
<color name="masterialDark_tertiaryFixedDim_mediumContrast">#FFAAF5</color>
|
||||
<color name="masterialDark_onTertiaryFixedVariant_mediumContrast">#650066</color>
|
||||
<color name="masterialDark_surfaceDim_mediumContrast">#13121D</color>
|
||||
<color name="masterialDark_surfaceBright_mediumContrast">#3A3844</color>
|
||||
<color name="masterialDark_surfaceContainerLowest_mediumContrast">#0E0D17</color>
|
||||
<color name="masterialDark_surfaceContainerLow_mediumContrast">#1C1A25</color>
|
||||
<color name="masterialDark_surfaceContainer_mediumContrast">#201E29</color>
|
||||
<color name="masterialDark_surfaceContainerHigh_mediumContrast">#2A2934</color>
|
||||
<color name="masterialDark_surfaceContainerHighest_mediumContrast">#35333F</color>
|
||||
<color name="masterialDark_primary_highContrast">#FEF9FF</color>
|
||||
<color name="masterialDark_onPrimary_highContrast">#000000</color>
|
||||
<color name="masterialDark_primaryContainer_highContrast">#CCC4FF</color>
|
||||
<color name="masterialDark_onPrimaryContainer_highContrast">#000000</color>
|
||||
<color name="masterialDark_secondary_highContrast">#FEF9FF</color>
|
||||
<color name="masterialDark_onSecondary_highContrast">#000000</color>
|
||||
<color name="masterialDark_secondaryContainer_highContrast">#CCC4FF</color>
|
||||
<color name="masterialDark_onSecondaryContainer_highContrast">#000000</color>
|
||||
<color name="masterialDark_tertiary_highContrast">#FFF9FA</color>
|
||||
<color name="masterialDark_onTertiary_highContrast">#000000</color>
|
||||
<color name="masterialDark_tertiaryContainer_highContrast">#FFB1F5</color>
|
||||
<color name="masterialDark_onTertiaryContainer_highContrast">#000000</color>
|
||||
<color name="masterialDark_error_highContrast">#FFF9F9</color>
|
||||
<color name="masterialDark_onError_highContrast">#000000</color>
|
||||
<color name="masterialDark_errorContainer_highContrast">#FFBAB1</color>
|
||||
<color name="masterialDark_onErrorContainer_highContrast">#000000</color>
|
||||
<color name="masterialDark_background_highContrast">#13121D</color>
|
||||
<color name="masterialDark_onBackground_highContrast">#E5E0F0</color>
|
||||
<color name="masterialDark_surface_highContrast">#13121D</color>
|
||||
<color name="masterialDark_onSurface_highContrast">#FFFFFF</color>
|
||||
<color name="masterialDark_surfaceVariant_highContrast">#474557</color>
|
||||
<color name="masterialDark_onSurfaceVariant_highContrast">#FEF9FF</color>
|
||||
<color name="masterialDark_outline_highContrast">#CDC8DE</color>
|
||||
<color name="masterialDark_outlineVariant_highContrast">#CDC8DE</color>
|
||||
<color name="masterialDark_scrim_highContrast">#000000</color>
|
||||
<color name="masterialDark_inverseSurface_highContrast">#E5E0F0</color>
|
||||
<color name="masterialDark_inverseOnSurface_highContrast">#000000</color>
|
||||
<color name="masterialDark_inversePrimary_highContrast">#25008C</color>
|
||||
<color name="masterialDark_primaryFixed_highContrast">#E9E3FF</color>
|
||||
<color name="masterialDark_onPrimaryFixed_highContrast">#000000</color>
|
||||
<color name="masterialDark_primaryFixedDim_highContrast">#CCC4FF</color>
|
||||
<color name="masterialDark_onPrimaryFixedVariant_highContrast">#130056</color>
|
||||
<color name="masterialDark_secondaryFixed_highContrast">#E9E3FF</color>
|
||||
<color name="masterialDark_onSecondaryFixed_highContrast">#000000</color>
|
||||
<color name="masterialDark_secondaryFixedDim_highContrast">#CCC4FF</color>
|
||||
<color name="masterialDark_onSecondaryFixedVariant_highContrast">#130056</color>
|
||||
<color name="masterialDark_tertiaryFixed_highContrast">#FFDDF6</color>
|
||||
<color name="masterialDark_onTertiaryFixed_highContrast">#000000</color>
|
||||
<color name="masterialDark_tertiaryFixedDim_highContrast">#FFB1F5</color>
|
||||
<color name="masterialDark_onTertiaryFixedVariant_highContrast">#2F0030</color>
|
||||
<color name="masterialDark_surfaceDim_highContrast">#13121D</color>
|
||||
<color name="masterialDark_surfaceBright_highContrast">#3A3844</color>
|
||||
<color name="masterialDark_surfaceContainerLowest_highContrast">#0E0D17</color>
|
||||
<color name="masterialDark_surfaceContainerLow_highContrast">#1C1A25</color>
|
||||
<color name="masterialDark_surfaceContainer_highContrast">#201E29</color>
|
||||
<color name="masterialDark_surfaceContainerHigh_highContrast">#2A2934</color>
|
||||
<color name="masterialDark_surfaceContainerHighest_highContrast">#35333F</color>
|
||||
<color name="masterialDark_colorGoldenrod">#FFEBCE</color>
|
||||
<color name="masterialDark_colorOnGoldenrod">#412D00</color>
|
||||
<color name="masterialDark_colorGoldenrodContainer">#F9B928</color>
|
||||
<color name="masterialDark_colorOnGoldenrodContainer">#463100</color>
|
||||
<color name="masterialDark_colorLime">#FFFFFF</color>
|
||||
<color name="masterialDark_colorOnLime">#233600</color>
|
||||
<color name="masterialDark_colorLimeContainer">#A5E820</color>
|
||||
<color name="masterialDark_colorOnLimeContainer">#2E4600</color>
|
||||
<color name="masterialDark_colorGoldenrod_mediumContrast">#FFEBCE</color>
|
||||
<color name="masterialDark_colorOnGoldenrod_mediumContrast">#412D00</color>
|
||||
<color name="masterialDark_colorGoldenrodContainer_mediumContrast">#F9B928</color>
|
||||
<color name="masterialDark_colorOnGoldenrodContainer_mediumContrast">#150C00</color>
|
||||
<color name="masterialDark_colorLime_mediumContrast">#FFFFFF</color>
|
||||
<color name="masterialDark_colorOnLime_mediumContrast">#233600</color>
|
||||
<color name="masterialDark_colorLimeContainer_mediumContrast">#A5E820</color>
|
||||
<color name="masterialDark_colorOnLimeContainer_mediumContrast">#162300</color>
|
||||
<color name="masterialDark_colorGoldenrod_highContrast">#FFFAF7</color>
|
||||
<color name="masterialDark_colorOnGoldenrod_highContrast">#000000</color>
|
||||
<color name="masterialDark_colorGoldenrodContainer_highContrast">#FFC03B</color>
|
||||
<color name="masterialDark_colorOnGoldenrodContainer_highContrast">#000000</color>
|
||||
<color name="masterialDark_colorLime_highContrast">#FFFFFF</color>
|
||||
<color name="masterialDark_colorOnLime_highContrast">#000000</color>
|
||||
<color name="masterialDark_colorLimeContainer_highContrast">#A5E820</color>
|
||||
<color name="masterialDark_colorOnLimeContainer_highContrast">#000000</color>
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="hundred_dp">100</integer>
|
||||
</resources>
|
|
@ -766,4 +766,15 @@
|
|||
<item quantity="one">%,d new notification</item>
|
||||
<item quantity="other">%,d new notifications</item>
|
||||
</plurals>
|
||||
<string name="dismiss">Dismiss</string>
|
||||
<string name="donation_once">Just once</string>
|
||||
<string name="donation_monthly">Monthly</string>
|
||||
<string name="donation_yearly">Yearly</string>
|
||||
<string name="currency">Currency</string>
|
||||
<string name="donation_success_share">Spread the word</string>
|
||||
<string name="donation_success_title">Thank you for your contribution!</string>
|
||||
<string name="donation_success_subtitle">You should receive an email confirming your donation soon.</string>
|
||||
<string name="donation_server_error">We are sorry, an error occurred and we have not been able to process your donation.\n\nPlease retry in a few minutes.</string>
|
||||
<string name="settings_donate">Donate to Mastodon</string>
|
||||
<string name="settings_manage_donations">Manage donations</string>
|
||||
</resources>
|
Loading…
Reference in New Issue