zk 1 год назад
Родитель
Сommit
27ff99534f

+ 4 - 0
android/app/src/main/AndroidManifest.xml

@@ -17,6 +17,10 @@
     <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
     <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
 
+    <queries>
+        <package android:name="com.tencent.mobileqq" />
+        <package android:name="com.tencent.mm" />
+    </queries>
     <application
         android:name=".MyApplication"
         android:allowBackup="false"

BIN
assets/images/icon_qq.webp


BIN
assets/images/icon_talk_share.webp


BIN
assets/images/icon_talk_share_close.webp


BIN
assets/images/icon_talk_share_original.webp


BIN
assets/images/icon_talk_share_summary.webp


BIN
assets/images/icon_talk_txt.webp


BIN
assets/images/icon_wx.webp


+ 1 - 0
assets/string/base/string.xml

@@ -140,4 +140,5 @@
     </string>
     <string name="add_desktop_shortcut_btn_txt">立即添加</string>
     <string name="desktop_shortcut_record_name">小听快听</string>
+    <string name="dialog_send_friend">发送给朋友</string>
 </resources>

+ 3 - 0
lib/data/api/atmob_api.dart

@@ -12,6 +12,7 @@ import 'package:electronic_assistant/data/api/request/order_pay_request.dart';
 import 'package:electronic_assistant/data/api/request/order_status_request.dart';
 import 'package:electronic_assistant/data/api/request/talk_create_request.dart';
 import 'package:electronic_assistant/data/api/request/talk_delete_request.dart';
+import 'package:electronic_assistant/data/api/request/talk_export_request.dart';
 import 'package:electronic_assistant/data/api/request/talk_generate_request.dart';
 import 'package:electronic_assistant/data/api/request/talk_paginate_request.dart';
 import 'package:electronic_assistant/data/api/request/talk_query_request.dart';
@@ -30,6 +31,7 @@ import 'package:electronic_assistant/data/api/response/order_pay_response.dart';
 import 'package:electronic_assistant/data/api/response/order_status_response.dart';
 import 'package:electronic_assistant/data/api/response/store_index_response.dart';
 import 'package:electronic_assistant/data/api/response/talk_check_electric_response.dart';
+import 'package:electronic_assistant/data/api/response/talk_export_response.dart';
 import 'package:electronic_assistant/data/api/response/talk_info_response.dart';
 import 'package:electronic_assistant/data/api/response/talk_original_response.dart';
 import 'package:electronic_assistant/data/api/response/talk_paginate_response.dart';
@@ -38,6 +40,7 @@ import 'package:electronic_assistant/data/api/response/tasks_running_response.da
 import 'package:electronic_assistant/data/api/response/user_info_response.dart';
 import 'package:electronic_assistant/data/bean/talks.dart';
 import 'package:electronic_assistant/data/consts/constants.dart';
+import 'package:retrofit/dio.dart';
 import 'package:retrofit/http.dart';
 
 part 'atmob_api.g.dart';

+ 30 - 0
lib/data/api/atmob_stream_api.c.dart

@@ -19,6 +19,36 @@ class _AtmobStreamApi implements AtmobStreamApi {
   String? baseUrl;
 
   @override
+  Future<ResponseBody> talkExportFile(TalkExportRequest request) async {
+    const _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{
+      r'Content-Type': 'application/json',
+    };
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _result =
+        await _dio.fetch<ResponseBody>(_setStreamType<ResponseBody>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+      responseType: ResponseType.stream,
+    )
+            .compose(
+              _dio.options,
+              '/project/secretary/v1/talk/export',
+              queryParameters: queryParameters,
+              data: _data,
+            )
+            .copyWith(
+                baseUrl: _combineBaseUrls(
+              _dio.options.baseUrl,
+              baseUrl,
+            ))));
+    return _result.data!;
+  }
+
+  @override
   Future<ResponseBody> chat(ChatRequest request) async {
     const _extra = <String, dynamic>{};
     final queryParameters = <String, dynamic>{};

+ 5 - 0
lib/data/api/atmob_stream_api.dart

@@ -1,6 +1,7 @@
 import 'package:dio/dio.dart' hide Headers;
 import 'package:dio/dio.dart';
 import 'package:electronic_assistant/data/api/request/chat_request.dart';
+import 'package:electronic_assistant/data/api/request/talk_export_request.dart';
 import 'package:retrofit/retrofit.dart';
 
 import '../consts/Constants.dart';
@@ -11,6 +12,10 @@ part 'atmob_stream_api.c.dart';
 abstract class AtmobStreamApi {
 
   Future<ResponseBody> chat(@Body() ChatRequest request);
+
+  @POST("/project/secretary/v1/talk/export")
+  @DioResponseType(ResponseType.stream)
+  Future<ResponseBody> talkExportFile(@Body() TalkExportRequest body);
 }
 
 final atmobStreamApi = _AtmobStreamApi(streamDio, baseUrl: Constants.baseUrl);

+ 18 - 0
lib/data/api/request/talk_export_request.dart

@@ -0,0 +1,18 @@
+import 'package:electronic_assistant/base/app_base_request.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+part 'talk_export_request.g.dart';
+
+@JsonSerializable()
+class TalkExportRequest extends AppBaseRequest {
+  @JsonKey(name: 'id')
+  String id;
+
+  @JsonKey(name: 'type')
+  int type;
+
+  TalkExportRequest(this.id, this.type);
+
+  @override
+  Map<String, dynamic> toJson() => _$TalkExportRequestToJson(this);
+}

+ 11 - 0
lib/data/api/response/talk_export_response.dart

@@ -0,0 +1,11 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'talk_export_response.g.dart';
+
+@JsonSerializable()
+class TalkExportResponse {
+  TalkExportResponse();
+
+  factory TalkExportResponse.fromJson(Map<String, dynamic> json) =>
+      _$TalkExportResponseFromJson(json);
+}

+ 24 - 1
lib/data/repositories/talk_repository.dart

@@ -1,15 +1,20 @@
 import 'dart:io';
 
+import 'package:dio/dio.dart';
 import 'package:electronic_assistant/data/api/atmob_api.dart';
 import 'package:electronic_assistant/data/api/atmob_file_api.dart';
+import 'package:electronic_assistant/data/api/atmob_stream_api.dart';
 import 'package:electronic_assistant/data/api/request/talk_create_request.dart';
 import 'package:electronic_assistant/data/api/request/talk_delete_request.dart';
 import 'package:electronic_assistant/data/api/request/talk_file_request.dart';
 import 'package:flutter_foreground_task/flutter_foreground_task.dart';
 import 'package:get/get.dart';
-
+import 'package:get/get_connect/http/src/request/request.dart';
+import 'package:path_provider/path_provider.dart';
+import '../../dialog/talk_share_dialog.dart';
 import '../../utils/foreground_util.dart';
 import '../../utils/http_handler.dart';
+import '../api/request/talk_export_request.dart';
 import '../api/request/talk_generate_request.dart';
 import '../api/request/talk_paginate_request.dart';
 import '../api/request/talk_rename_request.dart';
@@ -45,6 +50,24 @@ class TalkRepository {
     }
   }
 
+  Future<File> talkExport(
+      String talkId, String fileName, ShareTalkType type) async {
+    ResponseBody responseBody = await atmobStreamApi.talkExportFile(
+        TalkExportRequest(talkId, type == ShareTalkType.summary ? 1 : 2));
+
+    final directory = await getTemporaryDirectory();
+    final filePath = '${directory.path}/export/$fileName';
+
+    final file = File(filePath);
+
+    if (!await file.exists()) {
+      await file.create(recursive: true);
+    }
+
+    await file.writeAsBytes(await responseBody.stream.toBytes());
+    return file;
+  }
+
   void setTalkList(List<TalkBean> list) {
     _talkList.assignAll(list);
   }

+ 155 - 0
lib/dialog/talk_share_dialog.dart

@@ -0,0 +1,155 @@
+import 'package:electronic_assistant/resource/assets.gen.dart';
+import 'package:electronic_assistant/resource/string.gen.dart';
+import 'package:electronic_assistant/utils/expand.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:get/get.dart';
+
+import '../resource/colors.gen.dart';
+
+typedef TalkShareCallback = void Function(
+    ShareTalkType shareType, ShareTo shareTo, String dialogTag);
+
+void showTalkShareDialog(String title, TalkShareCallback callback) {
+  const String tag = 'showTalkShareDialog';
+
+  Rx<ShareTalkType> shareType = ShareTalkType.summary.obs;
+
+  SmartDialog.show(
+      tag: tag,
+      maskColor: Colors.black54,
+      alignment: const Alignment(0.0, 0.94),
+      builder: (_) => Container(
+            width: 336.w,
+            padding: EdgeInsets.all(16.w),
+            decoration: BoxDecoration(
+              color: ColorName.white,
+              borderRadius: BorderRadius.circular(12),
+            ),
+            child: IntrinsicHeight(
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  Row(
+                    children: [
+                      Assets.images.iconTalkTxt
+                          .image(width: 28.w, height: 32.w),
+                      SizedBox(width: 6.w),
+                      Expanded(
+                          child: Text(title,
+                              style: TextStyle(
+                                  fontSize: 14.sp,
+                                  fontWeight: FontWeight.bold,
+                                  color: ColorName.primaryTextColor))),
+                      SizedBox(width: 18.w),
+                      GestureDetector(
+                        onTap: () {
+                          SmartDialog.dismiss(tag: tag);
+                        },
+                        child: Assets.images.iconTalkShareClose
+                            .image(width: 28.w, height: 28.w),
+                      ),
+                    ],
+                  ),
+                  SizedBox(height: 12.h),
+                  Divider(color: '#F6F6F6'.toColor(), height: 1.h),
+                  SizedBox(height: 20.h),
+                  Obx(() {
+                    return Row(
+                      children: [
+                        Expanded(
+                            child: _buildShareTypeItem(
+                                StringName.talkTabSummary.tr,
+                                Assets.images.iconTalkShareSummary.provider(),
+                                shareType.value == ShareTalkType.summary, () {
+                          shareType.value = ShareTalkType.summary;
+                        })),
+                        SizedBox(width: 12.w),
+                        Expanded(
+                            child: _buildShareTypeItem(
+                                StringName.talkTabOriginal.tr,
+                                Assets.images.iconTalkShareOriginal.provider(),
+                                shareType.value == ShareTalkType.original, () {
+                          shareType.value = ShareTalkType.original;
+                        })),
+                      ],
+                    );
+                  }),
+                  SizedBox(height: 16.h),
+                  Text(
+                    '发送至',
+                    style: TextStyle(
+                        fontSize: 14.sp, color: ColorName.secondaryTextColor),
+                  ),
+                  SizedBox(height: 8.h),
+                  Row(
+                    children: [
+                      _buildShareItem(StringName.dialogSendFriend.tr,
+                          Assets.images.iconWx.provider(), () {
+                        callback(shareType.value, ShareTo.wechat, tag);
+                      }),
+                      _buildShareItem(StringName.dialogSendFriend.tr,
+                          Assets.images.iconQq.provider(), () {
+                        callback(shareType.value, ShareTo.qq, tag);
+                      }),
+                    ],
+                  )
+                ],
+              ),
+            ),
+          ));
+}
+
+Widget _buildShareTypeItem(String title, ImageProvider imageProvider,
+    bool isCheck, void Function() onTap) {
+  return GestureDetector(
+    onTap: onTap,
+    child: Container(
+      padding: EdgeInsets.only(top: 14.h, bottom: 12.h),
+      decoration: isCheck
+          ? BoxDecoration(
+              color: '#E7E9F6'.toColor(),
+              border: Border.all(color: ColorName.colorPrimary, width: 2),
+              borderRadius: BorderRadius.circular(8),
+            )
+          : BoxDecoration(
+              color: '#F6F5F8'.toColor(),
+              borderRadius: BorderRadius.circular(8),
+            ),
+      child: Column(
+        children: [
+          Image(image: imageProvider, width: 24.w, height: 24.w),
+          SizedBox(width: 4.h),
+          Text(title,
+              style: TextStyle(
+                  fontSize: 14.sp, color: ColorName.primaryTextColor)),
+        ],
+      ),
+    ),
+  );
+}
+
+Widget _buildShareItem(
+    String itemName, ImageProvider imageProvider, void Function() onTap) {
+  return Container(
+    margin: EdgeInsets.symmetric(horizontal: 10.w),
+    child: GestureDetector(
+      onTap: onTap,
+      child: Column(
+        children: [
+          Image(image: imageProvider, width: 40.w, height: 40.w),
+          SizedBox(height: 6.h),
+          Text(itemName,
+              style: TextStyle(
+                  fontSize: 14.sp, color: ColorName.secondaryTextColor)),
+        ],
+      ),
+    ),
+  );
+}
+
+enum ShareTalkType { summary, original }
+
+enum ShareTo { wechat, qq }

+ 28 - 1
lib/module/talk/controller.dart

@@ -24,10 +24,10 @@ import 'package:electronic_assistant/utils/mmkv_util.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
 import 'package:get/get.dart';
 import 'package:just_audio/just_audio.dart';
 import 'package:wakelock_plus/wakelock_plus.dart';
-
 import '../../data/api/request/agenda_update_bean.dart';
 import '../../data/bean/agenda.dart';
 import '../../data/bean/agenda_list_all_bean.dart';
@@ -36,7 +36,9 @@ import '../../data/repositories/agenda_repository.dart';
 import '../../data/repositories/talk_repository.dart';
 import '../../dialog/add_agenda_dialog.dart';
 import '../../dialog/alert_dialog.dart';
+import '../../dialog/talk_share_dialog.dart';
 import '../../utils/event_bus.dart';
+import '../../utils/system_share_util.dart';
 import '../../utils/toast_util.dart';
 import '../record/controller.dart';
 import 'original/view.dart';
@@ -551,6 +553,31 @@ class TalkController extends BaseController {
     }
   }
 
+  void onShareClick() {
+    if (talkBean.value?.status.value != TalkStatus.analysisSuccess) {
+      return;
+    }
+    String fileName = '${talkBean.value?.title.value}.txt';
+    showTalkShareDialog(fileName, (type, shareTo, tag) {
+      talkRepository
+          .talkExport(talkBean.value!.id, fileName, type)
+          .then((file) async {
+        if (shareTo == ShareTo.wechat) {
+          await SystemShareUtil.shareWechatFile(file.path);
+        } else {
+          await SystemShareUtil.shareQQFile(file.path);
+        }
+        SmartDialog.dismiss(tag: tag);
+      }).catchError((error) {
+        if (error is SystemShareException) {
+          ToastUtil.showToast(error.message);
+        } else {
+          ErrorHandler.toastError(error);
+        }
+      });
+    });
+  }
+
   @override
   void onClose() {
     super.onClose();

+ 22 - 8
lib/module/talk/view.dart

@@ -472,14 +472,28 @@ class TalkPage extends BasePage<TalkController> {
                           fontSize: 17.sp, color: ColorName.primaryTextColor)),
                 ),
               )
-            : IconButton(
-                icon: SizedBox(
-                    width: 24.w,
-                    height: 24.w,
-                    child: Assets.images.iconTalkEdit.image()),
-                onPressed: () {
-                  controller.onEditModelClick();
-                },
+            : Row(
+                children: [
+                  IconButton(
+                    icon: SizedBox(
+                        width: 24.w,
+                        height: 24.w,
+                        child: Assets.images.iconTalkEdit.image()),
+                    onPressed: () {
+                      controller.onEditModelClick();
+                    },
+                  ),
+                  IconButton(
+                    icon: SizedBox(
+                        width: 24.w,
+                        height: 24.w,
+                        child: Assets.images.iconTalkShare.image()),
+                    onPressed: () {
+                      controller.onShareClick();
+                    },
+                  ),
+                  SizedBox(width: 12.w)
+                ],
               ),
       );
     });

+ 41 - 0
lib/utils/system_share_util.dart

