Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="wc" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>

<!-- To disable the activity lifecycle breadcrumbs integration -->
Expand Down
36 changes: 36 additions & 0 deletions android/app/src/main/java/co/edgesecure/app/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<resources>
<string name="app_name">Edge</string>
<string name="shortcut_contact_support_short">Contact Support</string>
<string name="shortcut_contact_support_long">Contact support for help from a live agent</string>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning: Inconsistent copy between platforms.

Platform Contact Support (long)
Android "Contact support for help from a live agent"
iOS "Get help from our live support agents"

Same feature with different wording can be confusing for localization and UX.

Recommendation: Pick one and use it on both platforms.

<string name="shortcut_do_not_uninstall_short">⚠️ Save 2FA First!</string>
<string name="shortcut_do_not_uninstall_long">⚠️ Login requires 2FA &amp; credentials!</string>
</resources>
23 changes: 23 additions & 0 deletions android/app/src/main/res/xml/shortcuts.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning: Shortcut order differs between platforms.

  • iOS: 2FA warning first, Contact Support second
  • Android: Contact Support first, 2FA warning second

If the order is intentional for platform UX, add a comment explaining the rationale. Otherwise, align for consistency.

android:shortcutId="do_not_uninstall"
android:enabled="true"
android:icon="@android:drawable/ic_dialog_alert"
android:shortcutShortLabel="@string/shortcut_do_not_uninstall_short"
android:shortcutLongLabel="@string/shortcut_do_not_uninstall_long">
<intent
android:action="android.intent.action.VIEW"
android:data="https://support.edge.app/en/articles/13892372-getting-a-new-phone" />
</shortcut>
<shortcut
android:shortcutId="contact_support"
android:enabled="true"
android:icon="@android:drawable/ic_menu_help"
android:shortcutShortLabel="@string/shortcut_contact_support_short"
android:shortcutLongLabel="@string/shortcut_contact_support_long">
<intent
android:action="android.intent.action.VIEW"
android:data="https://support.edge.app/en/articles/14054649-need-help-reach-out-via-our-chat-bubble?chat=open" />
</shortcut>
</shortcuts>
4 changes: 4 additions & 0 deletions crowdin.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
37 changes: 37 additions & 0 deletions ios/edge/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding a brief comment explaining why shortcut handling is deferred.

The pattern of storing pendingShortcutItem and handling it after React Native setup is useful but not immediately obvious. A comment like:

// Shortcut actions are deferred until React Native is fully initialized
private var pendingShortcutItem: UIApplicationShortcutItem?

would clarify the intent for future readers.


var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?

Expand Down Expand Up @@ -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()
Expand All @@ -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 }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: UIApplication.shared.open has a completion handler that could help with debugging or analytics.

UIApplication.shared.open(url, options: [:]) { success in
  if !success {
    // Log or handle failure
  }
}

Not critical, but could be useful for future troubleshooting.

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.
Expand Down
33 changes: 33 additions & 0 deletions ios/edge/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,39 @@
</array>
<key>CFBundleVersion</key>
<string>99999999</string>
<key>UIApplicationShortcutItems</key>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Localization: These shortcut strings are hardcoded in English only.

The app supports 13 locales via React Native (src/locales/strings/*.json), but these native shortcuts won't be translated.

Recommendation: Use InfoPlist.strings for localization:

  1. Create ios/edge/en.lproj/InfoPlist.strings:
SHORTCUT_UNINSTALL_TITLE = "⚠️ Save 2FA First!";
SHORTCUT_SUPPORT_TITLE = "Contact Support";
  1. Create locale-specific .lproj folders (e.g., es.lproj, de.lproj) with translated strings.

  2. Update Info.plist to use the keys instead of hardcoded strings.

Similarly for Android, add values-es/strings.xml, values-de/strings.xml, etc.

This could be deferred to a follow-up PR, but should at least be documented as a known limitation.

<array>
<dict>
<key>UIApplicationShortcutItemTitle</key>
<string>⚠️ Save 2FA First!</string>
<key>UIApplicationShortcutItemSubtitle</key>
<string>Login requires 2FA &amp; credentials!</string>
<key>UIApplicationShortcutItemIconSymbolName</key>
<string>nosign</string>
<key>UIApplicationShortcutItemType</key>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: UIApplicationShortcutItemType is typically an identifier (e.g. com.edge.app.contact_support), not a full URL.

Storing URLs there works, but it mixes identity with data. Consider using a type like contact_support and passing the URL via userInfo for clearer separation. The handleShortcutItem function would then switch on the type and look up the URL.

<string>co.edgesecure.app.do_not_uninstall</string>
<key>UIApplicationShortcutItemUserInfo</key>
<dict>
<key>url</key>
<string>https://support.edge.app/en/articles/13892372-getting-a-new-phone</string>
</dict>
</dict>
<dict>
<key>UIApplicationShortcutItemTitle</key>
<string>Contact Support</string>
<key>UIApplicationShortcutItemSubtitle</key>
<string>Get help from our live support agents</string>
<key>UIApplicationShortcutItemIconSymbolName</key>
<string>message.fill</string>
<key>UIApplicationShortcutItemType</key>
<string>co.edgesecure.app.contact_support</string>
<key>UIApplicationShortcutItemUserInfo</key>
<dict>
<key>url</key>
<string>https://support.edge.app/en/articles/14054649-need-help-reach-out-via-our-chat-bubble?chat=open</string>
</dict>
</dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
Expand Down
5 changes: 5 additions & 0 deletions ios/edge/en.lproj/InfoPlist.strings
Original file line number Diff line number Diff line change
@@ -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";
Loading