Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions 5calls/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:scheme="https"
android:host="5calls.org"
android:pathPrefix="/issue/" />
</intent-filter>
</activity>
<activity android:name=".controller.IssueActivity"
android:launchMode="singleTop"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,34 @@ public boolean hasContacts() {
return !mContacts.isEmpty();
}

public List<Issue> getAllIssues() {
return mAllIssues;
}

/**
* Populates an issue's contacts list based on its contact areas.
* This is normally done in onBindViewHolder, but is needed for deep linking
* where we bypass the RecyclerView.
*/
public void populateIssueContacts(Issue issue) {
if (issue == null || issue.contactAreas.isEmpty()) {
return;
}

issue.contacts = new ArrayList<Contact>();
for (String contactArea : issue.contactAreas) {
for (Contact contact : mContacts) {
if (TextUtils.equals(contact.area, contactArea) &&
!issue.contacts.contains(contact)) {
if (TextUtils.equals(contact.area, Contact.AREA_HOUSE) && mIsSplitDistrict) {
issue.isSplit = true;
}
issue.contacts.add(contact);
}
}
}
}

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Expand Down Expand Up @@ -306,19 +334,7 @@ public void onClick(View v) {
return;
}

issue.contacts = new ArrayList<Contact>();
for (String contactArea : issue.contactAreas) {
for (Contact contact : mContacts) {
if (TextUtils.equals(contact.area, contactArea) &&
!issue.contacts.contains(contact)) {
if (TextUtils.equals(contact.area, Contact.AREA_HOUSE) && mIsSplitDistrict) {
issue.isSplit = true;
}

issue.contacts.add(contact);
}
}
}
populateIssueContacts(issue);
displayPreviousCallStats(issue, vh);
} else if (type == VIEW_TYPE_EMPTY_REQUEST) {
EmptyRequestViewHolder vh = (EmptyRequestViewHolder) holder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,12 @@ public class MainActivity extends AppCompatActivity implements IssuesAdapter.Cal
private static final String KEY_FILTER_ITEM_SELECTED = "filterItemSelected";
private static final String KEY_SEARCH_TEXT = "searchText";
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 final AccountManager accountManager = AccountManager.Instance;

private String mPendingDeepLinkPath = null;

private ArrayAdapter<String> mFilterAdapter;
private String mFilterText = "";
private String mSearchText = "";
Expand Down Expand Up @@ -137,6 +141,8 @@ protected void onCreate(Bundle savedInstanceState) {
FiveCallsApplication.analyticsManager().trackPageview("/", this);
}

maybeHandleDeepLink(intent);

setContentView(binding.getRoot());

setSupportActionBar(binding.toolbar);
Expand Down Expand Up @@ -284,6 +290,13 @@ protected void onDestroy() {
super.onDestroy();
}

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
maybeHandleDeepLink(intent);
}

@Override
protected void onResume() {
super.onResume();
Expand Down Expand Up @@ -457,6 +470,10 @@ public void onIssuesReceived(List<Issue> issues) {
mIssuesAdapter.setAllIssues(issues, IssuesAdapter.NO_ERROR);
mIssuesAdapter.setFilterAndSearch(mFilterText, mSearchText);
binding.swipeContainer.setRefreshing(false);
// Only handle deep link if we have both issues and contacts loaded
if (mIssuesAdapter.hasContacts()) {
maybeHandlePendingDeepLink();
}
}
};

Expand Down Expand Up @@ -523,12 +540,15 @@ public void onDismissed(Snackbar transientBottomBar, int event) {
mShowLowAccuracyWarning = false;
}
}

// If the state changed, refresh issues to get state-specific issues
if (stateChanged) {
FiveCallsApi api = AppSingleton.getInstance(getApplicationContext()).getJsonController();
api.getIssues();
}

// Handle pending deep link now that contacts are loaded
maybeHandlePendingDeepLink();
}
};

Expand Down Expand Up @@ -754,4 +774,63 @@ public void onDismissed(Snackbar transientBottomBar, int event) {
}
});
}

private void maybeHandleDeepLink(Intent intent) {
if (intent == null || intent.getData() == null) {
return;
}

Uri data = intent.getData();
if (data.getHost() != null && data.getHost().equals(DEEP_LINK_HOST)) {
List<String> pathSegments = data.getPathSegments();
if (pathSegments.size() >= 2 && pathSegments.get(0).equals(DEEP_LINK_PATH_ISSUE)) {
// Store the full path (e.g., "issue/slug" or "issue/state/slug")
// to avoid false matches with substring matching
StringBuilder pathBuilder = new StringBuilder();
for (int i = 0; i < pathSegments.size(); i++) {
if (i > 0) {
pathBuilder.append("/");
}
pathBuilder.append(pathSegments.get(i));
}
mPendingDeepLinkPath = pathBuilder.toString();
}
}
}

private void maybeHandlePendingDeepLink() {
if (mPendingDeepLinkPath == null) {
return;
}

// Wait for both issues and contacts to be loaded before handling deep link
if (mIssuesAdapter.getAllIssues().isEmpty() || !mIssuesAdapter.hasContacts()) {
return;
}

String path = mPendingDeepLinkPath;
mPendingDeepLinkPath = null;

// Normalize the path for matching: add leading/trailing slashes to match API format
// API permalinks are like "/issue/slug/" but our path is "issue/slug"
String normalizedPath = "/" + path + "/";

Issue targetIssue = null;
for (Issue issue : mIssuesAdapter.getAllIssues()) {
if (issue.permalink != null && issue.permalink.equals(normalizedPath)) {
targetIssue = issue;
break;
}
}

if (targetIssue != null) {
// Populate the issue's contacts before launching IssueActivity
// This is normally done in IssuesAdapter.onBindViewHolder, but we're bypassing that
mIssuesAdapter.populateIssueContacts(targetIssue);
startIssueActivity(this, targetIssue);
} else {
hideSnackbars();
showSnackbar(R.string.issue_not_found, Snackbar.LENGTH_LONG);
}
}
}
3 changes: 3 additions & 0 deletions 5calls/app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,9 @@
<!-- Action for a snackbar or button which brings you to the location update screen [CHAR_LIMIT=10] -->
<string name="update">Actualización</string>

<!-- Error message shown when a deep link issue is not found [CHAR_LIMIT=150] -->
<string name="issue_not_found">No se pudo encontrar este problema. Puede que ya no sea relevante.</string>

<!-- Name of the intent chooser for sending emails [CHAR_LIMIT=NONE] -->
<string name="send_email">Enviar correo electrónico</string>

Expand Down
3 changes: 3 additions & 0 deletions 5calls/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,9 @@
<!-- Error message shown on the address text field when a user tries to submit an empty address [CHAR_LIMIT=50] -->
<string name="error_address_empty">Enter an address or zip code</string>

<!-- Error message shown when a deep link issue is not found [CHAR_LIMIT=150] -->
<string name="issue_not_found">Could not find this issue. It may no longer be relevant</string>

<!-- Menu option to share your stats / impact [CHAR_LIMIT=50] -->
<string name="menu_share">Share</string>

Expand Down
Loading
Loading