diff --git a/.gitignore b/.gitignore
index 95b9495..b03841c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ PasswordMaker.apk
# Or iOS profile
PasswordMaker_App_Store_provisioning.mobileprovision
+PasswordMaker_ShareExtension_store_provisioning.mobileprovision
*~
*.sw[mnpcod]
diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle
index 9cde871..b62ff30 100644
--- a/android/app/capacitor.build.gradle
+++ b/android/app/capacitor.build.gradle
@@ -18,6 +18,7 @@ dependencies {
implementation project(':capacitor-haptics')
implementation project(':capacitor-keyboard')
implementation project(':capacitor-status-bar')
+ implementation project(':capgo-capacitor-share-target')
implementation "androidx.webkit:webkit:1.4.0"
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index aad5601..b7488f6 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -19,6 +19,11 @@
+
+
+
+
+
Archive_
* _Distribute App_ from the Organizer dialogue.
+## iOS provisioning profiles
+
+It's necessary to use manual ones on the individual team with no real iPhones.
+These are git-ignored and a distinct one is needed for the main app and for the
+`ShareExtension`. Both require the App Groups capability.
+
## Plugins
In addition to the Ionic-standard Capacitor plugins and Clipboard (for copying passwords),
diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj
index 1380e78..9d56b06 100644
--- a/ios/App/App.xcodeproj/project.pbxproj
+++ b/ios/App/App.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 48;
+ objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
@@ -16,8 +16,33 @@
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
53E6D92696FE42808EDA44F3 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 6B6854B257764378BC1D04DE /* PrivacyInfo.xcprivacy */; };
+ 59FA1C572F26D9E300438282 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 59FA1C4D2F26D9E300438282 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ 59FA1C552F26D9E300438282 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 504EC2FC1FED79650016851F /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 59FA1C4C2F26D9E300438282;
+ remoteInfo = ShareExtension;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 59FA1C442F26D48500438282 /* Embed Foundation Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ 59FA1C572F26D9E300438282 /* ShareExtension.appex in Embed Foundation Extensions */,
+ );
+ name = "Embed Foundation Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
/* Begin PBXFileReference section */
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = ""; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = ""; };
@@ -28,6 +53,8 @@
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; };
+ 59FA1C4D2F26D9E300438282 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 59FA1C5C2F26DC8600438282 /* Webful PasswordMaker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Webful PasswordMaker.entitlements"; sourceTree = ""; };
6B6854B257764378BC1D04DE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
9DB92B35A5C556B224182674 /* Pods_Webful_PasswordMaker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Webful_PasswordMaker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; };
@@ -36,6 +63,20 @@
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ 59FA1C582F26D9E300438282 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = 59FA1C4C2F26D9E300438282 /* ShareExtension */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 59FA1C4E2F26D9E300438282 /* ShareExtension */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59FA1C582F26D9E300438282 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = ShareExtension; sourceTree = ""; };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
/* Begin PBXFrameworksBuildPhase section */
504EC3011FED79650016851F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@@ -45,6 +86,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 59FA1C4A2F26D9E300438282 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -59,7 +107,9 @@
504EC2FB1FED79650016851F = {
isa = PBXGroup;
children = (
+ 59FA1C5C2F26DC8600438282 /* Webful PasswordMaker.entitlements */,
504EC3061FED79650016851F /* App */,
+ 59FA1C4E2F26D9E300438282 /* ShareExtension */,
504EC3051FED79650016851F /* Products */,
7F8756D8B27F46E3366F6CEA /* Pods */,
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */,
@@ -71,6 +121,7 @@
isa = PBXGroup;
children = (
504EC3041FED79650016851F /* Webful PasswordMaker.app */,
+ 59FA1C4D2F26D9E300438282 /* ShareExtension.appex */,
);
name = Products;
sourceTree = "";
@@ -114,23 +165,47 @@
504EC3021FED79650016851F /* Resources */,
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */,
B3D6F66F01657F49B9BFAD17 /* [CP] Copy Pods Resources */,
+ 59FA1C442F26D48500438282 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
+ 59FA1C562F26D9E300438282 /* PBXTargetDependency */,
);
name = "Webful PasswordMaker";
productName = App;
productReference = 504EC3041FED79650016851F /* Webful PasswordMaker.app */;
productType = "com.apple.product-type.application";
};
+ 59FA1C4C2F26D9E300438282 /* ShareExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 59FA1C592F26D9E300438282 /* Build configuration list for PBXNativeTarget "ShareExtension" */;
+ buildPhases = (
+ 59FA1C492F26D9E300438282 /* Sources */,
+ 59FA1C4A2F26D9E300438282 /* Frameworks */,
+ 59FA1C4B2F26D9E300438282 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 59FA1C4E2F26D9E300438282 /* ShareExtension */,
+ );
+ name = ShareExtension;
+ packageProductDependencies = (
+ );
+ productName = ShareExtension;
+ productReference = 59FA1C4D2F26D9E300438282 /* ShareExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
504EC2FC1FED79650016851F /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 920;
+ LastSwiftUpdateCheck = 2620;
LastUpgradeCheck = 920;
TargetAttributes = {
504EC3031FED79650016851F = {
@@ -138,6 +213,9 @@
LastSwiftMigration = 1100;
ProvisioningStyle = Manual;
};
+ 59FA1C4C2F26D9E300438282 = {
+ CreatedOnToolsVersion = 26.2;
+ };
};
};
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */;
@@ -154,6 +232,7 @@
projectRoot = "";
targets = (
504EC3031FED79650016851F /* Webful PasswordMaker */,
+ 59FA1C4C2F26D9E300438282 /* ShareExtension */,
);
};
/* End PBXProject section */
@@ -173,6 +252,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 59FA1C4B2F26D9E300438282 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -235,8 +321,23 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 59FA1C492F26D9E300438282 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ 59FA1C562F26D9E300438282 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 59FA1C4C2F26D9E300438282 /* ShareExtension */;
+ targetProxy = 59FA1C552F26D9E300438282 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
/* Begin PBXVariantGroup section */
504EC30B1FED79650016851F /* Main.storyboard */ = {
isa = PBXVariantGroup;
@@ -359,7 +460,8 @@
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
@@ -369,6 +471,7 @@
baseConfigurationReference = D5054C214B9034F29815F248 /* Pods-Webful PasswordMaker.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_ENTITLEMENTS = "Webful PasswordMaker.entitlements";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2.6.2;
@@ -376,7 +479,10 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = N88HM25UAZ;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
MARKETING_VERSION = 2.6.2;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = uk.webful.passwordmaker;
@@ -394,6 +500,7 @@
baseConfigurationReference = CF241BE1BA9577E81CFB14AA /* Pods-Webful PasswordMaker.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_ENTITLEMENTS = "Webful PasswordMaker.entitlements";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2.6.2;
@@ -401,7 +508,10 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = N88HM25UAZ;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
MARKETING_VERSION = 2.6.2;
PRODUCT_BUNDLE_IDENTIFIER = uk.webful.passwordmaker;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -413,6 +523,96 @@
};
name = Release;
};
+ 59FA1C5A2F26D9E300438282 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = N88HM25UAZ;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = ShareExtension/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = uk.webful.passwordmaker.ShareExtension;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "PasswordMaker ShareExtension store provisioning";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 59FA1C5B2F26D9E300438282 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = "";
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = N88HM25UAZ;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = ShareExtension/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = uk.webful.passwordmaker.ShareExtension;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "PasswordMaker ShareExtension store provisioning";
+ SKIP_INSTALL = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -434,6 +634,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 59FA1C592F26D9E300438282 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 59FA1C5A2F26D9E300438282 /* Debug */,
+ 59FA1C5B2F26D9E300438282 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
};
rootObject = 504EC2FC1FED79650016851F /* Project object */;
diff --git a/ios/App/App.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme b/ios/App/App.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme
new file mode 100644
index 0000000..d2294b9
--- /dev/null
+++ b/ios/App/App.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/App/App.xcodeproj/xcshareddata/xcschemes/Webful PasswordMaker.xcscheme b/ios/App/App.xcodeproj/xcshareddata/xcschemes/Webful PasswordMaker.xcscheme
new file mode 100644
index 0000000..447d625
--- /dev/null
+++ b/ios/App/App.xcodeproj/xcshareddata/xcschemes/Webful PasswordMaker.xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist
index fbfe6f8..f98927a 100644
--- a/ios/App/App/Info.plist
+++ b/ios/App/App/Info.plist
@@ -18,6 +18,17 @@
APPL
CFBundleShortVersionString
$(MARKETING_VERSION)
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ uk.webful.passwordmaker
+ CFBundleURLSchemes
+
+ passwordmaker
+
+
+
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
LSRequiresIPhoneOS
diff --git a/ios/App/App/capacitor.config.json b/ios/App/App/capacitor.config.json
index 297d3db..1738ebc 100644
--- a/ios/App/App/capacitor.config.json
+++ b/ios/App/App/capacitor.config.json
@@ -15,6 +15,9 @@
"scheme": "Webful PasswordMaker"
},
"plugins": {
+ "CapacitorShareTarget": {
+ "appGroupId": "group.urlshare.uk.webful.passwordmaker"
+ },
"CapacitorSQLite": {
"iosDatabaseLocation": "Library/CapacitorDatabase",
"iosIsEncryption": true,
@@ -39,6 +42,7 @@
"imageName": "Splashscreen"
},
"StatusBar": {
+ "backgroundColor": "#a11692",
"style": "LIGHT",
"overlaysWebView": false
},
@@ -62,6 +66,7 @@
"HapticsPlugin",
"KeyboardPlugin",
"StatusBarPlugin",
+ "CapacitorShareTargetPlugin",
"CDVPlugin"
]
}
diff --git a/ios/App/Podfile b/ios/App/Podfile
index 4dd4202..052b289 100644
--- a/ios/App/Podfile
+++ b/ios/App/Podfile
@@ -20,6 +20,7 @@ def capacitor_pods
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
+ pod 'CapgoCapacitorShareTarget', :path => '../../node_modules/@capgo/capacitor-share-target'
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
pod 'CordovaPluginsResources', :path => '../capacitor-cordova-ios-plugins'
end
diff --git a/ios/App/Podfile.lock b/ios/App/Podfile.lock
index 67cfbbe..942c1da 100644
--- a/ios/App/Podfile.lock
+++ b/ios/App/Podfile.lock
@@ -23,6 +23,8 @@ PODS:
- Capacitor
- CapacitorStatusBar (8.0.0):
- Capacitor
+ - CapgoCapacitorShareTarget (8.0.8):
+ - Capacitor
- CordovaPlugins (8.0.1):
- CapacitorCordova
- CordovaPluginsResources (0.0.105)
@@ -46,6 +48,7 @@ DEPENDENCIES:
- "CapacitorHaptics (from `../../node_modules/@capacitor/haptics`)"
- "CapacitorKeyboard (from `../../node_modules/@capacitor/keyboard`)"
- "CapacitorStatusBar (from `../../node_modules/@capacitor/status-bar`)"
+ - "CapgoCapacitorShareTarget (from `../../node_modules/@capgo/capacitor-share-target`)"
- CordovaPlugins (from `../capacitor-cordova-ios-plugins`)
- CordovaPluginsResources (from `../capacitor-cordova-ios-plugins`)
@@ -78,6 +81,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/@capacitor/keyboard"
CapacitorStatusBar:
:path: "../../node_modules/@capacitor/status-bar"
+ CapgoCapacitorShareTarget:
+ :path: "../../node_modules/@capgo/capacitor-share-target"
CordovaPlugins:
:path: "../capacitor-cordova-ios-plugins"
CordovaPluginsResources:
@@ -95,12 +100,13 @@ SPEC CHECKSUMS:
CapacitorHaptics: 2079d9fa04c5a71e18663b4722323c304c58245c
CapacitorKeyboard: d7868c079a4d5277a3deca27a10a488fcbbb8b69
CapacitorStatusBar: 312e2e67928dfe9514c4a8916a1fd610552f5f35
+ CapgoCapacitorShareTarget: 2b8423bc42b17ecff91ff3e3bc04e4b0d77e521e
CordovaPlugins: 27ecf72bbe2be80302bac68e70decdd8d3db5b20
CordovaPluginsResources: da93847212bf7b16ba770e4d6c3e7dba820174c7
IONFilesystemLib: 50b9a0f3052a9b40b9bedb43f328a8fe222f9f87
SQLCipher: 712e8416685e8e575b9b0706ffee71678b2fdcf8
ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37
-PODFILE CHECKSUM: 49adf4142110c9574528d44f3bfa138a3650a80c
+PODFILE CHECKSUM: 548203aa45bb7ef9ecc5adfb3db8b006d9766271
COCOAPODS: 1.16.2
diff --git a/ios/App/ShareExtension/Base.lproj/MainInterface.storyboard b/ios/App/ShareExtension/Base.lproj/MainInterface.storyboard
new file mode 100644
index 0000000..286a508
--- /dev/null
+++ b/ios/App/ShareExtension/Base.lproj/MainInterface.storyboard
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/App/ShareExtension/Info.plist b/ios/App/ShareExtension/Info.plist
new file mode 100644
index 0000000..827898b
--- /dev/null
+++ b/ios/App/ShareExtension/Info.plist
@@ -0,0 +1,23 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionAttributes
+
+ NSExtensionActivationRule
+
+ NSExtensionActivationSupportsText
+
+ NSExtensionActivationSupportsWebURLWithMaxCount
+ 1
+
+
+ NSExtensionMainStoryboard
+ MainInterface
+ NSExtensionPointIdentifier
+ com.apple.share-services
+
+
+
diff --git a/ios/App/ShareExtension/ShareExtension.entitlements b/ios/App/ShareExtension/ShareExtension.entitlements
new file mode 100644
index 0000000..3ee5836
--- /dev/null
+++ b/ios/App/ShareExtension/ShareExtension.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.urlshare.uk.webful.passwordmaker
+
+
+
diff --git a/ios/App/ShareExtension/ShareViewController.swift b/ios/App/ShareExtension/ShareViewController.swift
new file mode 100644
index 0000000..2d70d64
--- /dev/null
+++ b/ios/App/ShareExtension/ShareViewController.swift
@@ -0,0 +1,104 @@
+import UIKit
+import Social
+
+class ShareViewController: SLComposeServiceViewController {
+ // MUST match the appGroupId in capacitor.config.ts
+ let APP_GROUP_ID = "group.urlshare.uk.webful.passwordmaker"
+ // Custom URL scheme - registered in main app's Info.plist
+ let APP_URL_SCHEME = "passwordmaker"
+
+ override func isContentValid() -> Bool {
+ // Validate that we have some content
+ return true
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ navigationController?.navigationBar.topItem?.title = "PasswordMaker"
+ textView.text = "'Post' passes the URL to PasswordMaker"
+ textView.isEditable = false
+ }
+
+ override func didSelectPost() {
+ // Called when the user taps Post
+ guard let item = extensionContext?.inputItems.first as? NSExtensionItem else {
+ self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
+ return
+ }
+
+ Task {
+ var urlToShare: String? = nil
+
+ // Process attachments - prioritize URL type
+ if let attachments = item.attachments {
+ for provider in attachments {
+ // Handle URLs (most common from Safari)
+ if provider.hasItemConformingToTypeIdentifier("public.url") {
+ if let url = try? await provider.loadItem(forTypeIdentifier: "public.url", options: nil) as? URL {
+ urlToShare = url.absoluteString
+ break // Got a URL, that's what we want
+ }
+ }
+ }
+
+ // Fallback to plain text if no URL found
+ if urlToShare == nil {
+ for provider in attachments {
+ if provider.hasItemConformingToTypeIdentifier("public.plain-text") {
+ if let text = try? await provider.loadItem(forTypeIdentifier: "public.plain-text", options: nil) as? String {
+ urlToShare = text
+ break
+ }
+ }
+ }
+ }
+ }
+
+ guard let textToShare = urlToShare else {
+ NSLog("ShareExtension: No URL or text found")
+ self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
+ return
+ }
+
+ NSLog("ShareExtension: text to share: \(textToShare)")
+
+ // Save to shared UserDefaults
+ if let userDefaults = UserDefaults(suiteName: APP_GROUP_ID) {
+ let shareData: [String: Any] = [
+ "title": item.attributedTitle?.string ?? "",
+ "texts": [textToShare],
+ "files": []
+ ]
+ userDefaults.set(shareData, forKey: "share-target-data")
+ userDefaults.synchronize()
+ NSLog("ShareExtension: Saved to UserDefaults")
+ } else {
+ NSLog("ShareExtension: Failed to get UserDefaults!")
+ }
+
+ // Open the main app via URL scheme
+ if let url = URL(string: "\(APP_URL_SCHEME)://share") {
+ await self.openURL(url)
+ }
+
+ self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
+ }
+ }
+
+ override func configurationItems() -> [Any]! {
+ // Return any configuration items for the share sheet (optional)
+ return []
+ }
+
+ @MainActor
+ private func openURL(_ url: URL) async {
+ var responder: UIResponder? = self as UIResponder
+ while responder != nil {
+ if let application = responder as? UIApplication {
+ await application.open(url)
+ return
+ }
+ responder = responder?.next
+ }
+ }
+}
diff --git a/ios/App/Webful PasswordMaker.entitlements b/ios/App/Webful PasswordMaker.entitlements
new file mode 100644
index 0000000..3ee5836
--- /dev/null
+++ b/ios/App/Webful PasswordMaker.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.urlshare.uk.webful.passwordmaker
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 9c0efc0..5ebd3c3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,6 +28,7 @@
"@capacitor/ios": "^8.0.0",
"@capacitor/keyboard": "^8.0.0",
"@capacitor/status-bar": "^8.0.0",
+ "@capgo/capacitor-share-target": "^8.0.8",
"@ionic/angular": "^8.2.5",
"@ionic/storage-angular": "^4.0.0",
"@webful/passwordmaker-lib": "~0.1.9",
@@ -3549,6 +3550,15 @@
"integrity": "sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==",
"license": "ISC"
},
+ "node_modules/@capgo/capacitor-share-target": {
+ "version": "8.0.8",
+ "resolved": "https://registry.npmjs.org/@capgo/capacitor-share-target/-/capacitor-share-target-8.0.8.tgz",
+ "integrity": "sha512-bb4uPIgx1IONxivJfZL9Mzg7L387bGGsI3vtHX0FY4wSoDvt71C0xjssy9hhk1Zc2/SWqA9cNFfIZ0Saumbe0A==",
+ "license": "MPL-2.0",
+ "peerDependencies": {
+ "@capacitor/core": ">=8.0.0"
+ }
+ },
"node_modules/@colors/colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
diff --git a/package.json b/package.json
index 6e95320..686c02a 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"@capacitor/ios": "^8.0.0",
"@capacitor/keyboard": "^8.0.0",
"@capacitor/status-bar": "^8.0.0",
+ "@capgo/capacitor-share-target": "^8.0.8",
"@ionic/angular": "^8.2.5",
"@ionic/storage-angular": "^4.0.0",
"@webful/passwordmaker-lib": "~0.1.9",
@@ -117,4 +118,4 @@
"Safari >=14",
"iOS >=14"
]
-}
\ No newline at end of file
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 2e0a39b..3efde57 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,5 +1,8 @@
import { Component, inject } from '@angular/core';
+import { Capacitor } from '@capacitor/core';
+import { CapacitorShareTarget, ShareReceivedEvent } from '@capgo/capacitor-share-target';
import { Platform } from '@ionic/angular/standalone';
+import { ShareService } from './share.service';
@Component({
selector: 'app-root',
@@ -8,6 +11,7 @@ import { Platform } from '@ionic/angular/standalone';
})
export class AppComponent {
private platform = inject(Platform);
+ private shareService = inject(ShareService);
constructor() {
this.initializeApp();
@@ -20,6 +24,19 @@ export class AppComponent {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
this.toggleDarkTheme(prefersDark.matches);
prefersDark.addEventListener('change', event => this.toggleDarkTheme(event.matches));
+
+ if (Capacitor.isNativePlatform()) {
+ CapacitorShareTarget.addListener('shareReceived', event => this.receiveShareEvent(event));
+ }
+ }
+
+ receiveShareEvent(event: ShareReceivedEvent) {
+ if (!event.texts || event.texts.length === 0) {
+ return;
+ }
+
+ console.log('Received shared text: ', event.texts[0]);
+ this.shareService.setSharedHost(event.texts[0]);
}
/**
diff --git a/src/app/home/home.page.ts b/src/app/home/home.page.ts
index d7fd6da..9fa06f3 100644
--- a/src/app/home/home.page.ts
+++ b/src/app/home/home.page.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectorRef, Component, NgZone, OnInit, inject } from '@angular/core';
+import { ChangeDetectorRef, Component, NgZone, OnInit, effect, inject } from '@angular/core';
import { Clipboard } from '@capacitor/clipboard';
import { Keyboard } from '@capacitor/keyboard';
import { LoadingController, Platform, ToastController } from '@ionic/angular/standalone';
@@ -10,6 +10,7 @@ import { PasswordsService } from '../passwords.service';
import { Settings } from '../../models/Settings';
import { SettingsAdvanced } from '../../models/SettingsAdvanced';
import { SettingsService } from '../settings.service';
+import { ShareService } from '../share.service';
@Component({
selector: 'app-home',
@@ -23,6 +24,7 @@ export class HomePageComponent implements OnInit {
private passwordsService = inject(PasswordsService);
private platform = inject(Platform);
private settingsService = inject(SettingsService);
+ private shareService = inject(ShareService);
toast = inject(ToastController);
private zone = inject(NgZone);
@@ -41,6 +43,16 @@ export class HomePageComponent implements OnInit {
constructor() {
addIcons({ informationCircleOutline, warning, copy });
+
+ // React to shared text by populating the host field
+ effect(() => {
+ const sharedHost = this.shareService.sharedHost();
+ if (sharedHost) {
+ this.input.host = sharedHost;
+ this.shareService.clearSharedHost();
+ this.update();
+ }
+ });
}
async ngOnInit() {
diff --git a/src/app/share.service.ts b/src/app/share.service.ts
new file mode 100644
index 0000000..6d702ff
--- /dev/null
+++ b/src/app/share.service.ts
@@ -0,0 +1,26 @@
+import { Injectable, signal } from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ShareService {
+ /**
+ * Signal holding the most recently shared text to use as the host field.
+ * Components can watch this and react when it changes.
+ */
+ readonly sharedHost = signal(null);
+
+ /**
+ * Set the shared host text. This will trigger any effects watching the signal.
+ */
+ setSharedHost(host: string) {
+ this.sharedHost.set(host);
+ }
+
+ /**
+ * Clear the shared host after it's been consumed.
+ */
+ clearSharedHost() {
+ this.sharedHost.set(null);
+ }
+}
diff --git a/src/app/tabs/tabs.page.ts b/src/app/tabs/tabs.page.ts
index 17f50b9..19d6dbc 100644
--- a/src/app/tabs/tabs.page.ts
+++ b/src/app/tabs/tabs.page.ts
@@ -1,7 +1,8 @@
-import { Component, OnInit, ViewChild, inject } from '@angular/core';
+import { Component, OnInit, ViewChild, effect, inject } from '@angular/core';
import { IonTabs, Platform } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { key, settings } from 'ionicons/icons';
+import { ShareService } from '../share.service';
@Component({
selector: 'app-tabs',
@@ -10,11 +11,20 @@ import { key, settings } from 'ionicons/icons';
})
export class TabsPageComponent implements OnInit {
private platform = inject(Platform);
+ private shareService = inject(ShareService);
@ViewChild('tabs') tab: IonTabs;
constructor() {
addIcons({ key, settings });
+
+ // Switch to home tab when a share is received
+ effect(() => {
+ const sharedHost = this.shareService.sharedHost();
+ if (sharedHost) {
+ this.tab?.select('home');
+ }
+ });
}
ngOnInit() {