feat(Strava): Add Export route patch

This commit is contained in:
Lucas Martinati
2026-05-31 19:35:10 +00:00
committed by Ohan Melkonyan
parent b632d0f042
commit 9cb66197cf
4 changed files with 133 additions and 0 deletions
@@ -0,0 +1,88 @@
package app.revanced.extension.strava;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.revanced.extension.shared.Utils;
public final class AddRouteExportPatch {
public static void showExportDialog(Activity activity) {
try {
Intent intent = activity.getIntent();
if (intent == null) return;
Bundle extras = intent.getExtras();
if (extras == null) return;
String routeId = null;
// Scan all Intent extras to find the Route ID.
for (String key : extras.keySet()) {
Object val = extras.get(key);
if (val != null) {
String strVal = val.toString();
// Check if we have a "SavedRoute" or "SuggestedRoute" object.
if (val.getClass().getName().contains("SavedRoute")) {
try {
for (java.lang.reflect.Field field : val.getClass().getDeclaredFields()) {
field.setAccessible(true);
if (field.getType() == long.class) {
long id = field.getLong(val);
if (id > 0) {
routeId = String.valueOf(id);
break;
}
}
}
} catch (Exception e) {}
}
if (routeId != null) break;
// Fallback: regex search in the text content.
Matcher m = Pattern.compile("routes/([0-9]+)").matcher(strVal);
if (m.find()) {
routeId = m.group(1);
break;
}
}
}
if (routeId != null) {
final String finalRouteId = routeId;
final String finalUrl = "https://www.strava.com/routes/" + finalRouteId;
// Wait 500ms to allow Strava's BottomSheet to open,
// then display the popup on top of it.
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (activity.isDestroyed() || activity.isFinishing()) return;
new AlertDialog.Builder(activity)
.setTitle("Share options")
.setMessage("Do you want to export this route or use the share menu?")
.setPositiveButton("Export GPX", (dialog, which) -> {
Utils.openLink(finalUrl + "/export_gpx");
activity.finish();
})
.setNeutralButton("Export TCX", (dialog, which) -> {
Utils.openLink(finalUrl + "/export_tcx");
activity.finish();
})
.setNegativeButton("Share", (dialog, which) -> {
// Close the popup, Strava's share menu is already displayed underneath.
})
.show();
}, 500);
}
} catch (Exception e) {
// Silently ignore errors to avoid crashes on other types of shares.
}
}
}
@@ -0,0 +1,9 @@
package com.strava.routes.data;
import java.io.Serializable;
public class Route implements Serializable {
public String getId() {
return null;
}
}
@@ -0,0 +1,25 @@
package app.revanced.patches.strava.route.export
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.strava.misc.extension.sharedExtensionPatch
private const val ROUTE_EXPORT_CLASS_DESCRIPTOR = "Lapp/revanced/extension/strava/AddRouteExportPatch;"
@Suppress("unused")
val addRouteExportPatch = bytecodePatch(
name = "Add route export",
description = "Extends the route menu with items to export them in GPX or TCX formats.",
) {
compatibleWith("com.strava")
dependsOn(sharedExtensionPatch)
apply {
// Intercept onCreate to display the export dialog over.
shareSheetActivityOnCreateMethod.addInstructions(
0,
"invoke-static {p0}, $ROUTE_EXPORT_CLASS_DESCRIPTOR->showExportDialog(Landroid/app/Activity;)V"
)
}
}
@@ -0,0 +1,11 @@
package app.revanced.patches.strava.route.export
import app.revanced.patcher.definingClass
import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.name
import app.revanced.patcher.patch.BytecodePatchContext
internal val BytecodePatchContext.shareSheetActivityOnCreateMethod by gettingFirstMethodDeclaratively {
name("onCreate")
definingClass("Lcom/strava/sharing/view/ShareSheetActivity;")
}