diff --git a/index.share.js b/index.share.js new file mode 100644 index 00000000..d7072850 --- /dev/null +++ b/index.share.js @@ -0,0 +1,4 @@ +import { AppRegistry } from 'react-native' +import ShareExtension from './src/ShareExtension' + +AppRegistry.registerComponent('ShareMenuModuleComponent', () => ShareExtension) diff --git a/ios/Podfile b/ios/Podfile index 4d27b3b8..e9185d46 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -24,5 +24,20 @@ target 'tooot' do post_install do |installer| react_native_post_install(installer) + + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO' + end + end end end + +target 'ShareExtension' do + use_react_native!( + :hermes_enabled => podfile_properties['expo.jsEngine'] == 'hermes' + ) + + pod 'RNShareMenu', :path => '../node_modules/react-native-share-menu' + pod 'RNFS', :path => '../node_modules/react-native-fs' +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 996cb233..1b7b5d6a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,8 @@ PODS: - boost (1.76.0) - DoubleConversion (1.1.6) + - EXApplication (4.0.2): + - ExpoModulesCore - EXAV (10.2.1): - ExpoModulesCore - ReactCommon/turbomodule/core @@ -21,6 +23,8 @@ PODS: - EXFirebaseCore (4.1.1): - ExpoModulesCore - Firebase/Core (= 7.7.0) + - EXFont (10.0.5): + - ExpoModulesCore - EXImageLoader (3.1.1): - ExpoModulesCore - React-Core @@ -30,6 +34,8 @@ PODS: - EXImagePicker (12.0.2): - ExpoModulesCore - EXJSONUtils (0.2.1) + - EXKeepAwake (10.0.2): + - ExpoModulesCore - EXManifests (0.2.4): - EXJSONUtils - EXNotifications (0.14.1): @@ -367,15 +373,15 @@ PODS: - glog - react-native-blur (0.8.0): - React - - react-native-blurhash (1.1.9): + - react-native-blurhash (1.1.10): - React-Core - react-native-cameraroll (4.1.2): - React-Core - - react-native-netinfo (8.2.0): + - react-native-netinfo (8.3.0): - React-Core - - react-native-pager-view (5.4.15): + - react-native-pager-view (5.4.11): - React-Core - - react-native-safe-area-context (4.2.4): + - react-native-safe-area-context (4.2.5): - RCT-Folly - RCTRequired - RCTTypeSafety @@ -448,15 +454,17 @@ PODS: - React-jsi (= 0.67.4) - React-logger (= 0.67.4) - React-perflogger (= 0.67.4) - - RNCAsyncStorage (1.17.0): + - RNCAsyncStorage (1.17.3): - React-Core - RNFastImage (8.5.11): - React-Core - - SDWebImage (~> 5.12.3) + - SDWebImage (~> 5.12.5) - SDWebImageWebPCoder (~> 0.8.4) - - RNGestureHandler (2.3.2): + - RNFS (2.19.0): - React-Core - - RNReanimated (2.5.0): + - RNGestureHandler (2.4.1): + - React-Core + - RNReanimated (2.8.0): - DoubleConversion - FBLazyVector - FBReactNativeSpec @@ -464,7 +472,6 @@ PODS: - RCT-Folly - RCTRequired - RCTTypeSafety - - React - React-callinvoker - React-Core - React-Core/DevSupport @@ -487,9 +494,11 @@ PODS: - RNScreens (3.13.1): - React-Core - React-RCTImage - - RNSentry (3.3.6): + - RNSentry (3.4.1): - React-Core - Sentry (= 7.11.0) + - RNShareMenu (5.0.5): + - React - RNSVG (12.3.0): - React-Core - SDWebImage (5.12.5): @@ -508,6 +517,7 @@ PODS: DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - EXApplication (from `../node_modules/expo-application/ios`) - EXAV (from `../node_modules/expo-av/ios`) - EXConstants (from `../node_modules/expo-constants/ios`) - EXCrypto (from `../node_modules/expo-crypto/ios`) @@ -516,10 +526,12 @@ DEPENDENCIES: - EXFileSystem (from `../node_modules/expo-file-system/ios`) - EXFirebaseAnalytics (from `../node_modules/expo-firebase-analytics/ios`) - EXFirebaseCore (from `../node_modules/expo-firebase-core/ios`) + - EXFont (from `../node_modules/expo-font/ios`) - EXImageLoader (from `../node_modules/expo-image-loader/ios`) - EXImageManipulator (from `../node_modules/expo-image-manipulator/ios`) - EXImagePicker (from `../node_modules/expo-image-picker/ios`) - EXJSONUtils (from `../node_modules/expo-json-utils/ios`) + - EXKeepAwake (from `../node_modules/expo-keep-awake/ios`) - EXManifests (from `../node_modules/expo-manifests/ios`) - EXNotifications (from `../node_modules/expo-notifications/ios`) - Expo (from `../node_modules/expo/ios`) @@ -577,10 +589,12 @@ DEPENDENCIES: - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - RNFastImage (from `../node_modules/react-native-fast-image`) + - RNFS (from `../node_modules/react-native-fs`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) - "RNSentry (from `../node_modules/@sentry/react-native`)" + - RNShareMenu (from `../node_modules/react-native-share-menu`) - RNSVG (from `../node_modules/react-native-svg`) - UMTaskManagerInterface (from `../node_modules/unimodules-task-manager-interface/ios`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -610,6 +624,8 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + EXApplication: + :path: "../node_modules/expo-application/ios" EXAV: :path: "../node_modules/expo-av/ios" EXConstants: @@ -626,6 +642,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-firebase-analytics/ios" EXFirebaseCore: :path: "../node_modules/expo-firebase-core/ios" + EXFont: + :path: "../node_modules/expo-font/ios" EXImageLoader: :path: "../node_modules/expo-image-loader/ios" EXImageManipulator: @@ -634,6 +652,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-image-picker/ios" EXJSONUtils: :path: "../node_modules/expo-json-utils/ios" + EXKeepAwake: + :path: "../node_modules/expo-keep-awake/ios" EXManifests: :path: "../node_modules/expo-manifests/ios" EXNotifications: @@ -740,6 +760,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-async-storage/async-storage" RNFastImage: :path: "../node_modules/react-native-fast-image" + RNFS: + :path: "../node_modules/react-native-fs" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" RNReanimated: @@ -748,6 +770,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-screens" RNSentry: :path: "../node_modules/@sentry/react-native" + RNShareMenu: + :path: "../node_modules/react-native-share-menu" RNSVG: :path: "../node_modules/react-native-svg" UMTaskManagerInterface: @@ -758,6 +782,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: a7c83b31436843459a1961bfd74b96033dc77234 DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662 + EXApplication: 54fe5bd6268d697771645e8f1aef8b806a65247a EXAV: b9ed0c201092244c46aa78f907f5c66176bed236 EXConstants: 88bf79622fbd9b476c96d8ec57fe97ca44fe8e3c EXCrypto: ab2ba0df3136a5f2407b8c7e70eb498ac20d704f @@ -766,10 +791,12 @@ SPEC CHECKSUMS: EXFileSystem: 7bcd3c1428698150d5c8ca140c8183f2ee204048 EXFirebaseAnalytics: a7ec2dd1394ad0de5c0c63fac0deee496f798284 EXFirebaseCore: 52151d0b008b99983e6a120cea94466ee760a4e9 + EXFont: 2597c10ac85a69d348d44d7873eccf5a7576ef5e EXImageLoader: 347b72c2ec2df65120ccec40ea65a4c4f24317ff EXImageManipulator: 60d1bf3f1d7709453b1feb38adf8594b7f58710f EXImagePicker: bf4d62532cc2bf217edbe4abbb0014e73e079eac EXJSONUtils: 5ee0d5cf76da70ad86f0be1a41cc70f47d69e06f + EXKeepAwake: bf48d7f740a5cd2befed6cf9a49911d385c6c47d EXManifests: d3464cd2278f4a19cd80c1aa673231570b534c11 EXNotifications: a7d582fa800d77f4a75bd22d52e84e2fbcee26df Expo: 534e51e607aba8229293297da5585f4b26f50fa1 @@ -817,11 +844,11 @@ SPEC CHECKSUMS: React-jsinspector: f4775ea9118cbe1f72b834f0f842baa7a99508d8 React-logger: a1f028f6d8639a3f364ef80419e5e862e1115250 react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c - react-native-blurhash: 80ed027224075ef708c3cac6ab116e6f8ee3272f + react-native-blurhash: add4df9a937b4e021a24bc67a0714f13e0bd40b7 react-native-cameraroll: 2957f2bce63ae896a848fbe0d5352c1bd4d20866 - react-native-netinfo: e922cb2e3eaf9ccdf16b8d4744a89657377aa4a1 - react-native-pager-view: b1914469643f40042e65d78cbf3d3dfebd6fb0d9 - react-native-safe-area-context: f98b0b16d1546d208fc293b4661e3f81a895afd9 + react-native-netinfo: 3671b091c4843fda5e153612866ef4024b8f5d62 + react-native-pager-view: 7f00d63688f7df9fad86dfb0154814419cc5eb8d + react-native-safe-area-context: ebf8c413eb8b5f7c392a036a315eb7b46b96845f react-native-segmented-control: 65df6cd0619b780b3843d574a72d4c7cec396097 React-perflogger: 0afaf2f01a47fd0fc368a93bfbb5bd3b26db6e7f React-RCTActionSheet: 59f35c4029e0b532fc42114241a06e170b7431a2 @@ -835,12 +862,14 @@ SPEC CHECKSUMS: React-RCTVibration: 3b52a7dced19cdb025b4f88ab26ceb2d85f30ba2 React-runtimeexecutor: a9d3c82ddf7ffdad9fbe6a81c6d6f8c06385464d ReactCommon: 07d0c460b9ba9af3eaf1b8f5abe7daaad28c9c4e - RNCAsyncStorage: 4efdcd2f7378421b29467c9de58f43552b60cf5b - RNFastImage: cced864a4a2eac27c5c10ac16bd5e8b9d2be4504 - RNGestureHandler: 6e757e487a4834e7280e98e9bac66d2d9c575e9c - RNReanimated: 190b6930d5d94832061278e1070bbe313e50c830 + RNCAsyncStorage: 005c0e2f09575360f142d0d1f1f15e4ec575b1af + RNFastImage: 945abf54742505d790d9024d230c69b1e866bc88 + RNFS: fc610f78fdf8bfc89a9e5cc2f898519f4dba1002 + RNGestureHandler: 4f4986408310a43f1606c391f38f76e0d6e790d5 + RNReanimated: 46cdb89ca59ab7181334f4ed05a70e82ddb36751 RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19 - RNSentry: 6fbe6a4194cf632e5dc02a640741512faa943dbf + RNSentry: fbbdcd7213058e3de5fbaa452b25a06a16b4b382 + RNShareMenu: c69282e50ac439737a86949a55c7b023b90027c8 RNSVG: 302bfc9905bd8122f08966dc2ce2d07b7b52b9f8 SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815 @@ -848,6 +877,6 @@ SPEC CHECKSUMS: UMTaskManagerInterface: 3184c93ecc290bd422c6e344badc89b601e9c29b Yoga: d6b6a80659aa3e91aaba01d0012e7edcbedcbecd -PODFILE CHECKSUM: 9bf9d386bac4ff98f76fc93f120c9922660384b5 +PODFILE CHECKSUM: 26ee7ffc1b88088246dc6a04f532230c6d5a5884 COCOAPODS: 1.11.3 diff --git a/ios/ShareExtension/Base.lproj/MainInterface.storyboard b/ios/ShareExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 00000000..2e6951b8 --- /dev/null +++ b/ios/ShareExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/ShareExtension/Info.plist b/ios/ShareExtension/Info.plist new file mode 100644 index 00000000..4d392fd1 --- /dev/null +++ b/ios/ShareExtension/Info.plist @@ -0,0 +1,70 @@ + + + + + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + TRUEPREDICATE + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + HostAppBundleIdentifier + com.xmflsct.app.tooot + HostAppURLScheme + tooot:// + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsImageWithMaxCount + 4 + NSExtensionActivationSupportsMovieWithMaxCount + 1 + NSExtensionActivationSupportsText + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + ReactShareViewBackgroundColor + + Red + 0 + Green + 0 + Blue + 0 + Alpha + 0 + Transparent + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + + diff --git a/ios/ShareExtension/ShareExtension-Bridging-Header.h b/ios/ShareExtension/ShareExtension-Bridging-Header.h new file mode 100644 index 00000000..f788061b --- /dev/null +++ b/ios/ShareExtension/ShareExtension-Bridging-Header.h @@ -0,0 +1,8 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import +#import +#import +#import diff --git a/ios/tooot.xcodeproj/project.pbxproj b/ios/tooot.xcodeproj/project.pbxproj index ead1ac58..a87453ab 100644 --- a/ios/tooot.xcodeproj/project.pbxproj +++ b/ios/tooot.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 34A37A6C820725DC6DDAA0EE /* libPods-ShareExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B3640FCDF7C4396A68A74D1 /* libPods-ShareExtension.a */; }; 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; 5E36538325C9B8BD009F93EE /* RootViewColor.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E36538225C9B8BD009F93EE /* RootViewColor.xcassets */; }; 5EE088C926297820007E5FEC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5EE088CB26297820007E5FEC /* InfoPlist.strings */; }; @@ -19,8 +20,36 @@ BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; DA8B5B7F0DED488CAC0FF169 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */; }; E3BC22F5F8ABE515E14CF199 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D878F932AF7A9974E06E461 /* ExpoModulesProvider.swift */; }; + E633A426281EAEAB000E540F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E633A424281EAEAB000E540F /* MainInterface.storyboard */; }; + E633A42B281EAEAB000E540F /* ShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E633A420281EAEAB000E540F /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + E633A430281EAF38000E540F /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E633A42F281EAF38000E540F /* ShareViewController.swift */; }; + E633A437281EB5BC000E540F /* ReactShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E633A436281EB5BC000E540F /* ReactShareViewController.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + E633A428281EAEAB000E540F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E633A41F281EAEAB000E540F; + remoteInfo = ShareExtension; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + E633A42A281EAEAB000E540F /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + E633A42B281EAEAB000E540F /* ShareExtension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* tooot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tooot.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -30,6 +59,9 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = tooot/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = tooot/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = tooot/main.m; sourceTree = ""; }; + 1B3640FCDF7C4396A68A74D1 /* libPods-ShareExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ShareExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 312CB8F38010C3E0D27A8663 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = ""; }; + 49AC0972A79258360BEDD73B /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = ""; }; 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-tooot.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-tooot.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5E36538225C9B8BD009F93EE /* RootViewColor.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = RootViewColor.xcassets; path = tooot/RootViewColor.xcassets; sourceTree = ""; }; 5EE088CA26297820007E5FEC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -42,6 +74,12 @@ AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = tooot/SplashScreen.storyboard; sourceTree = ""; }; B96B72E5384D44A7B240B27E /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "tooot/GoogleService-Info.plist"; sourceTree = ""; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; + E633A420281EAEAB000E540F /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + E633A425281EAEAB000E540F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + E633A427281EAEAB000E540F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E633A42F281EAF38000E540F /* ShareViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ShareViewController.swift; path = "../../node_modules/react-native-share-menu/ios/ShareViewController.swift"; sourceTree = ""; }; + E633A431281EB55C000E540F /* ShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ShareExtension-Bridging-Header.h"; sourceTree = ""; }; + E633A436281EB5BC000E540F /* ReactShareViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ReactShareViewController.swift; path = "../../node_modules/react-native-share-menu/ios/ReactShareViewController.swift"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; /* End PBXFileReference section */ @@ -55,6 +93,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E633A41D281EAEAB000E540F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 34A37A6C820725DC6DDAA0EE /* libPods-ShareExtension.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -91,6 +137,7 @@ ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-tooot.a */, + 1B3640FCDF7C4396A68A74D1 /* libPods-ShareExtension.a */, ); name = Frameworks; sourceTree = ""; @@ -116,6 +163,7 @@ 5EE44DD52600124E00A9BCED /* File.swift */, 13B07FAE1A68108700A75B9A /* tooot */, 832341AE1AAA6A7D00B99B32 /* Libraries */, + E633A421281EAEAB000E540F /* ShareExtension */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, D65327D7A22EEC0BE12398D9 /* Pods */, @@ -131,6 +179,7 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* tooot.app */, + E633A420281EAEAB000E540F /* ShareExtension.appex */, ); name = Products; sourceTree = ""; @@ -149,10 +198,24 @@ children = ( 6C2E3173556A471DD304B334 /* Pods-tooot.debug.xcconfig */, 7A4D352CD337FB3A3BF06240 /* Pods-tooot.release.xcconfig */, + 49AC0972A79258360BEDD73B /* Pods-ShareExtension.debug.xcconfig */, + 312CB8F38010C3E0D27A8663 /* Pods-ShareExtension.release.xcconfig */, ); path = Pods; sourceTree = ""; }; + E633A421281EAEAB000E540F /* ShareExtension */ = { + isa = PBXGroup; + children = ( + E633A436281EB5BC000E540F /* ReactShareViewController.swift */, + E633A42F281EAF38000E540F /* ShareViewController.swift */, + E633A424281EAEAB000E540F /* MainInterface.storyboard */, + E633A427281EAEAB000E540F /* Info.plist */, + E633A431281EB55C000E540F /* ShareExtension-Bridging-Header.h */, + ); + path = ShareExtension; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -168,22 +231,45 @@ 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, 49D30A53634620EF2A5C6692 /* [CP] Embed Pods Frameworks */, + E633A42A281EAEAB000E540F /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + E633A429281EAEAB000E540F /* PBXTargetDependency */, ); name = tooot; productName = tooot; productReference = 13B07F961A680F5B00A75B9A /* tooot.app */; productType = "com.apple.product-type.application"; }; + E633A41F281EAEAB000E540F /* ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = E633A42E281EAEAB000E540F /* Build configuration list for PBXNativeTarget "ShareExtension" */; + buildPhases = ( + 8E32C2F04B8226F2A839525E /* [CP] Check Pods Manifest.lock */, + E633A41C281EAEAB000E540F /* Sources */, + E633A41D281EAEAB000E540F /* Frameworks */, + E633A41E281EAEAB000E540F /* Resources */, + 9620878489526FB1EDDF9FB7 /* [CP] Copy Pods Resources */, + E633A438281EB628000E540F /* Bundle React Native code and images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ShareExtension; + productName = ShareExtension; + productReference = E633A420281EAEAB000E540F /* ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1330; LastUpgradeCheck = 1320; TargetAttributes = { 13B07F861A680F5B00A75B9A = { @@ -191,6 +277,12 @@ LastSwiftMigration = 1240; ProvisioningStyle = Manual; }; + E633A41F281EAEAB000E540F = { + CreatedOnToolsVersion = 13.3.1; + DevelopmentTeam = 8EGBLQ2MA6; + LastSwiftMigration = 1330; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "tooot" */; @@ -208,6 +300,7 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* tooot */, + E633A41F281EAEAB000E540F /* ShareExtension */, ); }; /* End PBXProject section */ @@ -227,6 +320,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E633A41E281EAEAB000E540F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E633A426281EAEAB000E540F /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -306,6 +407,64 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-tooot/Pods-tooot-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 8E32C2F04B8226F2A839525E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ShareExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9620878489526FB1EDDF9FB7 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ShareExtension/Pods-ShareExtension-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ShareExtension/Pods-ShareExtension-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + E633A438281EB628000E540F /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\nexport ENTRY_FILE=index.share.js\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; + }; FD10A7F022414F080027D42C /* Start Packager */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -339,8 +498,25 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E633A41C281EAEAB000E540F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E633A437281EB5BC000E540F /* ReactShareViewController.swift in Sources */, + E633A430281EAF38000E540F /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + E633A429281EAEAB000E540F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E633A41F281EAEAB000E540F /* ShareExtension */; + targetProxy = E633A428281EAEAB000E540F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { isa = PBXVariantGroup; @@ -360,6 +536,14 @@ name = InfoPlist.strings; sourceTree = ""; }; + E633A424281EAEAB000E540F /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + E633A425281EAEAB000E540F /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -367,6 +551,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-tooot.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements; @@ -405,6 +590,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-tooot.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = tooot/tooot.entitlements; @@ -553,6 +739,85 @@ }; name = Release; }; + E633A42C281EAEAB000E540F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 49AC0972A79258360BEDD73B /* Pods-ShareExtension.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 8EGBLQ2MA6; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "ShareExtension/ShareExtension-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,6"; + }; + name = Debug; + }; + E633A42D281EAEAB000E540F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 312CB8F38010C3E0D27A8663 /* Pods-ShareExtension.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 8EGBLQ2MA6; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.xmflsct.app.tooot.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "ShareExtension/ShareExtension-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,6"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -574,6 +839,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + E633A42E281EAEAB000E540F /* Build configuration list for PBXNativeTarget "ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E633A42C281EAEAB000E540F /* Debug */, + E633A42D281EAEAB000E540F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; diff --git a/ios/tooot/Info.plist b/ios/tooot/Info.plist index 932e4fba..b50b00ed 100644 --- a/ios/tooot/Info.plist +++ b/ios/tooot/Info.plist @@ -25,8 +25,10 @@ CFBundleURLTypes + CFBundleTypeRole + Editor CFBundleURLName - gizmos + com.xmflsct.app.tooot CFBundleURLSchemes tooot diff --git a/ios/tooot/tooot.entitlements b/ios/tooot/tooot.entitlements index d7a186c6..9dadf2e3 100644 --- a/ios/tooot/tooot.entitlements +++ b/ios/tooot/tooot.entitlements @@ -6,6 +6,10 @@ development com.apple.security.app-sandbox + com.apple.security.application-groups + + group.com.xmflsct.app.tooot + com.apple.security.device.audio-input com.apple.security.device.camera diff --git a/package.json b/package.json index f77c29e7..71165f76 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "tooot", "versions": { - "native": "220328", - "major": 3, - "minor": 6, + "native": "220428", + "major": 4, + "minor": 0, "patch": 0, "expo": "44.0.0" }, @@ -27,19 +27,19 @@ "dependencies": { "@expo/react-native-action-sheet": "3.13.0", "@neverdull-agency/expo-unlimited-secure-store": "1.0.10", - "@react-native-async-storage/async-storage": "1.17.0", + "@react-native-async-storage/async-storage": "1.17.3", "@react-native-community/blur": "3.6.0", "@react-native-community/cameraroll": "4.1.2", - "@react-native-community/netinfo": "8.2.0", + "@react-native-community/netinfo": "8.3.0", "@react-native-community/segmented-control": "2.2.2", - "@react-navigation/bottom-tabs": "6.2.0", - "@react-navigation/native": "6.0.8", - "@react-navigation/native-stack": "6.5.2", - "@react-navigation/stack": "6.1.1", - "@reduxjs/toolkit": "1.8.0", - "@sentry/react-native": "3.3.6", - "@sharcoux/slider": "5.6.5", - "axios": "0.26.1", + "@react-navigation/bottom-tabs": "6.3.1", + "@react-navigation/native": "6.0.10", + "@react-navigation/native-stack": "6.6.2", + "@react-navigation/stack": "6.2.1", + "@reduxjs/toolkit": "1.8.1", + "@sentry/react-native": "3.4.1", + "@sharcoux/slider": "6.0.2", + "axios": "0.27.2", "expo": "44.0.6", "expo-auth-session": "3.5.0", "expo-av": "10.2.1", @@ -62,51 +62,54 @@ "expo-updates": "0.11.6", "expo-video-thumbnails": "6.2.0", "expo-web-browser": "10.1.1", - "i18next": "21.6.14", + "i18next": "21.6.16", "li": "1.3.0", "lodash": "4.17.21", "react": "17.0.2", "react-dom": "17.0.2", - "react-i18next": "11.16.2", + "react-i18next": "11.16.7", "react-native": "0.67.4", "react-native-animated-spinkit": "1.5.2", "react-native-base64": "^0.2.1", - "react-native-blurhash": "1.1.9", + "react-native-blurhash": "1.1.10", "react-native-fast-image": "8.5.11", "react-native-feather": "1.1.2", "react-native-flash-message": "0.2.1", - "react-native-gesture-handler": "2.3.2", + "react-native-fs": "^2.19.0", + "react-native-gesture-handler": "2.4.1", "react-native-htmlview": "0.16.0", - "react-native-pager-view": "5.4.15", - "react-native-reanimated": "2.5.0", - "react-native-safe-area-context": "4.2.4", + "react-native-pager-view": "5.4.11", + "react-native-reanimated": "2.8.0", + "react-native-safe-area-context": "4.2.5", "react-native-screens": "3.13.1", + "react-native-share-menu": "^5.0.5", "react-native-svg": "12.3.0", "react-native-swipe-list-view": "3.2.9", "react-native-tab-view": "3.1.1", - "react-query": "3.34.19", - "react-redux": "7.2.6", + "react-native-uuid": "^2.0.1", + "react-query": "3.38.0", + "react-redux": "8.0.1", "react-timeago": "6.2.1", "redux-persist": "6.0.0", "rn-placeholder": "3.0.3", - "sentry-expo": "4.1.0", - "tslib": "2.3.1", + "sentry-expo": "4.1.1", + "tslib": "2.4.0", "valid-url": "1.0.9" }, "devDependencies": { - "@babel/core": "7.17.8", + "@babel/core": "7.17.9", "@babel/plugin-proposal-optional-chaining": "7.16.7", "@babel/preset-typescript": "7.16.7", - "@expo/config": "6.0.19", - "@types/lodash": "4.14.180", + "@expo/config": "6.0.23", + "@types/lodash": "4.14.182", "@types/react": "17.0.43", "@types/react-dom": "17.0.14", - "@types/react-native": "0.67.3", + "@types/react-native": "0.67.6", "@types/react-native-base64": "^0.2.0", - "@types/react-redux": "7.1.23", + "@types/react-native-share-menu": "^5.0.2", "@types/react-timeago": "4.1.3", "@types/valid-url": "1.0.3", - "@welldone-software/why-did-you-render": "6.2.3", + "@welldone-software/why-did-you-render": "7.0.1", "babel-plugin-module-resolver": "4.1.0", "babel-plugin-transform-remove-console": "6.9.4", "chalk": "4.1.2", @@ -138,4 +141,4 @@ } } } -} \ No newline at end of file +} diff --git a/patches/@types+react-native-share-menu+5.0.2.patch b/patches/@types+react-native-share-menu+5.0.2.patch new file mode 100644 index 00000000..09c4417f --- /dev/null +++ b/patches/@types+react-native-share-menu+5.0.2.patch @@ -0,0 +1,25 @@ +diff --git a/node_modules/@types/react-native-share-menu/index.d.ts b/node_modules/@types/react-native-share-menu/index.d.ts +index f52822c..23d9f99 100755 +--- a/node_modules/@types/react-native-share-menu/index.d.ts ++++ b/node_modules/@types/react-native-share-menu/index.d.ts +@@ -6,9 +6,8 @@ + // Minimum TypeScript Version: 3.7 + + export interface ShareData { +- mimeType: string; +- data: string | string[]; +- extraData?: object | undefined; ++ data: {data: {mimeType: string; data: string}[]}; ++ extraData?: {share: {mimeType: string; data: string}[]} | undefined; + } + + export type ShareCallback = (share?: ShareData) => void; +@@ -28,7 +27,7 @@ interface ShareMenuReactView { + dismissExtension(error?: string): void; + openApp(): void; + continueInApp(extraData?: object): void; +- data(): Promise<{mimeType: string, data: string}>; ++ data(): Promise<{data: {mimeType: string; data: string}[]}>; + } + + export const ShareMenuReactView: ShareMenuReactView; diff --git a/patches/react-native-fast-image+8.5.11.patch b/patches/react-native-fast-image+8.5.11.patch index 3d5f42e9..1c11bf6d 100644 --- a/patches/react-native-fast-image+8.5.11.patch +++ b/patches/react-native-fast-image+8.5.11.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native-fast-image/RNFastImage.podspec b/node_modules/react-native-fast-image/RNFastImage.podspec -index db0fada..9a2457c 100644 +index db0fada..54d8d5b 100644 --- a/node_modules/react-native-fast-image/RNFastImage.podspec +++ b/node_modules/react-native-fast-image/RNFastImage.podspec @@ -16,6 +16,6 @@ Pod::Spec.new do |s| @@ -7,7 +7,7 @@ index db0fada..9a2457c 100644 s.dependency 'React-Core' - s.dependency 'SDWebImage', '~> 5.11.1' -+ s.dependency 'SDWebImage', '~> 5.12.3' ++ s.dependency 'SDWebImage', '~> 5.12.5' s.dependency 'SDWebImageWebPCoder', '~> 0.8.4' end diff --git a/node_modules/react-native-fast-image/android/build.gradle b/node_modules/react-native-fast-image/android/build.gradle diff --git a/patches/react-native-share-menu+5.0.5.patch b/patches/react-native-share-menu+5.0.5.patch new file mode 100644 index 00000000..66fe9eb9 --- /dev/null +++ b/patches/react-native-share-menu+5.0.5.patch @@ -0,0 +1,479 @@ +diff --git a/node_modules/react-native-share-menu/ios/Constants.swift b/node_modules/react-native-share-menu/ios/Constants.swift +index 2811008..63761c3 100644 +--- a/node_modules/react-native-share-menu/ios/Constants.swift ++++ b/node_modules/react-native-share-menu/ios/Constants.swift +@@ -23,7 +23,7 @@ public let COULD_NOT_PARSE_IMG_ERROR = "Couldn't parse image" + public let COULD_NOT_SAVE_FILE_ERROR = "Couldn't save file on disk" + public let NO_EXTENSION_CONTEXT_ERROR = "No extension context attached" + public let NO_DELEGATE_ERROR = "No ReactShareViewDelegate attached" +-public let COULD_NOT_FIND_ITEM_ERROR = "Couldn't find item attached to this share" ++public let COULD_NOT_FIND_ITEMS_ERROR = "Couldn't find items attached to this share" + + // MARK: Keys + +diff --git a/node_modules/react-native-share-menu/ios/Modules/ShareMenu.swift b/node_modules/react-native-share-menu/ios/Modules/ShareMenu.swift +index 6c4922a..1277df2 100644 +--- a/node_modules/react-native-share-menu/ios/Modules/ShareMenu.swift ++++ b/node_modules/react-native-share-menu/ios/Modules/ShareMenu.swift +@@ -9,7 +9,7 @@ class ShareMenu: RCTEventEmitter { + } + } + +- var sharedData: [String:String]? ++ var sharedData: [[String:String]?]? + + static var initialShare: (UIApplication, URL, [UIApplication.OpenURLOptionsKey : Any])? + +@@ -91,7 +91,7 @@ class ShareMenu: RCTEventEmitter { + + let extraData = userDefaults.object(forKey: USER_DEFAULTS_EXTRA_DATA_KEY) as? [String:Any] + +- if let data = userDefaults.object(forKey: USER_DEFAULTS_KEY) as? [String:String] { ++ if let data = userDefaults.object(forKey: USER_DEFAULTS_KEY) as? [[String:String]] { + sharedData = data + dispatchEvent(with: data, and: extraData) + userDefaults.removeObject(forKey: USER_DEFAULTS_KEY) +@@ -100,25 +100,22 @@ class ShareMenu: RCTEventEmitter { + + @objc(getSharedText:) + func getSharedText(callback: RCTResponseSenderBlock) { +- guard var data: [String:Any] = sharedData else { +- callback([]) +- return +- } ++ var data = [DATA_KEY: sharedData] as [String: Any] + + if let bundleId = Bundle.main.bundleIdentifier, let userDefaults = UserDefaults(suiteName: "group.\(bundleId)") { +- data[EXTRA_DATA_KEY] = userDefaults.object(forKey: USER_DEFAULTS_EXTRA_DATA_KEY) as? [String:Any] ++ data[EXTRA_DATA_KEY] = userDefaults.object(forKey: USER_DEFAULTS_EXTRA_DATA_KEY) as? [String: Any] + } else { + print("Error: \(NO_APP_GROUP_ERROR)") + } + + callback([data as Any]) +- sharedData = nil ++ sharedData = [] + } + +- func dispatchEvent(with data: [String:String], and extraData: [String:Any]?) { ++ func dispatchEvent(with data: [[String:String]], and extraData: [String:Any]?) { + guard hasListeners else { return } + +- var finalData = data as [String:Any] ++ var finalData = [DATA_KEY: data] as [String: Any] + if (extraData != nil) { + finalData[EXTRA_DATA_KEY] = extraData + } +diff --git a/node_modules/react-native-share-menu/ios/Modules/ShareMenuReactView.swift b/node_modules/react-native-share-menu/ios/Modules/ShareMenuReactView.swift +index 5d21773..d8a0847 100644 +--- a/node_modules/react-native-share-menu/ios/Modules/ShareMenuReactView.swift ++++ b/node_modules/react-native-share-menu/ios/Modules/ShareMenuReactView.swift +@@ -3,8 +3,9 @@ + // RNShareMenu + // + // Created by Gustavo Parreira on 28/07/2020. +-// ++// Modified by Veselin Stoyanov on 17/04/2021. + ++import Foundation + import MobileCoreServices + + @objc(ShareMenuReactView) +@@ -65,12 +66,12 @@ public class ShareMenuReactView: NSObject { + + let extensionContext = viewDelegate.loadExtensionContext() + +- guard let item = extensionContext.inputItems.first as? NSExtensionItem else { +- print("Error: \(COULD_NOT_FIND_ITEM_ERROR)") ++ guard let items = extensionContext.inputItems as? [NSExtensionItem] else { ++ print("Error: \(COULD_NOT_FIND_ITEMS_ERROR)") + return + } + +- viewDelegate.continueInApp(with: item, and: extraData) ++ viewDelegate.continueInApp(with: items, and: extraData) + } + + @objc(data:reject:) +@@ -82,91 +83,96 @@ public class ShareMenuReactView: NSObject { + return + } + +- extractDataFromContext(context: extensionContext) { (data, mimeType, error) in ++ extractDataFromContext(context: extensionContext) { (data, error) in + guard (error == nil) else { + reject("error", error?.description, nil) + return + } + +- resolve([MIME_TYPE_KEY: mimeType, DATA_KEY: data]) ++ resolve([DATA_KEY: data]) + } + } + +- func extractDataFromContext(context: NSExtensionContext, withCallback callback: @escaping (String?, String?, NSException?) -> Void) { +- let item:NSExtensionItem! = context.inputItems.first as? NSExtensionItem +- let attachments:[AnyObject]! = item.attachments +- +- var urlProvider:NSItemProvider! = nil +- var imageProvider:NSItemProvider! = nil +- var textProvider:NSItemProvider! = nil +- var dataProvider:NSItemProvider! = nil +- +- for provider in attachments { +- if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { +- urlProvider = provider as? NSItemProvider +- break +- } else if provider.hasItemConformingToTypeIdentifier(kUTTypeText as String) { +- textProvider = provider as? NSItemProvider +- break +- } else if provider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { +- imageProvider = provider as? NSItemProvider +- break +- } else if provider.hasItemConformingToTypeIdentifier(kUTTypeData as String) { +- dataProvider = provider as? NSItemProvider +- break +- } +- } ++ func extractDataFromContext(context: NSExtensionContext, withCallback callback: @escaping ([Any]?, NSException?) -> Void) { ++ DispatchQueue.global().async { ++ let semaphore = DispatchSemaphore(value: 0) ++ let items:[NSExtensionItem]! = context.inputItems as? [NSExtensionItem] ++ var results: [[String: String]] = [] + +- if (urlProvider != nil) { +- urlProvider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { (item, error) in +- let url: URL! = item as? URL ++ for item in items { ++ guard let attachments = item.attachments else { ++ callback(nil, NSException(name: NSExceptionName(rawValue: "Error"), reason:"couldn't find attachments", userInfo:nil)) ++ return ++ } + +- callback(url.absoluteString, "text/plain", nil) +- } +- } else if (imageProvider != nil) { +- imageProvider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { (item, error) in +- let imageUrl: URL! = item as? URL ++ for provider in attachments { ++ if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { ++ provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { (item, error) in ++ let url: URL! = item as? URL + +- if (imageUrl != nil) { +- if let imageData = try? Data(contentsOf: imageUrl) { +- callback(imageUrl.absoluteString, self.extractMimeType(from: imageUrl), nil) +- } +- } else { +- let image: UIImage! = item as? UIImage ++ results.append([DATA_KEY: url.absoluteString, MIME_TYPE_KEY: "text/plain"]) + +- if (image != nil) { +- let imageData: Data! = image.pngData(); ++ semaphore.signal() ++ } ++ semaphore.wait() ++ } else if provider.hasItemConformingToTypeIdentifier(kUTTypeText as String) { ++ provider.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil) { (item, error) in ++ let text:String! = item as? String ++ ++ results.append([DATA_KEY: text, MIME_TYPE_KEY: "text/plain"]) + +- // Creating temporary URL for image data (UIImage) +- guard let imageURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("TemporaryScreenshot.png") else { +- return ++ semaphore.signal() ++ } ++ semaphore.wait() ++ } else if provider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { ++ provider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { (item, error) in ++ let imageUrl: URL! = item as? URL ++ ++ if (imageUrl != nil) { ++ if let imageData = try? Data(contentsOf: imageUrl) { ++ results.append([DATA_KEY: imageUrl.absoluteString, MIME_TYPE_KEY: self.extractMimeType(from: imageUrl)]) ++ } ++ } else { ++ let image: UIImage! = item as? UIImage ++ ++ if (image != nil) { ++ let imageData: Data! = image.pngData(); ++ ++ // Creating temporary URL for image data (UIImage) ++ guard let imageURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("TemporaryScreenshot.png") else { ++ return ++ } ++ ++ do { ++ // Writing the image to the URL ++ try imageData.write(to: imageURL) ++ ++ results.append([DATA_KEY: imageUrl.absoluteString, MIME_TYPE_KEY: imageURL.extractMimeType()]) ++ } catch { ++ callback(nil, NSException(name: NSExceptionName(rawValue: "Error"), reason:"Can't load image", userInfo:nil)) ++ } ++ } ++ } ++ ++ semaphore.signal() + } ++ semaphore.wait() ++ } else if provider.hasItemConformingToTypeIdentifier(kUTTypeData as String) { ++ provider.loadItem(forTypeIdentifier: kUTTypeData as String, options: nil) { (item, error) in ++ let url: URL! = item as? URL + +- do { +- // Writing the image to the URL +- try imageData.write(to: imageURL) ++ results.append([DATA_KEY: url.absoluteString, MIME_TYPE_KEY: self.extractMimeType(from: url)]) + +- callback(imageURL.absoluteString, imageURL.extractMimeType(), nil) +- } catch { +- callback(nil, nil, NSException(name: NSExceptionName(rawValue: "Error"), reason:"Can't load image", userInfo:nil)) ++ semaphore.signal() + } ++ semaphore.wait() ++ } else { ++ callback(nil, NSException(name: NSExceptionName(rawValue: "Error"), reason:"couldn't find provider", userInfo:nil)) + } + } + } +- } else if (textProvider != nil) { +- textProvider.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil) { (item, error) in +- let text:String! = item as? String + +- callback(text, "text/plain", nil) +- } +- } else if (dataProvider != nil) { +- dataProvider.loadItem(forTypeIdentifier: kUTTypeData as String, options: nil) { (item, error) in +- let url: URL! = item as? URL +- +- callback(url.absoluteString, self.extractMimeType(from: url), nil) +- } +- } else { +- callback(nil, nil, NSException(name: NSExceptionName(rawValue: "Error"), reason:"couldn't find provider", userInfo:nil)) ++ callback(results, nil) + } + } + +diff --git a/node_modules/react-native-share-menu/ios/ReactShareViewController.swift b/node_modules/react-native-share-menu/ios/ReactShareViewController.swift +index 0189ef6..e620257 100644 +--- a/node_modules/react-native-share-menu/ios/ReactShareViewController.swift ++++ b/node_modules/react-native-share-menu/ios/ReactShareViewController.swift +@@ -62,7 +62,7 @@ class ReactShareViewController: ShareViewController, RCTBridgeDelegate, ReactSha + self.openHostApp() + } + +- func continueInApp(with item: NSExtensionItem, and extraData: [String:Any]?) { +- handlePost(item, extraData: extraData) ++ func continueInApp(with items: [NSExtensionItem], and extraData: [String:Any]?) { ++ handlePost(items, extraData: extraData) + } + } +\ No newline at end of file +diff --git a/node_modules/react-native-share-menu/ios/ReactShareViewDelegate.swift b/node_modules/react-native-share-menu/ios/ReactShareViewDelegate.swift +index 0aa4c58..d2bc970 100644 +--- a/node_modules/react-native-share-menu/ios/ReactShareViewDelegate.swift ++++ b/node_modules/react-native-share-menu/ios/ReactShareViewDelegate.swift +@@ -10,5 +10,5 @@ public protocol ReactShareViewDelegate { + + func openApp() + +- func continueInApp(with item: NSExtensionItem, and extraData: [String:Any]?) ++ func continueInApp(with items: [NSExtensionItem], and extraData: [String:Any]?) + } +\ No newline at end of file +diff --git a/node_modules/react-native-share-menu/ios/ShareViewController.swift b/node_modules/react-native-share-menu/ios/ShareViewController.swift +index 7faf6e4..81aef73 100644 +--- a/node_modules/react-native-share-menu/ios/ShareViewController.swift ++++ b/node_modules/react-native-share-menu/ios/ShareViewController.swift +@@ -6,7 +6,9 @@ + // + // Created by Gustavo Parreira on 26/07/2020. + // ++// Modified by Veselin Stoyanov on 17/04/2021. + ++import Foundation + import MobileCoreServices + import UIKit + import Social +@@ -15,6 +17,7 @@ import RNShareMenu + class ShareViewController: SLComposeServiceViewController { + var hostAppId: String? + var hostAppUrlScheme: String? ++ var sharedItems: [Any] = [] + + override func viewDidLoad() { + super.viewDidLoad() +@@ -39,12 +42,12 @@ class ShareViewController: SLComposeServiceViewController { + + override func didSelectPost() { + // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. +- guard let item = extensionContext?.inputItems.first as? NSExtensionItem else { ++ guard let items = extensionContext?.inputItems as? [NSExtensionItem] else { + cancelRequest() + return + } + +- handlePost(item) ++ handlePost(items) + } + + override func configurationItems() -> [Any]! { +@@ -52,24 +55,50 @@ class ShareViewController: SLComposeServiceViewController { + return [] + } + +- func handlePost(_ item: NSExtensionItem, extraData: [String:Any]? = nil) { +- guard let provider = item.attachments?.first else { +- cancelRequest() +- return +- } ++ func handlePost(_ items: [NSExtensionItem], extraData: [String:Any]? = nil) { ++ DispatchQueue.global().async { ++ guard let hostAppId = self.hostAppId else { ++ self.exit(withError: NO_INFO_PLIST_INDENTIFIER_ERROR) ++ return ++ } ++ guard let userDefaults = UserDefaults(suiteName: "group.\(hostAppId)") else { ++ self.exit(withError: NO_APP_GROUP_ERROR) ++ return ++ } + +- if let data = extraData { +- storeExtraData(data) +- } else { +- removeExtraData() +- } ++ if let data = extraData { ++ self.storeExtraData(data) ++ } else { ++ self.removeExtraData() ++ } + +- if provider.isText { +- storeText(withProvider: provider) +- } else if provider.isURL { +- storeUrl(withProvider: provider) +- } else { +- storeFile(withProvider: provider) ++ let semaphore = DispatchSemaphore(value: 0) ++ var results: [Any] = [] ++ ++ for item in items { ++ guard let attachments = item.attachments else { ++ self.cancelRequest() ++ return ++ } ++ ++ for provider in attachments { ++ if provider.isText { ++ self.storeText(withProvider: provider, semaphore) ++ } else if provider.isURL { ++ self.storeUrl(withProvider: provider, semaphore) ++ } else { ++ self.storeFile(withProvider: provider, semaphore) ++ } ++ ++ semaphore.wait() ++ } ++ } ++ ++ userDefaults.set(self.sharedItems, ++ forKey: USER_DEFAULTS_KEY) ++ userDefaults.synchronize() ++ ++ self.openHostApp() + } + } + +@@ -99,7 +128,7 @@ class ShareViewController: SLComposeServiceViewController { + userDefaults.synchronize() + } + +- func storeText(withProvider provider: NSItemProvider) { ++ func storeText(withProvider provider: NSItemProvider, _ semaphore: DispatchSemaphore) { + provider.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil) { (data, error) in + guard (error == nil) else { + self.exit(withError: error.debugDescription) +@@ -109,24 +138,13 @@ class ShareViewController: SLComposeServiceViewController { + self.exit(withError: COULD_NOT_FIND_STRING_ERROR) + return + } +- guard let hostAppId = self.hostAppId else { +- self.exit(withError: NO_INFO_PLIST_INDENTIFIER_ERROR) +- return +- } +- guard let userDefaults = UserDefaults(suiteName: "group.\(hostAppId)") else { +- self.exit(withError: NO_APP_GROUP_ERROR) +- return +- } + +- userDefaults.set([DATA_KEY: text, MIME_TYPE_KEY: "text/plain"], +- forKey: USER_DEFAULTS_KEY) +- userDefaults.synchronize() +- +- self.openHostApp() ++ self.sharedItems.append([DATA_KEY: text, MIME_TYPE_KEY: "text/plain"]) ++ semaphore.signal() + } + } + +- func storeUrl(withProvider provider: NSItemProvider) { ++ func storeUrl(withProvider provider: NSItemProvider, _ semaphore: DispatchSemaphore) { + provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { (data, error) in + guard (error == nil) else { + self.exit(withError: error.debugDescription) +@@ -136,24 +154,13 @@ class ShareViewController: SLComposeServiceViewController { + self.exit(withError: COULD_NOT_FIND_URL_ERROR) + return + } +- guard let hostAppId = self.hostAppId else { +- self.exit(withError: NO_INFO_PLIST_INDENTIFIER_ERROR) +- return +- } +- guard let userDefaults = UserDefaults(suiteName: "group.\(hostAppId)") else { +- self.exit(withError: NO_APP_GROUP_ERROR) +- return +- } +- +- userDefaults.set([DATA_KEY: url.absoluteString, MIME_TYPE_KEY: "text/plain"], +- forKey: USER_DEFAULTS_KEY) +- userDefaults.synchronize() + +- self.openHostApp() ++ self.sharedItems.append([DATA_KEY: url.absoluteString, MIME_TYPE_KEY: "text/plain"]) ++ semaphore.signal() + } + } + +- func storeFile(withProvider provider: NSItemProvider) { ++ func storeFile(withProvider provider: NSItemProvider, _ semaphore: DispatchSemaphore) { + provider.loadItem(forTypeIdentifier: kUTTypeData as String, options: nil) { (data, error) in + guard (error == nil) else { + self.exit(withError: error.debugDescription) +@@ -167,10 +174,6 @@ class ShareViewController: SLComposeServiceViewController { + self.exit(withError: NO_INFO_PLIST_INDENTIFIER_ERROR) + return + } +- guard let userDefaults = UserDefaults(suiteName: "group.\(hostAppId)") else { +- self.exit(withError: NO_APP_GROUP_ERROR) +- return +- } + guard let groupFileManagerContainer = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppId)") + else { +@@ -189,11 +192,8 @@ class ShareViewController: SLComposeServiceViewController { + return + } + +- userDefaults.set([DATA_KEY: filePath.absoluteString, MIME_TYPE_KEY: mimeType], +- forKey: USER_DEFAULTS_KEY) +- userDefaults.synchronize() +- +- self.openHostApp() ++ self.sharedItems.append([DATA_KEY: filePath.absoluteString, MIME_TYPE_KEY: mimeType]) ++ semaphore.signal() + } + } + diff --git a/src/@types/mastodon.d.ts b/src/@types/mastodon.d.ts index de1742b7..2e1663f7 100644 --- a/src/@types/mastodon.d.ts +++ b/src/@types/mastodon.d.ts @@ -320,8 +320,6 @@ declare namespace Mastodon { max_expiration: number } } - // Custom - to be deprecated in v4 - max_toot_chars?: number } type Mention = { @@ -428,6 +426,7 @@ declare namespace Mastodon { reblogs_count: number favourites_count: number replies_count: number + edited_at?: string // FEATURE edit_post favourited: boolean reblogged: boolean muted: boolean @@ -445,6 +444,17 @@ declare namespace Mastodon { text?: string } + type StatusHistory = { + content: Status['content'] + spoiler_text: Status['spoiler_text'] + sensitive: Status['sensitive'] + created_at: Status['created_at'] + poll: Status['poll'] + account: Status['account'] + media_attachments: Status['media_attachments'] + emojis: Status['emojis'] + } + type Source = { // Base note: string diff --git a/src/@types/untyped.d.ts b/src/@types/untyped.d.ts index 9220fed3..e3b7bc1a 100644 --- a/src/@types/untyped.d.ts +++ b/src/@types/untyped.d.ts @@ -4,3 +4,8 @@ declare module 'li' declare module 'react-native-feather' declare module 'react-native-htmlview' declare module 'react-native-toast-message' + +declare module '@helpers/features' { + const features: { feature: string; version: number; reference?: string }[] + export default features +} diff --git a/src/Screens.tsx b/src/Screens.tsx index 98bd115b..6d90d6c8 100644 --- a/src/Screens.tsx +++ b/src/Screens.tsx @@ -27,8 +27,10 @@ import { addScreenshotListener } from 'expo-screen-capture' import React, { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { Alert, Platform, StatusBar } from 'react-native' -import { useDispatch, useSelector } from 'react-redux' +import ShareMenu from 'react-native-share-menu' +import { useSelector } from 'react-redux' import * as Sentry from 'sentry-expo' +import { useAppDispatch } from './store' const Stack = createNativeStackNavigator() @@ -38,7 +40,7 @@ export interface Props { const Screens: React.FC = ({ localCorrupt }) => { const { t } = useTranslation('screens') - const dispatch = useDispatch() + const dispatch = useAppDispatch() const instanceActive = useSelector(getInstanceActive) const { colors, theme } = useTheme() @@ -157,6 +159,77 @@ const Screens: React.FC = ({ localCorrupt }) => { } }, [instanceActive, instances, deeplinked]) + // Share Extension + const handleShare = useCallback( + (item?: { + extraData?: { share: { mimeType: string; data: string }[] } + }) => { + if (instanceActive < 0) { + return + } + if ( + !item || + !item.extraData || + !Array.isArray(item.extraData.share) || + !item.extraData.share.length + ) { + return + } + + let text: string | undefined = undefined + let images: { type: string; uri: string }[] = [] + let video: { type: string; uri: string } | undefined = undefined + item.extraData.share.forEach((d, i) => { + const typesImage = ['png', 'jpg', 'jpeg', 'gif'] + const typesVideo = ['mp4', 'm4v', 'mov', 'webm'] + const { mimeType, data } = d + console.log('mimeType', mimeType) + console.log('data', data) + if (mimeType.startsWith('image/')) { + if (!typesImage.includes(mimeType.split('/')[1])) { + console.warn('Image type not supported:', mimeType.split('/')[1]) + return + } + images.push({ type: mimeType.split('/')[1], uri: data }) + } else if (mimeType.startsWith('video/')) { + if (!typesVideo.includes(mimeType.split('/')[1])) { + console.warn('Video type not supported:', mimeType.split('/')[1]) + return + } + video = { type: mimeType.split('/')[1], uri: data } + } else { + if (typesImage.includes(data.split('.').pop() || '')) { + images.push({ type: data.split('.').pop()!, uri: data }) + return + } + if (typesVideo.includes(data.split('.').pop() || '')) { + video = { type: data.split('.').pop()!, uri: data } + return + } + text = !text ? data : text.concat(text, `\n${data}`) + } + }) + navigationRef.navigate('Screen-Compose', { + type: 'share', + text, + images, + video + }) + }, + [instanceActive] + ) + useEffect(() => { + console.log('getting intial share') + ShareMenu.getInitialShare(handleShare) + }, []) + useEffect(() => { + console.log('getting just share') + const listener = ShareMenu.addNewShareListener(handleShare) + return () => { + listener.remove() + } + }, []) + return ( <> diff --git a/src/ShareExtension.tsx b/src/ShareExtension.tsx new file mode 100644 index 00000000..ef9fdd00 --- /dev/null +++ b/src/ShareExtension.tsx @@ -0,0 +1,105 @@ +import { useEffect, useState } from 'react' +import { Appearance, Platform, Pressable, Text } from 'react-native' +import { Circle } from 'react-native-animated-spinkit' +import RNFS from 'react-native-fs' +import { ShareMenuReactView } from 'react-native-share-menu' +import uuid from 'react-native-uuid' + +// mimeType +// text/plain - text only, website URL, video?! +// image/jpeg - image +// video/mp4 - video + +const colors = { + primary: { + light: 'rgb(18, 18, 18)', + dark: 'rgb(180, 180, 180)' + }, + background: { + light: 'rgb(250, 250, 250)', + dark: 'rgb(18, 18, 18)' + } +} + +const clearDir = async (dir: string) => { + try { + const files = await RNFS.readDir(dir) + for (const file of files) { + await RNFS.unlink(file.path) + } + } catch (err: any) { + console.warn(err.message) + } +} + +const ShareExtension = () => { + const [errorMessage, setErrorMessage] = useState() + + useEffect(() => { + ShareMenuReactView.data().then(async ({ data }) => { + console.log('length', data.length) + const newData = [] + switch (Platform.OS) { + case 'ios': + for (const d of data) { + if (d.data.startsWith('file:///')) { + const extension = d.data.split('.').pop()?.toLowerCase() + const filename = `${uuid.v4()}.${extension}` + const groupDirectory = await RNFS.pathForGroup( + 'group.com.xmflsct.app.tooot' + ) + await clearDir(groupDirectory) + const newFilepath = `file://${groupDirectory}/${filename}` + console.log('newFilepath', newFilepath) + try { + await RNFS.copyFile(d.data, newFilepath) + newData.push({ ...d, data: newFilepath }) + } catch (err: any) { + setErrorMessage(err.message) + console.warn(err.message) + } + } else { + newData.push(d) + } + } + break + case 'android': + break + default: + return + } + console.log('new data', newData) + if (!errorMessage) { + ShareMenuReactView.continueInApp({ share: newData }) + } + }) + }, []) + + const theme = Appearance.getColorScheme() || 'light' + + return ( + { + if (errorMessage) { + ShareMenuReactView.dismissExtension(errorMessage) + } + }} + > + {!errorMessage ? ( + + {errorMessage} + + ) : ( + + )} + + ) +} + +export default ShareExtension diff --git a/src/components/Emojis/List.tsx b/src/components/Emojis/List.tsx index 1f60f338..df213f1c 100644 --- a/src/components/Emojis/List.tsx +++ b/src/components/Emojis/List.tsx @@ -1,3 +1,4 @@ +import { useAppDispatch } from '@root/store' import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { countInstanceEmoji } from '@utils/slices/instancesSlice' import { StyleConstants } from '@utils/styles/constants' @@ -15,13 +16,12 @@ import { View } from 'react-native' import FastImage from 'react-native-fast-image' -import { useDispatch } from 'react-redux' import validUrl from 'valid-url' import EmojisContext from './helpers/EmojisContext' const EmojisList = React.memo( () => { - const dispatch = useDispatch() + const dispatch = useAppDispatch() const { reduceMotionEnabled } = useAccessibility() const { t } = useTranslation() diff --git a/src/components/GracefullyImage.tsx b/src/components/GracefullyImage.tsx index 081d7feb..5fa68666 100644 --- a/src/components/GracefullyImage.tsx +++ b/src/components/GracefullyImage.tsx @@ -1,6 +1,6 @@ import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useTheme } from '@utils/styles/ThemeManager' -import React, { useCallback, useMemo, useState } from 'react' +import React, { useMemo, useState } from 'react' import { AccessibilityProps, Image, @@ -39,125 +39,103 @@ export interface Props { > } -const GracefullyImage = React.memo( - ({ - accessibilityLabel, - accessibilityHint, - hidden = false, - uri, - blurhash, - dimension, - onPress, - style, - imageStyle, - setImageDimensions - }: Props) => { - const { reduceMotionEnabled } = useAccessibility() - const { colors } = useTheme() - const [originalFailed, setOriginalFailed] = useState(false) - const [imageLoaded, setImageLoaded] = useState(false) +const GracefullyImage = ({ + accessibilityLabel, + accessibilityHint, + hidden = false, + uri, + blurhash, + dimension, + onPress, + style, + imageStyle, + setImageDimensions +}: Props) => { + const { reduceMotionEnabled } = useAccessibility() + const { colors } = useTheme() + const [originalFailed, setOriginalFailed] = useState(false) + const [imageLoaded, setImageLoaded] = useState(false) - const source = useMemo(() => { - if (originalFailed) { - return { uri: uri.remote || undefined } - } else { - return { - uri: reduceMotionEnabled && uri.static ? uri.static : uri.original - } + const source = originalFailed + ? { uri: uri.remote || undefined } + : { + uri: reduceMotionEnabled && uri.static ? uri.static : uri.original } - }, [originalFailed]) - const onLoad = useCallback(() => { - setImageLoaded(true) - if (setImageDimensions && source.uri) { - Image.getSize(source.uri, (width, height) => - setImageDimensions({ width, height }) + const onLoad = () => { + setImageLoaded(true) + if (setImageDimensions && source.uri) { + Image.getSize(source.uri, (width, height) => + setImageDimensions({ width, height }) + ) + } + } + const onError = () => { + if (!originalFailed) { + setOriginalFailed(true) + } + } + + const blurhashView = useMemo(() => { + if (hidden || !imageLoaded) { + if (blurhash) { + return ( + ) - } - }, [source.uri]) - const onError = useCallback(() => { - if (!originalFailed) { - setOriginalFailed(true) - } - }, [originalFailed]) - - const previewView = useMemo( - () => - uri.preview && !imageLoaded ? ( - - ) : null, - [] - ) - const originalView = useMemo( - () => ( + ) + } + } else { + return null + } + }, [hidden, imageLoaded]) + + return ( + + ) +} const styles = StyleSheet.create({ placeholder: { diff --git a/src/components/Hashtag.tsx b/src/components/Hashtag.tsx index c32a9ef7..fede3ab9 100644 --- a/src/components/Hashtag.tsx +++ b/src/components/Hashtag.tsx @@ -1,5 +1,6 @@ import { useNavigation } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' +import { TabLocalStackParamList } from '@utils/navigation/navigators' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React, { useCallback } from 'react' @@ -19,7 +20,7 @@ const ComponentHashtag: React.FC = ({ }) => { const { colors } = useTheme() const navigation = - useNavigation>() + useNavigation>() const onPress = useCallback(() => { analytics('search_account_press', { page: origin }) diff --git a/src/components/Instance/Auth.tsx b/src/components/Instance/Auth.tsx index 8d0e9e49..2418bb49 100644 --- a/src/components/Instance/Auth.tsx +++ b/src/components/Instance/Auth.tsx @@ -1,11 +1,12 @@ import { useNavigation } from '@react-navigation/native' +import { useAppDispatch } from '@root/store' import { TabMeStackNavigationProp } from '@utils/navigation/navigators' import addInstance from '@utils/slices/instances/add' -import { Instance } from '@utils/slices/instancesSlice' +import { checkInstanceFeature, Instance } from '@utils/slices/instancesSlice' import * as AuthSession from 'expo-auth-session' import React, { useEffect } from 'react' import { useQueryClient } from 'react-query' -import { useDispatch } from 'react-redux' +import { useSelector } from 'react-redux' export interface Props { instanceDomain: string @@ -25,13 +26,18 @@ const InstanceAuth = React.memo( const navigation = useNavigation>() const queryClient = useQueryClient() - const dispatch = useDispatch() + const dispatch = useAppDispatch() + const deprecateAuthFollow = useSelector( + checkInstanceFeature('deprecate_auth_follow') + ) const [request, response, promptAsync] = AuthSession.useAuthRequest( { clientId: appData.clientId, clientSecret: appData.clientSecret, - scopes: ['read', 'write', 'follow', 'push'], + scopes: deprecateAuthFollow + ? ['read', 'write', 'push'] + : ['read', 'write', 'follow', 'push'], redirectUri }, { diff --git a/src/components/Parse/HTML.tsx b/src/components/Parse/HTML.tsx index 38d88536..cb9cb39d 100644 --- a/src/components/Parse/HTML.tsx +++ b/src/components/Parse/HTML.tsx @@ -4,6 +4,7 @@ import openLink from '@components/openLink' import ParseEmojis from '@components/Parse/Emojis' import { useNavigation, useRoute } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' +import { TabLocalStackParamList } from '@utils/navigation/navigators' import { getSettingsFontsize } from '@utils/slices/settingsSlice' import { StyleConstants } from '@utils/styles/constants' import layoutAnimation from '@utils/styles/layoutAnimation' @@ -35,7 +36,7 @@ const renderNode = ({ index: number adaptedFontsize: number adaptedLineheight: number - navigation: StackNavigationProp + navigation: StackNavigationProp mentions?: Mastodon.Mention[] tags?: Mastodon.Tag[] showFullLink: boolean @@ -194,7 +195,7 @@ const ParseHTML = React.memo( ) const navigation = - useNavigation>() + useNavigation>() const route = useRoute() const { colors, theme } = useTheme() const { t, i18n } = useTranslation('componentParse') @@ -301,7 +302,7 @@ const ParseHTML = React.memo( /> ) }, - () => true + (prev, next) => prev.content === next.content ) export default ParseHTML diff --git a/src/components/Timeline.tsx b/src/components/Timeline.tsx index d2a8040f..9d41b3fd 100644 --- a/src/components/Timeline.tsx +++ b/src/components/Timeline.tsx @@ -1,5 +1,6 @@ import ComponentSeparator from '@components/Separator' import { useScrollToTop } from '@react-navigation/native' +import { useAppDispatch } from '@root/store' import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline' import { getInstanceActive, @@ -20,7 +21,7 @@ import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import TimelineEmpty from './Timeline/Empty' import TimelineFooter from './Timeline/Footer' import TimelineRefresh, { @@ -127,7 +128,7 @@ const Timeline: React.FC = ({ } }) - const dispatch = useDispatch() + const dispatch = useAppDispatch() const viewabilityPairs = useRef([ { viewabilityConfig: { diff --git a/src/components/Timeline/Conversation.tsx b/src/components/Timeline/Conversation.tsx index 601ba7f9..212234d4 100644 --- a/src/components/Timeline/Conversation.tsx +++ b/src/components/Timeline/Conversation.tsx @@ -3,12 +3,14 @@ import analytics from '@components/analytics' import GracefullyImage from '@components/GracefullyImage' import { useNavigation } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' +import { TabLocalStackParamList } from '@utils/navigation/navigators' import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { getInstanceAccount } from '@utils/slices/instancesSlice' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' +import { isEqual } from 'lodash' import React, { useCallback } from 'react' -import { Pressable, StyleSheet, View } from 'react-native' +import { Pressable, View } from 'react-native' import { useMutation, useQueryClient } from 'react-query' import { useSelector } from 'react-redux' import TimelineActions from './Shared/Actions' @@ -53,117 +55,107 @@ export interface Props { highlighted?: boolean } -const TimelineConversation: React.FC = ({ - conversation, - queryKey, - highlighted = false -}) => { - const instanceAccount = useSelector( - getInstanceAccount, - (prev, next) => prev?.id === next?.id - ) - const { colors } = useTheme() +const TimelineConversation = React.memo( + ({ conversation, queryKey, highlighted = false }: Props) => { + const instanceAccount = useSelector( + getInstanceAccount, + (prev, next) => prev?.id === next?.id + ) + const { colors } = useTheme() - const queryClient = useQueryClient() - const fireMutation = useCallback(() => { - return apiInstance({ - method: 'post', - url: `conversations/${conversation.id}/read` - }) - }, []) - const { mutate } = useMutation(fireMutation, { - onSettled: () => { - queryClient.invalidateQueries(queryKey) - } - }) - - const navigation = - useNavigation>() - const onPress = useCallback(() => { - analytics('timeline_conversation_press') - if (conversation.last_status) { - conversation.unread && mutate() - navigation.push('Tab-Shared-Toot', { - toot: conversation.last_status, - rootQueryKey: queryKey + const queryClient = useQueryClient() + const fireMutation = useCallback(() => { + return apiInstance({ + method: 'post', + url: `conversations/${conversation.id}/read` }) - } - }, []) + }, []) + const { mutate } = useMutation(fireMutation, { + onSettled: () => { + queryClient.invalidateQueries(queryKey) + } + }) - return ( - - - - - + const navigation = + useNavigation>() + const onPress = useCallback(() => { + analytics('timeline_conversation_press') + if (conversation.last_status) { + conversation.unread && mutate() + navigation.push('Tab-Shared-Toot', { + toot: conversation.last_status, + rootQueryKey: queryKey + }) + } + }, []) - {conversation.last_status ? ( - <> - - + + + + + + {conversation.last_status ? ( + <> + + + {conversation.last_status.poll ? ( + + ) : null} + + account.acct)} + reblog={false} /> - {conversation.last_status.poll ? ( - - ) : null} - - account.acct)} - reblog={false} - /> - - ) : null} - - ) -} - -const styles = StyleSheet.create({ - base: { - flex: 1, - flexDirection: 'column', - padding: StyleConstants.Spacing.Global.PagePadding, - paddingBottom: 0 + + ) : null} + + ) }, - header: { - flex: 1, - width: '100%', - flexDirection: 'row' - } -}) + (prev, next) => isEqual(prev.conversation, next.conversation) +) export default TimelineConversation diff --git a/src/components/Timeline/Default.tsx b/src/components/Timeline/Default.tsx index 4d02049f..5a235b34 100644 --- a/src/components/Timeline/Default.tsx +++ b/src/components/Timeline/Default.tsx @@ -9,15 +9,16 @@ import TimelineHeaderDefault from '@components/Timeline/Shared/HeaderDefault' import TimelinePoll from '@components/Timeline/Shared/Poll' import { useNavigation } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' +import { TabLocalStackParamList } from '@utils/navigation/navigators' import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { getInstanceAccount } from '@utils/slices/instancesSlice' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' -import { uniqBy } from 'lodash' +import { isEqual, uniqBy } from 'lodash' import React, { useCallback } from 'react' -import { Pressable, StyleSheet, View } from 'react-native' +import { Pressable, View } from 'react-native' import { useSelector } from 'react-redux' -import TimelineActionsUsers from './Shared/ActionsUsers' +import TimelineFeedback from './Shared/Feedback' import TimelineFiltered, { shouldFilter } from './Shared/Filtered' import TimelineFullConversation from './Shared/FullConversation' import TimelineTranslate from './Shared/Translate' @@ -33,148 +34,143 @@ export interface Props { } // When the poll is long -const TimelineDefault: React.FC = ({ - item, - queryKey, - rootQueryKey, - origin, - highlighted = false, - disableDetails = false, - disableOnPress = false -}) => { - const { colors } = useTheme() - const instanceAccount = useSelector(getInstanceAccount, () => true) - const navigation = - useNavigation>() +const TimelineDefault = React.memo( + ({ + item, + queryKey, + rootQueryKey, + origin, + highlighted = false, + disableDetails = false, + disableOnPress = false + }: Props) => { + const { colors } = useTheme() + const instanceAccount = useSelector(getInstanceAccount, () => true) + const navigation = + useNavigation>() - const actualStatus = item.reblog ? item.reblog : item + const actualStatus = item.reblog ? item.reblog : item - const ownAccount = actualStatus.account?.id === instanceAccount?.id + const ownAccount = actualStatus.account?.id === instanceAccount?.id - if ( - !highlighted && - queryKey && - shouldFilter({ status: actualStatus, queryKey }) - ) { - return - } - - const onPress = useCallback(() => { - analytics('timeline_default_press', { - page: queryKey ? queryKey[1].page : origin - }) - !disableOnPress && + if ( !highlighted && - navigation.push('Tab-Shared-Toot', { - toot: actualStatus, - rootQueryKey: queryKey - }) - }, []) + queryKey && + shouldFilter({ status: actualStatus, queryKey }) + ) { + return + } - return ( - { + analytics('timeline_default_press', { + page: queryKey ? queryKey[1].page : origin + }) + !disableOnPress && + !highlighted && + navigation.push('Tab-Shared-Toot', { + toot: actualStatus, + rootQueryKey: queryKey + }) + }, []) + + return ( + - {item.reblog ? ( - - ) : item._pinned ? ( - - ) : null} - - - - - - - - {typeof actualStatus.content === 'string' && - actualStatus.content.length > 0 ? ( - + {item.reblog ? ( + + ) : item._pinned ? ( + ) : null} - {queryKey && actualStatus.poll ? ( - + + + + + + {typeof actualStatus.content === 'string' && + actualStatus.content.length > 0 ? ( + + ) : null} + {queryKey && actualStatus.poll ? ( + + ) : null} + {!disableDetails && + Array.isArray(actualStatus.media_attachments) && + actualStatus.media_attachments.length ? ( + + ) : null} + {!disableDetails && actualStatus.card ? ( + + ) : null} + {!disableDetails ? ( + + ) : null} + + + + + {queryKey && !disableDetails ? ( + d?.id !== instanceAccount?.id), + d => d?.id + ).map(d => d?.acct)} reblog={item.reblog ? true : false} - sameAccount={ownAccount} /> ) : null} - {!disableDetails && - Array.isArray(actualStatus.media_attachments) && - actualStatus.media_attachments.length ? ( - - ) : null} - {!disableDetails && actualStatus.card ? ( - - ) : null} - {!disableDetails ? ( - - ) : null} - - - - - {queryKey && !disableDetails ? ( - d?.id !== instanceAccount?.id), - d => d?.id - ).map(d => d?.acct)} - reblog={item.reblog ? true : false} - /> - ) : null} - - ) -} - -const styles = StyleSheet.create({ - statusView: { - padding: StyleConstants.Spacing.Global.PagePadding, - paddingBottom: 0 + + ) }, - header: { - flex: 1, - width: '100%', - flexDirection: 'row' - } -}) + (prev, next) => isEqual(prev.item, next.item) +) export default TimelineDefault diff --git a/src/components/Timeline/Empty.tsx b/src/components/Timeline/Empty.tsx index 8b7fdf4b..145db777 100644 --- a/src/components/Timeline/Empty.tsx +++ b/src/components/Timeline/Empty.tsx @@ -4,7 +4,7 @@ import Icon from '@components/Icon' import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' -import React, { useMemo } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import { StyleSheet, Text, View } from 'react-native' import { Circle } from 'react-native-animated-spinkit' @@ -20,10 +20,10 @@ const TimelineEmpty = React.memo( options: { notifyOnChangeProps: ['status'] } }) - const { colors, theme } = useTheme() - const { t, i18n } = useTranslation('componentTimeline') + const { colors } = useTheme() + const { t } = useTranslation('componentTimeline') - const children = useMemo(() => { + const children = () => { switch (status) { case 'loading': return ( @@ -67,24 +67,25 @@ const TimelineEmpty = React.memo( ) } - }, [theme, i18n.language, status]) + } return ( + style={{ + flex: 1, + minHeight: '100%', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: colors.backgroundDefault + }} + > + {children()} + ) }, () => true ) const styles = StyleSheet.create({ - base: { - flex: 1, - minHeight: '100%', - justifyContent: 'center', - alignItems: 'center' - }, error: { ...StyleConstants.FontStyle.M, marginTop: StyleConstants.Spacing.S, diff --git a/src/components/Timeline/Footer.tsx b/src/components/Timeline/Footer.tsx index d61df1ac..95392e59 100644 --- a/src/components/Timeline/Footer.tsx +++ b/src/components/Timeline/Footer.tsx @@ -4,7 +4,7 @@ import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React from 'react' import { Trans } from 'react-i18next' -import { StyleSheet, Text, View } from 'react-native' +import { Text, View } from 'react-native' import { Circle } from 'react-native-animated-spinkit' export interface Props { @@ -27,11 +27,20 @@ const TimelineFooter = React.memo( const { colors } = useTheme() return ( - + {!disableInfinity && hasNextPage ? ( ) : ( - + true ) -const styles = StyleSheet.create({ - base: { - flex: 1, - flexDirection: 'row', - justifyContent: 'center', - padding: StyleConstants.Spacing.M - }, - text: { - ...StyleConstants.FontStyle.S - } -}) - export default TimelineFooter diff --git a/src/components/Timeline/Lookback.tsx b/src/components/Timeline/Lookback.tsx index 25baf50f..ff4b78ca 100644 --- a/src/components/Timeline/Lookback.tsx +++ b/src/components/Timeline/Lookback.tsx @@ -2,7 +2,7 @@ import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React from 'react' import { useTranslation } from 'react-i18next' -import { StyleSheet, Text, View } from 'react-native' +import { Text, View } from 'react-native' const TimelineLookback = React.memo( () => { @@ -11,10 +11,19 @@ const TimelineLookback = React.memo( return ( {t('lookback.message')} @@ -24,16 +33,4 @@ const TimelineLookback = React.memo( () => true ) -const styles = StyleSheet.create({ - base: { - flex: 1, - flexDirection: 'row', - justifyContent: 'center', - padding: StyleConstants.Spacing.S - }, - text: { - ...StyleConstants.FontStyle.S - } -}) - export default TimelineLookback diff --git a/src/components/Timeline/Notifications.tsx b/src/components/Timeline/Notifications.tsx index 14c33f58..c4df686f 100644 --- a/src/components/Timeline/Notifications.tsx +++ b/src/components/Timeline/Notifications.tsx @@ -9,13 +9,14 @@ import TimelineHeaderNotification from '@components/Timeline/Shared/HeaderNotifi import TimelinePoll from '@components/Timeline/Shared/Poll' import { useNavigation } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' +import { TabLocalStackParamList } from '@utils/navigation/navigators' import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { getInstanceAccount } from '@utils/slices/instancesSlice' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' -import { uniqBy } from 'lodash' +import { isEqual, uniqBy } from 'lodash' import React, { useCallback } from 'react' -import { Pressable, StyleSheet, View } from 'react-native' +import { Pressable, View } from 'react-native' import { useSelector } from 'react-redux' import TimelineFiltered, { shouldFilter } from './Shared/Filtered' import TimelineFullConversation from './Shared/FullConversation' @@ -26,151 +27,137 @@ export interface Props { highlighted?: boolean } -const TimelineNotifications: React.FC = ({ - notification, - queryKey, - highlighted = false -}) => { - if ( - notification.status && - shouldFilter({ status: notification.status, queryKey }) - ) { - return - } +const TimelineNotifications = React.memo( + ({ notification, queryKey, highlighted = false }: Props) => { + if ( + notification.status && + shouldFilter({ status: notification.status, queryKey }) + ) { + return + } - const { colors } = useTheme() - const instanceAccount = useSelector( - getInstanceAccount, - (prev, next) => prev?.id === next?.id - ) - const navigation = - useNavigation>() + const { colors } = useTheme() + const instanceAccount = useSelector( + getInstanceAccount, + (prev, next) => prev?.id === next?.id + ) + const navigation = + useNavigation>() - const actualAccount = notification.status - ? notification.status.account - : notification.account + const actualAccount = notification.status + ? notification.status.account + : notification.account - const onPress = useCallback(() => { - analytics('timeline_notification_press') - notification.status && - navigation.push('Tab-Shared-Toot', { - toot: notification.status, - rootQueryKey: queryKey - }) - }, []) + const onPress = useCallback(() => { + analytics('timeline_notification_press') + notification.status && + navigation.push('Tab-Shared-Toot', { + toot: notification.status, + rootQueryKey: queryKey + }) + }, []) - return ( - - {notification.type !== 'mention' ? ( - - ) : null} - - - - - + ) : null} + + + + + + + + {notification.status ? ( + + {notification.status.content.length > 0 ? ( + + ) : null} + {notification.status.poll ? ( + + ) : null} + {notification.status.media_attachments.length > 0 ? ( + + ) : null} + {notification.status.card ? ( + + ) : null} + + + ) : null} {notification.status ? ( - - {notification.status.content.length > 0 ? ( - - ) : null} - {notification.status.poll ? ( - - ) : null} - {notification.status.media_attachments.length > 0 ? ( - - ) : null} - {notification.status.card ? ( - - ) : null} - - + d?.id !== instanceAccount?.id), + d => d?.id + ).map(d => d?.acct)} + reblog={false} + /> ) : null} - - - {notification.status ? ( - d?.id !== instanceAccount?.id), - d => d?.id - ).map(d => d?.acct)} - reblog={false} - /> - ) : null} - - ) -} - -const styles = StyleSheet.create({ - notificationView: { - padding: StyleConstants.Spacing.Global.PagePadding + + ) }, - header: { - flex: 1, - width: '100%', - flexDirection: 'row' - } -}) + (prev, next) => isEqual(prev.notification, next.notification) +) export default TimelineNotifications diff --git a/src/components/Timeline/Shared/Actioned.tsx b/src/components/Timeline/Shared/Actioned.tsx index c0ce1d26..47b59a9f 100644 --- a/src/components/Timeline/Shared/Actioned.tsx +++ b/src/components/Timeline/Shared/Actioned.tsx @@ -3,6 +3,7 @@ import Icon from '@components/Icon' import { ParseEmojis } from '@components/Parse' import { useNavigation } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' +import { TabLocalStackParamList } from '@utils/navigation/navigators' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React, { useCallback, useMemo } from 'react' @@ -20,7 +21,7 @@ const TimelineActioned = React.memo( const { t } = useTranslation('componentTimeline') const { colors } = useTheme() const navigation = - useNavigation>() + useNavigation>() const name = account.display_name || account.username const iconColor = colors.primaryDefault @@ -143,19 +144,23 @@ const TimelineActioned = React.memo( } }, []) - return + return ( + + ) }, () => true ) const styles = StyleSheet.create({ - actioned: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: StyleConstants.Spacing.S, - paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S, - paddingRight: StyleConstants.Spacing.Global.PagePadding - }, icon: { marginRight: StyleConstants.Spacing.S } diff --git a/src/components/Timeline/Shared/Actions.tsx b/src/components/Timeline/Shared/Actions.tsx index b36b691a..336a5217 100644 --- a/src/components/Timeline/Shared/Actions.tsx +++ b/src/components/Timeline/Shared/Actions.tsx @@ -2,6 +2,8 @@ import analytics from '@components/analytics' import Icon from '@components/Icon' import { displayMessage } from '@components/Message' import { useNavigation } from '@react-navigation/native' +import { StackNavigationProp } from '@react-navigation/stack' +import { RootStackParamList } from '@utils/navigation/navigators' import { MutationVarsTimelineUpdateStatusProperty, QueryKeyTimeline, @@ -31,7 +33,7 @@ const TimelineActions: React.FC = ({ accts, reblog }) => { - const navigation = useNavigation() + const navigation = useNavigation>() const { t } = useTranslation('componentTimeline') const { colors, theme } = useTheme() const iconColor = colors.secondary diff --git a/src/components/Timeline/Shared/ActionsUsers.tsx b/src/components/Timeline/Shared/ActionsUsers.tsx deleted file mode 100644 index 08ccbf00..00000000 --- a/src/components/Timeline/Shared/ActionsUsers.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import analytics from '@components/analytics' -import { useNavigation } from '@react-navigation/native' -import { StackNavigationProp } from '@react-navigation/stack' -import { StyleConstants } from '@utils/styles/constants' -import { useTheme } from '@utils/styles/ThemeManager' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { StyleSheet, Text, View } from 'react-native' - -export interface Props { - status: Mastodon.Status - highlighted: boolean -} - -const TimelineActionsUsers = React.memo( - ({ status, highlighted }: Props) => { - if (!highlighted) { - return null - } - - const { t } = useTranslation('componentTimeline') - const { colors } = useTheme() - const navigation = - useNavigation>() - - return ( - - {status.reblogs_count > 0 ? ( - { - analytics('timeline_shared_actionsusers_press_boosted', { - count: status.reblogs_count - }) - navigation.push('Tab-Shared-Users', { - reference: 'statuses', - id: status.id, - type: 'reblogged_by', - count: status.reblogs_count - }) - }} - > - {t('shared.actionsUsers.reblogged_by.text', { - count: status.reblogs_count - })} - - ) : null} - {status.favourites_count > 0 ? ( - { - analytics('timeline_shared_actionsusers_press_boosted', { - count: status.favourites_count - }) - navigation.push('Tab-Shared-Users', { - reference: 'statuses', - id: status.id, - type: 'favourited_by', - count: status.favourites_count - }) - }} - > - {t('shared.actionsUsers.favourited_by.text', { - count: status.favourites_count - })} - - ) : null} - - ) - }, - (prev, next) => - prev.status.reblogs_count === next.status.reblogs_count && - prev.status.favourites_count === next.status.favourites_count -) - -const styles = StyleSheet.create({ - base: { - flexDirection: 'row' - }, - text: { - ...StyleConstants.FontStyle.M, - padding: StyleConstants.Spacing.S, - paddingLeft: 0, - marginRight: StyleConstants.Spacing.S - } -}) - -export default TimelineActionsUsers diff --git a/src/components/Timeline/Shared/Attachment.tsx b/src/components/Timeline/Shared/Attachment.tsx index a0444e69..a4a4b670 100644 --- a/src/components/Timeline/Shared/Attachment.tsx +++ b/src/components/Timeline/Shared/Attachment.tsx @@ -6,13 +6,13 @@ import AttachmentImage from '@components/Timeline/Shared/Attachment/Image' import AttachmentUnsupported from '@components/Timeline/Shared/Attachment/Unsupported' import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video' import { useNavigation } from '@react-navigation/native' +import { StackNavigationProp } from '@react-navigation/stack' import { RootStackParamList } from '@utils/navigation/navigators' import { StyleConstants } from '@utils/styles/constants' import layoutAnimation from '@utils/styles/layoutAnimation' -import React, { useCallback, useMemo, useRef, useState } from 'react' -import { useEffect } from 'react' +import React, { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Pressable, StyleSheet, View } from 'react-native' +import { Pressable, View } from 'react-native' export interface Props { status: Pick @@ -23,24 +23,13 @@ const TimelineAttachment = React.memo( const { t } = useTranslation('componentTimeline') const [sensitiveShown, setSensitiveShown] = useState(status.sensitive) - const onPressBlurView = useCallback(() => { - analytics('timeline_shared_attachment_blurview_press_show') - layoutAnimation() - setSensitiveShown(false) - haptics('Light') - }, []) - const onPressShow = useCallback(() => { - analytics('timeline_shared_attachment_blurview_press_hide') - setSensitiveShown(true) - haptics('Light') - }, []) const imageUrls = useRef< RootStackParamList['Screen-ImagesViewer']['imageUrls'] >([]) - const navigation = useNavigation() - useEffect(() => { - status.media_attachments.forEach((attachment, index) => { + const navigation = useNavigation>() + const navigateToImagesViewer = (id: string) => { + status.media_attachments.forEach(attachment => { switch (attachment.type) { case 'image': imageUrls.current.push({ @@ -54,117 +43,136 @@ const TimelineAttachment = React.memo( }) } }) - }, []) - const navigateToImagesViewer = (id: string) => navigation.navigate('Screen-ImagesViewer', { imageUrls: imageUrls.current, id }) - const attachments = useMemo( - () => - status.media_attachments.map((attachment, index) => { - switch (attachment.type) { - case 'image': - return ( - - ) - case 'video': - return ( - - ) - case 'gifv': - return ( - - ) - case 'audio': - return ( - - ) - default: - if ( - attachment.preview_url?.endsWith('.jpg') || - attachment.preview_url?.endsWith('.jpeg') || - attachment.preview_url?.endsWith('.png') || - attachment.preview_url?.endsWith('.gif') || - attachment.remote_url?.endsWith('.jpg') || - attachment.remote_url?.endsWith('.jpeg') || - attachment.remote_url?.endsWith('.png') || - attachment.remote_url?.endsWith('.gif') - ) { - imageUrls.current.push({ - id: attachment.id, - preview_url: attachment.preview_url, - url: attachment.url, - remote_url: attachment.remote_url, - blurhash: attachment.blurhash, - width: attachment.meta?.original?.width, - height: attachment.meta?.original?.height - }) + } + + return ( + + + {status.media_attachments.map((attachment, index) => { + switch (attachment.type) { + case 'image': return ( ) - } else { + case 'video': return ( - ) - } - } - }), - [sensitiveShown] - ) - - return ( - - + case 'gifv': + return ( + + ) + case 'audio': + return ( + + ) + default: + if ( + attachment.preview_url?.endsWith('.jpg') || + attachment.preview_url?.endsWith('.jpeg') || + attachment.preview_url?.endsWith('.png') || + attachment.preview_url?.endsWith('.gif') || + attachment.remote_url?.endsWith('.jpg') || + attachment.remote_url?.endsWith('.jpeg') || + attachment.remote_url?.endsWith('.png') || + attachment.remote_url?.endsWith('.gif') + ) { + imageUrls.current.push({ + id: attachment.id, + preview_url: attachment.preview_url, + url: attachment.url, + remote_url: attachment.remote_url, + blurhash: attachment.blurhash, + width: attachment.meta?.original?.width, + height: attachment.meta?.original?.height + }) + return ( + + ) + } else { + return ( + + ) + } + } + })} + {status.sensitive && (sensitiveShown ? ( - +