Browse Source

receive-shared-files (#27)

Bank-1136: APP. Поширення фото/відео/документів з інших додатків у додаток TaskMe у чат
Reviewed-on: #27
Co-authored-by: Oksana Stepanenko <oksana.stepanenko@jetup.team>
Co-committed-by: Oksana Stepanenko <oksana.stepanenko@jetup.team>
change/chat-conversation
Oksana Stepanenko 9 months ago committed by Vitalik Yatsenko
parent
commit
2d0b061789
  1. 26
      android/app/src/main/AndroidManifest.xml
  2. 8
      android/app/src/main/java/com/taskme2/MainActivity.java
  3. 2
      android/build.gradle
  4. 24
      ios/ShareExtensionTaskme/Base.lproj/MainInterface.storyboard
  5. 30
      ios/ShareExtensionTaskme/Info.plist
  6. 10
      ios/ShareExtensionTaskme/ShareExtensionTaskme.entitlements
  7. 338
      ios/ShareExtensionTaskme/ShareViewController.swift
  8. 9
      ios/taskme2/AppDelegate.mm
  9. 12
      ios/taskme2/Info.plist
  10. 5
      ios/taskme2/taskme2.entitlements
  11. 17
      ios/taskme2/taskme2Debug.entitlements
  12. 5
      ios/taskme2/taskme2Stage.Release.entitlements
  13. 35151
      package-lock.json
  14. 2
      package.json
  15. 12
      src/modules/chats/hooks/use-selected-chats.hook.ts
  16. 1
      src/modules/chats/screens/index.ts
  17. 200
      src/modules/chats/screens/send-shared-files.screen.tsx
  18. 11
      src/modules/root/index.tsx
  19. 5
      src/modules/root/navigation-groups/users.group.tsx
  20. 32
      src/services/domain/chat-messages.service.ts
  21. 24
      src/services/system/fs.service.ts
  22. 1
      src/services/system/index.ts
  23. 31
      src/services/system/network.service.ts
  24. 1
      src/shared/configs/index.ts
  25. 607
      src/shared/configs/mime-types.config.ts
  26. 1
      src/shared/enums/route-key.enum.ts
  27. 14
      src/shared/helpers/fs.helpers.ts
  28. 1
      src/shared/helpers/index.ts
  29. 17
      src/shared/helpers/mime-type.helper.ts
  30. 1
      src/shared/hooks/index.ts
  31. 45
      src/shared/hooks/use-shared-files.hook.ts
  32. 1
      src/shared/index.ts
  33. 10
      src/shared/interfaces/media.interfaces.ts

26
android/app/src/main/AndroidManifest.xml

@ -44,7 +44,6 @@ @@ -44,7 +44,6 @@
<meta-data android:name="com.onesignal.BadgeCount" android:value="DISABLE" />
<meta-data android:name="com.onesignal.messaging.default_notification_icon" android:resource="@mipmap/ic_launcher" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
@ -56,6 +55,31 @@ @@ -56,6 +55,31 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
<data android:mimeType="application/pdf" />
<data android:mimeType="application/msword" />
<data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
<data android:mimeType="application/vnd.ms-excel" />
<data android:mimeType="application/zip" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
<data android:mimeType="application/pdf" />
<data android:mimeType="application/msword" />
<data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
<data android:mimeType="application/vnd.ms-excel" />
<data android:mimeType="application/zip" />
</intent-filter>
</activity>
<provider
android:name="com.vinzscam.reactnativefileviewer.FileProvider"

8
android/app/src/main/java/com/taskme2/MainActivity.java

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
package com.app.task_me;
import android.content.Intent;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
@ -30,4 +32,10 @@ public class MainActivity extends ReactActivity { @@ -30,4 +32,10 @@ public class MainActivity extends ReactActivity {
DefaultNewArchitectureEntryPoint.getFabricEnabled()
);
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
}

2
android/build.gradle

@ -11,6 +11,8 @@ buildscript { @@ -11,6 +11,8 @@ buildscript {
ndkVersion = "23.1.7779620"
pdfViewerVersion = "3.2.0-beta.1"
pdfViewerRepo = "com.github.mhiew"
kotlinVersion ='1.6.10'
}
repositories {
google()

24
ios/ShareExtensionTaskme/Base.lproj/MainInterface.storyboard

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

30
ios/ShareExtensionTaskme/Info.plist

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>PHSupportedMediaTypes</key>
<array>
<string>Video</string>
<string>Image</string>
</array>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>100</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>

10
ios/ShareExtensionTaskme/ShareExtensionTaskme.entitlements

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.app.taskme</string>
</array>
</dict>
</plist>

338
ios/ShareExtensionTaskme/ShareViewController.swift

@ -0,0 +1,338 @@ @@ -0,0 +1,338 @@
import UIKit
import Social
import MobileCoreServices
import Photos
class ShareViewController: SLComposeServiceViewController {
// TODO: IMPORTANT: This should be your host app bundle identifier
let hostAppBundleIdentifier = "com.app.taskme"
let shareProtocol = "ShareMediaTaskme" //share url protocol (must be unique to your app, suggest using your apple bundle id, ie: `hostAppBundleIdentifier`)
let sharedKey = "ShareKey"
var sharedMedia: [SharedMediaFile] = []
var sharedText: [String] = []
let imageContentType = kUTTypeImage as String
let videoContentType = kUTTypeMovie as String
let textContentType = kUTTypeText as String
let urlContentType = kUTTypeURL as String
let fileURLType = kUTTypeFileURL as String;
override func isContentValid() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad();
if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
if let contents = content.attachments {
for (index, attachment) in (contents).enumerated() {
if attachment.hasItemConformingToTypeIdentifier(imageContentType) {
handleImages(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(textContentType) {
handleText(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(fileURLType) {
handleFiles(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
handleUrl(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(videoContentType) {
handleVideos(content: content, attachment: attachment, index: index)
}
}
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func didSelectPost() {
print("didSelectPost");
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
private func handleText (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: textContentType, options: nil) { [weak self] data, error in
if error == nil, let item = data as? String, let this = self {
this.sharedText.append(item)
// If this is the last item, save imagesData in userDefaults and redirect to host app
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
userDefaults?.set(this.sharedText, forKey: this.sharedKey)
userDefaults?.synchronize()
this.redirectToHostApp(type: .text)
}
} else {
self?.dismissWithError()
}
}
}
private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in
if error == nil, let item = data as? URL, let this = self {
this.sharedText.append(item.absoluteString)
// If this is the last item, save imagesData in userDefaults and redirect to host app
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
userDefaults?.set(this.sharedText, forKey: this.sharedKey)
userDefaults?.synchronize()
this.redirectToHostApp(type: .text)
}
} else {
self?.dismissWithError()
}
}
}
private func handleImages (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: imageContentType, options: nil) { [weak self] data, error in
if error == nil, let url = data as? URL, let this = self {
// this.redirectToHostApp(type: .media)
// Always copy
let fileExtension = this.getExtension(from: url, type: .video)
let newName = UUID().uuidString
let newPath = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
.appendingPathComponent("\(newName).\(fileExtension)")
let copied = this.copyFile(at: url, to: newPath)
if(copied) {
this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image))
}
// If this is the last item, save imagesData in userDefaults and redirect to host app
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
userDefaults?.synchronize()
this.redirectToHostApp(type: .media)
}
} else {
self?.dismissWithError()
}
}
}
private func handleVideos (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: videoContentType, options:nil) { [weak self] data, error in
if error == nil, let url = data as? URL, let this = self {
// Always copy
let fileExtension = this.getExtension(from: url, type: .video)
let newName = UUID().uuidString
let newPath = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
.appendingPathComponent("\(newName).\(fileExtension)")
let copied = this.copyFile(at: url, to: newPath)
if(copied) {
guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else {
return
}
this.sharedMedia.append(sharedFile)
}
// If this is the last item, save imagesData in userDefaults and redirect to host app
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
userDefaults?.synchronize()
this.redirectToHostApp(type: .media)
}
} else {
self?.dismissWithError()
}
}
}
private func handleFiles (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
attachment.loadItem(forTypeIdentifier: fileURLType, options: nil) { [weak self] data, error in
if error == nil, let url = data as? URL, let this = self {
// Always copy
let newName = this.getFileName(from :url)
let newPath = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
.appendingPathComponent("\(newName)")
let copied = this.copyFile(at: url, to: newPath)
if (copied) {
this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .file))
}
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
userDefaults?.synchronize()
this.redirectToHostApp(type: .file)
}
} else {
self?.dismissWithError()
}
}
}
private func dismissWithError() {
print("[ERROR] Error loading data!")
let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
let action = UIAlertAction(title: "Error", style: .cancel) { _ in
self.dismiss(animated: true, completion: nil)
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
private func redirectToHostApp(type: RedirectType) {
let url = URL(string: "\(shareProtocol)://dataUrl=\(sharedKey)#\(type)")
var responder = self as UIResponder?
let selectorOpenURL = sel_registerName("openURL:")
while (responder != nil) {
if (responder?.responds(to: selectorOpenURL))! {
let _ = responder?.perform(selectorOpenURL, with: url)
}
responder = responder!.next
}
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
enum RedirectType {
case media
case text
case file
}
func getExtension(from url: URL, type: SharedMediaType) -> String {
let parts = url.lastPathComponent.components(separatedBy: ".")
var ex: String? = nil
if (parts.count > 1) {
ex = parts.last
}
if (ex == nil) {
switch type {
case .image:
ex = "PNG"
case .video:
ex = "MP4"
case .file:
ex = "TXT"
}
}
return ex ?? "Unknown"
}
func getFileName(from url: URL) -> String {
var name = url.lastPathComponent
if (name == "") {
name = UUID().uuidString + "." + getExtension(from: url, type: .file)
}
return name
}
func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
do {
if FileManager.default.fileExists(atPath: dstURL.path) {
try FileManager.default.removeItem(at: dstURL)
}
try FileManager.default.copyItem(at: srcURL, to: dstURL)
} catch (let error) {
print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
return false
}
return true
}
private func getSharedMediaFile(forVideo: URL) -> SharedMediaFile? {
let asset = AVAsset(url: forVideo)
let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
let thumbnailPath = getThumbnailPath(for: forVideo)
if FileManager.default.fileExists(atPath: thumbnailPath.path) {
return SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video)
}
var saved = false
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
// let scale = UIScreen.main.scale
assetImgGenerate.maximumSize = CGSize(width: 360, height: 360)
do {
let img = try assetImgGenerate.copyCGImage(at: CMTimeMakeWithSeconds(600, preferredTimescale: Int32(1.0)), actualTime: nil)
try UIImage.pngData(UIImage(cgImage: img))()?.write(to: thumbnailPath)
saved = true
} catch {
saved = false
}
return saved ? SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) : nil
}
private func getThumbnailPath(for url: URL) -> URL {
let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "==", with: "")
let path = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
.appendingPathComponent("\(fileName).jpg")
return path
}
class SharedMediaFile: Codable {
var path: String; // can be image, video or url path. It can also be text content
var thumbnail: String?; // video thumbnail
var duration: Double?; // video duration in milliseconds
var type: SharedMediaType;
init(path: String, thumbnail: String?, duration: Double?, type: SharedMediaType) {
self.path = path
self.thumbnail = thumbnail
self.duration = duration
self.type = type
}
// Debug method to print out SharedMediaFile details in the console
func toString() {
print("[SharedMediaFile] \n\tpath: \(self.path)\n\tthumbnail: \(self.thumbnail)\n\tduration: \(self.duration)\n\ttype: \(self.type)")
}
}
enum SharedMediaType: Int, Codable {
case image
case video
case file
}
func toData(data: [SharedMediaFile]) -> Data {
let encodedData = try? JSONEncoder().encode(data)
return encodedData!
}
}
extension Array {
subscript (safe index: UInt) -> Element? {
return Int(index) < count ? self[Int(index)] : nil
}
}

9
ios/taskme2/AppDelegate.mm

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
#import "AppDelegate.h"
#import <React/RCTLinkingManager.h>
#import <React/RCTBundleURLProvider.h>
@implementation AppDelegate
@ -23,6 +25,11 @@ @@ -23,6 +25,11 @@
#endif
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
return [RCTLinkingManager application:application openURL:url options:options];
}
@end

12
ios/taskme2/Info.plist

@ -88,5 +88,17 @@ @@ -88,5 +88,17 @@
<false/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ShareMediaTaskme</string> <!-- share url protocol (must be unique to your app, suggest using your apple bundle id) -->
</array>
</dict>
<dict/>
</array>
</dict>
</plist>

5
ios/taskme2/taskme2.entitlements

@ -4,9 +4,14 @@ @@ -4,9 +4,14 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:example.com</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.app.taskme.onesignal</string>
<string>group.com.app.taskme</string>
</array>
</dict>
</plist>

17
ios/taskme2/taskme2Debug.entitlements

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:example.com</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.app.taskme.onesignal</string>
<string>group.com.app.taskme</string>
</array>
</dict>
</plist>

5
ios/taskme2/taskme2Stage.Release.entitlements

@ -4,9 +4,14 @@ @@ -4,9 +4,14 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:example.com</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.app.taskme.stage.onesignal</string>
<string>group.com.app.taskme</string>
</array>
</dict>
</plist>

35151
package-lock.json generated

File diff suppressed because it is too large Load Diff

2
package.json

@ -26,6 +26,7 @@ @@ -26,6 +26,7 @@
"@react-native-async-storage/async-storage": "^1.18.2",
"@react-native-camera-roll/camera-roll": "^5.7.2",
"@react-native-community/clipboard": "^1.5.1",
"@react-native-community/datetimepicker": "^7.6.2",
"@react-native-community/netinfo": "^9.3.10",
"@react-native-picker/picker": "^2.4.10",
"@react-native/eslint-config": "^0.72.2",
@ -80,6 +81,7 @@ @@ -80,6 +81,7 @@
"react-native-print": "^0.10.0",
"react-native-raw-bottom-sheet": "^2.2.0",
"react-native-reanimated": "^3.4.1",
"react-native-receive-sharing-intent": "^2.0.0",
"react-native-restart": "^0.0.27",
"react-native-safe-area-context": "^4.7.1",
"react-native-screens": "^3.23.0",

12
src/modules/chats/hooks/use-selected-chats.hook.ts

@ -18,7 +18,8 @@ export const useSelectedChats = () => { @@ -18,7 +18,8 @@ export const useSelectedChats = () => {
})
const transformedChats = useMemo(
() => chats?.map(it => transformChatToSelectListItem(it, selectedChats)),
() =>
chats?.map(it => transformChatToSelectListItem(it, selectedChats)),
[selectedChats, chats],
)
@ -26,7 +27,9 @@ export const useSelectedChats = () => { @@ -26,7 +27,9 @@ export const useSelectedChats = () => {
const selectChat = (id: number | string) => {
setSelectedChats(state => {
const chat = transformedChats.find(it => it.id.toString() === id.toString())
const chat = transformedChats.find(
it => it.id.toString() === id.toString(),
)
if (chat) return [...state, chat]
return state
@ -34,7 +37,9 @@ export const useSelectedChats = () => { @@ -34,7 +37,9 @@ export const useSelectedChats = () => {
}
const unselectChat = (id: number | string) => {
setSelectedChats(state => state.filter(it => it.id.toString() !== id.toString()))
setSelectedChats(state =>
state.filter(it => it.id.toString() !== id.toString()),
)
}
useEffect(() => {
@ -50,5 +55,6 @@ export const useSelectedChats = () => { @@ -50,5 +55,6 @@ export const useSelectedChats = () => {
searchString,
setSearchString,
loadMore,
setSelectedChats,
}
}

1
src/modules/chats/screens/index.ts

@ -2,4 +2,5 @@ export * from './chats.screen' @@ -2,4 +2,5 @@ export * from './chats.screen'
export * from './group-chat-detail.screen'
export * from './create-group.screen'
export * from './forward-message.screen'
export * from './send-shared-files.screen'
export * from './create-personal.screen'

200
src/modules/chats/screens/send-shared-files.screen.tsx

@ -0,0 +1,200 @@ @@ -0,0 +1,200 @@
import React, { FC, useEffect, useMemo } from 'react'
import {
$size,
FooterWithBtn,
IRouteParams,
ISharedFile,
NotFound,
RouteKey,
ScreenLayout,
SearchWithBtn,
appEvents,
useTheme,
} from '@/shared'
import { FlatList, StyleSheet, View } from 'react-native'
import { SelectChatListItem, SelectedChatsRow } from '../components'
import { PartialTheme } from '@/shared/themes/interfaces'
import {
alertFileSizeExceeded,
checkFileSize,
isCloseToBottom,
} from '@/shared/helpers'
import _ from 'lodash'
import { useSelectedChats } from '../hooks'
import { fsService } from '@/services/system'
import { chatMessagesService } from '@/services/domain'
import { useSelector } from 'react-redux'
import { getFilesConfig } from '@/store/shared'
import { ISendFileMessage } from '@/api/chat-messages/requests.interfaces'
interface IProps extends IRouteParams {
route: {
params: {
files: ISharedFile[]
}
}
}
export const SendSharedFilesScreen: FC<IProps> = ({
navigation,
route: {
params: { files },
},
}) => {
const { styles } = useTheme(createStyles)
const { chatFilesSize, chatVideosSize } = useSelector(getFilesConfig)
const {
chats,
selectedChats,
selectAll,
selectChat,
unselectChat,
searchString,
setSearchString,
loadMore,
setSelectedChats,
} = useSelectedChats()
const renderSelectedChatsRow = useMemo(() => {
return (
<SelectedChatsRow
selectedChats={selectedChats}
onPressRemove={id => {
unselectChat(id)
}}
/>
)
}, [selectedChats])
useEffect(() => {
setSelectedChats([])
}, [files])
const send = async () => {
const chatsIds = selectedChats.map(it => it.id)
if (_.isEmpty(chatsIds)) return
try {
const filesStats = []
for await (const file of files) {
const fileStat = await fsService.getSharedFileStat(file)
if (fileStat) filesStats.push(fileStat)
}
const { allowed, exceeded } = checkFileSize(
filesStats,
chatFilesSize,
chatVideosSize,
)
if (!_.isEmpty(exceeded))
alertFileSizeExceeded(exceeded, chatFilesSize, chatVideosSize)
if (_.isEmpty(allowed)) return
for await (const chat of selectedChats) {
try {
await chatMessagesService.storeMessageByType(
allowed.map(it => {
const dataToStore: ISendFileMessage = {
file: it,
chatId: chat.id,
fileName: it.name,
}
return dataToStore
}),
)
} catch (e) {
console.log('Error on send shared file to chat', chat.id, e)
}
}
navigation.navigate(RouteKey.Chats)
} catch (e) {
appEvents.emit('openInfoModal', {
title: 'Виникла помилка, ',
message: 'спробуйте будь-ласка пізніше',
pressButtonText: 'Продовжити',
})
}
}
return (
<ScreenLayout
header={{
title: `Виберіть бесіди`,
goBack: () => {
navigation.goBack()
},
style: {
marginBottom: $size(20, 18),
paddingTop: $size(10, 10),
},
}}
footer={() => <FooterWithBtn btnTitle="Надіслати" onPress={send} />}
horizontalPadding={0}>
{selectedChats.length ? renderSelectedChatsRow : null}
<View style={styles.container}>
<SearchWithBtn
containerStyle={styles.searchContainer}
searchValue={searchString}
btnStyle={styles.searchBtn}
btnTitleStyle={styles.searchBtnTitle}
borderBottom={false}
onChange={setSearchString}
onPressBtn={selectAll}
btnTitle="Вибрати всі"
placeholder="Знайдіть бесіду"
/>
<FlatList
data={chats}
contentContainerStyle={styles.contentContainerStyle}
initialNumToRender={10}
onScroll={({ nativeEvent }) => {
if (isCloseToBottom(nativeEvent)) loadMore()
}}
keyExtractor={item => `${item.id}`}
renderItem={({ item }) => (
<SelectChatListItem
previewUri={item.previewUrl}
name={item.name}
isChecked={item.isSelected}
onSelectItem={() =>
!item.isSelected
? selectChat(item.id)
: unselectChat(item.id)
}
isDisabled={item.isDisabled}
/>
)}
ListEmptyComponent={<NotFound text="Бесіди не знайдені" />}
/>
</View>
</ScreenLayout>
)
}
const createStyles = (theme: PartialTheme) =>
StyleSheet.create({
container: {
flex: 1,
flexGrow: 300,
},
searchContainer: {
marginBottom: $size(10),
},
contentContainerStyle: {
paddingHorizontal: $size(18, 16),
minHeight: '60%',
},
smallContainer: {
maxHeight: '52%',
},
searchBtn: {
backgroundColor: theme?.chats?.selectItems?.$addAllBtnBg,
},
searchBtnTitle: {
color: theme?.chats?.selectItems?.$addAllBtnTxt,
},
})

11
src/modules/root/index.tsx

@ -2,7 +2,12 @@ import React, { FC, useEffect, useMemo, useState } from 'react' @@ -2,7 +2,12 @@ import React, { FC, useEffect, useMemo, useState } from 'react'
import { useSelector } from 'react-redux'
import { NavigationContainer } from '@react-navigation/native'
import { selectActiveNavigationModule } from '@/store/shared'
import { ActionSheet, NavigationModuleKey, useTheme } from '@/shared'
import {
ActionSheet,
NavigationModuleKey,
useSharedFiles,
useTheme,
} from '@/shared'
import {
AuthNavigationGroup,
NoInternetConnectionGroup,
@ -26,7 +31,6 @@ import { SelectTaxonomiesModalSmart } from '../taxonomies/smart-components' @@ -26,7 +31,6 @@ import { SelectTaxonomiesModalSmart } from '../taxonomies/smart-components'
import { SelectUserModalSmart } from '../users/smart-components'
import { LoadingScreen } from './screens'
import _ from 'lodash'
import { RecordAudioModalSmart } from '../media'
import { ChatSendImgModal } from '../chats/components'
import { NetStatus } from './components'
@ -38,6 +42,7 @@ import { PreviewFileModal } from '@/shared/components/modals/preview-file-modal. @@ -38,6 +42,7 @@ import { PreviewFileModal } from '@/shared/components/modals/preview-file-modal.
export const Navigation: FC = () => {
const activeModule = useSelector(selectActiveNavigationModule)
const { styles } = useTheme(createStyles)
const [isAppReady, setAppReady] = useState(false)
@ -46,6 +51,8 @@ export const Navigation: FC = () => { @@ -46,6 +51,8 @@ export const Navigation: FC = () => {
// const isConnected = true
// const status = 'online'
useSharedFiles()
useAppSocketListener()
useAppBadge()

5
src/modules/root/navigation-groups/users.group.tsx

@ -24,6 +24,7 @@ import { @@ -24,6 +24,7 @@ import {
CreatePersonalScreen,
ForwardMessageScreen,
GroupChatDetailScreen,
SendSharedFilesScreen,
} from '@/modules/chats'
const Stack = createNativeStackNavigator()
@ -74,6 +75,10 @@ export const UsersGroup: FC = () => ( @@ -74,6 +75,10 @@ export const UsersGroup: FC = () => (
name={RouteKey.ForwardMessage}
component={ForwardMessageScreen}
/>
<Stack.Screen
name={RouteKey.SendSharedFiles}
component={SendSharedFilesScreen}
/>
{/* --- Tasks --- */}
<Stack.Screen

32
src/services/domain/chat-messages.service.ts

@ -9,7 +9,11 @@ import { @@ -9,7 +9,11 @@ import {
} from '@/api/chat-messages/requests.interfaces'
import { chatMessageManager } from '@/managers/chat-message.manager'
import { appEvents, IChatMessage } from '@/shared'
import { runActionByType, showUknowError } from '@/shared/helpers'
import {
runActionByType,
runActionByTypeOrDefault,
showUknowError,
} from '@/shared/helpers'
import { SetUnreadMessagesCount } from '@/store/chats'
import { simpleDispatch } from '@/store/store-helpers'
import { Alert } from 'react-native'
@ -81,6 +85,31 @@ const storeMediaMessages = async (data: ISendFileMessage[]) => { @@ -81,6 +85,31 @@ const storeMediaMessages = async (data: ISendFileMessage[]) => {
}
}
const storeMessageByType = async (data: ISendFileMessage[]) => {
for await (const item of data) {
await runActionByTypeOrDefault(item.file, {
video: async () => {
await chatMessageManager.storeVideoMessage.bind(
chatMessageManager,
)(item)
},
image: async () => {
await chatMessageManager.storeImageMessage.bind(
chatMessageManager,
)(item)
},
default: async () => {
await chatMessageManager.storeFileMessage.bind(
chatMessageManager,
)({
chatId: item.chatId,
files: [item.file],
})
},
})
}
}
const clearChatMessages = async (
data: IClearChatPayload,
unreadMessagesCount?: number,
@ -170,4 +199,5 @@ export const chatMessagesService = { @@ -170,4 +199,5 @@ export const chatMessagesService = {
deleteChatMessage,
clearAllChatsMessages,
editTextMessage,
storeMessageByType,
}

24
src/services/system/fs.service.ts

@ -1,10 +1,11 @@ @@ -1,10 +1,11 @@
import { Alert, Platform } from 'react-native'
import { appEvents, FileType, IFile } from '@/shared'
import { appEvents, FileType, IFile, ISharedFile } from '@/shared'
import {
createUniqueFileName,
getFileExtension,
getFileName,
getFileUri,
getMimeTypeByExtension,
getNameFromFileUrl,
getUrlExtension,
isAndroid,
@ -18,6 +19,7 @@ import { saveToCameraRoll } from '@/services/system/camera-roll.service' @@ -18,6 +19,7 @@ import { saveToCameraRoll } from '@/services/system/camera-roll.service'
import Share from 'react-native-share'
import FileViewer from 'react-native-file-viewer'
import * as _ from 'lodash'
interface IWriteFileData {
content: string
fileName: string
@ -51,6 +53,25 @@ const getFileStat = async (filePath: string): Promise<IFile> => { @@ -51,6 +53,25 @@ const getFileStat = async (filePath: string): Promise<IFile> => {
}
}
const getSharedFileStat = async (file: ISharedFile): Promise<IFile> => {
if (!file || !file.filePath) return null
try {
const fileStat = await RNFS.stat(prepareUriToRNFS(file.filePath))
return {
name: file.fileName,
type: Platform.select({
ios: getMimeTypeByExtension(file.extension),
android: file.mimeType,
}),
size: fileStat.size,
uri: getFileUri(fileStat),
}
} catch (err) {
console.log('getFileStat ERR:', err)
}
}
const writeFile = (data: IWriteFileData) => {
return new Promise<string>((resolve, reject) => {
const directory = isAndroid(
@ -350,4 +371,5 @@ export const fsService = { @@ -350,4 +371,5 @@ export const fsService = {
shareFileOutside,
createPath,
previewFile,
getSharedFileStat,
}

1
src/services/system/index.ts

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
export * from './global-container.service'
export * from './network.service'
export * from './navigation.service'
export * from './storage.service'
export * from './device-info.service'

31
src/services/system/network.service.ts

@ -1,31 +0,0 @@ @@ -1,31 +0,0 @@
import { NavigationModuleKey } from '@/shared/enums'
import { SetNavigationModule } from '@/store/shared'
import { simpleDispatch } from '@/store/store-helpers'
// import { checkInternetConnection } from 'react-native-offline'
const TEST_CONNECTION_LINK = 'https://www.google.com/'
const init = async (): Promise<boolean> => {
// const isConnected = await checkInternetConnection(
// TEST_CONNECTION_LINK,
// 3000,
// false,
// )
// if (!isConnected) onOffline()
// return isConnected
return true
}
// const onOffline = () => {
// simpleDispatch(
// new SetNavigationModule({
// module: NavigationModuleKey.NoInternetConnection,
// }),
// )
// }
export const networkService = {
init,
}

1
src/shared/configs/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from './mime-types.config'

607
src/shared/configs/mime-types.config.ts

@ -0,0 +1,607 @@ @@ -0,0 +1,607 @@
export const mimeTypes = {
'.323': 'text/h323',
'.3g2': 'video/3gpp2',
'.3gp': 'video/3gpp',
'.3gp2': 'video/3gpp2',
'.3gpp': 'video/3gpp',
'.7z': 'application/x-7z-compressed',
'.aa': 'audio/audible',
'.AAC': 'audio/aac',
'.aaf': 'application/octet-stream',
'.aax': 'audio/vnd.audible.aax',
'.ac3': 'audio/ac3',
'.aca': 'application/octet-stream',
'.accda': 'application/msaccess.addin',
'.accdb': 'application/msaccess',
'.accdc': 'application/msaccess.cab',
'.accde': 'application/msaccess',
'.accdr': 'application/msaccess.runtime',
'.accdt': 'application/msaccess',
'.accdw': 'application/msaccess.webapplication',
'.accft': 'application/msaccess.ftemplate',
'.acx': 'application/internet-property-stream',
'.AddIn': 'text/xml',
'.ade': 'application/msaccess',
'.adobebridge': 'application/x-bridge-url',
'.adp': 'application/msaccess',
'.ADT': 'audio/vnd.dlna.adts',
'.ADTS': 'audio/aac',
'.afm': 'application/octet-stream',
'.ai': 'application/postscript',
'.aif': 'audio/aiff',
'.aifc': 'audio/aiff',
'.aiff': 'audio/aiff',
'.air': 'application/vnd.adobe.air-application-installer-package+zip',
'.amc': 'application/mpeg',
'.anx': 'application/annodex',
'.apk': 'application/vnd.android.package-archive',
'.application': 'application/x-ms-application',
'.art': 'image/x-jg',
'.asa': 'application/xml',
'.asax': 'application/xml',
'.ascx': 'application/xml',
'.asd': 'application/octet-stream',
'.asf': 'video/x-ms-asf',
'.ashx': 'application/xml',
'.asi': 'application/octet-stream',
'.asm': 'text/plain',
'.asmx': 'application/xml',
'.aspx': 'application/xml',
'.asr': 'video/x-ms-asf',
'.asx': 'video/x-ms-asf',
'.atom': 'application/atom+xml',
'.au': 'audio/basic',
'.avi': 'video/x-msvideo',
'.axa': 'audio/annodex',
'.axs': 'application/olescript',
'.axv': 'video/annodex',
'.bas': 'text/plain',
'.bcpio': 'application/x-bcpio',
'.bin': 'application/octet-stream',
'.bmp': 'image/bmp',
'.c': 'text/plain',
'.cab': 'application/octet-stream',
'.caf': 'audio/x-caf',
'.calx': 'application/vnd.ms-office.calx',
'.cat': 'application/vnd.ms-pki.seccat',
'.cc': 'text/plain',
'.cd': 'text/plain',
'.cdda': 'audio/aiff',
'.cdf': 'application/x-cdf',
'.cer': 'application/x-x509-ca-cert',
'.cfg': 'text/plain',
'.chm': 'application/octet-stream',
'.class': 'application/x-java-applet',
'.clp': 'application/x-msclip',
'.cmd': 'text/plain',
'.cmx': 'image/x-cmx',
'.cnf': 'text/plain',
'.cod': 'image/cis-cod',
'.config': 'application/xml',
'.contact': 'text/x-ms-contact',
'.coverage': 'application/xml',
'.cpio': 'application/x-cpio',
'.cpp': 'text/plain',
'.crd': 'application/x-mscardfile',
'.crl': 'application/pkix-crl',
'.crt': 'application/x-x509-ca-cert',
'.cs': 'text/plain',
'.csdproj': 'text/plain',
'.csh': 'application/x-csh',
'.csproj': 'text/plain',
'.css': 'text/css',
'.csv': 'text/csv',
'.cur': 'application/octet-stream',
'.cxx': 'text/plain',
'.dat': 'application/octet-stream',
'.datasource': 'application/xml',
'.dbproj': 'text/plain',
'.dcr': 'application/x-director',
'.def': 'text/plain',
'.deploy': 'application/octet-stream',
'.der': 'application/x-x509-ca-cert',
'.dgml': 'application/xml',
'.dib': 'image/bmp',
'.dif': 'video/x-dv',
'.dir': 'application/x-director',
'.disco': 'text/xml',
'.divx': 'video/divx',
'.dll': 'application/x-msdownload',
'.dll.config': 'text/xml',
'.dlm': 'text/dlm',
'.doc': 'application/msword',
'.docm': 'application/vnd.ms-word.document.macroEnabled.12',
'.docx':
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.dot': 'application/msword',
'.dotm': 'application/vnd.ms-word.template.macroEnabled.12',
'.dotx':
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'.dsp': 'application/octet-stream',
'.dsw': 'text/plain',
'.dtd': 'text/xml',
'.dtsConfig': 'text/xml',
'.dv': 'video/x-dv',
'.dvi': 'application/x-dvi',
'.dwf': 'drawing/x-dwf',
'.dwp': 'application/octet-stream',
'.dxr': 'application/x-director',
'.eml': 'message/rfc822',
'.emz': 'application/octet-stream',
'.eot': 'application/vnd.ms-fontobject',
'.eps': 'application/postscript',
'.etl': 'application/etl',
'.etx': 'text/x-setext',
'.evy': 'application/envoy',
'.exe': 'application/octet-stream',
'.exe.config': 'text/xml',
'.fdf': 'application/vnd.fdf',
'.fif': 'application/fractals',
'.filters': 'application/xml',
'.fla': 'application/octet-stream',
'.flac': 'audio/flac',
'.flr': 'x-world/x-vrml',
'.flv': 'video/x-flv',
'.fsscript': 'application/fsharp-script',
'.fsx': 'application/fsharp-script',
'.generictest': 'application/xml',
'.gif': 'image/gif',
'.gpx': 'application/gpx+xml',
'.group': 'text/x-ms-group',
'.gsm': 'audio/x-gsm',
'.gtar': 'application/x-gtar',
'.gz': 'application/x-gzip',
'.h': 'text/plain',
'.hdf': 'application/x-hdf',
'.hdml': 'text/x-hdml',
'.hhc': 'application/x-oleobject',
'.hhk': 'application/octet-stream',
'.hhp': 'application/octet-stream',
'.hlp': 'application/winhlp',
'.hpp': 'text/plain',
'.hqx': 'application/mac-binhex40',
'.hta': 'application/hta',
'.htc': 'text/x-component',
'.htm': 'text/html',
'.html': 'text/html',
'.htt': 'text/webviewhtml',
'.hxa': 'application/xml',
'.hxc': 'application/xml',
'.hxd': 'application/octet-stream',
'.hxe': 'application/xml',
'.hxf': 'application/xml',
'.hxh': 'application/octet-stream',
'.hxi': 'application/octet-stream',
'.hxk': 'application/xml',
'.hxq': 'application/octet-stream',
'.hxr': 'application/octet-stream',
'.hxs': 'application/octet-stream',
'.hxt': 'text/html',
'.hxv': 'application/xml',
'.hxw': 'application/octet-stream',
'.hxx': 'text/plain',
'.i': 'text/plain',
'.ico': 'image/x-icon',
'.ics': 'application/octet-stream',
'.idl': 'text/plain',
'.ief': 'image/ief',
'.iii': 'application/x-iphone',
'.inc': 'text/plain',
'.inf': 'application/octet-stream',
'.ini': 'text/plain',
'.inl': 'text/plain',
'.ins': 'application/x-internet-signup',
'.ipa': 'application/x-itunes-ipa',
'.ipg': 'application/x-itunes-ipg',
'.ipproj': 'text/plain',
'.ipsw': 'application/x-itunes-ipsw',
'.iqy': 'text/x-ms-iqy',
'.isp': 'application/x-internet-signup',
'.ite': 'application/x-itunes-ite',
'.itlp': 'application/x-itunes-itlp',
'.itms': 'application/x-itunes-itms',
'.itpc': 'application/x-itunes-itpc',
'.IVF': 'video/x-ivf',
'.jar': 'application/java-archive',
'.java': 'application/octet-stream',
'.jck': 'application/liquidmotion',
'.jcz': 'application/liquidmotion',
'.jfif': 'image/pjpeg',
'.jnlp': 'application/x-java-jnlp-file',
'.jpb': 'application/octet-stream',
'.jpe': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.jpg': 'image/jpeg',
'.js': 'application/javascript',
'.json': 'application/json',
'.jsx': 'text/jscript',
'.jsxbin': 'text/plain',
'.latex': 'application/x-latex',
'.library-ms': 'application/windows-library+xml',
'.lit': 'application/x-ms-reader',
'.loadtest': 'application/xml',
'.lpk': 'application/octet-stream',
'.lsf': 'video/x-la-asf',
'.lst': 'text/plain',
'.lsx': 'video/x-la-asf',
'.lzh': 'application/octet-stream',
'.m13': 'application/x-msmediaview',
'.m14': 'application/x-msmediaview',
'.m1v': 'video/mpeg',
'.m2t': 'video/vnd.dlna.mpeg-tts',
'.m2ts': 'video/vnd.dlna.mpeg-tts',
'.m2v': 'video/mpeg',
'.m3u': 'audio/x-mpegurl',
'.m3u8': 'audio/x-mpegurl',
'.m4a': 'audio/m4a',
'.m4b': 'audio/m4b',
'.m4p': 'audio/m4p',
'.m4r': 'audio/x-m4r',
'.m4v': 'video/x-m4v',
'.mac': 'image/x-macpaint',
'.mak': 'text/plain',
'.man': 'application/x-troff-man',
'.manifest': 'application/x-ms-manifest',
'.map': 'text/plain',
'.master': 'application/xml',
'.mda': 'application/msaccess',
'.mdb': 'application/x-msaccess',
'.mde': 'application/msaccess',
'.mdp': 'application/octet-stream',
'.me': 'application/x-troff-me',
'.mfp': 'application/x-shockwave-flash',
'.mht': 'message/rfc822',
'.mhtml': 'message/rfc822',
'.mid': 'audio/mid',
'.midi': 'audio/mid',
'.mix': 'application/octet-stream',
'.mk': 'text/plain',
'.mmf': 'application/x-smaf',
'.mno': 'text/xml',
'.mny': 'application/x-msmoney',
'.mod': 'video/mpeg',
'.mov': 'video/quicktime',
'.movie': 'video/x-sgi-movie',
'.mp2': 'video/mpeg',
'.mp2v': 'video/mpeg',
'.mp3': 'audio/mpeg',
'.mp4': 'video/mp4',
'.mp4v': 'video/mp4',
'.mpa': 'video/mpeg',
'.mpe': 'video/mpeg',
'.mpeg': 'video/mpeg',
'.mpf': 'application/vnd.ms-mediapackage',
'.mpg': 'video/mpeg',
'.mpp': 'application/vnd.ms-project',
'.mpv2': 'video/mpeg',
'.mqv': 'video/quicktime',
'.ms': 'application/x-troff-ms',
'.msi': 'application/octet-stream',
'.mso': 'application/octet-stream',
'.mts': 'video/vnd.dlna.mpeg-tts',
'.mtx': 'application/xml',
'.mvb': 'application/x-msmediaview',
'.mvc': 'application/x-miva-compiled',
'.mxp': 'application/x-mmxp',
'.nc': 'application/x-netcdf',
'.nsc': 'video/x-ms-asf',
'.nws': 'message/rfc822',
'.ocx': 'application/octet-stream',
'.oda': 'application/oda',
'.odb': 'application/vnd.oasis.opendocument.database',
'.odc': 'application/vnd.oasis.opendocument.chart',
'.odf': 'application/vnd.oasis.opendocument.formula',
'.odg': 'application/vnd.oasis.opendocument.graphics',
'.odh': 'text/plain',
'.odi': 'application/vnd.oasis.opendocument.image',
'.odl': 'text/plain',
'.odm': 'application/vnd.oasis.opendocument.text-master',
'.odp': 'application/vnd.oasis.opendocument.presentation',
'.ods': 'application/vnd.oasis.opendocument.spreadsheet',
'.odt': 'application/vnd.oasis.opendocument.text',
'.oga': 'audio/ogg',
'.ogg': 'audio/ogg',
'.ogv': 'video/ogg',
'.ogx': 'application/ogg',
'.one': 'application/onenote',
'.onea': 'application/onenote',
'.onepkg': 'application/onenote',
'.onetmp': 'application/onenote',
'.onetoc': 'application/onenote',
'.onetoc2': 'application/onenote',
'.opus': 'audio/ogg',
'.orderedtest': 'application/xml',
'.osdx': 'application/opensearchdescription+xml',
'.otf': 'application/font-sfnt',
'.otg': 'application/vnd.oasis.opendocument.graphics-template',
'.oth': 'application/vnd.oasis.opendocument.text-web',
'.otp': 'application/vnd.oasis.opendocument.presentation-template',
'.ots': 'application/vnd.oasis.opendocument.spreadsheet-template',
'.ott': 'application/vnd.oasis.opendocument.text-template',
'.oxt': 'application/vnd.openofficeorg.extension',
'.p10': 'application/pkcs10',
'.p12': 'application/x-pkcs12',
'.p7b': 'application/x-pkcs7-certificates',
'.p7c': 'application/pkcs7-mime',
'.p7m': 'application/pkcs7-mime',
'.p7r': 'application/x-pkcs7-certreqresp',
'.p7s': 'application/pkcs7-signature',
'.pbm': 'image/x-portable-bitmap',
'.pcast': 'application/x-podcast',
'.pct': 'image/pict',
'.pcx': 'application/octet-stream',
'.pcz': 'application/octet-stream',
'.pdf': 'application/pdf',
'.pfb': 'application/octet-stream',
'.pfm': 'application/octet-stream',
'.pfx': 'application/x-pkcs12',
'.pgm': 'image/x-portable-graymap',
'.pic': 'image/pict',
'.pict': 'image/pict',
'.pkgdef': 'text/plain',
'.pkgundef': 'text/plain',
'.pko': 'application/vnd.ms-pki.pko',
'.pls': 'audio/scpls',
'.pma': 'application/x-perfmon',
'.pmc': 'application/x-perfmon',
'.pml': 'application/x-perfmon',
'.pmr': 'application/x-perfmon',
'.pmw': 'application/x-perfmon',
'.png': 'image/png',
'.pnm': 'image/x-portable-anymap',
'.pnt': 'image/x-macpaint',
'.pntg': 'image/x-macpaint',
'.pnz': 'image/png',
'.pot': 'application/vnd.ms-powerpoint',
'.potm': 'application/vnd.ms-powerpoint.template.macroEnabled.12',
'.potx':
'application/vnd.openxmlformats-officedocument.presentationml.template',
'.ppa': 'application/vnd.ms-powerpoint',
'.ppam': 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
'.ppm': 'image/x-portable-pixmap',
'.pps': 'application/vnd.ms-powerpoint',
'.ppsm': 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
'.ppsx':
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptm': 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
'.pptx':
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'.prf': 'application/pics-rules',
'.prm': 'application/octet-stream',
'.prx': 'application/octet-stream',
'.ps': 'application/postscript',
'.psc1': 'application/PowerShell',
'.psd': 'application/octet-stream',
'.psess': 'application/xml',
'.psm': 'application/octet-stream',
'.psp': 'application/octet-stream',
'.pub': 'application/x-mspublisher',
'.pwz': 'application/vnd.ms-powerpoint',
'.qht': 'text/x-html-insertion',
'.qhtm': 'text/x-html-insertion',
'.qt': 'video/quicktime',
'.qti': 'image/x-quicktime',
'.qtif': 'image/x-quicktime',
'.qtl': 'application/x-quicktimeplayer',
'.qxd': 'application/octet-stream',
'.ra': 'audio/x-pn-realaudio',
'.ram': 'audio/x-pn-realaudio',
'.rar': 'application/x-rar-compressed',
'.ras': 'image/x-cmu-raster',
'.rat': 'application/rat-file',
'.rc': 'text/plain',
'.rc2': 'text/plain',
'.rct': 'text/plain',
'.rdlc': 'application/xml',
'.reg': 'text/plain',
'.resx': 'application/xml',
'.rf': 'image/vnd.rn-realflash',
'.rgb': 'image/x-rgb',
'.rgs': 'text/plain',
'.rm': 'application/vnd.rn-realmedia',
'.rmi': 'audio/mid',
'.rmp': 'application/vnd.rn-rn_music_package',
'.roff': 'application/x-troff',
'.rpm': 'audio/x-pn-realaudio-plugin',
'.rqy': 'text/x-ms-rqy',
'.rtf': 'application/rtf',
'.rtx': 'text/richtext',
'.ruleset': 'application/xml',
'.s': 'text/plain',
'.safariextz': 'application/x-safari-safariextz',
'.scd': 'application/x-msschedule',
'.scr': 'text/plain',
'.sct': 'text/scriptlet',
'.sd2': 'audio/x-sd2',
'.sdp': 'application/sdp',
'.sea': 'application/octet-stream',
'.searchConnector-ms': 'application/windows-search-connector+xml',
'.setpay': 'application/set-payment-initiation',
'.setreg': 'application/set-registration-initiation',
'.settings': 'application/xml',
'.sgimb': 'application/x-sgimb',
'.sgml': 'text/sgml',
'.sh': 'application/x-sh',
'.shar': 'application/x-shar',
'.shtml': 'text/html',
'.sit': 'application/x-stuffit',
'.sitemap': 'application/xml',
'.skin': 'application/xml',
'.sldm': 'application/vnd.ms-powerpoint.slide.macroEnabled.12',
'.sldx':
'application/vnd.openxmlformats-officedocument.presentationml.slide',
'.slk': 'application/vnd.ms-excel',
'.sln': 'text/plain',
'.slupkg-ms': 'application/x-ms-license',
'.smd': 'audio/x-smd',
'.smi': 'application/octet-stream',
'.smx': 'audio/x-smd',
'.smz': 'audio/x-smd',
'.snd': 'audio/basic',
'.snippet': 'application/xml',
'.snp': 'application/octet-stream',
'.sol': 'text/plain',
'.sor': 'text/plain',
'.spc': 'application/x-pkcs7-certificates',
'.spl': 'application/futuresplash',
'.spx': 'audio/ogg',
'.src': 'application/x-wais-source',
'.srf': 'text/plain',
'.SSISDeploymentManifest': 'text/xml',
'.ssm': 'application/streamingmedia',
'.sst': 'application/vnd.ms-pki.certstore',
'.stl': 'application/vnd.ms-pki.stl',
'.sv4cpio': 'application/x-sv4cpio',
'.sv4crc': 'application/x-sv4crc',
'.svc': 'application/xml',
'.svg': 'image/svg+xml',
'.swf': 'application/x-shockwave-flash',
'.step': 'application/step',
'.stp': 'application/step',
'.t': 'application/x-troff',
'.tar': 'application/x-tar',
'.tcl': 'application/x-tcl',
'.testrunconfig': 'application/xml',
'.testsettings': 'application/xml',
'.tex': 'application/x-tex',
'.texi': 'application/x-texinfo',
'.texinfo': 'application/x-texinfo',
'.tgz': 'application/x-compressed',
'.thmx': 'application/vnd.ms-officetheme',
'.thn': 'application/octet-stream',
'.tif': 'image/tiff',
'.tiff': 'image/tiff',
'.tlh': 'text/plain',
'.tli': 'text/plain',
'.toc': 'application/octet-stream',
'.tr': 'application/x-troff',
'.trm': 'application/x-msterminal',
'.trx': 'application/xml',
'.ts': 'video/vnd.dlna.mpeg-tts',
'.tsv': 'text/tab-separated-values',
'.ttf': 'application/font-sfnt',
'.tts': 'video/vnd.dlna.mpeg-tts',
'.txt': 'text/plain',
'.u32': 'application/octet-stream',
'.uls': 'text/iuls',
'.user': 'text/plain',
'.ustar': 'application/x-ustar',
'.vb': 'text/plain',
'.vbdproj': 'text/plain',
'.vbk': 'video/mpeg',
'.vbproj': 'text/plain',
'.vbs': 'text/vbscript',
'.vcf': 'text/x-vcard',
'.vcproj': 'application/xml',
'.vcs': 'text/plain',
'.vcxproj': 'application/xml',
'.vddproj': 'text/plain',
'.vdp': 'text/plain',
'.vdproj': 'text/plain',
'.vdx': 'application/vnd.ms-visio.viewer',
'.vml': 'text/xml',
'.vscontent': 'application/xml',
'.vsct': 'text/xml',
'.vsd': 'application/vnd.visio',
'.vsi': 'application/ms-vsi',
'.vsix': 'application/vsix',
'.vsixlangpack': 'text/xml',
'.vsixmanifest': 'text/xml',
'.vsmdi': 'application/xml',
'.vspscc': 'text/plain',
'.vss': 'application/vnd.visio',
'.vsscc': 'text/plain',
'.vssettings': 'text/xml',
'.vssscc': 'text/plain',
'.vst': 'application/vnd.visio',
'.vstemplate': 'text/xml',
'.vsto': 'application/x-ms-vsto',
'.vsw': 'application/vnd.visio',
'.vsx': 'application/vnd.visio',
'.vtx': 'application/vnd.visio',
'.wav': 'audio/wav',
'.wave': 'audio/wav',
'.wax': 'audio/x-ms-wax',
'.wbk': 'application/msword',
'.wbmp': 'image/vnd.wap.wbmp',
'.wcm': 'application/vnd.ms-works',
'.wdb': 'application/vnd.ms-works',
'.wdp': 'image/vnd.ms-photo',
'.webarchive': 'application/x-safari-webarchive',
'.webm': 'video/webm',
'.webp': 'image/webp',
'.webtest': 'application/xml',
'.wiq': 'application/xml',
'.wiz': 'application/msword',
'.wks': 'application/vnd.ms-works',
'.WLMP': 'application/wlmoviemaker',
'.wlpginstall': 'application/x-wlpg-detect',
'.wlpginstall3': 'application/x-wlpg3-detect',
'.wm': 'video/x-ms-wm',
'.wma': 'audio/x-ms-wma',
'.wmd': 'application/x-ms-wmd',
'.wmf': 'application/x-msmetafile',
'.wml': 'text/vnd.wap.wml',
'.wmlc': 'application/vnd.wap.wmlc',
'.wmls': 'text/vnd.wap.wmlscript',
'.wmlsc': 'application/vnd.wap.wmlscriptc',
'.wmp': 'video/x-ms-wmp',
'.wmv': 'video/x-ms-wmv',
'.wmx': 'video/x-ms-wmx',
'.wmz': 'application/x-ms-wmz',
'.woff': 'application/font-woff',
'.wpl': 'application/vnd.ms-wpl',
'.wps': 'application/vnd.ms-works',
'.wri': 'application/x-mswrite',
'.wrl': 'x-world/x-vrml',
'.wrz': 'x-world/x-vrml',
'.wsc': 'text/scriptlet',
'.wsdl': 'text/xml',
'.wvx': 'video/x-ms-wvx',
'.x': 'application/directx',
'.xaf': 'x-world/x-vrml',
'.xaml': 'application/xaml+xml',
'.xap': 'application/x-silverlight-app',
'.xbap': 'application/x-ms-xbap',
'.xbm': 'image/x-xbitmap',
'.xdr': 'text/plain',
'.xht': 'application/xhtml+xml',
'.xhtml': 'application/xhtml+xml',
'.xla': 'application/vnd.ms-excel',
'.xlam': 'application/vnd.ms-excel.addin.macroEnabled.12',
'.xlc': 'application/vnd.ms-excel',
'.xld': 'application/vnd.ms-excel',
'.xlk': 'application/vnd.ms-excel',
'.xll': 'application/vnd.ms-excel',
'.xlm': 'application/vnd.ms-excel',
'.xls': 'application/vnd.ms-excel',
'.xlsb': 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'.xlsm': 'application/vnd.ms-excel.sheet.macroEnabled.12',
'.xlsx':
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.xlt': 'application/vnd.ms-excel',
'.xltm': 'application/vnd.ms-excel.template.macroEnabled.12',
'.xltx':
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'.xlw': 'application/vnd.ms-excel',
'.xml': 'text/xml',
'.xmta': 'application/xml',
'.xof': 'x-world/x-vrml',
'.XOML': 'text/plain',
'.xpm': 'image/x-xpixmap',
'.xps': 'application/vnd.ms-xpsdocument',
'.xrm-ms': 'text/xml',
'.xsc': 'application/xml',
'.xsd': 'text/xml',
'.xsf': 'text/xml',
'.xsl': 'text/xml',
'.xslt': 'text/xml',
'.xsn': 'application/octet-stream',
'.xss': 'application/xml',
'.xspf': 'application/xspf+xml',
'.xtp': 'application/octet-stream',
'.xwd': 'image/x-xwindowdump',
'.z': 'application/x-compress',
'.zip': 'application/zip',
}

1
src/shared/enums/route-key.enum.ts

@ -33,6 +33,7 @@ export enum RouteKey { @@ -33,6 +33,7 @@ export enum RouteKey {
CreatePersonal = 'CreatePersonal',
GroupChatDetail = 'GroupChatDetail',
ForwardMessage = 'ForwardMessage',
SendSharedFiles = 'SendSharedFiles',
Executors = 'Executors',

14
src/shared/helpers/fs.helpers.ts

@ -77,6 +77,20 @@ export const runActionByType = ( @@ -77,6 +77,20 @@ export const runActionByType = (
return actions[key]()
}
export const runActionByTypeOrDefault = (
file: IFile,
actions: Record<string, () => Promise<void>>,
) => {
const keys = Object.keys(actions)
const key = keys.find(key => {
return file.type.includes(key)
})
let action = actions[key]
if (!action && actions.default) action = actions.default
return action()
}
export const prepareFormData = (file: IFile, formData: FormData) => {
const dataToSave = {
uri: Platform.OS == 'ios' ? file.uri.replace('file://', '') : file.uri,

1
src/shared/helpers/index.ts

@ -21,4 +21,5 @@ export * from './url.helpers' @@ -21,4 +21,5 @@ export * from './url.helpers'
export * from './versions.helper'
export * from './configs.helpers'
export * from './copy-to-buffer.helper'
export * from './mime-type.helper'
export * from './htm-content.helper'

17
src/shared/helpers/mime-type.helper.ts

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
import { mimeTypes } from '../configs'
export const getMimeTypeByExtension = (ext: string) => {
if (!ext) return ''
const extension = prepareExtension(ext)
const mimeType = mimeTypes[extension]
if (!mimeType) return extension
return mimeType
}
function prepareExtension(ext: string) {
if (ext.startsWith('.')) return ext.toLowerCase()
return `.${ext.toLowerCase()}`
}

1
src/shared/hooks/index.ts

@ -9,4 +9,5 @@ export * from './use-navigation.hook' @@ -9,4 +9,5 @@ export * from './use-navigation.hook'
export * from './use-keyboard.hook'
export * from './use-selected-executor.hook'
export * from './use-socket.hook'
export * from './use-shared-files.hook'
export * from './use-file-content.hook'

45
src/shared/hooks/use-shared-files.hook.ts

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
import { useEffect, useState } from 'react'
import ReceiveSharingIntent from 'react-native-receive-sharing-intent'
import { ISharedFile } from '../interfaces'
import { NavigationModuleKey, RouteKey } from '../enums'
import _ from 'lodash'
import { useSelector } from 'react-redux'
import { Alert } from 'react-native'
import { NavigationService } from '@/services/system'
import { selectActiveNavigationModule } from '@/store/shared'
export const useSharedFiles = () => {
const activeModule = useSelector(selectActiveNavigationModule)
const [files, setFiles] = useState<ISharedFile[]>([])
useEffect(() => {
ReceiveSharingIntent.getReceivedFiles(
(receivedFiles: ISharedFile[]) => {
setFiles(receivedFiles)
},
(error: unknown) => {
console.log('Error on receive shared files', error)
},
'ShareMediaTaskme',
)
}, [])
useEffect(() => {
if (activeModule === NavigationModuleKey.User && !_.isEmpty(files)) {
NavigationService.navigate(RouteKey.SendSharedFiles, {
files,
})
setFiles([])
} else if (
activeModule === NavigationModuleKey.Auth &&
!_.isEmpty(files)
) {
Alert.alert(
'Ви не авторизовані',
'Авторизуйтесь та спробуйте ще раз поширити файли',
)
setFiles([])
}
}, [files, activeModule])
}

1
src/shared/index.ts

@ -14,3 +14,4 @@ export { getTheme } from './themes' @@ -14,3 +14,4 @@ export { getTheme } from './themes'
export * from './interfaces'
export * from './utils'
export * from './exceptions'
export * from './configs'

10
src/shared/interfaces/media.interfaces.ts

@ -8,3 +8,13 @@ export interface IFile { @@ -8,3 +8,13 @@ export interface IFile {
width?: number
height?: number
}
export interface ISharedFile {
filePath?: string
text?: string
weblink?: string
mimeType?: string
contentUri?: string
fileName?: string
extension?: string
}

Loading…
Cancel
Save