diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3494d9e8417..c58b0ae9b1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
## Unreleased (develop)
+- added: Home screen long-press shortcut to contact support
+- added: Home screen long-press shortcut warning about 2FA and credentials needed after uninstall
+
## 4.47.0 (staging)
- added: Include Zano sweep private key support for ZANO and tokens.
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 33e6b4cb839..4b5e0c6e9a9 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -92,6 +92,9 @@
+
diff --git a/android/app/src/main/java/co/edgesecure/app/MainActivity.kt b/android/app/src/main/java/co/edgesecure/app/MainActivity.kt
index 178abd63ef8..470b7e8941d 100644
--- a/android/app/src/main/java/co/edgesecure/app/MainActivity.kt
+++ b/android/app/src/main/java/co/edgesecure/app/MainActivity.kt
@@ -1,5 +1,6 @@
package co.edgesecure.app
+import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
@@ -38,6 +39,41 @@ class MainActivity : ReactActivity() {
if (resources.getBoolean(R.bool.portrait_only)) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
+
+ maybeOpenInBrowser(intent)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ if (!maybeOpenInBrowser(intent)) {
+ super.onNewIntent(intent)
+ }
+ }
+
+ /**
+ * Opens https/http URLs in the default browser when they aren't
+ * registered deep link hosts handled by React Native. This prevents
+ * shortcut intents from being misrouted through the deep link handler.
+ * Returns true if the URL was opened in the browser.
+ */
+ private fun maybeOpenInBrowser(intent: Intent?): Boolean {
+ val data = intent?.data ?: return false
+ val scheme = data.scheme
+ if (scheme != "https" && scheme != "http") return false
+
+ val host = data.host
+ if (DEEP_LINK_HOSTS.contains(host)) return false
+
+ val browserIntent = Intent(Intent.ACTION_VIEW, data)
+ startActivity(browserIntent)
+ return true
+ }
+
+ companion object {
+ private val DEEP_LINK_HOSTS = setOf(
+ "deep.edge.app",
+ "dl.edge.app",
+ "return.edge.app"
+ )
}
// Edge addition
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 806da384537..7ccc4c6d6bc 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -1,3 +1,7 @@
Edge
+ Contact Support
+ Contact support for help from a live agent
+ ⚠️ Save 2FA First!
+ ⚠️ Login requires 2FA & credentials!
diff --git a/android/app/src/main/res/xml/shortcuts.xml b/android/app/src/main/res/xml/shortcuts.xml
new file mode 100644
index 00000000000..d29e573bee2
--- /dev/null
+++ b/android/app/src/main/res/xml/shortcuts.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/crowdin.yml b/crowdin.yml
index 45423568ad9..4796e3bef94 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -1,6 +1,10 @@
files:
- source: /src/locales/strings/enUS.json
translation: /src/locales/strings/%two_letters_code%.json
+ - source: /android/app/src/main/res/values/strings.xml
+ translation: /android/app/src/main/res/values-%android_code%/strings.xml
+ - source: /ios/edge/en.lproj/InfoPlist.strings
+ translation: /ios/edge/%osx_code%/InfoPlist.strings
- source: /localization/AppStoreDescription.txt
translation: /localization/AppStoreDescription.%two_letters_code%.txt
- source: /localization/PlayStoreDescription.txt
diff --git a/ios/edge/AppDelegate.swift b/ios/edge/AppDelegate.swift
index 9483d70d2ba..0395451041f 100644
--- a/ios/edge/AppDelegate.swift
+++ b/ios/edge/AppDelegate.swift
@@ -12,6 +12,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var securityView: UIView?
+ // Deferred until React Native is fully initialized in didFinishLaunchingWithOptions
+ private var pendingShortcutItem: UIApplicationShortcutItem?
+
var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?
@@ -49,6 +52,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
+ if let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem {
+ pendingShortcutItem = shortcutItem
+ }
+
// Initialize SDK's:
initializeSentry()
FirebaseApp.configure()
@@ -72,9 +79,39 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
launchOptions: launchOptions
)
+ if let shortcutItem = pendingShortcutItem {
+ _ = handleShortcutItem(shortcutItem)
+ pendingShortcutItem = nil
+ }
+
return true
}
+ func application(
+ _ application: UIApplication,
+ performActionFor shortcutItem: UIApplicationShortcutItem,
+ completionHandler: @escaping (Bool) -> Void
+ ) {
+ let handled = handleShortcutItem(shortcutItem)
+ completionHandler(handled)
+ }
+
+ private func handleShortcutItem(_ shortcutItem: UIApplicationShortcutItem) -> Bool {
+ guard let urlString = shortcutItem.userInfo?["url"] as? String,
+ let url = URL(string: urlString) else { return false }
+
+ if url.scheme == "https" || url.scheme == "http" {
+ UIApplication.shared.open(url, options: [:]) { success in
+ if !success {
+ print("Failed to open shortcut URL: \(urlString)")
+ }
+ }
+ return true
+ }
+
+ return RCTLinkingManager.application(UIApplication.shared, open: url, options: [:])
+ }
+
/**
* Periodic background fetch logic.
* Edge addition.
diff --git a/ios/edge/Info.plist b/ios/edge/Info.plist
index 2a9afea9725..28a8e401a31 100644
--- a/ios/edge/Info.plist
+++ b/ios/edge/Info.plist
@@ -57,6 +57,39 @@
CFBundleVersion
99999999
+ UIApplicationShortcutItems
+
+
+ UIApplicationShortcutItemTitle
+ ⚠️ Save 2FA First!
+ UIApplicationShortcutItemSubtitle
+ Login requires 2FA & credentials!
+ UIApplicationShortcutItemIconSymbolName
+ nosign
+ UIApplicationShortcutItemType
+ co.edgesecure.app.do_not_uninstall
+ UIApplicationShortcutItemUserInfo
+
+ url
+ https://support.edge.app/en/articles/13892372-getting-a-new-phone
+
+
+
+ UIApplicationShortcutItemTitle
+ Contact Support
+ UIApplicationShortcutItemSubtitle
+ Get help from our live support agents
+ UIApplicationShortcutItemIconSymbolName
+ message.fill
+ UIApplicationShortcutItemType
+ co.edgesecure.app.contact_support
+ UIApplicationShortcutItemUserInfo
+
+ url
+ https://support.edge.app/en/articles/14054649-need-help-reach-out-via-our-chat-bubble?chat=open
+
+
+
LSApplicationQueriesSchemes
https
diff --git a/ios/edge/en.lproj/InfoPlist.strings b/ios/edge/en.lproj/InfoPlist.strings
new file mode 100644
index 00000000000..f63ca39dd2f
--- /dev/null
+++ b/ios/edge/en.lproj/InfoPlist.strings
@@ -0,0 +1,5 @@
+/* Home screen shortcut titles and subtitles */
+"⚠️ Save 2FA First!" = "⚠️ Save 2FA First!";
+"Login requires 2FA & credentials!" = "Login requires 2FA & credentials!";
+"Contact Support" = "Contact Support";
+"Get help from our live support agents" = "Get help from our live support agents";