Skip to content

Commit

Permalink
Add button to enable reminders in the tutorial (#218)
Browse files Browse the repository at this point in the history
* Add button to turn on notifications in tutorial (closes #217)

* Hides button once notifications are denied or accepted
* Shows help text once button is interacted with, for each possible outcome
* Removes outdated upgrade path in main for reminding the user about notifications
* Tracks from-notification main page views differently from main page views

* Share code & onResume in tutorial
  • Loading branch information
dektar authored Mar 3, 2025
1 parent 498ff19 commit 8e69a20
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static android.view.View.VISIBLE;
Expand Down Expand Up @@ -112,22 +113,12 @@ protected void onCreate(Bundle savedInstanceState) {
return;
}

// TODO: Remove this as probably no one is running the old version of the app any more.
if (!accountManager.isRemindersInfoShown(this)) {
// We haven't yet told the user that reminders exist, they probably upgraded to get here
// instead of learning about it in the tutorial. Give a dialog explaining more.
// This should only happen for users upgrading from a pretty old version of the app.
DialogFragment fragment = NewSettingsDialog.newInstance(R.string.reminders_dialog_title,
R.string.reminders_dialog_content);
getSupportFragmentManager().beginTransaction().add(fragment, NewSettingsDialog.TAG)
.commit();
accountManager.setRemindersInfoShown(this, true);
SettingsActivity.turnOnReminders(this, accountManager);
}

Intent intent = getIntent();
if (intent != null && intent.getExtras() != null &&
intent.getExtras().getBoolean(EXTRA_FROM_NOTIFICATION, false)) {
new AnalyticsManager().trackPageviewWithProps("/", this,
Map.of("fromNotification", "true"));
} else {
new AnalyticsManager().trackPageview("/", this);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference;
Expand Down Expand Up @@ -107,6 +108,21 @@ public static void turnOnReminders(Context context, AccountManager manager) {
}
}

public static ActivityResultLauncher<String> createNotificationPermissionRequest(
Fragment fragment, Consumer<Boolean> isGranted) {
// Only needed on SDK 33 (Tiramisu) and newer
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
return null;
}
if (ContextCompat.checkSelfPermission(fragment.getContext(),
Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
return null;
}
return fragment.registerForActivityResult(
new ActivityResultContracts.RequestPermission(), isGranted::accept
);
}

public static void updateNotificationsPreference(FiveCallsApplication application,
AccountManager accountManager,
String result) {
Expand Down Expand Up @@ -141,7 +157,7 @@ public static class SettingsFragment extends PreferenceFragmentCompat implements
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestNotificationPermission((isGranted) -> {
mNotificationPermissionRequest = createNotificationPermissionRequest(this, (isGranted) -> {
// If the user denied the notification permission, set the preference to false
// Otherwise they granted and we will set the permission to true
accountManager.setAllowReminders(getActivity(), isGranted);
Expand Down Expand Up @@ -211,7 +227,8 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin
accountManager.setAllowAnalytics(getActivity(), result);
} else if (TextUtils.equals(key, AccountManager.KEY_ALLOW_REMINDERS)) {
boolean result = sharedPreferences.getBoolean(key, false);
if (result && !NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()) {
if (result &&
!NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()) {
// Trying to enable reminders and notification permission is not granted
if (mNotificationPermissionRequest != null
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Expand Down Expand Up @@ -254,20 +271,6 @@ public void onStop() {
super.onStop();
}

private void requestNotificationPermission(Consumer<Boolean> isGranted) {
// Only needed on SDK 33 (Tiramisu) and newer
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
return;
}
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED) {
return;
}
mNotificationPermissionRequest = registerForActivityResult(
new ActivityResultContracts.RequestPermission(), isGranted::accept
);
}

private void updateReminderDaysSummary(MultiSelectListPreference daysPreference,
Set<String> savedValues) {
if (savedValues == null || savedValues.size() == 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package org.a5calls.android.a5calls.controller;

import android.Manifest;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;

import androidx.activity.OnBackPressedCallback;
import androidx.annotation.Nullable;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.appcompat.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import org.a5calls.android.a5calls.AppSingleton;
Expand All @@ -25,6 +20,15 @@
import java.text.NumberFormat;
import java.util.Locale;

import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;

/**
* Tutorial / splash screen activity
*/
Expand Down Expand Up @@ -146,6 +150,10 @@ public static class ThirdTutorialPageFragment extends Fragment {

private FiveCallsApi.CallRequestListener mStatusListener;
private TextView callsToDate;
private Button remindersBtn;
private TextView remindersDoneText;

private ActivityResultLauncher<String> mNotificationPermissionRequest;

public static ThirdTutorialPageFragment newInstance() {
ThirdTutorialPageFragment fragment = new ThirdTutorialPageFragment();
Expand All @@ -155,6 +163,18 @@ public static ThirdTutorialPageFragment newInstance() {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNotificationPermissionRequest =
SettingsActivity.createNotificationPermissionRequest(this, (isGranted) -> {
// If the user denied the notification permission, set the preference to false
// Otherwise they granted and we will set the permission to true.
if (isGranted) {
turnOnReminders();
} else {
remindersBtn.setVisibility(View.GONE);
remindersDoneText.setText(R.string.about_reminders_off);
remindersDoneText.setVisibility(View.VISIBLE);
}
});
}

@Override
Expand All @@ -169,30 +189,32 @@ public void onDestroy() {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.tutorial_item_3, container, false);
callsToDate = (TextView) rootView.findViewById(R.id.calls_to_date);
callsToDate = rootView.findViewById(R.id.calls_to_date);
remindersBtn = rootView.findViewById(R.id.reminders_btn);
remindersDoneText = rootView.findViewById(R.id.reminders_done);

remindersBtn.setOnClickListener(v -> {
if (!NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Show notification permission then turn on reminders depending on the result.
mNotificationPermissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS);
}
} else {
// Don't need to ask for notification permission, can simply
// directly enable reminders.
turnOnReminders();
}
});

// TODO: Re-use this listener between AboutActivity and here, since it's really the same.
mStatusListener = new FiveCallsApi.CallRequestListener() {
@Override
public void onRequestError() {
if (!isAdded()) {
// No longer attached to the activity!
return;
}
Snackbar.make(callsToDate,
getResources().getString(R.string.request_error),
Snackbar.LENGTH_LONG).show();
// unused
}

@Override
public void onJsonError() {
if (!isAdded()) {
// No longer attached to the activity!
return;
}
Snackbar.make(callsToDate,
getResources().getString(R.string.json_error),
Snackbar.LENGTH_LONG).show();
// unused
}

@Override
Expand All @@ -201,6 +223,7 @@ public void onReportReceived(int count, boolean donateOn) {
// No longer attached to the activity!
return;
}
callsToDate.setVisibility(View.VISIBLE);
callsToDate.setText(String.format(
getResources().getString(R.string.calls_to_date),
NumberFormat.getNumberInstance(Locale.US).format(count)));
Expand All @@ -216,28 +239,36 @@ public void onCallReported() {
controller.registerCallRequestListener(mStatusListener);
controller.getReport();

rootView.findViewById(R.id.btn_back).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((TutorialActivity) getActivity()).onPreviousPagePressed();
}
});
rootView.findViewById(R.id.btn_back).setOnClickListener(v ->
((TutorialActivity) getActivity()).onPreviousPagePressed());
rootView.findViewById(R.id.get_started_btn).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Set that the user has seen info about reminders.
AccountManager.Instance.setRemindersInfoShown(getActivity(), true);
SettingsActivity.turnOnReminders(getActivity(), AccountManager.Instance);

// Return to the main activity
AccountManager.Instance.setTutorialSeen(getActivity(), true);
Intent intent = new Intent(getActivity(), MainActivity.class);
startActivity(intent);
getActivity().finish();
}
});
v -> {
// Set that the user has seen info about reminders.
AccountManager.Instance.setRemindersInfoShown(getActivity(), true);

// Return to the main activity
AccountManager.Instance.setTutorialSeen(getActivity(), true);
Intent intent = new Intent(getActivity(), MainActivity.class);
startActivity(intent);
getActivity().finish();
});
return rootView;
}

@Override
public void onResume() {
super.onResume();
if (AccountManager.Instance.getAllowReminders(getContext())) {
remindersBtn.setVisibility(View.GONE);
remindersDoneText.setVisibility(View.VISIBLE);
}
}

private void turnOnReminders() {
AccountManager.Instance.setAllowReminders(getActivity(), true);
SettingsActivity.turnOnReminders(getActivity(), AccountManager.Instance);
remindersBtn.setVisibility(View.GONE);
remindersDoneText.setVisibility(View.VISIBLE);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ class AnalyticsManager {
Plausible.pageView(path, props = staticProps)
}
}

fun trackPageviewWithProps(path: String, context: Context, extraProps: Map<String, String>) {
if (!BuildConfig.DEBUG && AccountManager.Instance.allowAnalytics(context)) {
Plausible.pageView(path, path, props = extraProps + staticProps)
}
}
}
16 changes: 16 additions & 0 deletions 5calls/app/src/main/res/layout/tutorial_item_3.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<TextView
android:id="@+id/calls_to_date"
style="@style/subheadingTutorialStyle"
android:visibility="gone"
/>

<TextView
Expand All @@ -22,6 +23,21 @@
android:text="@string/about_splash_3"
/>

<Button
style="@style/genericButtonStyle"
android:id="@+id/reminders_btn"
android:text="@string/tutorial_reminders_btn"
/>

<TextView
style="@style/splashTextStyle"
android:layout_marginTop="@dimen/margin_text"
android:layout_marginBottom="@dimen/margin_text"
android:text="@string/about_reminders_on"
android:id="@+id/reminders_done"
android:visibility="gone"
/>

</LinearLayout>

<Button
Expand Down
13 changes: 4 additions & 9 deletions 5calls/app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@
<string name="about_splash_2">5 Calls te da contactos y guiones para que puedas llamar rápido y fácilmente. Usamos su ubicación para darle a sus representantes locales para que sus llamadas sean más impactantes.</string>

<!-- Text shown to the user on the splash / tutorial screen letting them know about notifications and reminders [CHAR_LIMIT=NONE] -->
<string name="about_splash_3">La aplicación de 5 Calls le ayuda a mantener la participación recordándole varias veces a la semana para hacer llamadas.
\nPuede personalizar o desactivar los recordatorios en la configuración.</string>
<string name="about_splash_3">La aplicación de 5 Calls le ayuda a mantener la participación recordándole varias veces a la semana para hacer llamadas.</string>

<!-- Text shown after reminders are enabled [CHAR_LIMIT=50] -->
<string name="about_reminders_on">Puede personalizar o activar los recordatorios en la configuración.</string>

<!-- Button shown to the user on the splash / tutorial screen to get to the next page of the tutorial [CHAR_LIMIT=30] -->
<string name="next">Siguiente</string>
Expand Down Expand Up @@ -285,13 +287,6 @@
<!-- Text for a notification reminder to make 5 calls [CHAR_LIMIT=50]-->
<string name="notification_text">Dedique 5 minutos para ponerse en contacto con sus representantes.</string>

<!-- Title for a dialog that lets users know on upgrade that reminders have been added [CHAR_LIMIT=50] -->
<string name="reminders_dialog_title">Nueva función ańadida</string>

<!-- Content for a dialog that lets users know on upgrade that reminders have been added [CHAR_LIMIT=50] -->
<string name="reminders_dialog_content">La aplicación de 5 Calls ahora puede ayudarle a mantener la participación recordándole varias veces a la semana para hacer llamadas.
\n\nPuede personalizar o desactivar los recordatorios en la configuración.</string>

<!-- Button to go to the settings activity [CHAR_LIMIT=25] -->
<string name="go_to_settings">Ve a configuración</string>

Expand Down
19 changes: 10 additions & 9 deletions 5calls/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,16 @@
<string name="about_splash_2">5 Calls provides phone numbers and scripts so calling is quick and easy, finds your local representatives so your calls have more impact.</string>

<!-- Text shown to the user on the splash / tutorial screen letting them know about notifications and reminders [CHAR_LIMIT=NONE] -->
<string name="about_splash_3">The 5 Calls app can help you maintain involvement by reminding you several times a week to make calls.
\n\nYou can turn on and customize reminders in settings.</string>
<string name="about_splash_3">The 5 Calls app can help you maintain involvement by reminding you several times a week to make calls.</string>

<!-- Button shown to turn on reminders [CHAR_LIMIT=50] -->
<string name="tutorial_reminders_btn">Turn on reminders</string>

<!-- Text shown after reminders are enabled [CHAR_LIMIT=50] -->
<string name="about_reminders_on">You can customize reminders in settings.</string>

<!-- Text shown after notifications are declined [CHAR_LIMIT=50] -->
<string name="about_reminders_off">If you change your mind, you can turn on reminders in settings.</string>

<!-- Button shown to the user on the splash / tutorial screen to get to the next page of the tutorial [CHAR_LIMIT=30] -->
<string name="next">Next</string>
Expand Down Expand Up @@ -330,13 +338,6 @@
<!-- Text for a notification reminder to make 5 calls [CHAR_LIMIT=50] -->
<string name="notification_text">Spend 5 minutes to contact your representatives.</string>

<!-- Title for a dialog that lets users know on upgrade that reminders have been added [CHAR_LIMIT=50] -->
<string name="reminders_dialog_title">New feature added</string>

<!-- Content for a dialog that lets users know on upgrade that reminders have been added [CHAR_LIMIT=50] -->
<string name="reminders_dialog_content">The 5 Calls app can now help you maintain involvement by reminding you several times a week to make calls.
\n\nYou can customize or turn off reminders in settings.</string>

<!-- Button to go to the settings activity [CHAR_LIMIT=25] -->
<string name="go_to_settings">Go to settings</string>

Expand Down

0 comments on commit 8e69a20

Please sign in to comment.