Initial implementation of donations
This commit is contained in:
parent
1124bc48c2
commit
b2d49c3143
|
@ -13,7 +13,7 @@ android {
|
||||||
applicationId "org.joinmastodon.android"
|
applicationId "org.joinmastodon.android"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 93
|
versionCode 94
|
||||||
versionName "2.5.0"
|
versionName "2.5.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.PaginatedResponse;
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.model.SearchResult;
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -45,8 +44,8 @@ import me.grishka.appkit.utils.WorkerThread;
|
||||||
public class CacheController{
|
public class CacheController{
|
||||||
private static final String TAG="CacheController";
|
private static final String TAG="CacheController";
|
||||||
private static final int DB_VERSION=3;
|
private static final int DB_VERSION=3;
|
||||||
private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
public static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
||||||
private static final Handler uiHandler=new Handler(Looper.getMainLooper());
|
public static final Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
private final String accountID;
|
private final String accountID;
|
||||||
private DatabaseHelper db;
|
private DatabaseHelper db;
|
||||||
|
@ -467,9 +466,4 @@ public class CacheController{
|
||||||
db.execSQL("ALTER TABLE `notifications_mentions` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.joinmastodon.android.api.requests.catalog;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.donations.DonationCampaign;
|
||||||
|
|
||||||
|
public class GetDonationCampaigns extends MastodonAPIRequest<DonationCampaign>{
|
||||||
|
private final String locale, seed;
|
||||||
|
|
||||||
|
public GetDonationCampaigns(String locale, String seed){
|
||||||
|
super(HttpMethod.GET, null, DonationCampaign.class);
|
||||||
|
this.locale=locale;
|
||||||
|
this.seed=seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getURL(){
|
||||||
|
Uri.Builder builder=new Uri.Builder()
|
||||||
|
.scheme("https")
|
||||||
|
.authority("api.joinmastodon.org")
|
||||||
|
.path("/donations/campaigns")
|
||||||
|
.appendQueryParameter("platform", "android")
|
||||||
|
.appendQueryParameter("locale", locale)
|
||||||
|
.appendQueryParameter("seed", seed);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,6 @@ import org.joinmastodon.android.model.TimelineMarkers;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
import org.joinmastodon.android.utils.ObjectIdComparator;
|
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
@ -276,4 +275,12 @@ public class AccountSession{
|
||||||
public void setNotificationsMentionsOnly(boolean mentionsOnly){
|
public void setNotificationsMentionsOnly(boolean mentionsOnly){
|
||||||
getRawLocalPreferences().edit().putBoolean("notificationsMentionsOnly", mentionsOnly).apply();
|
getRawLocalPreferences().edit().putBoolean("notificationsMentionsOnly", mentionsOnly).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEligibleForDonations(){
|
||||||
|
return "mastodon.social".equalsIgnoreCase(domain) || "mastodon.online".equalsIgnoreCase(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Activity;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.content.pm.ShortcutInfo;
|
||||||
import android.content.pm.ShortcutManager;
|
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.graphics.drawable.Icon;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
@ -18,11 +23,13 @@ import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.CacheController;
|
||||||
|
import org.joinmastodon.android.api.DatabaseRunnable;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
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.filters.GetLegacyFilters;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
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.instance.GetInstance;
|
||||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||||
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
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.Application;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.EmojiCategory;
|
import org.joinmastodon.android.model.EmojiCategory;
|
||||||
import org.joinmastodon.android.model.LegacyFilter;
|
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.model.LegacyFilter;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -60,6 +68,7 @@ public class AccountSessionManager{
|
||||||
private static final String TAG="AccountSessionManager";
|
private static final String TAG="AccountSessionManager";
|
||||||
public static final String SCOPE="read write follow push";
|
public static final String SCOPE="read write follow push";
|
||||||
public static final String REDIRECT_URI="mastodon-android-auth://callback";
|
public static final String REDIRECT_URI="mastodon-android-auth://callback";
|
||||||
|
private static final int DB_VERSION=1;
|
||||||
|
|
||||||
private static final AccountSessionManager instance=new AccountSessionManager();
|
private static final AccountSessionManager instance=new AccountSessionManager();
|
||||||
|
|
||||||
|
@ -73,6 +82,8 @@ public class AccountSessionManager{
|
||||||
private String lastActiveAccountID;
|
private String lastActiveAccountID;
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
private boolean loadedInstances;
|
private boolean loadedInstances;
|
||||||
|
private DatabaseHelper db;
|
||||||
|
private final Runnable databaseCloseRunnable=this::closeDatabase;
|
||||||
|
|
||||||
public static AccountSessionManager getInstance(){
|
public static AccountSessionManager getInstance(){
|
||||||
return instance;
|
return instance;
|
||||||
|
@ -442,6 +453,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{
|
private static class SessionsStorageWrapper{
|
||||||
public List<AccountSession> accounts;
|
public List<AccountSession> accounts;
|
||||||
}
|
}
|
||||||
|
@ -451,4 +524,24 @@ public class AccountSessionManager{
|
||||||
public List<Emoji> emojis;
|
public List<Emoji> emojis;
|
||||||
public long lastUpdated;
|
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,52 @@
|
||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.webkit.WebResourceRequest;
|
||||||
|
|
||||||
|
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/donation/success";
|
||||||
|
public static final String FAILURE_URL="https://sponsor.joinmastodon.org/donation/failure";
|
||||||
|
|
||||||
|
@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)){
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle("Success")
|
||||||
|
.setMessage("Some sort of UI that would tell the user that their payment was successful")
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.setOnDismissListener(dlg->Nav.finish(this))
|
||||||
|
.show();
|
||||||
|
String campaignID=getArguments().getString("campaignID");
|
||||||
|
AccountSessionManager.getInstance().markDonationCampaignAsDismissed(campaignID);
|
||||||
|
E.post(new DismissDonationCampaignBannerEvent(campaignID));
|
||||||
|
return true;
|
||||||
|
}else if(url.equalsIgnoreCase(FAILURE_URL)){
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle("Failure")
|
||||||
|
.setMessage("Some sort of UI that would tell the user that their payment didn't go through")
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.setOnDismissListener(dlg->Nav.finish(this))
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,13 +7,19 @@ import android.animation.ObjectAnimator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
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.Gravity;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewStub;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
@ -29,11 +35,13 @@ import com.squareup.otto.Subscribe;
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
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.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.DismissDonationCampaignBannerEvent;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
|
@ -41,8 +49,10 @@ import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.FollowList;
|
import org.joinmastodon.android.model.FollowList;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineMarkers;
|
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.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.sheets.DonationSheet;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.ui.viewcontrollers.HomeTimelineMenuController;
|
import org.joinmastodon.android.ui.viewcontrollers.HomeTimelineMenuController;
|
||||||
import org.joinmastodon.android.ui.viewcontrollers.ToolbarDropdownMenuController;
|
import org.joinmastodon.android.ui.viewcontrollers.ToolbarDropdownMenuController;
|
||||||
|
@ -53,6 +63,7 @@ import org.parceler.Parcels;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -81,9 +92,12 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||||
private FollowList currentList;
|
private FollowList currentList;
|
||||||
private MergeRecyclerAdapter mergeAdapter;
|
private MergeRecyclerAdapter mergeAdapter;
|
||||||
private DiscoverInfoBannerHelper localTimelineBannerHelper;
|
private DiscoverInfoBannerHelper localTimelineBannerHelper;
|
||||||
|
private View donationBanner;
|
||||||
|
private boolean donationBannerDismissing;
|
||||||
|
|
||||||
private String maxID;
|
private String maxID;
|
||||||
private String lastSavedMarkerID;
|
private String lastSavedMarkerID;
|
||||||
|
private DonationCampaign currentDonationCampaign;
|
||||||
|
|
||||||
public HomeTimelineFragment(){
|
public HomeTimelineFragment(){
|
||||||
setListLayoutId(R.layout.fragment_timeline);
|
setListLayoutId(R.layout.fragment_timeline);
|
||||||
|
@ -93,6 +107,23 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
localTimelineBannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE, accountID);
|
localTimelineBannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE, accountID);
|
||||||
|
|
||||||
|
// TODO how often do we do this request? Maybe cache something somewhere?
|
||||||
|
if(AccountSessionManager.get(accountID).isEligibleForDonations()){
|
||||||
|
new GetDonationCampaigns(Locale.getDefault().toLanguageTag().replace('-', '_'), String.valueOf(AccountSessionManager.get(accountID).getDonationSeed()))
|
||||||
|
.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("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -599,6 +630,12 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD
|
||||||
updateUpdateState(ev.state);
|
updateUpdateState(ev.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onDismissDonationCampaignBanner(DismissDonationCampaignBannerEvent ev){
|
||||||
|
if(currentDonationCampaign!=null && ev.campaignID.equals(currentDonationCampaign.id)){
|
||||||
|
dismissDonationBanner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||||
return true;
|
return true;
|
||||||
|
@ -661,6 +698,75 @@ 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(){
|
||||||
|
new DonationSheet(getActivity(), currentDonationCampaign, accountID).show();
|
||||||
|
}
|
||||||
|
|
||||||
private enum ListMode{
|
private enum ListMode{
|
||||||
FOLLOWING,
|
FOLLOWING,
|
||||||
LOCAL,
|
LOCAL,
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
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.api.MastodonErrorResponse;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.fragments.LoaderFragment;
|
||||||
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
|
|
||||||
|
public abstract class WebViewFragment extends LoaderFragment implements OnBackPressedListener{
|
||||||
|
protected WebView webView;
|
||||||
|
|
||||||
|
@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){
|
||||||
|
dataLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error){
|
||||||
|
onError(new MastodonErrorResponse(error.getDescription().toString(), -1, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request){
|
||||||
|
return WebViewFragment.this.shouldOverrideUrlLoading(request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
webView.getSettings().setJavaScriptEnabled(true);
|
||||||
|
return webView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRefresh(){
|
||||||
|
webView.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onBackPressed(){
|
||||||
|
if(webView.canGoBack()){
|
||||||
|
webView.goBack();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onToolbarNavigationClick(){
|
||||||
|
Nav.finish(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean shouldOverrideUrlLoading(WebResourceRequest req);
|
||||||
|
}
|
|
@ -28,7 +28,8 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
||||||
selfUpdateItem=new ListItem<>("Force self-update", null, this::onForceSelfUpdateClick),
|
selfUpdateItem=new ListItem<>("Force self-update", null, this::onForceSelfUpdateClick),
|
||||||
resetUpdateItem=new ListItem<>("Reset self-updater", null, this::onResetUpdaterClick),
|
resetUpdateItem=new ListItem<>("Reset self-updater", null, this::onResetUpdaterClick),
|
||||||
new ListItem<>("Reset search info banners", null, this::onResetDiscoverBannersClick),
|
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)
|
||||||
));
|
));
|
||||||
if(!GithubSelfUpdater.needSelfUpdating()){
|
if(!GithubSelfUpdater.needSelfUpdating()){
|
||||||
resetUpdateItem.isEnabled=selfUpdateItem.isEnabled=false;
|
resetUpdateItem.isEnabled=selfUpdateItem.isEnabled=false;
|
||||||
|
@ -70,6 +71,11 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
|
||||||
Toast.makeText(getActivity(), "Pre-reply sheets were reset", Toast.LENGTH_SHORT).show();
|
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(){
|
private void restartUI(){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
@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,288 @@
|
||||||
|
package org.joinmastodon.android.ui.sheets;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
|
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.Currency;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
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 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){
|
||||||
|
super(activity);
|
||||||
|
this.campaign=campaign;
|
||||||
|
this.accountID=accountID;
|
||||||
|
this.activity=activity;
|
||||||
|
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);
|
||||||
|
|
||||||
|
LinearLayout 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;
|
||||||
|
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
lp.rightMargin=V.dp(8);
|
||||||
|
suggestedAmounts.addView(btn, lp);
|
||||||
|
}
|
||||||
|
updateSuggestedAmounts(campaign.defaultCurrency);
|
||||||
|
button.setEnabled(false);
|
||||||
|
buttonText.setText(campaign.bannerButtonText);
|
||||||
|
button.setOnClickListener(v->openWebView());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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("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);
|
||||||
|
Nav.go(activity, DonationWebViewFragment.class, args);
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,336 @@
|
||||||
|
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());
|
||||||
|
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);
|
||||||
|
this.paint.setAlpha(77);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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){
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<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">
|
<item android:state_selected="true">
|
||||||
<ripple android:color="@color/m3_on_secondary_container_overlay">
|
<ripple android:color="@color/m3_on_secondary_container_overlay">
|
||||||
<item>
|
<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,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="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,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"/>
|
android:text="@string/see_new_posts"/>
|
||||||
</FrameLayout>
|
</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>
|
</FrameLayout>
|
||||||
</me.grishka.appkit.views.RecursiveSwipeRefreshLayout>
|
</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"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/suggested_amounts"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
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"
|
||||||
|
tools:text="Donate"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</org.joinmastodon.android.ui.views.CustomScrollView>
|
|
@ -0,0 +1,336 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- light -->
|
||||||
|
<color name="masterialLight_primary">#5E5791</color>
|
||||||
|
<color name="masterialLight_onPrimary">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_primaryContainer">#E5DEFF</color>
|
||||||
|
<color name="masterialLight_onPrimaryContainer">#1A1249</color>
|
||||||
|
<color name="masterialLight_secondary">#5F5C71</color>
|
||||||
|
<color name="masterialLight_onSecondary">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_secondaryContainer">#E5DFF9</color>
|
||||||
|
<color name="masterialLight_onSecondaryContainer">#1C192C</color>
|
||||||
|
<color name="masterialLight_tertiary">#7B5265</color>
|
||||||
|
<color name="masterialLight_onTertiary">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_tertiaryContainer">#FFD8E7</color>
|
||||||
|
<color name="masterialLight_onTertiaryContainer">#301121</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">#1C1B20</color>
|
||||||
|
<color name="masterialLight_surface">#FCF8FF</color>
|
||||||
|
<color name="masterialLight_onSurface">#1C1B20</color>
|
||||||
|
<color name="masterialLight_surfaceVariant">#E5E0EC</color>
|
||||||
|
<color name="masterialLight_onSurfaceVariant">#47464F</color>
|
||||||
|
<color name="masterialLight_outline">#78767F</color>
|
||||||
|
<color name="masterialLight_outlineVariant">#C9C5D0</color>
|
||||||
|
<color name="masterialLight_scrim">#000000</color>
|
||||||
|
<color name="masterialLight_inverseSurface">#313036</color>
|
||||||
|
<color name="masterialLight_inverseOnSurface">#F4EFF7</color>
|
||||||
|
<color name="masterialLight_inversePrimary">#C7BFFF</color>
|
||||||
|
<color name="masterialLight_primaryFixed">#E5DEFF</color>
|
||||||
|
<color name="masterialLight_onPrimaryFixed">#1A1249</color>
|
||||||
|
<color name="masterialLight_primaryFixedDim">#C7BFFF</color>
|
||||||
|
<color name="masterialLight_onPrimaryFixedVariant">#463F77</color>
|
||||||
|
<color name="masterialLight_secondaryFixed">#E5DFF9</color>
|
||||||
|
<color name="masterialLight_onSecondaryFixed">#1C192C</color>
|
||||||
|
<color name="masterialLight_secondaryFixedDim">#C8C3DC</color>
|
||||||
|
<color name="masterialLight_onSecondaryFixedVariant">#474459</color>
|
||||||
|
<color name="masterialLight_tertiaryFixed">#FFD8E7</color>
|
||||||
|
<color name="masterialLight_onTertiaryFixed">#301121</color>
|
||||||
|
<color name="masterialLight_tertiaryFixedDim">#ECB8CE</color>
|
||||||
|
<color name="masterialLight_onTertiaryFixedVariant">#613B4D</color>
|
||||||
|
<color name="masterialLight_surfaceDim">#DDD8E0</color>
|
||||||
|
<color name="masterialLight_surfaceBright">#FCF8FF</color>
|
||||||
|
<color name="masterialLight_surfaceContainerLowest">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_surfaceContainerLow">#F7F2FA</color>
|
||||||
|
<color name="masterialLight_surfaceContainer">#F1ECF4</color>
|
||||||
|
<color name="masterialLight_surfaceContainerHigh">#EBE6EF</color>
|
||||||
|
<color name="masterialLight_surfaceContainerHighest">#E5E1E9</color>
|
||||||
|
<color name="masterialLight_primary_mediumContrast">#423B73</color>
|
||||||
|
<color name="masterialLight_onPrimary_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_primaryContainer_mediumContrast">#746DA9</color>
|
||||||
|
<color name="masterialLight_onPrimaryContainer_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_secondary_mediumContrast">#434055</color>
|
||||||
|
<color name="masterialLight_onSecondary_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_secondaryContainer_mediumContrast">#767288</color>
|
||||||
|
<color name="masterialLight_onSecondaryContainer_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_tertiary_mediumContrast">#5D3749</color>
|
||||||
|
<color name="masterialLight_onTertiary_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_tertiaryContainer_mediumContrast">#94687B</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">#1C1B20</color>
|
||||||
|
<color name="masterialLight_surface_mediumContrast">#FCF8FF</color>
|
||||||
|
<color name="masterialLight_onSurface_mediumContrast">#1C1B20</color>
|
||||||
|
<color name="masterialLight_surfaceVariant_mediumContrast">#E5E0EC</color>
|
||||||
|
<color name="masterialLight_onSurfaceVariant_mediumContrast">#44424B</color>
|
||||||
|
<color name="masterialLight_outline_mediumContrast">#605E67</color>
|
||||||
|
<color name="masterialLight_outlineVariant_mediumContrast">#7C7983</color>
|
||||||
|
<color name="masterialLight_scrim_mediumContrast">#000000</color>
|
||||||
|
<color name="masterialLight_inverseSurface_mediumContrast">#313036</color>
|
||||||
|
<color name="masterialLight_inverseOnSurface_mediumContrast">#F4EFF7</color>
|
||||||
|
<color name="masterialLight_inversePrimary_mediumContrast">#C7BFFF</color>
|
||||||
|
<color name="masterialLight_primaryFixed_mediumContrast">#746DA9</color>
|
||||||
|
<color name="masterialLight_onPrimaryFixed_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_primaryFixedDim_mediumContrast">#5C558E</color>
|
||||||
|
<color name="masterialLight_onPrimaryFixedVariant_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_secondaryFixed_mediumContrast">#767288</color>
|
||||||
|
<color name="masterialLight_onSecondaryFixed_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_secondaryFixedDim_mediumContrast">#5D596F</color>
|
||||||
|
<color name="masterialLight_onSecondaryFixedVariant_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_tertiaryFixed_mediumContrast">#94687B</color>
|
||||||
|
<color name="masterialLight_onTertiaryFixed_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_tertiaryFixedDim_mediumContrast">#795063</color>
|
||||||
|
<color name="masterialLight_onTertiaryFixedVariant_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_surfaceDim_mediumContrast">#DDD8E0</color>
|
||||||
|
<color name="masterialLight_surfaceBright_mediumContrast">#FCF8FF</color>
|
||||||
|
<color name="masterialLight_surfaceContainerLowest_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_surfaceContainerLow_mediumContrast">#F7F2FA</color>
|
||||||
|
<color name="masterialLight_surfaceContainer_mediumContrast">#F1ECF4</color>
|
||||||
|
<color name="masterialLight_surfaceContainerHigh_mediumContrast">#EBE6EF</color>
|
||||||
|
<color name="masterialLight_surfaceContainerHighest_mediumContrast">#E5E1E9</color>
|
||||||
|
<color name="masterialLight_primary_highContrast">#211950</color>
|
||||||
|
<color name="masterialLight_onPrimary_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_primaryContainer_highContrast">#423B73</color>
|
||||||
|
<color name="masterialLight_onPrimaryContainer_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_secondary_highContrast">#222033</color>
|
||||||
|
<color name="masterialLight_onSecondary_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_secondaryContainer_highContrast">#434055</color>
|
||||||
|
<color name="masterialLight_onSecondaryContainer_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_tertiary_highContrast">#381728</color>
|
||||||
|
<color name="masterialLight_onTertiary_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_tertiaryContainer_highContrast">#5D3749</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">#1C1B20</color>
|
||||||
|
<color name="masterialLight_surface_highContrast">#FCF8FF</color>
|
||||||
|
<color name="masterialLight_onSurface_highContrast">#000000</color>
|
||||||
|
<color name="masterialLight_surfaceVariant_highContrast">#E5E0EC</color>
|
||||||
|
<color name="masterialLight_onSurfaceVariant_highContrast">#24232B</color>
|
||||||
|
<color name="masterialLight_outline_highContrast">#44424B</color>
|
||||||
|
<color name="masterialLight_outlineVariant_highContrast">#44424B</color>
|
||||||
|
<color name="masterialLight_scrim_highContrast">#000000</color>
|
||||||
|
<color name="masterialLight_inverseSurface_highContrast">#313036</color>
|
||||||
|
<color name="masterialLight_inverseOnSurface_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_inversePrimary_highContrast">#EFE9FF</color>
|
||||||
|
<color name="masterialLight_primaryFixed_highContrast">#423B73</color>
|
||||||
|
<color name="masterialLight_onPrimaryFixed_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_primaryFixedDim_highContrast">#2C255B</color>
|
||||||
|
<color name="masterialLight_onPrimaryFixedVariant_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_secondaryFixed_highContrast">#434055</color>
|
||||||
|
<color name="masterialLight_onSecondaryFixed_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_secondaryFixedDim_highContrast">#2D2A3E</color>
|
||||||
|
<color name="masterialLight_onSecondaryFixedVariant_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_tertiaryFixed_highContrast">#5D3749</color>
|
||||||
|
<color name="masterialLight_onTertiaryFixed_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_tertiaryFixedDim_highContrast">#442233</color>
|
||||||
|
<color name="masterialLight_onTertiaryFixedVariant_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_surfaceDim_highContrast">#DDD8E0</color>
|
||||||
|
<color name="masterialLight_surfaceBright_highContrast">#FCF8FF</color>
|
||||||
|
<color name="masterialLight_surfaceContainerLowest_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_surfaceContainerLow_highContrast">#F7F2FA</color>
|
||||||
|
<color name="masterialLight_surfaceContainer_highContrast">#F1ECF4</color>
|
||||||
|
<color name="masterialLight_surfaceContainerHigh_highContrast">#EBE6EF</color>
|
||||||
|
<color name="masterialLight_surfaceContainerHighest_highContrast">#E5E1E9</color>
|
||||||
|
<color name="masterialLight_colorGoldenrod">#7A590C</color>
|
||||||
|
<color name="masterialLight_colorOnGoldenrod">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_colorGoldenrodContainer">#FFDEA6</color>
|
||||||
|
<color name="masterialLight_colorOnGoldenrodContainer">#271900</color>
|
||||||
|
<color name="masterialLight_colorLime">#4F6628</color>
|
||||||
|
<color name="masterialLight_colorOnLime">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_colorLimeContainer">#D1ECA0</color>
|
||||||
|
<color name="masterialLight_colorOnLimeContainer">#121F00</color>
|
||||||
|
<color name="masterialLight_colorGoldenrod_mediumContrast">#583E00</color>
|
||||||
|
<color name="masterialLight_colorOnGoldenrod_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_colorGoldenrodContainer_mediumContrast">#936F23</color>
|
||||||
|
<color name="masterialLight_colorOnGoldenrodContainer_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_colorLime_mediumContrast">#34490F</color>
|
||||||
|
<color name="masterialLight_colorOnLime_mediumContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialLight_colorLimeContainer_mediumContrast">#657C3C</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">#34490F</color>
|
||||||
|
<color name="masterialLight_colorOnLimeContainer_highContrast">#FFFFFF</color>
|
||||||
|
|
||||||
|
<!-- dark -->
|
||||||
|
<color name="masterialDark_primary">#C7BFFF</color>
|
||||||
|
<color name="masterialDark_onPrimary">#2F285F</color>
|
||||||
|
<color name="masterialDark_primaryContainer">#463F77</color>
|
||||||
|
<color name="masterialDark_onPrimaryContainer">#E5DEFF</color>
|
||||||
|
<color name="masterialDark_secondary">#C8C3DC</color>
|
||||||
|
<color name="masterialDark_onSecondary">#312E41</color>
|
||||||
|
<color name="masterialDark_secondaryContainer">#474459</color>
|
||||||
|
<color name="masterialDark_onSecondaryContainer">#E5DFF9</color>
|
||||||
|
<color name="masterialDark_tertiary">#ECB8CE</color>
|
||||||
|
<color name="masterialDark_onTertiary">#482536</color>
|
||||||
|
<color name="masterialDark_tertiaryContainer">#613B4D</color>
|
||||||
|
<color name="masterialDark_onTertiaryContainer">#FFD8E7</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">#141318</color>
|
||||||
|
<color name="masterialDark_onBackground">#E5E1E9</color>
|
||||||
|
<color name="masterialDark_surface">#141318</color>
|
||||||
|
<color name="masterialDark_onSurface">#E5E1E9</color>
|
||||||
|
<color name="masterialDark_surfaceVariant">#47464F</color>
|
||||||
|
<color name="masterialDark_onSurfaceVariant">#C9C5D0</color>
|
||||||
|
<color name="masterialDark_outline">#928F99</color>
|
||||||
|
<color name="masterialDark_outlineVariant">#47464F</color>
|
||||||
|
<color name="masterialDark_scrim">#000000</color>
|
||||||
|
<color name="masterialDark_inverseSurface">#E5E1E9</color>
|
||||||
|
<color name="masterialDark_inverseOnSurface">#313036</color>
|
||||||
|
<color name="masterialDark_inversePrimary">#5E5791</color>
|
||||||
|
<color name="masterialDark_primaryFixed">#E5DEFF</color>
|
||||||
|
<color name="masterialDark_onPrimaryFixed">#1A1249</color>
|
||||||
|
<color name="masterialDark_primaryFixedDim">#C7BFFF</color>
|
||||||
|
<color name="masterialDark_onPrimaryFixedVariant">#463F77</color>
|
||||||
|
<color name="masterialDark_secondaryFixed">#E5DFF9</color>
|
||||||
|
<color name="masterialDark_onSecondaryFixed">#1C192C</color>
|
||||||
|
<color name="masterialDark_secondaryFixedDim">#C8C3DC</color>
|
||||||
|
<color name="masterialDark_onSecondaryFixedVariant">#474459</color>
|
||||||
|
<color name="masterialDark_tertiaryFixed">#FFD8E7</color>
|
||||||
|
<color name="masterialDark_onTertiaryFixed">#301121</color>
|
||||||
|
<color name="masterialDark_tertiaryFixedDim">#ECB8CE</color>
|
||||||
|
<color name="masterialDark_onTertiaryFixedVariant">#613B4D</color>
|
||||||
|
<color name="masterialDark_surfaceDim">#141318</color>
|
||||||
|
<color name="masterialDark_surfaceBright">#3A383E</color>
|
||||||
|
<color name="masterialDark_surfaceContainerLowest">#0E0E13</color>
|
||||||
|
<color name="masterialDark_surfaceContainerLow">#1C1B20</color>
|
||||||
|
<color name="masterialDark_surfaceContainer">#201F25</color>
|
||||||
|
<color name="masterialDark_surfaceContainerHigh">#2A292F</color>
|
||||||
|
<color name="masterialDark_surfaceContainerHighest">#35343A</color>
|
||||||
|
<color name="masterialDark_primary_mediumContrast">#CCC4FF</color>
|
||||||
|
<color name="masterialDark_onPrimary_mediumContrast">#150B44</color>
|
||||||
|
<color name="masterialDark_primaryContainer_mediumContrast">#9189C7</color>
|
||||||
|
<color name="masterialDark_onPrimaryContainer_mediumContrast">#000000</color>
|
||||||
|
<color name="masterialDark_secondary_mediumContrast">#CDC8E1</color>
|
||||||
|
<color name="masterialDark_onSecondary_mediumContrast">#161426</color>
|
||||||
|
<color name="masterialDark_secondaryContainer_mediumContrast">#928EA5</color>
|
||||||
|
<color name="masterialDark_onSecondaryContainer_mediumContrast">#000000</color>
|
||||||
|
<color name="masterialDark_tertiary_mediumContrast">#F1BCD2</color>
|
||||||
|
<color name="masterialDark_onTertiary_mediumContrast">#2A0B1C</color>
|
||||||
|
<color name="masterialDark_tertiaryContainer_mediumContrast">#B28498</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">#141318</color>
|
||||||
|
<color name="masterialDark_onBackground_mediumContrast">#E5E1E9</color>
|
||||||
|
<color name="masterialDark_surface_mediumContrast">#141318</color>
|
||||||
|
<color name="masterialDark_onSurface_mediumContrast">#FEF9FF</color>
|
||||||
|
<color name="masterialDark_surfaceVariant_mediumContrast">#47464F</color>
|
||||||
|
<color name="masterialDark_onSurfaceVariant_mediumContrast">#CDC9D4</color>
|
||||||
|
<color name="masterialDark_outline_mediumContrast">#A5A1AC</color>
|
||||||
|
<color name="masterialDark_outlineVariant_mediumContrast">#85828C</color>
|
||||||
|
<color name="masterialDark_scrim_mediumContrast">#000000</color>
|
||||||
|
<color name="masterialDark_inverseSurface_mediumContrast">#E5E1E9</color>
|
||||||
|
<color name="masterialDark_inverseOnSurface_mediumContrast">#2A292F</color>
|
||||||
|
<color name="masterialDark_inversePrimary_mediumContrast">#474179</color>
|
||||||
|
<color name="masterialDark_primaryFixed_mediumContrast">#E5DEFF</color>
|
||||||
|
<color name="masterialDark_onPrimaryFixed_mediumContrast">#0F053F</color>
|
||||||
|
<color name="masterialDark_primaryFixedDim_mediumContrast">#C7BFFF</color>
|
||||||
|
<color name="masterialDark_onPrimaryFixedVariant_mediumContrast">#352E65</color>
|
||||||
|
<color name="masterialDark_secondaryFixed_mediumContrast">#E5DFF9</color>
|
||||||
|
<color name="masterialDark_onSecondaryFixed_mediumContrast">#110F21</color>
|
||||||
|
<color name="masterialDark_secondaryFixedDim_mediumContrast">#C8C3DC</color>
|
||||||
|
<color name="masterialDark_onSecondaryFixedVariant_mediumContrast">#363447</color>
|
||||||
|
<color name="masterialDark_tertiaryFixed_mediumContrast">#FFD8E7</color>
|
||||||
|
<color name="masterialDark_onTertiaryFixed_mediumContrast">#230716</color>
|
||||||
|
<color name="masterialDark_tertiaryFixedDim_mediumContrast">#ECB8CE</color>
|
||||||
|
<color name="masterialDark_onTertiaryFixedVariant_mediumContrast">#4F2B3C</color>
|
||||||
|
<color name="masterialDark_surfaceDim_mediumContrast">#141318</color>
|
||||||
|
<color name="masterialDark_surfaceBright_mediumContrast">#3A383E</color>
|
||||||
|
<color name="masterialDark_surfaceContainerLowest_mediumContrast">#0E0E13</color>
|
||||||
|
<color name="masterialDark_surfaceContainerLow_mediumContrast">#1C1B20</color>
|
||||||
|
<color name="masterialDark_surfaceContainer_mediumContrast">#201F25</color>
|
||||||
|
<color name="masterialDark_surfaceContainerHigh_mediumContrast">#2A292F</color>
|
||||||
|
<color name="masterialDark_surfaceContainerHighest_mediumContrast">#35343A</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">#CDC8E1</color>
|
||||||
|
<color name="masterialDark_onSecondaryContainer_highContrast">#000000</color>
|
||||||
|
<color name="masterialDark_tertiary_highContrast">#FFF9F9</color>
|
||||||
|
<color name="masterialDark_onTertiary_highContrast">#000000</color>
|
||||||
|
<color name="masterialDark_tertiaryContainer_highContrast">#F1BCD2</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">#141318</color>
|
||||||
|
<color name="masterialDark_onBackground_highContrast">#E5E1E9</color>
|
||||||
|
<color name="masterialDark_surface_highContrast">#141318</color>
|
||||||
|
<color name="masterialDark_onSurface_highContrast">#FFFFFF</color>
|
||||||
|
<color name="masterialDark_surfaceVariant_highContrast">#47464F</color>
|
||||||
|
<color name="masterialDark_onSurfaceVariant_highContrast">#FEF9FF</color>
|
||||||
|
<color name="masterialDark_outline_highContrast">#CDC9D4</color>
|
||||||
|
<color name="masterialDark_outlineVariant_highContrast">#CDC9D4</color>
|
||||||
|
<color name="masterialDark_scrim_highContrast">#000000</color>
|
||||||
|
<color name="masterialDark_inverseSurface_highContrast">#E5E1E9</color>
|
||||||
|
<color name="masterialDark_inverseOnSurface_highContrast">#000000</color>
|
||||||
|
<color name="masterialDark_inversePrimary_highContrast">#292258</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">#150B44</color>
|
||||||
|
<color name="masterialDark_secondaryFixed_highContrast">#E9E3FD</color>
|
||||||
|
<color name="masterialDark_onSecondaryFixed_highContrast">#000000</color>
|
||||||
|
<color name="masterialDark_secondaryFixedDim_highContrast">#CDC8E1</color>
|
||||||
|
<color name="masterialDark_onSecondaryFixedVariant_highContrast">#161426</color>
|
||||||
|
<color name="masterialDark_tertiaryFixed_highContrast">#FFDEEA</color>
|
||||||
|
<color name="masterialDark_onTertiaryFixed_highContrast">#000000</color>
|
||||||
|
<color name="masterialDark_tertiaryFixedDim_highContrast">#F1BCD2</color>
|
||||||
|
<color name="masterialDark_onTertiaryFixedVariant_highContrast">#2A0B1C</color>
|
||||||
|
<color name="masterialDark_surfaceDim_highContrast">#141318</color>
|
||||||
|
<color name="masterialDark_surfaceBright_highContrast">#3A383E</color>
|
||||||
|
<color name="masterialDark_surfaceContainerLowest_highContrast">#0E0E13</color>
|
||||||
|
<color name="masterialDark_surfaceContainerLow_highContrast">#1C1B20</color>
|
||||||
|
<color name="masterialDark_surfaceContainer_highContrast">#201F25</color>
|
||||||
|
<color name="masterialDark_surfaceContainerHigh_highContrast">#2A292F</color>
|
||||||
|
<color name="masterialDark_surfaceContainerHighest_highContrast">#35343A</color>
|
||||||
|
<color name="masterialDark_colorGoldenrod">#EDC06C</color>
|
||||||
|
<color name="masterialDark_colorOnGoldenrod">#412D00</color>
|
||||||
|
<color name="masterialDark_colorGoldenrodContainer">#5D4200</color>
|
||||||
|
<color name="masterialDark_colorOnGoldenrodContainer">#FFDEA6</color>
|
||||||
|
<color name="masterialDark_colorLime">#B5D087</color>
|
||||||
|
<color name="masterialDark_colorOnLime">#233600</color>
|
||||||
|
<color name="masterialDark_colorLimeContainer">#384D12</color>
|
||||||
|
<color name="masterialDark_colorOnLimeContainer">#D1ECA0</color>
|
||||||
|
<color name="masterialDark_colorGoldenrod_mediumContrast">#F1C470</color>
|
||||||
|
<color name="masterialDark_colorOnGoldenrod_mediumContrast">#201400</color>
|
||||||
|
<color name="masterialDark_colorGoldenrodContainer_mediumContrast">#B28B3D</color>
|
||||||
|
<color name="masterialDark_colorOnGoldenrodContainer_mediumContrast">#000000</color>
|
||||||
|
<color name="masterialDark_colorLime_mediumContrast">#B9D48A</color>
|
||||||
|
<color name="masterialDark_colorOnLime_mediumContrast">#0E1900</color>
|
||||||
|
<color name="masterialDark_colorLimeContainer_mediumContrast">#809956</color>
|
||||||
|
<color name="masterialDark_colorOnLimeContainer_mediumContrast">#000000</color>
|
||||||
|
<color name="masterialDark_colorGoldenrod_highContrast">#FFFAF7</color>
|
||||||
|
<color name="masterialDark_colorOnGoldenrod_highContrast">#000000</color>
|
||||||
|
<color name="masterialDark_colorGoldenrodContainer_highContrast">#F1C470</color>
|
||||||
|
<color name="masterialDark_colorOnGoldenrodContainer_highContrast">#000000</color>
|
||||||
|
<color name="masterialDark_colorLime_highContrast">#F5FFDC</color>
|
||||||
|
<color name="masterialDark_colorOnLime_highContrast">#000000</color>
|
||||||
|
<color name="masterialDark_colorLimeContainer_highContrast">#B9D48A</color>
|
||||||
|
<color name="masterialDark_colorOnLimeContainer_highContrast">#000000</color>
|
||||||
|
</resources>
|
|
@ -747,4 +747,9 @@
|
||||||
<string name="notifications_from_user">Notifications from %s</string>
|
<string name="notifications_from_user">Notifications from %s</string>
|
||||||
<string name="notifications_muted">Notifications from %s have been dismissed.</string>
|
<string name="notifications_muted">Notifications from %s have been dismissed.</string>
|
||||||
<string name="notifications_allowed">%s will now appear in your notification list.</string>
|
<string name="notifications_allowed">%s will now appear in your notification list.</string>
|
||||||
|
<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>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue