前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >flutter-如何实现类型微信分享的功能

flutter-如何实现类型微信分享的功能

作者头像
用户1974410
发布2022-09-20 16:48:25
2.5K0
发布2022-09-20 16:48:25
举报
文章被收录于专栏:flutter开发精选

我们应该怎样接受其他APP的分享的照片、视频、文本、链接或者其他类型的文件呢?即如下图效果,让我们的APP也出现在分享列表之中:

本文将介绍,如何将我们flutter开发的APP也出现在分享列表之中。

下面我们将分成3部分介绍:

1.Android和iOS平台的配置

2.Flutter端的实现

3.编译问题及实现

原生端的配置

安卓配置

首先,我们在AndroidManifest.xml中增加些 intent filters,用来接收其他APP的分享文件。

代码语言:javascript
复制
<activity>
            ... 
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/*" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="image/*" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="video/*" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="*/*" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="image/*" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="video/*" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="*/*" />
            </intent-filter>
            ...
</activity>

android:name=”android.intent.action.SEND: 接收单个文件

android:name=”android.intent.action.SEND_MULTIPLE: 接收多个文件

android:mimeType=”<Type>/*”. 类型:[文本、图像、视频,/(任意)]

我们还需要添加*android:launchMode=”singleTask” : 作为独立的任务启动。*

代码语言:javascript
复制
<activity
            android:name=".MainActivity"
            android:launchMode="singleTask"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            ...
</activity>

在onCreate中加入如下代码:

代码语言:javascript
复制
package com.flutter.kaifajingxuan.receivesharing

import android.content.Intent
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
import android.os.Bundle

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);
        java.lang.Thread.sleep(1000);
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        if (intent.getIntExtra("org.chromium.chrome.extra.TASK_ID", -1) == this.taskId) {
            this.finish()
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }
        super.onCreate(savedInstanceState)
    }
}

安卓的配置到此结束。

iOS的配置:

首先,我们需要在info.plist中添加相关类型的权限。

代码语言:javascript
复制
<?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>CFBundleURLTypes</key>
     <array>
      <dict>
       <key>CFBundleTypeRole</key>
       <string>Editor</string>
       <key>CFBundleURLSchemes</key>
       <array>
        <string>ShareMedia</string>
       </array>
      </dict>
      <dict/>
     </array>
 <key>CFBundleVersion</key>
 <string>$(FLUTTER_BUILD_NUMBER)</string>
 <key>LSRequiresIPhoneOS</key>
 <true/>
 <key>NSPhotoLibraryUsageDescription</key>
        <string>ReceiveSharing requires the Photos Permission to select and upload the photo</string>
</dict>
</plist>

接下来,还需要配置为我们的APP田间share extension ,具体步骤如下:

  1. 选择File -> New ->Target.

2. 搜索Share -> Share Extension->Next.

3. 依次设置Product Name:「Share Extension」 > Team :选择你自己的team ->Finish.

4. Runner. app中Deployment target选择9.0或更高

5. 依次选择Share Extension -> Info.plist -> Open as -> Source Code

6.添加以下的关键代码

代码语言:javascript
复制
<?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>NSExtensionActivationRule</key>
       <dict>
        <key>NSExtensionActivationSupportsFileWithMaxCount</key>
        <integer>15</integer>
        <key>NSExtensionActivationSupportsImageWithMaxCount</key>
        <integer>15</integer>
        <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
        <integer>15</integer>
        <key>NSExtensionActivationSupportsText</key>
        <true/>
        <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
        <integer>1</integer>
       </dict>
       <key>PHSupportedMediaTypes</key>
       <array>
        <string>Video</string>
        <string>Image</string>
       </array>
      </dict>
  <key>NSExtensionMainStoryboard</key>
  <string>MainInterface</string>
  <key>NSExtensionPointIdentifier</key>
  <string>com.apple.share-services</string>
 </dict>
</dict>
</plist>

❝PHSupportedMediaTypes : video & image NSExtensionActivationSupportsText : Text NSExtensionActivationSupportsWebURLWithMaxCount : No of Urls to share NSExtensionActivationSupportsImageWithMaxCount : No of Images to share NSExtensionActivationSupportsMovieWithMaxCount : No of Movies to share NSExtensionActivationSupportsFileWithMaxCount : No of Files to share ❞

7. 在ShareViewController.swift中添加如下关键代码:

代码语言:javascript
复制
import UIKit
import Social
import MobileCoreServices
import Photos

class ShareViewController: SLComposeServiceViewController {
    let hostAppBundleIdentifier = "com.jp.receivesharing"
    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();
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
        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 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 {

                // Always copy
                let fileName = this.getFileName(from: url, type: .image)
                let newPath = FileManager.default
                    .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
                    .appendingPathComponent(fileName)
                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 fileName = this.getFileName(from: url, type: .video)
                let newPath = FileManager.default
                    .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
                    .appendingPathComponent(fileName)
                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 fileName = this.getFileName(from :url, type: .file)
                let newPath = FileManager.default
                    .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
                    .appendingPathComponent(fileName)
                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: "ShareMedia://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, type: SharedMediaType) -> String {
        var name = url.lastPathComponent

        if (name.isEmpty) {
            name = UUID().uuidString + "." + getExtension(from: url, type: type)
        }

        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
    }
}

hostAppBundleIdentifier: 你的包名(package name)

8. 将Runner 和 Share Extension加入到相同的group中。

9.选择Capabilities tab -> App Groups -> Add a new group, 都取名为 group.<host-bundle-indentifier>

比如group.flutter.kaifajingxuan.receivesharing

10. 两个taget最终都有相同的group。

编译问题及修复

  1. 检查share extension下的 **Build Settings,**并且移除Linking/Other Linker Flags下的所有选项。
  2. disabled extension target 的bitcode选项。
  3. invalid Bundle. 设置路径‘Runner. app/Plugins/.appex contains disallowed file ‘Frameworks’。我的设置如下:

Always Embed Swift Standard Libraries: YES

share extension 的设置如下

Always Embed Swift Standard Libraries: NO

flutter端的实现

我们需要先引入一个 receive_sharing_intent插件

代码语言:javascript
复制
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  receive_sharing_intent: ^1.4.5

demo 一共有3个页面

  1. Home Screen: 接收其他 apps的文件
  2. User Listing Screen: 选择你要分享文件的用户
  3. Sharing Media Preview Screen: 要分享文件的预览页面

「home_screen.dart」的实现如下:

代码语言:javascript
复制
//All listeners to listen Sharing media files & text
  void listenShareMediaFiles(BuildContext context) {
    // For sharing images coming from outside the app
    // while the app is in the memory
    ReceiveSharingIntent.getMediaStream().listen((List<SharedMediaFile> value) {
      navigateToShareMedia(context, value);
    }, onError: (err) {
      debugPrint("$err");
    });

    // For sharing images coming from outside the app while the app is closed
    ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
      navigateToShareMedia(context, value);
    });

    // For sharing or opening urls/text coming from outside the app while the app is in the memory
    ReceiveSharingIntent.getTextStream().listen((String value) {
      navigateToShareText(context, value);
    }, onError: (err) {
      debugPrint("$err");
    });

    // For sharing or opening urls/text coming from outside the app while the app is closed
    ReceiveSharingIntent.getInitialText().then((String? value) {
      navigateToShareText(context, value);
    });
  }

  void navigateToShareMedia(BuildContext context, List<SharedMediaFile> value) {
    if (value.isNotEmpty) {
      var newFiles = <File>[];
      value.forEach((element) {
        newFiles.add(File(
          Platform.isIOS
              ? element.type == SharedMediaType.FILE
                  ? element.path
                      .toString()
                      .replaceAll(AppConstants.replaceableText, "")
                  : element.path
              : element.path,
        ));
      });
      Navigator.of(context).push(MaterialPageRoute(
          builder: (context) => UserListingScreen(
                files: newFiles,
                text: "",
              )));
    }
  }

  void navigateToShareText(BuildContext context, String? value) {
    if (value != null && value.toString().isNotEmpty) {
      Navigator.of(context).push(MaterialPageRoute(
          builder: (context) => UserListingScreen(
                files: [],
                text: value,
              )));
    }
  }

❝getMediaStream : 当APP在后台运行时接收分享的媒体文件 getInitialMedia : 当APP被杀掉时接收分享的媒体文件 getTextStream : 当APP在后台运行时接收分享的文本 getInitialText : 当APP被杀掉时接收分享的文本 ❞

initState 中调用listenShareMediaFiles 方法.

「user_listing_screen」.dart的代码如下:

代码语言:javascript
复制
class UserListingScreen extends StatefulWidget {
  final List<File>? files;
  final String? text;
  UserListingScreen({this.files, this.text = ""});
  @override
  _UserListingScreenState createState() => _UserListingScreenState();
}

class _UserListingScreenState extends State<UserListingScreen> {
  List<UserDetailModel> _userNames = [
    UserDetailModel(
        name: "Harsh", email: "harsh.dev@gmail.com", isSelected: false),
    UserDetailModel(
        name: "Jaimil", email: "jaimil.dev@gmail.com", isSelected: false),
    UserDetailModel(
        name: "Piyush", email: "piyush.dev@gmail.com", isSelected: false),
    UserDetailModel(
        name: "Niket", email: "niket.dev@gmail.com", isSelected: false),
    UserDetailModel(
        name: "Shailin", email: "shailin.dev@gmail.com", isSelected: false),
    UserDetailModel(
        name: "Nishat", email: "nishat.dev@gmail.com", isSelected: false),
  ];
  bool _isSelected = false;
  List<String?> _selectedNames = [];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [_userListingView(context), _selectedUserListingView(context)],
    ).generalScaffold(
        context: context,
        appTitle: "Users",
        isShowFab: _isSelected,
        files: widget.files,
        userList: _userNames,
        sharedText: widget.text);
  }

  Widget _userListingView(BuildContext context) => Expanded(
      child: ListView.builder(
          itemCount: _userNames.length,
          itemBuilder: (context, index) {
            return _userListItemView(context, index);
          }));

  Widget _userListItemView(BuildContext context, int index) => Card(
        elevation: 3,
        child: ListTile(
          selected: _userNames[index].isSelected,
          selectedTileColor: ColorConstants.primaryColor,
          dense: true,
          onTap: () {
            _onListTileTap(index);
          },
          leading: _leadingCircularView(index),
          title: _titleView(index),
          subtitle: _subTitleView(index),
        ),
      );

  Widget _leadingCircularView(int index) => CircleAvatar(
      backgroundColor: _userNames[index].isSelected
          ? ColorConstants.whiteColor
          : ColorConstants.primaryColor,
      child: Text(
        _userNames[index].name!.substring(0, 1),
        style: TextStyle(
            color: _userNames[index].isSelected
                ? ColorConstants.primaryColor
                : ColorConstants.whiteColor),
      ));

  Widget _titleView(int index) => Text(_userNames[index].name!,
      style: TextStyle(
          color: _userNames[index].isSelected
              ? ColorConstants.whiteColor
              : ColorConstants.primaryColor));

  Widget _subTitleView(int index) => Text(_userNames[index].email!,
      style: TextStyle(color: ColorConstants.greyColor));

  Widget _selectedUserListingView(BuildContext context) => (_selectedNames
          .isNotEmpty)
      ? Container(
          height: DimensionConstants.containerHeight50,
          decoration:
              BoxDecoration(color: ColorConstants.whiteColor, boxShadow: [
            BoxShadow(offset: Offset(0, -3), blurRadius: 5, color: Colors.grey)
          ]),
          padding: EdgeInsets.symmetric(
              horizontal: DimensionConstants.horizontalPadding10),
          child: ListView.builder(
              itemCount: _selectedNames.length,
              scrollDirection: Axis.horizontal,
              itemBuilder: (context, index) {
                return Center(
                    child: Text(
                        "${"${_selectedNames[index]}"}${index == _selectedNames.length - 1 ? "" : " , "}",
                        style: TextStyle(
                            fontSize: FontSizeWeightConstants.fontSize14,
                            fontWeight:
                                FontSizeWeightConstants.fontWeight500)));
              }))
      : SizedBox();

  void _onListTileTap(int index) {
    setState(() {
      _userNames[index].isSelected = !_userNames[index].isSelected;
      for (var names in _userNames) {
        if (names.isSelected) {
          if (!_selectedNames.contains(names.name)) {
            _selectedNames.add(names.name);
          }
        } else {
          _selectedNames.remove(names.name);
        }
      }
      _isSelected = _selectedNames.isNotEmpty;
    });
  }
}

sharing_media_preview_screen.dart的代码如下

代码语言:javascript
复制
class SharingMediaPreviewScreen extends StatefulWidget {
  final List<UserDetailModel>? userList;
  final List<File>? files;
  final String? text;
  SharingMediaPreviewScreen({this.userList, this.files, this.text = ""});
  @override
  _SharingMediaPreviewScreenState createState() =>
      _SharingMediaPreviewScreenState();
}

class _SharingMediaPreviewScreenState extends State<SharingMediaPreviewScreen> {
  final PageController _pageController =
      PageController(initialPage: 0, viewportFraction: 0.95, keepPage: false);
  final List<MediaPreviewItem> _galleryItems = [];
  int _initialIndex = 0;

  @override
  void initState() {
    super.initState();
    SchedulerBinding.instance?.addPostFrameCallback((timeStamp) {
      setState(() {
        var i = 0;
        widget.files?.forEach((element) {
          _galleryItems.add(MediaPreviewItem(
              id: i,
              resource: element,
              controller: TextEditingController(),
              isSelected: i == 0 ? true : false));
          i++;
        });
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return _galleryItems.isNotEmpty
        ? Column(
            children: [
              SizedBox(height: DimensionConstants.sizedBoxHeight5),
              _fullMediaPreview(context),
            ],
          ).generalScaffold(
            context: context,
            appTitle: "Send to...",
            files: widget.files,
            userList: widget.userList)
        : widget.text!.isNotEmpty
            ? _sharedTextView(context).generalScaffold(
                context: context,
                appTitle: "Send to...",
                files: widget.files,
                userList: widget.userList)
            : EmptyView(
                topLine: "No files are here..",
                bottomLine: "Select files from gallery or file manager.",
              ).generalScaffold(
                context: context,
                appTitle: "Send to...",
                files: widget.files,
                userList: widget.userList);
  }

  Widget _fullMediaPreview(BuildContext context) => Expanded(
          child: PageView(
        controller: _pageController,
        physics: ClampingScrollPhysics(),
        scrollDirection: Axis.horizontal,
        onPageChanged: (value) {
          _mediaPreviewChanged(value);
        },
        children: _galleryItems
            .map((e) => AppConstants.imageExtensions
                    .contains(e.resource?.path.split('.').last.toLowerCase())
                ? Image.file(File(e.resource!.path))
                : Image.asset(
                    FileConstants.icFile,
                  ))
            .toList(),
      ));

  void _mediaPreviewChanged(int value) {
    _initialIndex = value;
    setState(() {
      var i = 0;
      _galleryItems.forEach((element) {
        if (i == value) {
          _galleryItems[i].isSelected = true;
        } else {
          _galleryItems[i].isSelected = false;
        }
        i++;
      });
    });
  }

}

最后,我们再给sharing_media_preview_screen添加以下分享的描述就OK了,最终如下;

代码语言:javascript
复制
class SharingMediaPreviewScreen extends StatefulWidget {
  final List<UserDetailModel>? userList;
  final List<File>? files;
  final String? text;
  SharingMediaPreviewScreen({this.userList, this.files, this.text = ""});
  @override
  _SharingMediaPreviewScreenState createState() =>
      _SharingMediaPreviewScreenState();
}

class _SharingMediaPreviewScreenState extends State<SharingMediaPreviewScreen> {
  ...

  @override
  Widget build(BuildContext context) {
    return _galleryItems.isNotEmpty
        ? Column(
            children: [
              SizedBox(height: DimensionConstants.sizedBoxHeight5),
              _fullMediaPreview(context),
              _fileName(context),
              _addCaptionPreview(context),
              _horizontalMediaFilesView(context)
            ],
          ).generalScaffold(
            context: context,
            appTitle: "Send to...",
            files: widget.files,
            userList: widget.userList)
        : widget.text!.isNotEmpty
            ? _sharedTextView(context).generalScaffold(
                context: context,
                appTitle: "Send to...",
                files: widget.files,
                userList: widget.userList)
            : EmptyView(
                topLine: "No files are here..",
                bottomLine: "Select files from gallery or file manager.",
              ).generalScaffold(
                context: context,
                appTitle: "Send to...",
                files: widget.files,
                userList: widget.userList);
  }
  
  ...

  Widget _fileName(BuildContext context) => Padding(
        padding: const EdgeInsets.all(DimensionConstants.padding8),
        child: Text(
            "${_galleryItems[_initialIndex].resource!.path.split('/').last}"),
      );

  Widget _addCaptionPreview(BuildContext context) => Row(children: [
        Expanded(
            child: Padding(
                padding: const EdgeInsets.only(
                    left: DimensionConstants.leftPadding15,
                    right: DimensionConstants.rightPadding20,
                    top: DimensionConstants.topPadding10),
                child: TextFormField(
                    controller: _galleryItems[_initialIndex].controller,
                    textInputAction: TextInputAction.done,
                    focusNode: FocusNode(),
                    style: TextStyle(
                        color: ColorConstants.blackColor,
                        fontSize: FontSizeWeightConstants.fontSize14,
                        fontWeight: FontSizeWeightConstants.fontWeightNormal),
                    decoration: InputDecoration(
                        hintText: "Add Caption",
                        hintStyle: TextStyle(
                            color: ColorConstants.blackColor,
                            fontSize: FontSizeWeightConstants.fontSize14,
                            fontWeight:
                                FontSizeWeightConstants.fontWeightNormal),
                        filled: true,
                        fillColor: ColorConstants.offWhiteColor,
                        counter: Offstage(),
                        contentPadding: EdgeInsets.symmetric(
                            horizontal: DimensionConstants.horizontalPadding5),
                        border: InputBorder.none),
                    onFieldSubmitted: (value) {},
                    keyboardType: TextInputType.text,
                    onTap: () {}))),
        GestureDetector(
            onTap: () {
              _onSharingTap(context);
            },
            child: Padding(
                padding: const EdgeInsets.only(
                    bottom: DimensionConstants.bottomPadding8),
                child: Image.asset(FileConstants.icSend, scale: 2.7)))
      ]);

  Widget _horizontalMediaFilesView(BuildContext context) =>
      (MediaQuery.of(context).viewInsets.bottom == 0)
          ? Container(
              height: DimensionConstants.containerHeight60,
              margin: const EdgeInsets.only(
                  left: DimensionConstants.leftPadding15,
                  bottom: DimensionConstants.bottomPadding10,
                  top: DimensionConstants.topPadding5),
              child: ListView.separated(
                  itemCount: _galleryItems.length,
                  separatorBuilder: (context, index) {
                    return SizedBox(width: DimensionConstants.sizedBoxWidth10);
                  },
                  itemBuilder: (context, index) {
                    return GestureDetector(
                        onTap: () {
                          _onTapHorizontalMedia(context, index);
                        },
                        child: Container(
                            decoration: BoxDecoration(
                                border: Border.all(
                                    color: _galleryItems[index].isSelected
                                        ? ColorConstants.greyColor
                                        : ColorConstants.whiteColor,
                                    width: 1.0)),
                            child: AppConstants.imageExtensions.contains(
                                    _galleryItems[index]
                                        .resource
                                        ?.path
                                        .split('.')
                                        .last
                                        .toLowerCase())
                                ? Image.file(
                                    File(_galleryItems[index].resource!.path))
                                : Image.asset(FileConstants.icFile)));
                  },
                  scrollDirection: Axis.horizontal))
          : SizedBox();

  void _onTapHorizontalMedia(BuildContext context, int index) {
    setState(() {
      var i = 0;
      _galleryItems.forEach((element) {
        if (i == index) {
          _galleryItems[i].isSelected = true;
        } else {
          _galleryItems[i].isSelected = false;
        }
        i++;
      });
    });
    _pageController.animateToPage(index,
        duration: Duration(milliseconds: 400), curve: Curves.easeIn);
  }

  Widget _sharedTextView(BuildContext context) =>
      Column(mainAxisAlignment: MainAxisAlignment.end, children: [
        Text("Shared text here...",
            style: TextStyle(
                color: ColorConstants.greyColor,
                fontSize: FontSizeWeightConstants.fontSize20)),
        Padding(
            padding: const EdgeInsets.symmetric(
                horizontal: DimensionConstants.horizontalPadding10),
            child: Row(children: [
              Text(widget.text!,
                  style:
                      TextStyle(fontSize: FontSizeWeightConstants.fontSize20)),
              Spacer(),
              GestureDetector(
                  onTap: () {
                    _onSharingTap(context);
                  },
                  child: Padding(
                      padding: const EdgeInsets.only(
                          bottom: DimensionConstants.bottomPadding8),
                      child: Image.asset(FileConstants.icSend, scale: 2.7)))
            ]))
      ]);

  void _onSharingTap(BuildContext context) {
    //You can use this method to share media file or text based on your requirements
  }
}

‍‍

安卓效果

总结

我们实现一个接收分享文件的app,就像微信的分享功能一样,虽然样式很丑,但功能还是可以的,

github的地址:https://github.com/JaimilPatel/ReceiveSharing

少年别走,交个朋友~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 flutter开发精选 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原生端的配置
    • 安卓配置
      • iOS的配置:
      • 编译问题及修复
      • flutter端的实现
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档