From aef3369c5ec60473756682342a2863ab3ae0218c Mon Sep 17 00:00:00 2001 From: Katie Dektar Date: Mon, 16 Feb 2026 10:59:18 -0800 Subject: [PATCH 01/10] Adds a placeholder starter issue with a placeholder fake contact TODOs: strings in XML, tests, check with issues that have actual placeholder reps. --- .../a5calls/adapter/IssuesAdapter.java | 26 +++++++++++++++-- .../a5calls/controller/IssueActivity.java | 16 +++++++++-- .../a5calls/controller/MainActivity.java | 22 +++++++++++++-- .../a5calls/controller/RepCallActivity.java | 22 +++++++++++++-- .../a5calls/controller/SettingsActivity.java | 7 +++++ .../android/a5calls/model/AccountManager.java | 18 ++++++++++++ .../android/a5calls/model/Contact.java | 13 +++++++-- .../a5calls/android/a5calls/model/Issue.java | 28 +++++++++++++++++++ .../android/a5calls/model/IssueStats.java | 4 +++ .../src/main/res/layout/activity_issue.xml | 5 ++++ .../main/res/layout/placeholder_done_view.xml | 20 +++++++++++++ 5calls/app/src/main/res/xml/settings.xml | 9 ++++++ 12 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 5calls/app/src/main/res/layout/placeholder_done_view.xml diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/adapter/IssuesAdapter.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/adapter/IssuesAdapter.java index a2298ed4..e70004a4 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/adapter/IssuesAdapter.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/adapter/IssuesAdapter.java @@ -12,6 +12,7 @@ import org.a5calls.android.a5calls.AppSingleton; import org.a5calls.android.a5calls.R; +import org.a5calls.android.a5calls.model.AccountManager; import org.a5calls.android.a5calls.model.Category; import org.a5calls.android.a5calls.model.Contact; import org.a5calls.android.a5calls.model.DatabaseHelper; @@ -335,6 +336,21 @@ public void onClick(View v) { } }); + if (issue.isPlaceholder) { + vh.numCalls.setVisibility(View.VISIBLE); + if (AccountManager.Instance.getPlaceholderIssueCalled(mActivity)) { + vh.numCalls.setVisibility(View.GONE); + vh.previousCallStats.setVisibility(View.VISIBLE); + // TODO move to strings.xml + vh.previousCallStats.setText( + "1 previous (pretend) call"); + } else { + vh.numCalls.setText(mActivity.getResources().getString(R.string.call_count_one)); + vh.previousCallStats.setVisibility(View.GONE); + } + return; + } + if (mAddressErrorType != NO_ERROR) { // If there was an address error, clear the number of calls to make. vh.numCalls.setText(""); @@ -527,16 +543,20 @@ public EmptySearchViewHolder(View itemView) { /** * Sorts a list of issues to prioritize those with meta values (state abbreviations) at the top, - * then sorts the remaining issues. Both groups maintain their internal sort order. + * then sorts the remaining issues. Both groups maintain their internal sort order. Placeholder + * issues are always put at the very top. */ @VisibleForTesting ArrayList sortIssuesWithMetaPriority(List issues) { + ArrayList placeholders = new ArrayList<>(); ArrayList withMeta = new ArrayList<>(); ArrayList withoutMeta = new ArrayList<>(); // Separate issues with and without meta values for (Issue issue : issues) { - if (!TextUtils.isEmpty(issue.meta)) { + if (issue.isPlaceholder) { + placeholders.add(issue); + } else if (!TextUtils.isEmpty(issue.meta)) { withMeta.add(issue); } else { withoutMeta.add(issue); @@ -544,11 +564,13 @@ ArrayList sortIssuesWithMetaPriority(List issues) { } // Sort each group independently by sort field (maintaining consistent order) + Collections.sort(placeholders, (a, b) -> Integer.compare(a.sort, b.sort)); Collections.sort(withMeta, (a, b) -> Integer.compare(a.sort, b.sort)); Collections.sort(withoutMeta, (a, b) -> Integer.compare(a.sort, b.sort)); // Combine: meta issues first, then regular issues ArrayList result = new ArrayList<>(); + result.addAll(placeholders); result.addAll(withMeta); result.addAll(withoutMeta); diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/IssueActivity.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/IssueActivity.java index 633b119e..b5ad357b 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/IssueActivity.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/IssueActivity.java @@ -79,12 +79,14 @@ public class IssueActivity extends AppCompatActivity implements FiveCallsApi.Scr public static final int RESULT_OK = 1; public static final int RESULT_SERVER_ERROR = 2; + public static final int RESULT_DEMO_CALLED = 3; private static final String DONATE_URL = "https://secure.actblue.com/donate/5calls-donate?refcode=android&refcode2="; private static final int MIN_CALLS_TO_SHOW_CALL_STATS = 10; private boolean mShowServerError = false; + private boolean mShowPlaceholderCalled = false; private Issue mIssue; private String mAddress; @@ -115,6 +117,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { if (result.getResultCode() == RESULT_SERVER_ERROR) { mShowServerError = true; } + if (result.getResultCode() == RESULT_DEMO_CALLED) { + mShowPlaceholderCalled = true; + } }); FiveCallsApi api = AppSingleton.getInstance(this).getJsonController(); @@ -350,6 +355,10 @@ protected void onResume() { binding.noContactAreas.setVisibility(View.VISIBLE); return; } + if (mShowPlaceholderCalled) { + binding.placeholderDone.getRoot().setVisibility(View.VISIBLE); + AccountManager.Instance.setShowPlaceholderIssue(getApplicationContext(), false); + } showContactsUi(); } @@ -583,7 +592,7 @@ private void populateRepView(View repView, Contact contact, final int index, } else { contactReason.setVisibility(View.GONE); } - if (!contact.isPlaceholder) { + if (!contact.isPlaceholder || contact.area.equals(Contact.AREA_DEMO)) { if (!TextUtils.isEmpty(contact.photoURL)) { Glide.with(getApplicationContext()) .load(contact.photoURL) @@ -593,7 +602,7 @@ private void populateRepView(View repView, Contact contact, final int index, .into(repImage); } // Show a bit about whether they've been contacted yet today. - if (hasCalledToday) { + if (hasCalledToday || (mIssue.isPlaceholder && mShowPlaceholderCalled)) { contactChecked.setImageLevel(1); contactChecked.setContentDescription(getResources().getString( R.string.contact_done_img_description)); @@ -676,6 +685,9 @@ private void showIssueDetails() { @VisibleForTesting static String getIssueDetailsMessage(Context context, Issue issue) { StringBuilder result = new StringBuilder(); + if (issue.isPlaceholder) { + return "This is an example issue. To see it again later, you can go to Settings."; + } if (issue.categories.length > 0) { if (issue.categories.length == 1) { result.append(context.getResources().getString(R.string.issue_category_one)); diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java index ad4374a2..1de4b35e 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java @@ -93,6 +93,7 @@ public class MainActivity extends AppCompatActivity implements IssuesAdapter.Cal private boolean mShowLowAccuracyWarning = true; private boolean mDonateIsOn = false; private FirebaseAuth mAuth = null; + private int mCallCount = 0; private ActivityMainBinding binding; @@ -461,6 +462,21 @@ public void onJsonError() { @Override public void onIssuesReceived(List issues) { populateFilterAdapterIfNeeded(issues); + + // TODO refactor into helper. + // Option to force show the placeholder call in settings. + boolean forceShowPlaceholder = AccountManager.Instance.showPlaceholderIssue(getApplicationContext()); + // If they've called more than 3 times, don't bother with the placeholder any more. + boolean showPlaceholderIfEarly = mCallCount <= 3 && !accountManager.getPlaceholderIssueCalled(getApplicationContext()); + if (forceShowPlaceholder || showPlaceholderIfEarly) { + // TODO put strings in strings.xml + Contact demoContact = Contact.createPlaceholder("0", "Representative Name", "(555) 555-5555", Contact.AREA_DEMO, "This is a pretend contact"); + Issue demoIssue = Issue.createPlaceholder("0", "Feeling unsure? Start here!", "/issue/demoIssue", + "This is where we'll put a description. You can get back to this in the app's settings, \"Show the example call\".\n\nDon't worry, this is just an example, you won't be connected with your representative.", + "*You can read from the script or improvise. Tip: tap the contact's phone number above to open your dialer app, start the call on speaker phone, then switch back to 5 Calls to see the script while you are calling.*\n\nHi, my name is **[NAME]** and I’m a constituent from [CITY, ZIP]. I'm calling to ask [REP/SEN NAME] to support bill 123. Thank you for your time.\n\n*When you have finished the call, mark your result depending on whether you reached a person, left a voicemail, or called but could do neither.*\n\n***Practice reading the script out loud, then go ahead and click a result!***", true, 0, + Collections.singletonList(demoContact), Collections.emptyList(), Collections.emptyList()); + issues.add(demoIssue); + } mIssuesAdapter.setAllIssues(issues, IssuesAdapter.NO_ERROR); mIssuesAdapter.setFilterAndSearch(mFilterText, mSearchText); binding.swipeContainer.setRefreshing(false); @@ -648,12 +664,12 @@ public void onNothingSelected(AdapterView adapterView) { } private void loadStats() { - int callCount = AppSingleton.getInstance(getApplicationContext()) + mCallCount = AppSingleton.getInstance(getApplicationContext()) .getDatabaseHelper().getCallsCount(); - if (callCount > 1) { + if (mCallCount > 1) { // Don't bother if it is less than 1. binding.actionBarSubtitle.setText(String.format( - getResources().getString(R.string.your_call_count_summary), callCount)); + getResources().getString(R.string.your_call_count_summary), mCallCount)); } } diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/RepCallActivity.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/RepCallActivity.java index 61044f6b..0ac653cd 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/RepCallActivity.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/RepCallActivity.java @@ -153,8 +153,13 @@ public void onCallReported() { outcomeAdapter = new OutcomeAdapter(issueOutcomes, new OutcomeAdapter.Callback() { @Override public void onOutcomeClicked(Outcome outcome) { - reportEvent(outcome.label); - reportCall(outcome, address); + if (!mIssue.isPlaceholder) { + reportEvent(outcome.label); + reportCall(outcome, address); + } else { + AccountManager.Instance.setPlaceholderIssueCalled(getApplicationContext(), true); + returnToIssueWithDemoCalled(); + } } }); @@ -379,6 +384,19 @@ private void returnToIssueWithServerError() { finish(); } + private void returnToIssueWithDemoCalled() { + if (isFinishing()) { + return; + } + Intent upIntent = NavUtils.getParentActivityIntent(this); + if (upIntent == null) { + return; + } + upIntent.putExtra(IssueActivity.KEY_ISSUE, mIssue); + setResult(IssueActivity.RESULT_DEMO_CALLED, upIntent); + finish(); + } + private int getSpanCount(Activity activity) { DisplayMetrics displayMetrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/SettingsActivity.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/SettingsActivity.java index 6ff16ada..9eca8aef 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/SettingsActivity.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/SettingsActivity.java @@ -218,6 +218,10 @@ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable S ListPreference notificationPref = (ListPreference) findPreference(AccountManager.KEY_NOTIFICATIONS); notificationPref.setValue(notificationSetting); + + boolean showPlaceholderIssue = accountManager.showPlaceholderIssue(getActivity()); + ((SwitchPreference) findPreference(AccountManager.KEY_SHOW_PLACEHOLDER_CALLED)) + .setChecked(hasReminders); } @Override @@ -276,6 +280,9 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin } else if (TextUtils.equals(key, "prefsKeyScriptTextSize")) { String value = sharedPreferences.getString(AccountManager.KEY_SCRIPT_TEXT_SIZE_SP, getString(R.string.script_text_size_normal_sp)); AccountManager.Instance.setScriptTextSize(getActivity(), Float.parseFloat(value)); + } else if (TextUtils.equals(key, AccountManager.KEY_SHOW_PLACEHOLDER_CALLED)) { + boolean result = sharedPreferences.getBoolean(key, false); + AccountManager.Instance.setShowPlaceholderIssue(getActivity(), result); } } diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/AccountManager.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/AccountManager.java index 407af9b6..da6683b8 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/AccountManager.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/AccountManager.java @@ -42,6 +42,8 @@ public enum AccountManager { public static final String KEY_SCRIPT_TEXT_SIZE_SP = "prefsKeyScriptTextSize"; private static final String KEY_USER_STATE = "prefsKeyUserState"; private static final String KEY_USER_DISTRICT = "prefsKeyUserDistrict"; + private static final String KEY_PLACEHOLDER_CALLED = "prefsKeyPlaceholderCalled"; + public static final String KEY_SHOW_PLACEHOLDER_CALLED = "prefsKeyShowPlaceholderIssue"; // Default to 11 am. public static final int DEFAULT_REMINDER_MINUTES = 60 * 11; @@ -231,6 +233,22 @@ public void setScriptTextSize(Context context, float value) { getSharedPrefs(context).edit().putFloat(KEY_SCRIPT_TEXT_SIZE_SP, value).apply(); } + public void setPlaceholderIssueCalled(Context context, boolean value) { + getSharedPrefs(context).edit().putBoolean(KEY_PLACEHOLDER_CALLED, value).apply(); + } + + public boolean getPlaceholderIssueCalled(Context context) { + return getSharedPrefs(context).getBoolean(KEY_PLACEHOLDER_CALLED, false); + } + + public void setShowPlaceholderIssue(Context context, boolean value) { + getSharedPrefs(context).edit().putBoolean(KEY_SHOW_PLACEHOLDER_CALLED, value).apply(); + } + + public boolean showPlaceholderIssue(Context context) { + return getSharedPrefs(context).getBoolean(KEY_SHOW_PLACEHOLDER_CALLED, false); + } + private SharedPreferences getSharedPrefs(Context context) { return context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); } diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Contact.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Contact.java index d50a1f00..dd905ee0 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Contact.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Contact.java @@ -20,6 +20,10 @@ public class Contact implements Parcelable { public static final String AREA_ATTORNEY_GENERAL = "AttorneyGeneral"; public static final String AREA_SECRETARY_OF_STATE = "SecretaryOfState"; + // Used to show the placeholder contact for the demonstration issue. + public static final String AREA_DEMO = "demo"; + + public String id; public String name; public String phone; @@ -49,16 +53,21 @@ protected Contact(Parcel in) { isPlaceholder = in.readInt() == 1; } - protected Contact(String id, String name, String reason, String area, boolean isPlaceholder) { + protected Contact(String id, String name, String reason, String phone, String area, boolean isPlaceholder) { this.id = id; this.name = name; this.reason = reason; + this.phone = phone; this.area = area; this.isPlaceholder = isPlaceholder; } public static Contact createPlaceholder(String id, String name, String reason, String area) { - return new Contact(id, name, reason, area, /* isPlaceholder= */ true); + return createPlaceholder(id, name, reason, /*phone=*/"", area); + } + + public static Contact createPlaceholder(String id, String name, String phone, String area, String reason) { + return new Contact(id, name, reason, phone, area, /* isPlaceholder= */ true); } public static final Creator CREATOR = new Creator() { diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Issue.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Issue.java index 0a19a349..3116945c 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Issue.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Issue.java @@ -4,6 +4,7 @@ import android.os.Parcelable; import android.text.TextUtils; +import java.util.Collections; import java.util.List; /** @@ -28,9 +29,34 @@ public class Issue implements Parcelable { public Category[] categories; public boolean isSplit; public IssueStats stats; + public boolean isPlaceholder = false; public List customizedScripts; + public static Issue createPlaceholder(String id, String name, String permalink, String reason, + String script, boolean active, int sort, List contacts, + List contactAreas, List outcomeModels) { + Issue issue = new Issue(); + issue.id = id; + issue.name = name; + issue.permalink = permalink; + issue.reason = reason; + issue.script = script; + issue.active = active; + issue.sort = sort; + issue.contacts = contacts; + issue.contactAreas = Collections.singletonList("demo"); + issue.outcomeModels = outcomeModels; + + issue.stats = new IssueStats(0); + issue.isPlaceholder = true; + return issue; + } + + private Issue() { + + } + protected Issue(Parcel in) { id = in.readString(); name = in.readString(); @@ -53,6 +79,7 @@ protected Issue(Parcel in) { categories = in.createTypedArray(Category.CREATOR); stats = IssueStats.CREATOR.createFromParcel(in); customizedScripts = in.createTypedArrayList(CustomizedContactScript.CREATOR); + isPlaceholder = in.readInt() != 0; } public static final Creator CREATOR = new Creator() { @@ -92,6 +119,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeTypedArray(categories, PARCELABLE_WRITE_RETURN_VALUE); stats.writeToParcel(dest, flags); dest.writeTypedList(customizedScripts); + dest.writeInt(isPlaceholder ? 1 : 0); } public String getScriptForContact(String contactId) { diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/IssueStats.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/IssueStats.java index 64605b24..8b6341d2 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/IssueStats.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/IssueStats.java @@ -6,6 +6,10 @@ public class IssueStats implements Parcelable { public int calls; + public IssueStats(int calls) { + this.calls = calls; + } + protected IssueStats(Parcel in) { calls = in.readInt(); } diff --git a/5calls/app/src/main/res/layout/activity_issue.xml b/5calls/app/src/main/res/layout/activity_issue.xml index 08479d6e..b465d807 100644 --- a/5calls/app/src/main/res/layout/activity_issue.xml +++ b/5calls/app/src/main/res/layout/activity_issue.xml @@ -59,6 +59,11 @@ layout="@layout/issue_done_view" /> + + + + + + + + \ No newline at end of file diff --git a/5calls/app/src/main/res/xml/settings.xml b/5calls/app/src/main/res/xml/settings.xml index 920f4f91..e80fef11 100644 --- a/5calls/app/src/main/res/xml/settings.xml +++ b/5calls/app/src/main/res/xml/settings.xml @@ -98,6 +98,15 @@ android:singleLineTitle="false" /> + + \ No newline at end of file From 1209c6903c17edc86c225dd5551becbc631dac62 Mon Sep 17 00:00:00 2001 From: Katie Dektar Date: Mon, 16 Feb 2026 15:33:21 -0800 Subject: [PATCH 02/10] Move strings to strings.xml --- .../a5calls/adapter/IssuesAdapter.java | 3 +- .../a5calls/controller/IssueActivity.java | 2 +- .../a5calls/controller/MainActivity.java | 18 ++++++--- .../main/res/layout/placeholder_done_view.xml | 3 +- 5calls/app/src/main/res/values/strings.xml | 40 +++++++++++++++++++ 5calls/app/src/main/res/xml/settings.xml | 4 +- 6 files changed, 58 insertions(+), 12 deletions(-) diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/adapter/IssuesAdapter.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/adapter/IssuesAdapter.java index e70004a4..ece0f697 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/adapter/IssuesAdapter.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/adapter/IssuesAdapter.java @@ -341,9 +341,8 @@ public void onClick(View v) { if (AccountManager.Instance.getPlaceholderIssueCalled(mActivity)) { vh.numCalls.setVisibility(View.GONE); vh.previousCallStats.setVisibility(View.VISIBLE); - // TODO move to strings.xml vh.previousCallStats.setText( - "1 previous (pretend) call"); + mActivity.getResources().getString(R.string.demo_previous_call_stats_one)); } else { vh.numCalls.setText(mActivity.getResources().getString(R.string.call_count_one)); vh.previousCallStats.setVisibility(View.GONE); diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/IssueActivity.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/IssueActivity.java index b5ad357b..82031e90 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/IssueActivity.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/IssueActivity.java @@ -686,7 +686,7 @@ private void showIssueDetails() { static String getIssueDetailsMessage(Context context, Issue issue) { StringBuilder result = new StringBuilder(); if (issue.isPlaceholder) { - return "This is an example issue. To see it again later, you can go to Settings."; + return context.getResources().getString(R.string.demo_issue_details_message); } if (issue.categories.length > 0) { if (issue.categories.length == 1) { diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java index 1de4b35e..24a8a8c6 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java @@ -73,6 +73,7 @@ public class MainActivity extends AppCompatActivity implements IssuesAdapter.Cal private static final String KEY_SHOW_LOW_ACCURACY_WARNING = "showLowAccuracyWarning"; private static final String DEEP_LINK_HOST = "5calls.org"; private static final String DEEP_LINK_PATH_ISSUE = "issue"; + private static final String DEMO_ISSUE_PERMALINK = "/issue/demoIssue"; private final AccountManager accountManager = AccountManager.Instance; private String mPendingDeepLinkPath = null; @@ -469,12 +470,17 @@ public void onIssuesReceived(List issues) { // If they've called more than 3 times, don't bother with the placeholder any more. boolean showPlaceholderIfEarly = mCallCount <= 3 && !accountManager.getPlaceholderIssueCalled(getApplicationContext()); if (forceShowPlaceholder || showPlaceholderIfEarly) { - // TODO put strings in strings.xml - Contact demoContact = Contact.createPlaceholder("0", "Representative Name", "(555) 555-5555", Contact.AREA_DEMO, "This is a pretend contact"); - Issue demoIssue = Issue.createPlaceholder("0", "Feeling unsure? Start here!", "/issue/demoIssue", - "This is where we'll put a description. You can get back to this in the app's settings, \"Show the example call\".\n\nDon't worry, this is just an example, you won't be connected with your representative.", - "*You can read from the script or improvise. Tip: tap the contact's phone number above to open your dialer app, start the call on speaker phone, then switch back to 5 Calls to see the script while you are calling.*\n\nHi, my name is **[NAME]** and I’m a constituent from [CITY, ZIP]. I'm calling to ask [REP/SEN NAME] to support bill 123. Thank you for your time.\n\n*When you have finished the call, mark your result depending on whether you reached a person, left a voicemail, or called but could do neither.*\n\n***Practice reading the script out loud, then go ahead and click a result!***", true, 0, - Collections.singletonList(demoContact), Collections.emptyList(), Collections.emptyList()); + Contact demoContact = Contact.createPlaceholder("0", + getResources().getString(R.string.demo_rep_name), + getResources().getString(R.string.demo_rep_phone), + Contact.AREA_DEMO, getResources().getString(R.string.demo_rep_reason)); + Issue demoIssue = Issue.createPlaceholder("0", + getResources().getString(R.string.demo_issue_name), + DEMO_ISSUE_PERMALINK, + getResources().getString(R.string.demo_issue_reason), + getResources().getString(R.string.demo_issue_script), true, 0, + Collections.singletonList(demoContact), + Collections.emptyList(), Collections.emptyList()); issues.add(demoIssue); } mIssuesAdapter.setAllIssues(issues, IssuesAdapter.NO_ERROR); diff --git a/5calls/app/src/main/res/layout/placeholder_done_view.xml b/5calls/app/src/main/res/layout/placeholder_done_view.xml index cbcbd32f..370fdcab 100644 --- a/5calls/app/src/main/res/layout/placeholder_done_view.xml +++ b/5calls/app/src/main/res/layout/placeholder_done_view.xml @@ -12,9 +12,10 @@ android:textSize="@dimen/subheading_text_size" android:textStyle="bold" /> + \ No newline at end of file diff --git a/5calls/app/src/main/res/values/strings.xml b/5calls/app/src/main/res/values/strings.xml index bac9db17..07145e56 100644 --- a/5calls/app/src/main/res/values/strings.xml +++ b/5calls/app/src/main/res/values/strings.xml @@ -36,6 +36,9 @@ %1$d previous calls + + 1 previous (pretend) call + Enter your result: @@ -671,4 +674,41 @@ This %1$s seat is currently vacant + + Representative Name + + + "(555) 555-5555" + + + This is a pretend contact + + + Feeling unsure? Start here! + + + This is where we\'ll put a description. Who writes the issues and script? Include what to expect, who might answer and what they want from you, leaving a voicemail, why they ask for your name/number, who is a constituent, links to videos of making a call, You can get back to this in the app\'s settings, "Show the example call". +\n\n +Don\'t worry, this is just an example, you won\'t be connected with your representative. + + + *You can read from the script or improvise. Tip: tap the contact\'s phone number above to open your dialer app, start the call on speaker phone, then switch back to 5 Calls to see the script while you are calling.* +\n\n +Hi, my name is **[NAME]** and I’m a constituent from [CITY, ZIP]. I\'m calling to ask [REP/SEN NAME] to support bill 123. Thank you for your time. +\n\n +*When you have finished the call, mark your result depending on whether you reached a person, left a voicemail, or called but could do neither.* +\n\n +***Practice reading the script out loud, then go ahead and click a result!*** + + + For a real call, you can see information here like call count, date created and category. + + + That\'s all there is to it! Now you are ready to make some real calls. You can do it! \n\nIf you want to see this pretend issue again, you can get back to it in settings. + + + Show an example call + + + Show an example issue in the list with hints about how to use 5 Calls. diff --git a/5calls/app/src/main/res/xml/settings.xml b/5calls/app/src/main/res/xml/settings.xml index e80fef11..afd590f3 100644 --- a/5calls/app/src/main/res/xml/settings.xml +++ b/5calls/app/src/main/res/xml/settings.xml @@ -99,9 +99,9 @@ /> Date: Mon, 16 Feb 2026 16:35:38 -0800 Subject: [PATCH 03/10] Words --- 5calls/app/src/main/res/values/strings.xml | 57 ++++++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/5calls/app/src/main/res/values/strings.xml b/5calls/app/src/main/res/values/strings.xml index 07145e56..f529c1d2 100644 --- a/5calls/app/src/main/res/values/strings.xml +++ b/5calls/app/src/main/res/values/strings.xml @@ -687,18 +687,55 @@ Feeling unsure? Start here! - This is where we\'ll put a description. Who writes the issues and script? Include what to expect, who might answer and what they want from you, leaving a voicemail, why they ask for your name/number, who is a constituent, links to videos of making a call, You can get back to this in the app\'s settings, "Show the example call". -\n\n -Don\'t worry, this is just an example, you won\'t be connected with your representative. + + **Practice mode:** + For many of us, making phone calls can be intimidating. 5 Calls tries to + make it easier by giving you detailed background information about each issue, your + representatives\' phone numbers, and a call script. + \n\n + **What to expect:** + You will tap the contacts in the list below to get their office phone number and a script. + Calls will be answered by a staffer or go to voicemail. Either way, your position will be + added to a tally for your representative. + \n\n + **Tips for calling:** + \n\n- *Keep it short:* Calls can take less than a minute + \n\n- *Make your stance clear:* Use supporting bill numbers if applicable so the staffer can easily tally your call. + \n\n- *Be respectful:* No matter which party they work for, the staffers that pick up the phone will just record your position. + \n\n If you feel nervous or don\'t want to talk to a person, call outside of business + hours! + \n\n + **Ready? Tap the pretend contact below to get to the practice script.** + \n\n + *You can always find this again in the app\'s settings, \"Show an example call\".* + - *You can read from the script or improvise. Tip: tap the contact\'s phone number above to open your dialer app, start the call on speaker phone, then switch back to 5 Calls to see the script while you are calling.* -\n\n -Hi, my name is **[NAME]** and I’m a constituent from [CITY, ZIP]. I\'m calling to ask [REP/SEN NAME] to support bill 123. Thank you for your time. -\n\n -*When you have finished the call, mark your result depending on whether you reached a person, left a voicemail, or called but could do neither.* -\n\n -***Practice reading the script out loud, then go ahead and click a result!*** + + Hi, my name is **[NAME]** and I’m a constituent from [CITY, ZIP]. I\'m calling to ask + [REP/SEN NAME] to support Bill 123. Thank you for your time and consideration. + \n\n + --- + \n\n + ***Step 1: Place the call*** + \n\n + *You would tap the phone number above to open your phone app. Pro tip: start the call on + speakerphone, then switch back to 5 Calls to see the script while you are calling.* + \n\n + ***Step 2: Read the script, or improvise*** + \n\n + *Try reading the script above out loud now to get the "feel" of a call.* + \n\n + ***Step 3: Tell us how it went*** + \n\n + *This helps us track our collective power.* + \n\n + \u0020\u0020- *Unavailable: The mailbox was full or the line was busy*\n\n + \u0020\u0020- *Left Voicemail: This still counts as a call!*\n\n + \u0020\u0020- *Made Contact: Use this if you talked to a staffer.* + \n\n + *Enter a pretend result now!* + For a real call, you can see information here like call count, date created and category. From e6a6ea9818e94e084c71a00a7a61b456e949820c Mon Sep 17 00:00:00 2001 From: Katie Dektar Date: Mon, 16 Feb 2026 16:45:43 -0800 Subject: [PATCH 04/10] Fix contact constructor ordering --- .../org/a5calls/android/a5calls/controller/MainActivity.java | 5 +++-- .../main/java/org/a5calls/android/a5calls/model/Contact.java | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java index 24a8a8c6..320b0a94 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java @@ -472,8 +472,9 @@ public void onIssuesReceived(List issues) { if (forceShowPlaceholder || showPlaceholderIfEarly) { Contact demoContact = Contact.createPlaceholder("0", getResources().getString(R.string.demo_rep_name), - getResources().getString(R.string.demo_rep_phone), - Contact.AREA_DEMO, getResources().getString(R.string.demo_rep_reason)); + getResources().getString(R.string.demo_rep_reason), + Contact.AREA_DEMO, + getResources().getString(R.string.demo_rep_phone)); Issue demoIssue = Issue.createPlaceholder("0", getResources().getString(R.string.demo_issue_name), DEMO_ISSUE_PERMALINK, diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Contact.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Contact.java index dd905ee0..10424695 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Contact.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/model/Contact.java @@ -63,10 +63,10 @@ protected Contact(String id, String name, String reason, String phone, String ar } public static Contact createPlaceholder(String id, String name, String reason, String area) { - return createPlaceholder(id, name, reason, /*phone=*/"", area); + return createPlaceholder(id, name, reason, area, /*phone=*/""); } - public static Contact createPlaceholder(String id, String name, String phone, String area, String reason) { + public static Contact createPlaceholder(String id, String name, String reason, String area, String phone) { return new Contact(id, name, reason, phone, area, /* isPlaceholder= */ true); } From a1e5d4accab5f920ac60bed5ea0adb473c2f65c6 Mon Sep 17 00:00:00 2001 From: Katie Dektar Date: Thu, 19 Feb 2026 08:15:08 -0600 Subject: [PATCH 05/10] Add MainActivity tests for placeholder call --- .../controller/MainActivityHappyPathTest.java | 127 +++++++++++++++++- 1 file changed, 124 insertions(+), 3 deletions(-) diff --git a/5calls/app/src/androidTest/java/org/a5calls/android/a5calls/controller/MainActivityHappyPathTest.java b/5calls/app/src/androidTest/java/org/a5calls/android/a5calls/controller/MainActivityHappyPathTest.java index 0e50289d..e107d2f1 100644 --- a/5calls/app/src/androidTest/java/org/a5calls/android/a5calls/controller/MainActivityHappyPathTest.java +++ b/5calls/app/src/androidTest/java/org/a5calls/android/a5calls/controller/MainActivityHappyPathTest.java @@ -2,14 +2,18 @@ import android.content.Context; import android.view.View; + import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.volley.toolbox.HttpResponse; import com.google.android.material.appbar.CollapsingToolbarLayout; +import org.a5calls.android.a5calls.AppSingleton; import org.a5calls.android.a5calls.FakeJSONData; +import org.a5calls.android.a5calls.FiveCallsApplication; import org.a5calls.android.a5calls.R; import org.a5calls.android.a5calls.model.AccountManager; +import org.a5calls.android.a5calls.model.DatabaseHelper; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; @@ -30,6 +34,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withInputType; import static androidx.test.espresso.matcher.ViewMatchers.withText; + import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.test.platform.app.InstrumentationRegistry; @@ -70,7 +75,7 @@ public void describeTo(Description description) { /** * Sets up mock responses for API calls */ - private void setupMockResponses(boolean isSplit, boolean hasLocation) { + private void setupMockResponses(boolean isSplit, boolean hasLocation) { // Set up the mock to handle all possible requests with appropriate responses mHttpStack.clearUrlPatternResponses(); @@ -120,8 +125,8 @@ public void testMainUILoadsCorrectly() throws JSONException { // Check that the collapsing toolbar is displayed and contains the location text onView(withId(R.id.collapsing_toolbar)) - .check(matches(isDisplayed())) - .check(matches(withCollapsingToolbarTitle(containsString("BOWLING GREEN")))); + .check(matches(isDisplayed())) + .check(matches(withCollapsingToolbarTitle(containsString("BOWLING GREEN")))); // Check that no location error was shown. onView(withText(R.string.low_accuracy_warning)).check(doesNotExist()); @@ -163,10 +168,126 @@ public void testMainUILoadsCorrectly_NoLocation() { // Verify that a "set your location" button is displayed. onView(withContentDescription(R.string.first_location_title)).check(matches(isDisplayed())); + // No calls to make displayed. + onView(withText("3 calls to make")).check(doesNotExist()); + onView(withText("2 calls to make")).check(doesNotExist()); + // Set the address again for the sake of the next test. AccountManager.Instance.setAddress(context, address); } + @Test + public void testMainUILoadsCorrectly_noLocation_placeholderShown() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + // Clear the location. + String address = AccountManager.Instance.getAddress(context); + AccountManager.Instance.setAddress(context, ""); + setupMockResponses(/*isSplit=*/false, /*hasLocation=*/false); + setupMockRequestQueue(); + + launchMainActivity(1000); + + // Verify that the demo issue is displayed. + onView(withText(R.string.demo_issue_name)).check(matches(isDisplayed())); + // There should be a "1 call to make" note for the demo issue. + onView(withText(R.string.call_count_one)).check(matches(isDisplayed())); + + // Reset address. + AccountManager.Instance.setAddress(context, address); + } + + @Test + public void testMainUILoadCorrectly_threeCalls_placeholderShown() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + setupMockResponses(/*isSplit=*/false, /*hasLocation=*/true); + setupMockRequestQueue(); + DatabaseHelper databaseHelper = AppSingleton.getInstance(context).getDatabaseHelper(); + // Add three fake calls. + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + + launchMainActivity(1000); + + // Verify that the demo issue is displayed. + onView(withText(R.string.demo_issue_name)).check(matches(isDisplayed())); + // There should be a "1 call to make" note for the demo issue. + onView(withText(R.string.call_count_one)).check(matches(isDisplayed())); + + // Reset the database. + databaseHelper.getWritableDatabase().delete("UserCallsDatabase", null, null); + } + + @Test + public void testMainUILoadCorrectly_fourCalls_placeholderNotShown() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + setupMockResponses(/*isSplit=*/false, /*hasLocation=*/true); + setupMockRequestQueue(); + DatabaseHelper databaseHelper = AppSingleton.getInstance(context).getDatabaseHelper(); + // Add four fake calls. + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + + launchMainActivity(1000); + + // Verify that the demo issue is not displayed. + onView(withText(R.string.demo_issue_name)).check(doesNotExist()); + + // Reset the database. + databaseHelper.getWritableDatabase().delete("UserCallsDatabase", null, null); + } + + @Test + public void testMainUILoadCorrectly_fourCalls_placeholderPrefSet_placeholderShown() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + setupMockResponses(/*isSplit=*/false, /*hasLocation=*/true); + setupMockRequestQueue(); + DatabaseHelper databaseHelper = AppSingleton.getInstance(context).getDatabaseHelper(); + // Add four fake calls. + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); + // Pretend the user has turned on the placeholder anyway in settings. + AccountManager.Instance.setShowPlaceholderIssue(context, true); + + launchMainActivity(1000); + + // Verify that the demo issue is displayed with "one call to make". + onView(withText(R.string.demo_issue_name)).check(matches(isDisplayed())); + onView(withText(R.string.demo_previous_call_stats_one)).check(doesNotExist()); + onView(withText(R.string.call_count_one)).check(matches(isDisplayed())); + + // Reset the database. + databaseHelper.getWritableDatabase().delete("UserCallsDatabase", null, null); + AccountManager.Instance.setShowPlaceholderIssue(context, false); + } + + @Test + public void testMainUILoadCorrectly_placeholderAlreadyCalled_placeholderPrefSet_placeholderShown() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + setupMockResponses(/*isSplit=*/false, /*hasLocation=*/true); + setupMockRequestQueue(); + // The user has already done the placeholder call once. + AccountManager.Instance.setPlaceholderIssueCalled(context, true); + // Pretend the user has turned on the placeholder anyway in settings. + AccountManager.Instance.setShowPlaceholderIssue(context, true); + + launchMainActivity(1000); + + // Verify that the demo issue is displayed. + onView(withText(R.string.demo_issue_name)).check(matches(isDisplayed())); + // The "one pretend previous call" is shown. + onView(withText(R.string.demo_previous_call_stats_one)).check(matches(isDisplayed())); + + // Reset state + AccountManager.Instance.setPlaceholderIssueCalled(context, false); + AccountManager.Instance.setShowPlaceholderIssue(context, false); + } + + @Test public void testNavigationDrawerOpens() throws JSONException { setupMockResponses(/*isSplit=*/ false, /*hasLocation=*/true); From fb41205894718eb6287cc3ff14043a55ae9374ad Mon Sep 17 00:00:00 2001 From: Katie Dektar Date: Sun, 22 Feb 2026 20:05:08 -0600 Subject: [PATCH 06/10] Dialog for clicking phone, decrease max --- .../controller/MainActivityHappyPathTest.java | 8 +- .../a5calls/controller/MainActivity.java | 5 +- .../a5calls/controller/RepCallActivity.java | 28 +++++-- .../src/main/res/layout/activity_rep_call.xml | 2 +- 5calls/app/src/main/res/values/strings.xml | 73 ++++++++++--------- 5calls/app/src/main/res/values/styles.xml | 5 ++ 6 files changed, 71 insertions(+), 50 deletions(-) diff --git a/5calls/app/src/androidTest/java/org/a5calls/android/a5calls/controller/MainActivityHappyPathTest.java b/5calls/app/src/androidTest/java/org/a5calls/android/a5calls/controller/MainActivityHappyPathTest.java index 68121bc9..30db92df 100644 --- a/5calls/app/src/androidTest/java/org/a5calls/android/a5calls/controller/MainActivityHappyPathTest.java +++ b/5calls/app/src/androidTest/java/org/a5calls/android/a5calls/controller/MainActivityHappyPathTest.java @@ -197,15 +197,13 @@ public void testMainUILoadsCorrectly_noLocation_placeholderShown() { } @Test - public void testMainUILoadCorrectly_threeCalls_placeholderShown() { + public void testMainUILoadCorrectly_oneCall_placeholderShown() { Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); setupMockResponses(/*isSplit=*/false, /*hasLocation=*/true); setupMockRequestQueue(); DatabaseHelper databaseHelper = AppSingleton.getInstance(context).getDatabaseHelper(); // Add three fake calls. databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); - databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); - databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); launchMainActivity(1000); @@ -219,7 +217,7 @@ public void testMainUILoadCorrectly_threeCalls_placeholderShown() { } @Test - public void testMainUILoadCorrectly_fourCalls_placeholderNotShown() { + public void testMainUILoadCorrectly_twoCalls_placeholderNotShown() { Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); setupMockResponses(/*isSplit=*/false, /*hasLocation=*/true); setupMockRequestQueue(); @@ -227,8 +225,6 @@ public void testMainUILoadCorrectly_fourCalls_placeholderNotShown() { // Add four fake calls. databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); - databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); - databaseHelper.addCall("issueId", "issueName", "contactId", "contactName", "result", "location"); launchMainActivity(1000); diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java index 509b6d26..ab93c571 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/MainActivity.java @@ -74,6 +74,7 @@ public class MainActivity extends AppCompatActivity implements IssuesAdapter.Cal private static final String DEEP_LINK_HOST = "5calls.org"; private static final String DEEP_LINK_PATH_ISSUE = "issue"; private static final String DEMO_ISSUE_PERMALINK = "/issue/demoIssue"; + private static final int MAX_CALLS_FOR_DEMO = 1; private final AccountManager accountManager = AccountManager.Instance; private String mPendingDeepLinkPath = null; @@ -467,8 +468,8 @@ public void onIssuesReceived(List issues) { // TODO refactor into helper. // Option to force show the placeholder call in settings. boolean forceShowPlaceholder = AccountManager.Instance.showPlaceholderIssue(getApplicationContext()); - // If they've called more than 3 times, don't bother with the placeholder any more. - boolean showPlaceholderIfEarly = mCallCount <= 3 && !accountManager.getPlaceholderIssueCalled(getApplicationContext()); + // If they've called more than N times, don't bother with the placeholder any more. + boolean showPlaceholderIfEarly = mCallCount <= MAX_CALLS_FOR_DEMO && !accountManager.getPlaceholderIssueCalled(getApplicationContext()); if (forceShowPlaceholder || showPlaceholderIfEarly) { Contact demoContact = Contact.createPlaceholder("0", getResources().getString(R.string.demo_rep_name), diff --git a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/RepCallActivity.java b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/RepCallActivity.java index 8b6c2f7f..4069ff52 100644 --- a/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/RepCallActivity.java +++ b/5calls/app/src/main/java/org/a5calls/android/a5calls/controller/RepCallActivity.java @@ -5,6 +5,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.graphics.Paint; import android.os.Bundle; import android.text.TextUtils; import android.text.method.LinkMovementMethod; @@ -230,7 +231,7 @@ private void setupContactUi(int index, boolean expandLocalSection) { .into(binding.repImage); } - linkPhoneNumber(binding.phoneNumber, contact.phone); + linkPhoneNumber(binding.phoneNumber, contact.phone, contact.isPlaceholder); if (expandLocalSection) { binding.localOfficeButton.setVisibility(View.INVISIBLE); @@ -342,7 +343,7 @@ private void expandLocalOfficeSection(Contact contact) { TextView numberView = (TextView) localOfficeInfo.findViewById( R.id.field_office_number); numberView.setText(contact.field_offices[i].phone); - linkPhoneNumber(numberView, contact.field_offices[i].phone); + linkPhoneNumber(numberView, contact.field_offices[i].phone, contact.isPlaceholder); if (!TextUtils.isEmpty(contact.field_offices[i].city)) { ((TextView) localOfficeInfo.findViewById(R.id.field_office_city)).setText( "- " + contact.field_offices[i].city); @@ -408,11 +409,26 @@ private int getSpanCount(Activity activity) { return (int) (displayMetrics.widthPixels / minButtonWidth); } - private static void linkPhoneNumber(TextView textView, String phoneNumber) { + private void linkPhoneNumber(TextView textView, String phoneNumber, + boolean isPlaceholder) { textView.setText(phoneNumber); - Linkify.addLinks(textView, Patterns.PHONE, "tel:", - Linkify.sPhoneNumberMatchFilter, - Linkify.sPhoneNumberTransformFilter); + if (!isPlaceholder) { + Linkify.addLinks(textView, Patterns.PHONE, "tel:", + Linkify.sPhoneNumberMatchFilter, + Linkify.sPhoneNumberTransformFilter); + } else { + textView.setPaintFlags(textView.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); + textView.setOnClickListener(v -> { + new AlertDialog.Builder(RepCallActivity.this) + .setTitle(R.string.demo_phone_click_dialog_title) + .setMessage(R.string.demo_phone_click_dialog_description) + .setPositiveButton(android.R.string.ok, + (dialog, which) -> { + + }) + .show(); + }); + } } private void updateScriptDisplay() { diff --git a/5calls/app/src/main/res/layout/activity_rep_call.xml b/5calls/app/src/main/res/layout/activity_rep_call.xml index 775d30ac..054890a1 100644 --- a/5calls/app/src/main/res/layout/activity_rep_call.xml +++ b/5calls/app/src/main/res/layout/activity_rep_call.xml @@ -82,7 +82,7 @@