@@ -0,0 +1,41 @@
+import 'dart:io';
+
+import 'package:system_share/system_share.dart';
+
+class SystemShareUtil {
+  SystemShareUtil._();
+
+  static Future<void> shareWechatFile(String filePath) async {
+    if (Platform.isAndroid) {
+      String wechatPackageName = 'com.tencent.mm';
+      //检查是否安装微信
+      bool isInstalled = await SystemShare.isInstalled(wechatPackageName);
+      if (!isInstalled) {
+        throw SystemShareException('未安装微信');
+      }
+      SystemShare.shareFile(filePath, wechatPackageName, '分享到微信');
+    }
+  }
+
+  static Future<void> shareQQFile(String filePath) async {
+    if (Platform.isAndroid) {
+      String qqPackageName = 'com.tencent.mobileqq';
+      //检查是否安装微信
+      bool isInstalled = await SystemShare.isInstalled(qqPackageName);
+      if (!isInstalled) {
+        throw SystemShareException('未安装QQ');
+      }
+      SystemShare.shareFile(filePath, qqPackageName, '分享到QQ');
+    }
+  }
+
+  static Future<bool> isInstalled(String packageName) {
+    return SystemShare.isInstalled(packageName);
+  }
+}
+
+class SystemShareException implements Exception {
+  final String message;
+
+  SystemShareException(this.message);
+}

+ 0 - 3
plugin/shortcut/android/build.gradle

@@ -57,9 +57,6 @@ android {
 
         //AndroidX
         compileOnly "androidx.core:core:1.13.1"
-
-        testImplementation("junit:junit:4.13.2")
-        testImplementation("org.mockito:mockito-core:5.0.0")
     }
 
     testOptions {

+ 3 - 0
plugin/system_share/android/gradle.properties

@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true

+ 42 - 0
plugin/system_share/android/src/main/java/com/atmob/system_share/SystemShareUtil.java

@@ -0,0 +1,42 @@
+package com.atmob.system_share;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+
+import java.io.File;
+
+
+public class SystemShareUtil {
+
+    static boolean isInstalled(Context context, String packageName) {
+        PackageManager packageManager = context.getPackageManager();
+        try {
+            PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
+            return packageInfo != null;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    static void shareFile(Context context, File file, String packageName, String shareTitle) {
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.setType("*/*");
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            Uri fileUri = UriUtil.getFileUri(context, file);
+            intent.putExtra(Intent.EXTRA_STREAM, fileUri);
+            context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } else {
+            intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
+        }
+        intent.setPackage(packageName);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Intent shareIntent = Intent.createChooser(intent, shareTitle);
+        shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(shareIntent);
+    }
+
+}

+ 41 - 0
plugin/system_share/android/src/main/java/com/atmob/system_share/UriUtil.java

@@ -0,0 +1,41 @@
+package com.atmob.system_share;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+
+import java.io.File;
+import java.lang.reflect.Method;
+
+public class UriUtil {
+    public static Uri getFileUri(Context context, File file) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            return Uri.fromFile(file);
+        }
+        Uri uri = getFileUriFromXFileProvider(context, file);
+        if (uri == null) {
+            uri = getFileUriFromV4FileProvider(context, file);
+        }
+        return uri;
+    }
+
+    private static Uri getFileUriFromV4FileProvider(Context context, File file) {
+        try {
+            Class<?> v4FileProvider = Class.forName("android.support.v4.content.FileProvider");
+            Method getUriForFile = v4FileProvider.getMethod("getUriForFile", Context.class, String.class, File.class);
+            return (Uri) getUriForFile.invoke(null, context, context.getPackageName() + ".fileprovider", file);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static Uri getFileUriFromXFileProvider(Context context, File file) {
+        try {
+            Class<?> xFileProvider = Class.forName("androidx.core.content.FileProvider");
+            Method getUriForFile = xFileProvider.getMethod("getUriForFile", Context.class, String.class, File.class);
+            return (Uri) getUriForFile.invoke(null, context, context.getPackageName() + ".fileprovider", file);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 13 - 0
plugin/system_share/android/src/main/res/xml/atmob_filepath.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <external-path
+        name="external"
+        path="." />
+
+    <cache-path
+        name="cache"
+        path="." />
+    <files-path
+        name="files"
+        path="." />
+</paths>

+ 7 - 0
pubspec.lock

@@ -1351,6 +1351,13 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "3.3.0+3"
+  system_share:
+    dependency: "direct main"
+    description:
+      path: "plugin/system_share"
+      relative: true
+    source: path
+    version: "0.0.1"
   term_glyph:
     dependency: transitive
     description:

+ 4 - 0
pubspec.yaml

@@ -118,6 +118,10 @@ dependencies:
   shortcut:
     path: plugin/shortcut
 
+  #系统分享
+  system_share:
+    path: plugin/system_share
+
 dev_dependencies:
   flutter_test:
     sdk: flutter