diff --git a/android/app/build.gradle b/android/app/build.gradle index 3488c5a444..23e0c2bbb3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -153,6 +153,7 @@ repositories { dependencies { + compile project(':react-native-fetch-blob') compile project(':react-native-sound') compile project(':react-native-fabric') compile project(':react-native-device-info') diff --git a/android/app/src/main/java/com/zulipmobile/MainApplication.java b/android/app/src/main/java/com/zulipmobile/MainApplication.java index 0c13c12627..68073e8178 100644 --- a/android/app/src/main/java/com/zulipmobile/MainApplication.java +++ b/android/app/src/main/java/com/zulipmobile/MainApplication.java @@ -6,6 +6,7 @@ import android.util.Log; import com.facebook.react.ReactApplication; +import com.RNFetchBlob.RNFetchBlobPackage; import com.zmxv.RNSound.RNSoundPackage; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; @@ -41,6 +42,7 @@ protected List getPackages() { return Arrays.asList( new FabricPackage(), new MainReactPackage(), + new RNFetchBlobPackage(), new RNSoundPackage(), new RNDeviceInfo(), new ZulipNativePackage(), @@ -65,4 +67,4 @@ public void onCreate() { public IPushNotification getPushNotification(Context context, Bundle bundle, AppLifecycleFacade defaultFacade, AppLaunchHelper defaultAppLaunchHelper) { return new GCMPushNotifications(context, bundle, defaultFacade, defaultAppLaunchHelper, new JsIOHelper()); } -} \ No newline at end of file +} diff --git a/android/settings.gradle b/android/settings.gradle index 401bfc72ca..3cab020251 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,6 @@ rootProject.name = 'ZulipMobile' +include ':react-native-fetch-blob' +project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android') include ':react-native-sound' project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android') include ':react-native-device-info' diff --git a/ios/ZulipMobile.xcodeproj/project.pbxproj b/ios/ZulipMobile.xcodeproj/project.pbxproj index 45289b222a..8bb63f9b59 100644 --- a/ios/ZulipMobile.xcodeproj/project.pbxproj +++ b/ios/ZulipMobile.xcodeproj/project.pbxproj @@ -38,10 +38,12 @@ A14EA8771EACE522009D9E83 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A14EA85B1EACE4CF009D9E83 /* libRCTAnimation.a */; }; A155C0061DD8E7A800A8B695 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A155BFEC1DD8E54100A8B695 /* libRCTCameraRoll.a */; }; A83E9861943143E6A10A689E /* libSafariViewManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E518466F398E458DA2C685AF /* libSafariViewManager.a */; }; + AC8907C1FC1D41A2895679ED /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B5340A2B6063464CABA5B5F8 /* libRNFetchBlob.a */; }; C2FB87564E22416A9EDEABD3 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = AEF58326BC25479294083E9C /* EvilIcons.ttf */; }; C478B42A63D649EFA2CFD12D /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E53FD60A6D0409A8E58DCB5 /* SimpleLineIcons.ttf */; }; CF15A6B21E5C0C8A003EA77F /* UIView+Tag.m in Sources */ = {isa = PBXBuildFile; fileRef = CF15A6B11E5C0C8A003EA77F /* UIView+Tag.m */; }; CF15A6B61E5C56E5003EA77F /* OptimizedGifDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = CF15A6B51E5C56E5003EA77F /* OptimizedGifDecoder.m */; }; + CF1624861EE4799800E7FA13 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A155BFEC1DD8E54100A8B695 /* libRCTCameraRoll.a */; }; CF1953241E5BE67D00B14FF0 /* AnchorScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = CF1953231E5BE67D00B14FF0 /* AnchorScrollView.m */; }; CF1953261E5BF2E000B14FF0 /* AnchorScrollViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = CF1953251E5BF2E000B14FF0 /* AnchorScrollViewManager.m */; }; CF19532E1E5C062B00B14FF0 /* TaggedViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = CF19532D1E5C062B00B14FF0 /* TaggedViewManager.m */; }; @@ -207,6 +209,13 @@ remoteGlobalIDString = DA5891D81BA9A9FC002B4DB2; remoteInfo = RNDeviceInfo; }; + CF1624801EE478ED00E7FA13 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F4990F2438CE41A291EDBCC9 /* RNFetchBlob.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = A15C300E1CD25C330074CB35; + remoteInfo = RNFetchBlob; + }; CF68BE0D1E2CD177001D50C6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; @@ -263,13 +272,6 @@ remoteGlobalIDString = 3D3CD9181DE5FBD800167DC4; remoteInfo = "jschelpers-tvOS"; }; - CFA67D171EC232E70070048E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 6DA76D9085EA458BB979ED8D /* RNVectorIcons.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = A39873CE1EA65EE60051E01A; - remoteInfo = "RNVectorIcons-tvOS"; - }; CFA67D1A1EC232E70070048E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 96B2280917D24AE692AD70FE /* SafariViewManager.xcodeproj */; @@ -329,6 +331,7 @@ ACCD980949044096B1BAD249 /* RNDeviceInfo.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNDeviceInfo.xcodeproj; path = "../node_modules/react-native-device-info/RNDeviceInfo.xcodeproj"; sourceTree = ""; }; AEF58326BC25479294083E9C /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = ""; }; AF03F5DAC0924A13BDD49574 /* RNSound.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSound.xcodeproj; path = "../node_modules/react-native-sound/RNSound.xcodeproj"; sourceTree = ""; }; + B5340A2B6063464CABA5B5F8 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = ""; }; BD9BD8DB981C415B9CD9C8B9 /* SMXCrashlytics.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = SMXCrashlytics.xcodeproj; path = "../node_modules/react-native-fabric/ios/SMXCrashlytics.xcodeproj"; sourceTree = ""; }; C9B0BDEA40534DFA9BDC4C75 /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = ""; }; CA3376A965524CF8E47D2150 /* Pods-ZulipMobileTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZulipMobileTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ZulipMobileTests/Pods-ZulipMobileTests.release.xcconfig"; sourceTree = ""; }; @@ -348,6 +351,7 @@ D5664A74FA8048439CBAB734 /* libRNDeviceInfo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNDeviceInfo.a; sourceTree = ""; }; E518466F398E458DA2C685AF /* libSafariViewManager.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libSafariViewManager.a; sourceTree = ""; }; EBE1731503514D0FBFEE11BF /* libSMXCrashlytics.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libSMXCrashlytics.a; sourceTree = ""; }; + F4990F2438CE41A291EDBCC9 /* RNFetchBlob.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFetchBlob.xcodeproj; path = "../node_modules/react-native-fetch-blob/ios/RNFetchBlob.xcodeproj"; sourceTree = ""; }; F56CBB1B9A6449F895C858C6 /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = ""; }; FD9027BAF72ED45247660C16 /* Pods-ZulipMobileTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZulipMobileTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ZulipMobileTests/Pods-ZulipMobileTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -367,6 +371,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CF1624861EE4799800E7FA13 /* libRCTCameraRoll.a in Frameworks */, A14EA8771EACE522009D9E83 /* libRCTAnimation.a in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, @@ -384,6 +389,7 @@ 78FA95D8058C4372AB199525 /* libRNSound.a in Frameworks */, F6397C64C31B4108B66C0839 /* libSMXCrashlytics.a in Frameworks */, A83E9861943143E6A10A689E /* libSafariViewManager.a in Frameworks */, + AC8907C1FC1D41A2895679ED /* libRNFetchBlob.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -557,6 +563,7 @@ AF03F5DAC0924A13BDD49574 /* RNSound.xcodeproj */, BD9BD8DB981C415B9CD9C8B9 /* SMXCrashlytics.xcodeproj */, 96B2280917D24AE692AD70FE /* SafariViewManager.xcodeproj */, + F4990F2438CE41A291EDBCC9 /* RNFetchBlob.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -600,7 +607,6 @@ isa = PBXGroup; children = ( A1198F071DD517CE007C3837 /* libRNVectorIcons.a */, - CFA67D181EC232E70070048E /* libRNVectorIcons-tvOS.a */, ); name = Products; sourceTree = ""; @@ -638,6 +644,14 @@ name = Products; sourceTree = ""; }; + CF1624621EE478ED00E7FA13 /* Products */ = { + isa = PBXGroup; + children = ( + CF1624811EE478ED00E7FA13 /* libRNFetchBlob.a */, + ); + name = Products; + sourceTree = ""; + }; CFA67CF51EC232E70070048E /* Products */ = { isa = PBXGroup; children = ( @@ -799,6 +813,10 @@ ProductGroup = A1F9F7411E1BE26300A70FA5 /* Products */; ProjectRef = ACCD980949044096B1BAD249 /* RNDeviceInfo.xcodeproj */; }, + { + ProductGroup = CF1624621EE478ED00E7FA13 /* Products */; + ProjectRef = F4990F2438CE41A291EDBCC9 /* RNFetchBlob.xcodeproj */; + }, { ProductGroup = A148FEDC1E9D8C9600479280 /* Products */; ProjectRef = AF03F5DAC0924A13BDD49574 /* RNSound.xcodeproj */; @@ -972,6 +990,13 @@ remoteRef = A1F9F7561E1BE26300A70FA5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + CF1624811EE478ED00E7FA13 /* libRNFetchBlob.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRNFetchBlob.a; + remoteRef = CF1624801EE478ED00E7FA13 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; CF68BE0E1E2CD177001D50C6 /* libRCTLinking-tvOS.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1028,13 +1053,6 @@ remoteRef = CF68BE2C1E2CD177001D50C6 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - CFA67D181EC232E70070048E /* libRNVectorIcons-tvOS.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libRNVectorIcons-tvOS.a"; - remoteRef = CFA67D171EC232E70070048E /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; CFA67D1B1EC232E70070048E /* libSafariViewManager.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1268,6 +1286,7 @@ "$(SRCROOT)/../node_modules/react-native-sound/RNSound", "$(SRCROOT)/../node_modules/react-native-fabric/ios/SMXCrashlytics", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**", ); INFOPLIST_FILE = ZulipMobileTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.2; @@ -1280,6 +1299,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "$(inherited)", @@ -1305,6 +1325,7 @@ "$(SRCROOT)/../node_modules/react-native-sound/RNSound", "$(SRCROOT)/../node_modules/react-native-fabric/ios/SMXCrashlytics", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**", ); INFOPLIST_FILE = ZulipMobileTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.2; @@ -1317,6 +1338,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "$(inherited)", @@ -1343,6 +1365,7 @@ "$(SRCROOT)/../node_modules/react-native-sound/RNSound", "$(SRCROOT)/../node_modules/react-native-fabric/ios/SMXCrashlytics", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**", ); INFOPLIST_FILE = ZulipMobile/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1373,6 +1396,7 @@ "$(SRCROOT)/../node_modules/react-native-sound/RNSound", "$(SRCROOT)/../node_modules/react-native-fabric/ios/SMXCrashlytics", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**", ); INFOPLIST_FILE = ZulipMobile/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; diff --git a/ios/ZulipMobile/Info.plist b/ios/ZulipMobile/Info.plist index 74fe1b572a..42801d36ec 100644 --- a/ios/ZulipMobile/Info.plist +++ b/ios/ZulipMobile/Info.plist @@ -89,5 +89,7 @@ + NSPhotoLibraryUsageDescription + This app requires access to the photo library. diff --git a/package.json b/package.json index f430bec67d..ddb9e89562 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react-native-drawer": "^2.3.0", "react-native-fabric": "^0.4.1", "react-native-notifications": "^1.1.10", + "react-native-fetch-blob": "^0.10.5", "react-native-safari-view": "^2.0.0", "react-native-scrollable-tab-view": "^0.7.0", "react-native-sound": "^0.10.1", diff --git a/src/api/downloadFile.android.js b/src/api/downloadFile.android.js new file mode 100644 index 0000000000..5fb0a36f31 --- /dev/null +++ b/src/api/downloadFile.android.js @@ -0,0 +1,21 @@ +/* @flow */ +import RNFetchBlob from 'react-native-fetch-blob'; + +import { getAuthHeader } from '../utils/url'; +import userAgent from '../utils/userAgent'; +import { Auth } from '../types'; + +export default (url: string, auth: Auth) => + RNFetchBlob.config({ + addAndroidDownloads: { + useDownloadManager: true, + // Optional, but recommended since android DownloadManager will fail when + // the url does not contains a file extension, by default the mime type will be text/plain + mime: 'text/plain', + title: url.split('/').pop(), + }, + }).fetch('GET', url, { + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', + 'User-Agent': userAgent, + Authorization: getAuthHeader(auth.email, auth.apiKey), + }); diff --git a/src/api/downloadFile.ios.js b/src/api/downloadFile.ios.js new file mode 100644 index 0000000000..dd5cf9c455 --- /dev/null +++ b/src/api/downloadFile.ios.js @@ -0,0 +1,4 @@ +import { CameraRoll, AlertIOS } from 'react-native'; + +export default (url: string) => + CameraRoll.saveToCameraRoll(url).then(() => AlertIOS.alert('Download complete', 'File saved to CameraRoll.')); diff --git a/src/light-box/share.js b/src/light-box/share.js new file mode 100644 index 0000000000..e95c00c827 --- /dev/null +++ b/src/light-box/share.js @@ -0,0 +1,12 @@ +/* @flow */ +import { Share } from 'react-native'; + +import { BRAND_COLOR } from '../styles'; + +export default (url: string) => { + const shareOptions = { + message: url, + title: 'Shared using Zulip!', + }; + Share.share(shareOptions, { tintColor: BRAND_COLOR }).catch(err => {}); +};