Skip to content

Commit

Permalink
Adds Donation UI
Browse files Browse the repository at this point in the history
In short, this adds a card UI to the map screen that prompts the user to donate money. Tapping "Learn More" will show a new activity that provides more information about why OBA needs money. Tapping "Donate" will open a browser directed at the donations page. Tapping the "X" button will show an alert asking the user if they're willing to donate later potentially.

This UI will only be shown in the main OBA build flavor. White label apps won't show it.
  • Loading branch information
aaronbrethorst committed Mar 30, 2024
1 parent 930cead commit 4441278
Show file tree
Hide file tree
Showing 20 changed files with 491 additions and 6 deletions.
1 change: 1 addition & 0 deletions onebusaway-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ dependencies {
implementation 'edu.usf.cutr.opentripplanner.android:opentripplanner-pojos:1.0.0-SNAPSHOT'
// Pelias for point-of-interest search and geocoding for trip planning origin and destination
implementation 'edu.usf.cutr:pelias-client-library:1.1.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
// Google Play Services Maps (only for Google flavor)
googleImplementation 'com.google.android.gms:play-services-maps:18.2.0'
// Google Play Services Places is required by ProprietaryMapHelpV2 (only for Google flavor)
Expand Down
10 changes: 10 additions & 0 deletions onebusaway-android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@
android:name="android.app.default_searchable"
android:value="org.onebusaway.android.ui.SearchActivity" />

<activity
android:name=".ui.DonationLearnMoreActivity"
android:exported="false"
android:label="@string/title_activity_donation_learn_more"
android:parentActivityName=".ui.HomeActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.onebusaway.android.ui.PreferencesActivity" />
</activity>

<activity
android:name="org.onebusaway.android.ui.SearchActivity"
android:launchMode="singleTop"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

import org.onebusaway.android.BuildConfig;
import org.onebusaway.android.R;
import org.onebusaway.android.donations.DonationsManager;
import org.onebusaway.android.io.ObaAnalytics;
import org.onebusaway.android.io.ObaApi;
import org.onebusaway.android.io.elements.ObaRegion;
Expand Down Expand Up @@ -76,6 +77,8 @@ public class Application extends MultiDexApplication {

private SharedPreferences mPrefs;

private DonationsManager mDonationsManager;

private static Application mApp;

/**
Expand Down Expand Up @@ -109,6 +112,7 @@ public void onCreate() {
createNotificationChannels();

TravelBehaviorManager.startCollectingData(getApplicationContext());
mDonationsManager = new DonationsManager(mPrefs, mFirebaseAnalytics, getResources());
}

/**
Expand All @@ -133,6 +137,8 @@ public static SharedPreferences getPrefs() {
return get().mPrefs;
}

public static DonationsManager getDonationsManager() { return get().mDonationsManager; }

/**
* Returns the last known location that the application has seen, or null if we haven't seen a
* location yet. When trying to get a most recent location in one shot, this method should
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package org.onebusaway.android.donations;

import com.google.firebase.analytics.FirebaseAnalytics;

import org.onebusaway.android.R;
import org.onebusaway.android.app.Application;
import org.onebusaway.android.io.ObaAnalytics;
import org.onebusaway.android.util.BuildFlavorUtils;
import org.onebusaway.android.util.PreferenceUtils;

import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.net.Uri;

import java.util.Date;

public class DonationsManager {
private SharedPreferences mPreferences;

private Resources mResources;

public DonationsManager(SharedPreferences preferences, FirebaseAnalytics firebaseAnalytics, Resources resources) {
this.mPreferences = preferences;
this.mAnalytics = firebaseAnalytics;
this.mResources = resources;
}

// region Analytics

private FirebaseAnalytics mAnalytics;

/**
* Reports UI events using Firebase
* @param resourceID ID of the UI element to report
* @param state the state or variant of the UI item, or null if the item doesn't have a state or variant
*/
private void reportUIEvent(Integer resourceID, String state) {
ObaAnalytics.reportUiEvent(mAnalytics, mResources.getString(resourceID), state);
}

// endregion

// Preference Keys

private static String donationRequestDismissedDateKey = "donationRequestDismissedDateKey";
private static String donationRequestReminderDateKey = "donationRequestReminderDateKey";

// region Donation Request Dismissal

/**
* @return The date that donation requests were hidden by the user, either because they donated
* or because they tapped the 'dismiss' button, or null if the date has not been set.
*/
public Date getDonationRequestDismissedDate() {
Long timestamp = mPreferences.getLong(donationRequestDismissedDateKey, -1);
if (timestamp < 1) {
return null;
}

return new Date(timestamp);
}

/**
* Sets the date that the donation request UI was dismissed on. Pass in null to 'reset' the UI.
* @param date The dismissal date.
*/
public void setDonationRequestDismissedDate(Date date) {
PreferenceUtils.saveLong(
mPreferences,
donationRequestDismissedDateKey,
date == null ? -1 : date.getTime()
);
}

/**
* Hides subsequent attempts to request donations.
*/
public void dismissDonationRequests() {
setDonationRequestDismissedDate(new Date());
}

// endregion

// region Donation Request Reminder

/**
* @return Optional date at which the app should remind the user to donate.
*/
public Date getDonationRequestReminderDate() {
Long timestamp = mPreferences.getLong(donationRequestReminderDateKey, -1);
if (timestamp < 1) {
return null;
}

return new Date(timestamp);
}

public void setDonationRequestReminderDate(Date date) {
PreferenceUtils.saveLong(
mPreferences,
donationRequestReminderDateKey,
date == null ? -1 : date.getTime()
);
}

/**
* Sets a date two weeks in the future on which the app will remind the user to donate.
*/
public void remindUserLater() {
long twoWeeksInMilliseconds = 86400 * 14 * 1000; // Seconds in a day * 14 days * 1000 milliseconds
Date futureDate = new Date((new Date()).getTime() + twoWeeksInMilliseconds);
setDonationRequestReminderDate(futureDate);
}

// endregion

// region State

/**
* Determines whether the app should show the donation UI based on a number of factors.
* @return True if the donation UI should be shown; false otherwise.
*/
public boolean shouldShowDonationUI() {
// white-label apps should not show the donation UI.
if (!BuildFlavorUtils.isOBABuildFlavor()) {
return false;
}

// Don't show the UI if there's a reminder date that is still in the future.
Date reminderDate = getDonationRequestReminderDate();
if (reminderDate != null && reminderDate.after(new Date())) {
return false;
}

// Show the donation UI if the user has not explicitly dismissed it.
return getDonationRequestDismissedDate() == null;
}

// endregion

// region UI/Activities

public Intent buildOpenDonationsPageIntent() {
reportUIEvent(R.string.analytics_label_button_press_donate, null);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mResources.getString(R.string.donate_url)));
return intent;
}

// endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.onebusaway.android.ui;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import org.onebusaway.android.R;
import org.onebusaway.android.app.Application;

public class DonationLearnMoreActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_donation_learn_more);

Button btn = findViewById(R.id.btnDonationViewDonate);
btn.setOnClickListener(b -> {
Application.getDonationsManager().dismissDonationRequests();
Intent i = Application.getDonationsManager().buildOpenDonationsPageIntent();
startActivity(i);
finish();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.onebusaway.android.BuildConfig;
import org.onebusaway.android.R;
import org.onebusaway.android.app.Application;
import org.onebusaway.android.donations.DonationsManager;
import org.onebusaway.android.io.ObaAnalytics;
import org.onebusaway.android.io.elements.ObaRegion;
import org.onebusaway.android.io.elements.ObaRoute;
Expand Down Expand Up @@ -86,6 +87,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
Expand All @@ -107,6 +109,7 @@
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
Expand Down Expand Up @@ -186,6 +189,8 @@ interface SlidingPanelController {

CardView weatherView;

View mDonationView;

private FloatingActionButton mFabMyLocation;

uk.co.markormesher.android_fab.FloatingActionButton mLayersFab;
Expand Down Expand Up @@ -395,6 +400,8 @@ public void onCreate(Bundle savedInstanceState) {

UIUtils.setupActionBar(this);

setupDonationView(this);

// To enable checkBatteryOptimizations, also uncomment the
// REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission in AndroidManifest.xml
// See https://github.com/OneBusAway/onebusaway-android/pull/988#discussion_r299950506
Expand Down Expand Up @@ -451,6 +458,8 @@ public void onResume() {
updateLayersFab();

mFabMyLocation.requestLayout();

updateDonationsUIVisibility();
}

@Override
Expand Down Expand Up @@ -2046,4 +2055,69 @@ public void onWeatherResponseReceived(ObaWeatherResponse response) {
public void onWeatherRequestFailed() {
Log.d(TAG,"Weather Request Fail");
}

private void setupDonationView(HomeActivity homeActivity) {
mDonationView = findViewById(R.id.donationView);
AppCompatImageButton closeButton = mDonationView.findViewById(R.id.btnDonationViewClose);
Button learnMoreButton = mDonationView.findViewById(R.id.btnDonationViewLearnMore);
Button donateButton = mDonationView.findViewById(R.id.btnDonationViewDonate);

closeButton.setOnClickListener(b -> {
AlertDialog dismissDialog = buildDismissDonationsDialog();
dismissDialog.show();
});

learnMoreButton.setOnClickListener(b -> {
Intent intent = new Intent(this, DonationLearnMoreActivity.class);
startActivity(intent);
});

donateButton.setOnClickListener(b -> {
DonationsManager donationsManager = Application.getDonationsManager();
donationsManager.dismissDonationRequests();

Intent intent = donationsManager.buildOpenDonationsPageIntent();
startActivity(intent);
});

updateDonationsUIVisibility();
}

private void updateDonationsUIVisibility() {
mDonationView = findViewById(R.id.donationView);
DonationsManager donationsManager = Application.getDonationsManager();

if (donationsManager.shouldShowDonationUI()) {
mDonationView.setVisibility(View.VISIBLE);
}
else {
mDonationView.setVisibility(View.GONE);
}
}

/**
* Creates an AlertDialog that will give the user options for dismissing the donations UI.
* @return the AlertDialog for presentation.
*/
private AlertDialog buildDismissDonationsDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle(R.string.donation_dismiss_dialog_title)
.setMessage(R.string.donation_dismiss_dialog_body)
.setNegativeButton(
R.string.donation_dismiss_dialog_dont_want_to_help_button,
(dialog, which) -> {
Application.getDonationsManager().dismissDonationRequests();
updateDonationsUIVisibility();
}
)
.setNeutralButton(R.string.donation_dismiss_dialog_remind_me_later_button,
(dialog, which) -> {
Application.getDonationsManager().remindUserLater();
updateDonationsUIVisibility();
})
.setPositiveButton(R.string.donation_dismiss_dialog_cancel_button, (d, w) -> {})
.setCancelable(true);

return builder.create();
}
}
Loading

0 comments on commit 4441278

Please sign in to comment.