diff --git a/mastodon/build.gradle b/mastodon/build.gradle index 8fc25ecc..0328eaae 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -13,7 +13,7 @@ android { applicationId "org.joinmastodon.android" minSdk 23 targetSdk 33 - versionCode 99 + versionCode 100 versionName "2.5.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/mastodon/src/main/AndroidManifest.xml b/mastodon/src/main/AndroidManifest.xml index 7a97cfce..122e86a7 100644 --- a/mastodon/src/main/AndroidManifest.xml +++ b/mastodon/src/main/AndroidManifest.xml @@ -79,6 +79,7 @@ + diff --git a/mastodon/src/main/java/org/joinmastodon/android/DonationFragmentActivity.java b/mastodon/src/main/java/org/joinmastodon/android/DonationFragmentActivity.java new file mode 100644 index 00000000..5a72cd46 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/DonationFragmentActivity.java @@ -0,0 +1,29 @@ +package org.joinmastodon.android; + +import android.os.Bundle; + +import org.joinmastodon.android.fragments.DonationWebViewFragment; + +import androidx.annotation.Nullable; +import me.grishka.appkit.FragmentStackActivity; + +// This exists because our designer wanted to avoid extra sheet showing/hiding animations. +// This is the only way to show a fragment on top of a sheet without having to rewrite way too many things. +public class DonationFragmentActivity extends FragmentStackActivity{ + @Override + protected void onCreate(@Nullable Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + if(savedInstanceState==null){ + DonationWebViewFragment fragment=new DonationWebViewFragment(); + fragment.setArguments(getIntent().getBundleExtra("fragmentArgs")); + showFragment(fragment); + overridePendingTransition(R.anim.fragment_enter, R.anim.no_op_300ms); + } + } + + @Override + public void finish(){ + super.finish(); + overridePendingTransition(0, R.anim.fragment_exit); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/DonationWebViewFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/DonationWebViewFragment.java index 6ca545c4..1770ccf3 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/DonationWebViewFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/DonationWebViewFragment.java @@ -1,14 +1,20 @@ package org.joinmastodon.android.fragments; +import android.app.Activity; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.webkit.WebResourceRequest; +import org.joinmastodon.android.BuildConfig; import org.joinmastodon.android.E; import org.joinmastodon.android.R; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.DismissDonationCampaignBannerEvent; import org.joinmastodon.android.ui.M3AlertDialogBuilder; +import org.joinmastodon.android.ui.sheets.DonationSuccessfulSheet; import java.util.Objects; @@ -18,6 +24,14 @@ 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 onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + if(BuildConfig.DEBUG){ + setHasOptionsMenu(true); + } + } + @Override public void onViewCreated(View view, Bundle savedInstanceState){ super.onViewCreated(view, savedInstanceState); @@ -28,25 +42,47 @@ public class DonationWebViewFragment extends WebViewFragment{ 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)); + onSuccess(); 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(); + onFailure(); return true; } return false; } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ + super.onCreateOptionsMenu(menu, inflater); + if(BuildConfig.DEBUG){ + menu.add(0, 0, 0, "Simulate success"); + menu.add(0, 1, 0, "Simulate failure"); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + if(item.getItemId()==0) + onSuccess(); + else if(item.getItemId()==1) + onFailure(); + return super.onOptionsItemSelected(item); + } + + private void onFailure(){ + new M3AlertDialogBuilder(getActivity()) + .setTitle("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(); + } + + private void onSuccess(){ + String campaignID=getArguments().getString("campaignID"); + AccountSessionManager.getInstance().markDonationCampaignAsDismissed(campaignID); + E.post(new DismissDonationCampaignBannerEvent(campaignID)); + getActivity().setResult(Activity.RESULT_OK); + getActivity().finish(); + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java index bc19dec6..d4322135 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java @@ -6,6 +6,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.text.SpannableStringBuilder; @@ -55,6 +56,7 @@ import org.joinmastodon.android.model.donations.DonationCampaign; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.sheets.DonationSheet; +import org.joinmastodon.android.ui.sheets.DonationSuccessfulSheet; import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper; import org.joinmastodon.android.ui.viewcontrollers.HomeTimelineMenuController; import org.joinmastodon.android.ui.viewcontrollers.ToolbarDropdownMenuController; @@ -77,8 +79,11 @@ import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.V; +import me.grishka.appkit.views.BottomSheet; public class HomeTimelineFragment extends StatusListFragment implements ToolbarDropdownMenuController.HostFragment{ + private static final int DONATION_RESULT=211; + private ImageButton fab; private LinearLayout listsDropdown; private FixedAspectRatioImageView listsDropdownArrow; @@ -100,6 +105,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD private String maxID; private String lastSavedMarkerID; private DonationCampaign currentDonationCampaign; + private BottomSheet donationSheet; public HomeTimelineFragment(){ setListLayoutId(R.layout.fragment_timeline); @@ -129,6 +135,13 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD }) .execNoAuth(""); } + E.register(this); + } + + @Override + public void onDestroy(){ + super.onDestroy(); + E.unregister(this); } @Override @@ -639,6 +652,7 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD updateUpdateState(ev.state); } + @Subscribe public void onDismissDonationCampaignBanner(DismissDonationCampaignBannerEvent ev){ if(currentDonationCampaign!=null && ev.campaignID.equals(currentDonationCampaign.id)){ dismissDonationBanner(); @@ -699,6 +713,17 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD super.onDataLoaded(d, more); } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data){ + if(requestCode==DONATION_RESULT){ + if(donationSheet!=null) + donationSheet.dismissWithoutAnimation(); + if(resultCode==Activity.RESULT_OK){ + new DonationSuccessfulSheet(getActivity(), accountID).showWithoutAnimation(); + } + } + } + private String getCurrentListTitle(){ return switch(listMode){ case FOLLOWING -> getString(R.string.timeline_following); @@ -773,7 +798,9 @@ public class HomeTimelineFragment extends StatusListFragment implements ToolbarD } private void openDonationSheet(){ - new DonationSheet(getActivity(), currentDonationCampaign, accountID).show(); + donationSheet=new DonationSheet(getActivity(), currentDonationCampaign, accountID, intent->startActivityForResult(intent, DONATION_RESULT)); + donationSheet.setOnDismissListener(dialog->donationSheet=null); + donationSheet.show(); } private enum ListMode{ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DonationSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DonationSheet.java index f14140cd..ee5f8498 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DonationSheet.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DonationSheet.java @@ -2,6 +2,7 @@ package org.joinmastodon.android.ui.sheets; import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; @@ -11,11 +12,11 @@ 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.DonationFragmentActivity; import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.DonationWebViewFragment; import org.joinmastodon.android.model.donations.DonationCampaign; @@ -29,10 +30,10 @@ import java.util.Arrays; import java.util.Currency; import java.util.List; import java.util.Locale; +import java.util.function.Consumer; import java.util.stream.Collectors; import androidx.annotation.NonNull; -import me.grishka.appkit.Nav; import me.grishka.appkit.utils.CustomViewHelper; import me.grishka.appkit.utils.V; import me.grishka.appkit.views.BottomSheet; @@ -40,6 +41,7 @@ import me.grishka.appkit.views.BottomSheet; public class DonationSheet extends BottomSheet{ private final DonationCampaign campaign; private final String accountID; + private final Consumer startCallback; private DonationFrequency frequency=DonationFrequency.MONTHLY; private View onceTab, monthlyTab, yearlyTab; @@ -50,11 +52,12 @@ public class DonationSheet extends BottomSheet{ private TextView buttonText; private Activity activity; - public DonationSheet(@NonNull Activity activity, DonationCampaign campaign, String accountID){ + public DonationSheet(@NonNull Activity activity, DonationCampaign campaign, String accountID, Consumer startCallback){ super(activity); this.campaign=campaign; this.accountID=accountID; this.activity=activity; + this.startCallback=startCallback; Context context=activity; View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_donation, null); @@ -246,8 +249,8 @@ public class DonationSheet extends BottomSheet{ args.putString("url", builder.build().toString()); args.putString("account", accountID); args.putString("campaignID", campaign.id); - Nav.go(activity, DonationWebViewFragment.class, args); - dismiss(); + args.putBoolean("_can_go_back", true); + startCallback.accept(new Intent(activity, DonationFragmentActivity.class).putExtra("fragmentArgs", args)); } private static long getMinimumChargeAmount(String currency){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DonationSuccessfulSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DonationSuccessfulSheet.java new file mode 100644 index 00000000..cce55203 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DonationSuccessfulSheet.java @@ -0,0 +1,36 @@ +package org.joinmastodon.android.ui.sheets; + +import android.app.Activity; +import android.content.Context; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.fragments.ComposeFragment; +import org.joinmastodon.android.ui.utils.UiUtils; + +import androidx.annotation.NonNull; +import me.grishka.appkit.Nav; +import me.grishka.appkit.views.BottomSheet; + +public class DonationSuccessfulSheet extends BottomSheet{ + + public DonationSuccessfulSheet(@NonNull Context context, @NonNull String accountID){ + super(context); + View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_donation_success, null); + setContentView(content); + setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface), + UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme()); + + content.findViewById(R.id.btn_done).setOnClickListener(v->dismiss()); + content.findViewById(R.id.btn_share).setOnClickListener(v->{ + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putString("prefilledText", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi a sapien metus. Nunc feugiat a felis sed hendrerit."); + Nav.go((Activity) context, ComposeFragment.class, args); + dismiss(); + }); + } +} diff --git a/mastodon/src/main/res/anim/fragment_enter.xml b/mastodon/src/main/res/anim/fragment_enter.xml new file mode 100644 index 00000000..073d62d1 --- /dev/null +++ b/mastodon/src/main/res/anim/fragment_enter.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/anim/fragment_exit.xml b/mastodon/src/main/res/anim/fragment_exit.xml new file mode 100644 index 00000000..8dcf659c --- /dev/null +++ b/mastodon/src/main/res/anim/fragment_exit.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/anim/no_op_300ms.xml b/mastodon/src/main/res/anim/no_op_300ms.xml new file mode 100644 index 00000000..86216b32 --- /dev/null +++ b/mastodon/src/main/res/anim/no_op_300ms.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable-nodpi/donation_successful_art.webp b/mastodon/src/main/res/drawable-nodpi/donation_successful_art.webp new file mode 100644 index 00000000..aea38372 Binary files /dev/null and b/mastodon/src/main/res/drawable-nodpi/donation_successful_art.webp differ diff --git a/mastodon/src/main/res/drawable/ic_campaign_20px.xml b/mastodon/src/main/res/drawable/ic_campaign_20px.xml new file mode 100644 index 00000000..b386285c --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_campaign_20px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/interpolator-v21/cubic_bezier_default.xml b/mastodon/src/main/res/interpolator-v21/cubic_bezier_default.xml new file mode 100644 index 00000000..9d568940 --- /dev/null +++ b/mastodon/src/main/res/interpolator-v21/cubic_bezier_default.xml @@ -0,0 +1,3 @@ + + diff --git a/mastodon/src/main/res/layout/sheet_donation_success.xml b/mastodon/src/main/res/layout/sheet_donation_success.xml new file mode 100644 index 00000000..a9600665 --- /dev/null +++ b/mastodon/src/main/res/layout/sheet_donation_success.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + +