基础页面
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '9.0'
|
||||
platform :ios, '9.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
108
ios/Podfile.lock
Normal file
108
ios/Podfile.lock
Normal file
@@ -0,0 +1,108 @@
|
||||
PODS:
|
||||
- Flutter (1.0.0)
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- Toast
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- HydraAsync (2.0.6)
|
||||
- image_cropper (0.0.4):
|
||||
- Flutter
|
||||
- TOCropViewController (~> 2.6.1)
|
||||
- open_file (0.0.1):
|
||||
- Flutter
|
||||
- path_provider_ios (0.0.1):
|
||||
- Flutter
|
||||
- photo_manager (2.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- scan (0.0.1):
|
||||
- Flutter
|
||||
- smart_auth (0.0.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.2):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- tencent_im_sdk_plugin (4.0.3):
|
||||
- Flutter
|
||||
- HydraAsync
|
||||
- TXIMSDK_Plus_iOS (= 6.7.3184)
|
||||
- Toast (4.0.0)
|
||||
- TOCropViewController (2.6.1)
|
||||
- TXIMSDK_Plus_iOS (6.7.3184)
|
||||
- vibration (1.7.5):
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
|
||||
- open_file (from `.symlinks/plugins/open_file/ios`)
|
||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||
- scan (from `.symlinks/plugins/scan/ios`)
|
||||
- smart_auth (from `.symlinks/plugins/smart_auth/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- tencent_im_sdk_plugin (from `.symlinks/plugins/tencent_im_sdk_plugin/ios`)
|
||||
- vibration (from `.symlinks/plugins/vibration/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- FMDB
|
||||
- HydraAsync
|
||||
- Toast
|
||||
- TOCropViewController
|
||||
- TXIMSDK_Plus_iOS
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
image_cropper:
|
||||
:path: ".symlinks/plugins/image_cropper/ios"
|
||||
open_file:
|
||||
:path: ".symlinks/plugins/open_file/ios"
|
||||
path_provider_ios:
|
||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||
photo_manager:
|
||||
:path: ".symlinks/plugins/photo_manager/ios"
|
||||
scan:
|
||||
:path: ".symlinks/plugins/scan/ios"
|
||||
smart_auth:
|
||||
:path: ".symlinks/plugins/smart_auth/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
tencent_im_sdk_plugin:
|
||||
:path: ".symlinks/plugins/tencent_im_sdk_plugin/ios"
|
||||
vibration:
|
||||
:path: ".symlinks/plugins/vibration/ios"
|
||||
video_player_avfoundation:
|
||||
:path: ".symlinks/plugins/video_player_avfoundation/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||
fluttertoast: 74526702fea2c060ea55dde75895b7e1bde1c86b
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
HydraAsync: 8d589bd725b0224f899afafc9a396327405f8063
|
||||
image_cropper: 60c2789d1f1a78c873235d4319ca0c34a69f2d98
|
||||
open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d
|
||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
||||
scan: aea35bb4aa59ccc8839c576a18cd57c7d492cc86
|
||||
smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
|
||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||
tencent_im_sdk_plugin: 26c668a5d2f456a5541e2c820dcfcb3d15fdba9a
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
|
||||
TXIMSDK_Plus_iOS: 5412f55a77f058b2b5a8575900334daccbae3b08
|
||||
vibration: 7d883d141656a1c1a6d8d238616b2042a51a1241
|
||||
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
|
||||
|
||||
PODFILE CHECKSUM: a75497545d4391e2d394c3668e20cfb1c2bbd4aa
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
@@ -3,12 +3,13 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
70F84DE20E16DC49868EF51A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CDE8CA2C88702AA59A75259 /* Pods_Runner.framework */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
@@ -32,9 +33,12 @@
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
3F9F50CF9C873D1460CCEE80 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
5F1992123E9973AEE97006AF /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
7CDE8CA2C88702AA59A75259 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -42,6 +46,7 @@
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
E29514A41E605422DF139C6D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -49,6 +54,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
70F84DE20E16DC49868EF51A /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -72,6 +78,8 @@
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
C394D966EF1617064336C312 /* Pods */,
|
||||
C37FF88E875204B875862A7F /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -98,6 +106,24 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C37FF88E875204B875862A7F /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7CDE8CA2C88702AA59A75259 /* Pods_Runner.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C394D966EF1617064336C312 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5F1992123E9973AEE97006AF /* Pods-Runner.debug.xcconfig */,
|
||||
3F9F50CF9C873D1460CCEE80 /* Pods-Runner.release.xcconfig */,
|
||||
E29514A41E605422DF139C6D /* Pods-Runner.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -105,12 +131,14 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
70B6D40A25B40589C6B223C0 /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
2EC1B54F88AAEC33384CE737 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -169,6 +197,23 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
2EC1B54F88AAEC33384CE737 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -183,6 +228,28 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
70B6D40A25B40589C6B223C0 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -287,13 +354,14 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = site.zhchain.chat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -415,13 +483,14 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = site.zhchain.chat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -437,13 +506,14 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = site.zhchain.chat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
||||
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
@@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Chat</string>
|
||||
<string>ZH-Chat</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -13,15 +13,15 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>chat</string>
|
||||
<string>zhchat</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
||||
@@ -52,7 +52,7 @@ class Themes {
|
||||
fontSize: 11,
|
||||
),
|
||||
unselectedLabelStyle: TextStyle(
|
||||
fontSize: 10,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
228
lib/controllers/group_controller.dart
Normal file
228
lib/controllers/group_controller.dart
Normal file
@@ -0,0 +1,228 @@
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:chat/models/im/group_conversation_model.dart';
|
||||
import 'package:chat/services/auth_service.dart';
|
||||
import 'package:chat/services/tim/conversation_service.dart';
|
||||
import 'package:chat/services/tim/group_service.dart';
|
||||
import 'package:chat/services/tim_service.dart';
|
||||
import 'package:chat/utils/ui_tools.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_member_filter_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_member_role.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_member_role_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member_full_info.dart';
|
||||
|
||||
class GroupController extends GetxController {
|
||||
static GroupController get to => Get.find<GroupController>();
|
||||
|
||||
Rx<GroupConversationModel> currentGroup =
|
||||
GroupConversationModel(groupID: '').obs;
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
currentGroup.value = GroupConversationModel(groupID: '');
|
||||
TimService.to.currentConversationId.value = '';
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
/// 设置当前操作的群组
|
||||
Future<void> setCurrentGroup(String groupId) async {
|
||||
var group = await TimGroupService.to.info(groupId);
|
||||
|
||||
if (group != null) {
|
||||
TimService.to.currentConversationId.value = 'group_' + groupId;
|
||||
|
||||
currentGroup.value.group = group;
|
||||
|
||||
currentGroup.value.groupID = groupId;
|
||||
|
||||
var selfInfo = await TimGroupService.to.getMemberInfo(
|
||||
group,
|
||||
AuthService.to.userId,
|
||||
);
|
||||
|
||||
if (selfInfo?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN) {
|
||||
currentGroup.value.isAdmin = true;
|
||||
}
|
||||
|
||||
if (selfInfo?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER) {
|
||||
currentGroup.value.isOwner = true;
|
||||
}
|
||||
|
||||
currentGroup.value.selfInfo = await TimGroupService.to.getMemberInfo(
|
||||
group,
|
||||
AuthService.to.userId,
|
||||
);
|
||||
|
||||
currentGroup.value.conversation = await TimConversationService.to.getById(
|
||||
'group_' + groupId,
|
||||
);
|
||||
|
||||
currentGroup.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取群成员列表
|
||||
Future<void> fetchGroupMemberList() async {
|
||||
var members = await TimGroupService.to.members(
|
||||
currentGroup.value.groupID,
|
||||
count: 13,
|
||||
);
|
||||
|
||||
currentGroup.value.memberList = members;
|
||||
|
||||
var admins = await TimGroupService.to.members(
|
||||
currentGroup.value.groupID,
|
||||
count: 100,
|
||||
filter: GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_ADMIN,
|
||||
);
|
||||
|
||||
currentGroup.value.adminList = admins;
|
||||
currentGroup.refresh();
|
||||
}
|
||||
|
||||
/// 更新群名称
|
||||
Future<void> updateGroupName(String name) async {
|
||||
var result = await TimGroupService.to.updateName(
|
||||
currentGroup.value.group!,
|
||||
name,
|
||||
);
|
||||
|
||||
if (result) {
|
||||
currentGroup.value.group!.groupName = name;
|
||||
currentGroup.value.conversation!.showName = name;
|
||||
currentGroup.refresh();
|
||||
UiTools.toast('群名称修改成功');
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新我的群名片
|
||||
Future<void> updateGroupNameCard(String nameCard) async {
|
||||
var res = await TimGroupService.to.setMemberInfo(
|
||||
currentGroup.value.group!,
|
||||
AuthService.to.userId,
|
||||
nameCard,
|
||||
);
|
||||
if (res) {
|
||||
currentGroup.value.selfInfo!.nameCard = nameCard;
|
||||
currentGroup.refresh();
|
||||
|
||||
UiTools.toast('群名片修改成功');
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateGroupNotification(String notification) async {
|
||||
var res = await TimGroupService.to.updateNotification(
|
||||
currentGroup.value.group!,
|
||||
notification,
|
||||
);
|
||||
if (res) {
|
||||
currentGroup.value.group!.notification = notification;
|
||||
currentGroup.refresh();
|
||||
|
||||
UiTools.toast('群公告更新成功');
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> togglePinned() async {
|
||||
var res = await TimConversationService.to.setOnTop(
|
||||
currentGroup.value.conversation!,
|
||||
);
|
||||
|
||||
if (res) {
|
||||
currentGroup.value.conversation = await TimConversationService.to.getById(
|
||||
'group_' + currentGroup.value.groupID,
|
||||
);
|
||||
|
||||
currentGroup.refresh();
|
||||
UiTools.toast('修改成功');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleReceiveOpt() async {
|
||||
var res = await TimConversationService.to.setReceiveOpt(
|
||||
currentGroup.value.conversation!,
|
||||
);
|
||||
|
||||
if (res) {
|
||||
currentGroup.value.conversation = await TimConversationService.to.getById(
|
||||
'group_' + currentGroup.value.groupID,
|
||||
);
|
||||
currentGroup.refresh();
|
||||
|
||||
UiTools.toast('修改成功');
|
||||
}
|
||||
}
|
||||
|
||||
/// 移除群成员
|
||||
Future<bool> kick(List<String> ids) async {
|
||||
var result = await TimGroupService.to.kickMember(
|
||||
GroupController.to.currentGroup.value.group!,
|
||||
ids,
|
||||
);
|
||||
|
||||
if (result) {
|
||||
setCurrentGroup(currentGroup.value.groupID);
|
||||
fetchGroupMemberList();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<bool> transfer(V2TimGroupMemberFullInfo member) async {
|
||||
OkCancelResult result = await showOkCancelAlertDialog(
|
||||
style: AdaptiveStyle.iOS,
|
||||
context: Get.context!,
|
||||
title: '操作提示',
|
||||
message: '确定选择 ${member.nickName} 为新群主,您将自动放弃群主身份。',
|
||||
okLabel: '确定',
|
||||
cancelLabel: '取消',
|
||||
defaultType: OkCancelAlertDefaultType.ok,
|
||||
);
|
||||
|
||||
if (result == OkCancelResult.ok) {
|
||||
var res = await TimGroupService.to.transfer(
|
||||
currentGroup.value.group!,
|
||||
member.userID,
|
||||
);
|
||||
|
||||
if (res) {
|
||||
/// 直接修改当前用户的身份,为普通用户
|
||||
currentGroup.value.isAdmin = false;
|
||||
currentGroup.value.isOwner = false;
|
||||
await fetchGroupMemberList();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> setAdmin(String userID) async {
|
||||
var result = await TimGroupService.to.setMemberRole(
|
||||
currentGroup.value.group!,
|
||||
userID,
|
||||
GroupMemberRoleTypeEnum.V2TIM_GROUP_MEMBER_ROLE_ADMIN,
|
||||
);
|
||||
|
||||
if (result) {
|
||||
UiTools.toast('设置群管理成功');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<bool> cancelAdmin(String userID) async {
|
||||
var result = await TimGroupService.to.setMemberRole(
|
||||
currentGroup.value.group!,
|
||||
userID,
|
||||
GroupMemberRoleTypeEnum.V2TIM_GROUP_MEMBER_ROLE_MEMBER,
|
||||
);
|
||||
if (result) {
|
||||
UiTools.toast('取消群管理成功');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
111
lib/controllers/private_controller.dart
Normal file
111
lib/controllers/private_controller.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
import 'package:chat/models/im/private_conversation_model.dart';
|
||||
import 'package:chat/services/tim/conversation_service.dart';
|
||||
import 'package:chat/services/tim/friend_service.dart';
|
||||
import 'package:chat/services/tim_service.dart';
|
||||
import 'package:chat/utils/ui_tools.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info_result.dart';
|
||||
|
||||
class PrivateController extends GetxController {
|
||||
static PrivateController get to => Get.find<PrivateController>();
|
||||
|
||||
Rx<PrivateConversationModel> currentFriend =
|
||||
PrivateConversationModel(userID: '').obs;
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
currentFriend.value = PrivateConversationModel(userID: '');
|
||||
TimService.to.currentConversationId.value = '';
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
/// 设置当前好友
|
||||
Future<void> setCurrentFriend(String userID) async {
|
||||
V2TimFriendInfoResult? info = await TimFriendService.to.friendInfo(userID);
|
||||
|
||||
if (info != null) {
|
||||
TimService.to.currentConversationId.value = 'c2c_' + userID;
|
||||
|
||||
currentFriend.value.userID = userID;
|
||||
currentFriend.value.isFriend =
|
||||
info.relation == UserRelationEnum.V2TIM_FRIEND_RELATION_TYPE_BOTH_WAY;
|
||||
|
||||
currentFriend.value.friendRemark = info.friendInfo!.friendRemark!;
|
||||
currentFriend.value.userProfile = info.friendInfo!.userProfile;
|
||||
|
||||
/// 通过自定义Staffer字段,判断是否是客服,属于哪个店铺
|
||||
if (info.friendInfo!.userProfile?.customInfo!['Staffer']?.isNotEmpty ==
|
||||
true) {
|
||||
currentFriend.value.shopId =
|
||||
info.friendInfo!.userProfile?.customInfo!['Staffer'];
|
||||
currentFriend.value.isStaffer = true;
|
||||
}
|
||||
|
||||
/// 通过用户自定义字段,判断是否允许陌生人消息
|
||||
if (info.friendInfo!.userProfile?.customInfo!['Stranger']?.isNotEmpty ==
|
||||
true) {
|
||||
currentFriend.value.allowStranger = false;
|
||||
}
|
||||
|
||||
/// 设置会话
|
||||
currentFriend.value.conversation =
|
||||
await TimConversationService.to.getById('c2c_' + userID);
|
||||
|
||||
currentFriend.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> togglePinned() async {
|
||||
var res = await TimConversationService.to.setOnTop(
|
||||
currentFriend.value.conversation!,
|
||||
);
|
||||
|
||||
if (res) {
|
||||
currentFriend.value.conversation =
|
||||
await TimConversationService.to.getById(
|
||||
'c2c_' + currentFriend.value.userID,
|
||||
);
|
||||
|
||||
currentFriend.refresh();
|
||||
UiTools.toast('修改成功');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changeReceiveOpt() async {
|
||||
var res = await TimConversationService.to.setReceiveOpt(
|
||||
currentFriend.value.conversation!,
|
||||
);
|
||||
|
||||
if (res) {
|
||||
currentFriend.value.conversation =
|
||||
await TimConversationService.to.getById(
|
||||
'c2c_' + currentFriend.value.userID,
|
||||
);
|
||||
currentFriend.refresh();
|
||||
|
||||
UiTools.toast('修改成功');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setRemark(String remark) async {
|
||||
var result = await TimFriendService.to.setFriendRemark(
|
||||
currentFriend.value.userID,
|
||||
remark,
|
||||
);
|
||||
|
||||
if (result) {
|
||||
currentFriend.value.friendRemark = remark;
|
||||
|
||||
currentFriend.value.conversation =
|
||||
await TimConversationService.to.getById(
|
||||
'c2c_' + currentFriend.value.userID,
|
||||
);
|
||||
|
||||
currentFriend.refresh();
|
||||
|
||||
TimConversationService.to.fetchList();
|
||||
UiTools.toast('备注修改成功');
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
121
lib/models/im/calling_model.dart
Normal file
121
lib/models/im/calling_model.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:chat/models/im/custom_message_model.dart';
|
||||
|
||||
class CallingModel extends CustomMessageModel {
|
||||
CallingModel({
|
||||
this.businessID = CustomMessageType.CALL,
|
||||
required this.callType,
|
||||
required this.inviter,
|
||||
required this.inviteeList,
|
||||
required this.data,
|
||||
required this.timeout,
|
||||
required this.actionType,
|
||||
required this.onlineUserOnly,
|
||||
required this.isGroup,
|
||||
});
|
||||
|
||||
@override
|
||||
String businessID;
|
||||
|
||||
/// 通话类型 videoCall audioCall
|
||||
String callType;
|
||||
|
||||
/// 邀请人
|
||||
String inviter;
|
||||
|
||||
/// 被邀请人
|
||||
List<String> inviteeList;
|
||||
|
||||
/// 通话时长
|
||||
int timeout;
|
||||
// 1: 邀请方发起邀请
|
||||
// 2: 邀请方取消邀请
|
||||
// 3: 被邀请方接受邀请
|
||||
// 4: 被邀请方拒绝邀请
|
||||
// 5: 邀请超时
|
||||
int actionType;
|
||||
|
||||
bool onlineUserOnly;
|
||||
|
||||
/// 是否是群语音
|
||||
bool isGroup;
|
||||
CallingModelData data;
|
||||
|
||||
String get actionTypeText {
|
||||
final actionMessage = {
|
||||
1: "发起通话",
|
||||
2: "取消通话",
|
||||
3: "接受通话",
|
||||
4: "拒绝通话",
|
||||
5: "超时未接听",
|
||||
};
|
||||
return actionMessage[actionType] ?? "";
|
||||
}
|
||||
|
||||
factory CallingModel.fromJson(Map<String, dynamic> json) => CallingModel(
|
||||
callType: jsonDecode(json['data'])['data']['cmd'],
|
||||
inviter: json['inviter'],
|
||||
inviteeList: List<String>.from(
|
||||
json['inviteeList'].map(
|
||||
(x) => x.toString(),
|
||||
),
|
||||
),
|
||||
data: CallingModelData.fromJson(jsonDecode(json['data'])),
|
||||
timeout: json['timeout'],
|
||||
actionType: json['actionType'],
|
||||
onlineUserOnly: json['onlineUserOnly'],
|
||||
isGroup: jsonDecode(json['data'])['is_group'],
|
||||
);
|
||||
|
||||
@override
|
||||
String toJson() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
class CallingModelData {
|
||||
CallingModelData({
|
||||
required this.version,
|
||||
required this.callType,
|
||||
required this.data,
|
||||
required this.roomId,
|
||||
required this.isGroup,
|
||||
});
|
||||
|
||||
int version;
|
||||
int callType;
|
||||
DataData data;
|
||||
int roomId;
|
||||
bool isGroup;
|
||||
|
||||
factory CallingModelData.fromJson(Map<String, dynamic> json) =>
|
||||
CallingModelData(
|
||||
version: json['version'],
|
||||
callType: json['call_type'],
|
||||
data: DataData.fromJson(json['data']),
|
||||
roomId: json['room_id'],
|
||||
isGroup: json['is_group'],
|
||||
);
|
||||
}
|
||||
|
||||
class DataData {
|
||||
DataData({
|
||||
required this.cmd,
|
||||
required this.roomId,
|
||||
required this.message,
|
||||
required this.cmdInfo,
|
||||
});
|
||||
|
||||
String cmd; // videoCall audioCall
|
||||
int roomId;
|
||||
String message;
|
||||
String cmdInfo;
|
||||
|
||||
factory DataData.fromJson(Map<String, dynamic> json) => DataData(
|
||||
cmd: json['cmd'],
|
||||
roomId: json['room_id'],
|
||||
message: json['message'],
|
||||
cmdInfo: json['cmd_info'],
|
||||
);
|
||||
}
|
||||
21
lib/models/im/contact_info_model.dart
Normal file
21
lib/models/im/contact_info_model.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:azlistview/azlistview.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info.dart';
|
||||
|
||||
class ContactInfoModel extends ISuspensionBean {
|
||||
String name;
|
||||
String userID;
|
||||
String? tagIndex;
|
||||
String? namePinyin;
|
||||
V2TimFriendInfo? friendInfo;
|
||||
|
||||
ContactInfoModel({
|
||||
required this.name,
|
||||
required this.userID,
|
||||
this.tagIndex,
|
||||
this.namePinyin,
|
||||
this.friendInfo,
|
||||
});
|
||||
|
||||
@override
|
||||
String getSuspensionTag() => tagIndex!;
|
||||
}
|
||||
25
lib/models/im/custom_message_model.dart
Normal file
25
lib/models/im/custom_message_model.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
abstract class CustomMessageModel {
|
||||
abstract String businessID;
|
||||
|
||||
String toJson();
|
||||
}
|
||||
|
||||
class CustomMessageType {
|
||||
// ignore: constant_identifier_names
|
||||
static const String NAME_CARD = 'name_card';
|
||||
// ignore: constant_identifier_names
|
||||
static const String GROUP_CARD = 'group_card';
|
||||
// ignore: constant_identifier_names
|
||||
static const String DT_TRANSFER = 'dt_transfer';
|
||||
// ignore: constant_identifier_names
|
||||
static const String TYPING_STATUS = 'user_typing_status';
|
||||
// ignore: constant_identifier_names
|
||||
static const String EVALUATION = 'evaluation';
|
||||
// ignore: constant_identifier_names
|
||||
static const String CALL = '1';
|
||||
}
|
||||
|
||||
class CallingType {
|
||||
static const String audioCall = 'audioCall';
|
||||
static const String videoCall = 'videoCall';
|
||||
}
|
||||
14
lib/models/im/emoji_model.dart
Normal file
14
lib/models/im/emoji_model.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
class EmojiModel {
|
||||
EmojiModel({
|
||||
required this.name,
|
||||
required this.unicode,
|
||||
});
|
||||
|
||||
String name;
|
||||
int unicode;
|
||||
|
||||
factory EmojiModel.fromJson(Map<String, dynamic> json) => EmojiModel(
|
||||
name: json['name'],
|
||||
unicode: json['unicode'],
|
||||
);
|
||||
}
|
||||
29
lib/models/im/evaluation_model.dart
Normal file
29
lib/models/im/evaluation_model.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:chat/models/im/custom_message_model.dart';
|
||||
|
||||
class EvaluationModel extends CustomMessageModel {
|
||||
@override
|
||||
String businessID;
|
||||
int version;
|
||||
double score;
|
||||
String comment;
|
||||
|
||||
EvaluationModel({
|
||||
this.businessID = 'evaluation',
|
||||
required this.version,
|
||||
required this.score,
|
||||
required this.comment,
|
||||
});
|
||||
|
||||
factory EvaluationModel.fromJson(Map<String, dynamic> json) =>
|
||||
EvaluationModel(
|
||||
businessID: json['businessID'],
|
||||
version: json['version'],
|
||||
score: double.parse(json['score']),
|
||||
comment: json['comment'],
|
||||
);
|
||||
|
||||
@override
|
||||
String toJson() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
33
lib/models/im/group_card_model.dart
Normal file
33
lib/models/im/group_card_model.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:chat/models/im/custom_message_model.dart';
|
||||
|
||||
class GroupCardModel extends CustomMessageModel {
|
||||
@override
|
||||
String businessID;
|
||||
String groupID;
|
||||
String groupName;
|
||||
String inviterID;
|
||||
|
||||
GroupCardModel({
|
||||
this.businessID = CustomMessageType.GROUP_CARD,
|
||||
required this.groupID,
|
||||
required this.groupName,
|
||||
required this.inviterID,
|
||||
});
|
||||
|
||||
factory GroupCardModel.fromJson(Map<String, dynamic> json) => GroupCardModel(
|
||||
businessID: json['businessID'],
|
||||
groupID: json['groupID'],
|
||||
groupName: json['groupName'],
|
||||
inviterID: json['inviterID'],
|
||||
);
|
||||
|
||||
@override
|
||||
String toJson() => json.encode({
|
||||
'businessID': businessID,
|
||||
'groupID': groupID,
|
||||
'groupName': groupName,
|
||||
'inviterID': inviterID,
|
||||
});
|
||||
}
|
||||
25
lib/models/im/group_conversation_model.dart
Normal file
25
lib/models/im/group_conversation_model.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member_full_info.dart';
|
||||
|
||||
class GroupConversationModel {
|
||||
String groupID;
|
||||
V2TimGroupInfo? group;
|
||||
List<V2TimGroupMemberFullInfo?>? memberList;
|
||||
List<V2TimGroupMemberFullInfo?>? adminList;
|
||||
V2TimGroupMemberFullInfo? selfInfo;
|
||||
V2TimConversation? conversation;
|
||||
bool isAdmin;
|
||||
bool isOwner;
|
||||
|
||||
GroupConversationModel({
|
||||
required this.groupID,
|
||||
this.group,
|
||||
this.memberList,
|
||||
this.adminList,
|
||||
this.selfInfo,
|
||||
this.conversation,
|
||||
this.isAdmin = false,
|
||||
this.isOwner = false,
|
||||
});
|
||||
}
|
||||
23
lib/models/im/location_model.dart
Normal file
23
lib/models/im/location_model.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
class LocationModel {
|
||||
LocationModel({
|
||||
required this.name,
|
||||
required this.address,
|
||||
required this.list,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
});
|
||||
|
||||
String name;
|
||||
String address;
|
||||
List<dynamic> list;
|
||||
double latitude;
|
||||
double longitude;
|
||||
|
||||
factory LocationModel.fromJson(Map<String, dynamic> json) => LocationModel(
|
||||
name: json['name'],
|
||||
address: json['address'],
|
||||
list: List<dynamic>.from(json['list'].map((x) => x)),
|
||||
latitude: json['latitude'].toDouble(),
|
||||
longitude: json['longitude'].toDouble(),
|
||||
);
|
||||
}
|
||||
33
lib/models/im/name_card_model.dart
Normal file
33
lib/models/im/name_card_model.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:chat/models/im/custom_message_model.dart';
|
||||
|
||||
class NameCardModel extends CustomMessageModel {
|
||||
@override
|
||||
String businessID;
|
||||
String avatar;
|
||||
String userID;
|
||||
String userName;
|
||||
|
||||
NameCardModel({
|
||||
this.businessID = CustomMessageType.NAME_CARD,
|
||||
required this.avatar,
|
||||
required this.userID,
|
||||
required this.userName,
|
||||
});
|
||||
|
||||
factory NameCardModel.fromJson(Map<String, dynamic> json) => NameCardModel(
|
||||
businessID: json['businessID'],
|
||||
avatar: json['avatar'],
|
||||
userID: json['userID'],
|
||||
userName: json['userName'],
|
||||
);
|
||||
|
||||
@override
|
||||
String toJson() => json.encode({
|
||||
'businessID': businessID,
|
||||
'avatar': avatar,
|
||||
'userID': userID,
|
||||
'userName': userName,
|
||||
});
|
||||
}
|
||||
40
lib/models/im/private_conversation_model.dart
Normal file
40
lib/models/im/private_conversation_model.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_user_full_info.dart';
|
||||
|
||||
class PrivateConversationModel {
|
||||
String userID;
|
||||
bool isFriend;
|
||||
String friendRemark;
|
||||
V2TimConversation? conversation;
|
||||
V2TimUserFullInfo? userProfile;
|
||||
bool isStaffer;
|
||||
bool allowStranger; // 允许陌生人消息
|
||||
String? shopId; // 他是哪个店铺的客服
|
||||
|
||||
PrivateConversationModel({
|
||||
required this.userID,
|
||||
this.friendRemark = '',
|
||||
this.isFriend = false,
|
||||
this.conversation,
|
||||
this.userProfile,
|
||||
this.isStaffer = false,
|
||||
this.allowStranger = true,
|
||||
this.shopId,
|
||||
});
|
||||
}
|
||||
|
||||
class UserRelationEnum {
|
||||
/// 不是好友
|
||||
static const int V2TIM_FRIEND_RELATION_TYPE_NONE = 0;
|
||||
|
||||
/// 对方在我的好友列表
|
||||
static const int V2TIM_FRIEND_RELATION_TYPE_IN_MY_FRIEND_LIST = 1;
|
||||
|
||||
/// 我在对方的好友列表
|
||||
static const int V2TIM_FRIEND_RELATION_TYPE_IN_OTHER_FRIEND_LIST = 2;
|
||||
|
||||
/// 表示对方在我的好友列表中
|
||||
static const int V2TIM_FRIEND_RELATION_TYPE_BOTH_WAY = 3;
|
||||
}
|
||||
18
lib/models/im/search_user_model.dart
Normal file
18
lib/models/im/search_user_model.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
class SearchUserModel {
|
||||
SearchUserModel({
|
||||
required this.userID,
|
||||
required this.nickname,
|
||||
required this.avatar,
|
||||
});
|
||||
|
||||
String userID;
|
||||
String nickname;
|
||||
String avatar;
|
||||
|
||||
factory SearchUserModel.fromJson(Map<String, dynamic> json) =>
|
||||
SearchUserModel(
|
||||
userID: json['user_id'].toString(),
|
||||
nickname: json['nickname'],
|
||||
avatar: json['avatar'],
|
||||
);
|
||||
}
|
||||
32
lib/models/im/transfer_model.dart
Normal file
32
lib/models/im/transfer_model.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:chat/models/im/custom_message_model.dart';
|
||||
|
||||
class TransferModel extends CustomMessageModel {
|
||||
@override
|
||||
String businessID;
|
||||
String amount;
|
||||
int orderId;
|
||||
bool isReceived;
|
||||
|
||||
TransferModel({
|
||||
this.businessID = CustomMessageType.DT_TRANSFER,
|
||||
required this.amount,
|
||||
required this.orderId,
|
||||
this.isReceived = false,
|
||||
});
|
||||
|
||||
factory TransferModel.fromJson(Map<String, dynamic> json) => TransferModel(
|
||||
businessID: json['businessID'],
|
||||
amount: json['amount'],
|
||||
orderId: json['orderId'] ?? 33,
|
||||
);
|
||||
|
||||
@override
|
||||
String toJson() => json.encode({
|
||||
'businessID': businessID,
|
||||
'amount': amount,
|
||||
'orderId': orderId,
|
||||
'isReceived': isReceived,
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
import 'package:chat/routes/app_routes.dart';
|
||||
import 'package:chat/routes/auth_routes.dart';
|
||||
import 'package:chat/routes/contact_routes.dart';
|
||||
import 'package:chat/routes/conversation_routes.dart';
|
||||
import 'package:chat/routes/user_routes.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class AppRouter {
|
||||
// 路由页面
|
||||
static final List<GetPage<dynamic>> getPages = [
|
||||
AppRoutes.router,
|
||||
AuthRoutes.router,
|
||||
AppRoutes.router,
|
||||
ConversationRoutes.router,
|
||||
ContactRoutes.router,
|
||||
UserRoutes.router,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:chat/views/conversation/index_page.dart';
|
||||
import 'package:chat/views/home/index_page.dart';
|
||||
import 'package:chat/views/public/app_page.dart';
|
||||
import 'package:chat/views/public/scan_page.dart';
|
||||
@@ -10,9 +11,11 @@ abstract class AppRoutes {
|
||||
static const String app = '/';
|
||||
static const String transit = '/transit';
|
||||
static const String notfound = '/notfound';
|
||||
static const String home = '/home';
|
||||
static const String scan = '/scan';
|
||||
|
||||
static const String home = '/home';
|
||||
static const String search = '/search';
|
||||
|
||||
static GetPage router = GetPage(
|
||||
name: '/',
|
||||
page: () => AppPage(),
|
||||
@@ -21,13 +24,17 @@ abstract class AppRoutes {
|
||||
name: AppRoutes.transit,
|
||||
page: () => const TransitPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: AppRoutes.scan,
|
||||
page: () => const ScanPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: AppRoutes.home,
|
||||
page: () => const HomePage(),
|
||||
),
|
||||
GetPage(
|
||||
name: AppRoutes.scan,
|
||||
page: () => const ScanPage(),
|
||||
name: AppRoutes.search,
|
||||
page: () => const ConversationPage(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import 'package:chat/middleware/auth_middleware.dart';
|
||||
import 'package:chat/views/contact/group/create/index_page.dart';
|
||||
import 'package:chat/views/contact/group/index_page.dart';
|
||||
import 'package:chat/views/contact/group/manage/index_page.dart';
|
||||
import 'package:chat/views/contact/group/notification/index_page.dart';
|
||||
import 'package:chat/views/contact/index/index_page.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
abstract class ContactRoutes {
|
||||
/// 身份验证页面
|
||||
static const String index = '/contact';
|
||||
|
||||
static const String friend = '/contact/friend';
|
||||
static const String friendSearch = '/contact/friend/search';
|
||||
static const String friendProfile = '/contact/friend/profile';
|
||||
|
||||
static const String group = '/contact/group';
|
||||
static const String groupQrCode = '/contact/group/qrCode';
|
||||
static const String groupCreate = '/contact/group/create';
|
||||
static const String groupNotification = '/contact/group/notification';
|
||||
static const String groupManage = '/contact/group/manage';
|
||||
static const String groupApprove = '/contact/group/approve';
|
||||
static const String groupNickname = '/contact/group/nickname';
|
||||
static const String groupKick = '/contact/group/kick';
|
||||
|
||||
static GetPage router = GetPage(
|
||||
name: ContactRoutes.index,
|
||||
@@ -15,9 +30,41 @@ abstract class ContactRoutes {
|
||||
],
|
||||
page: () => const ContactPage(),
|
||||
children: [
|
||||
GetPage(
|
||||
name: '/friend',
|
||||
page: () => const ContactGroupPage(),
|
||||
children: [
|
||||
GetPage(
|
||||
name: '/search',
|
||||
page: () => const ContactGroupCreatePage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GetPage(
|
||||
name: '/group',
|
||||
page: () => const ContactGroupPage(),
|
||||
children: [
|
||||
GetPage(
|
||||
name: '/create',
|
||||
page: () => const ContactGroupCreatePage(),
|
||||
),
|
||||
GetPage(
|
||||
name: '/qrCode',
|
||||
page: () => const ContactGroupCreatePage(),
|
||||
),
|
||||
GetPage(
|
||||
name: '/notification',
|
||||
page: () => const ContactGroupNotificationPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: '/manage',
|
||||
page: () => const ContactGroupManagePage(),
|
||||
),
|
||||
GetPage(
|
||||
name: '/approve',
|
||||
page: () => const ContactGroupManagePage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
36
lib/routes/conversation_routes.dart
Normal file
36
lib/routes/conversation_routes.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:chat/middleware/auth_middleware.dart';
|
||||
import 'package:chat/views/conversation/index_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
abstract class ConversationRoutes {
|
||||
/// 身份验证页面
|
||||
static const String index = '/conversation';
|
||||
|
||||
static const String infoGroup = '/conversation/info/group';
|
||||
static const String infoPrivate = '/conversation/info/private';
|
||||
|
||||
static GetPage router = GetPage(
|
||||
name: ConversationRoutes.index,
|
||||
middlewares: [
|
||||
EnsureAuthMiddleware(),
|
||||
],
|
||||
page: () => const ConversationPage(),
|
||||
children: [
|
||||
GetPage(
|
||||
name: '/info',
|
||||
page: () => Container(),
|
||||
children: [
|
||||
GetPage(
|
||||
name: '/private',
|
||||
page: () => Container(),
|
||||
),
|
||||
GetPage(
|
||||
name: '/group',
|
||||
page: () => Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
24
lib/routes/user_routes.dart
Normal file
24
lib/routes/user_routes.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:chat/middleware/auth_middleware.dart';
|
||||
import 'package:chat/views/contact/index/index_page.dart';
|
||||
import 'package:chat/views/user/qr_code/index_page.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
abstract class UserRoutes {
|
||||
/// 身份验证页面
|
||||
static const String index = '/user';
|
||||
static const String qrCode = '/user/qrCode';
|
||||
|
||||
static GetPage router = GetPage(
|
||||
name: UserRoutes.index,
|
||||
middlewares: [
|
||||
EnsureAuthMiddleware(),
|
||||
],
|
||||
page: () => const ContactPage(),
|
||||
children: [
|
||||
GetPage(
|
||||
name: '/qrCode',
|
||||
page: () => const UserQrCodePage(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:chat/routes/auth_routes.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:tencent_im_sdk_plugin/tencent_im_sdk_plugin.dart';
|
||||
|
||||
class AuthService extends GetxService {
|
||||
static AuthService get to => Get.find<AuthService>();
|
||||
@@ -12,19 +14,24 @@ class AuthService extends GetxService {
|
||||
/// 登录状态记录,可监听的,这样ever才能监听到
|
||||
final RxBool isLogin = false.obs;
|
||||
|
||||
/// 登录的token,供请求时调用,载入内存,是为了每次使用的时候,不需要从磁盘获取
|
||||
late String userToken = '';
|
||||
/// 供请求时调用,载入内存,是为了每次使用的时候,不需要从磁盘获取
|
||||
late String userId = '';
|
||||
late String userSig = '';
|
||||
|
||||
/// 获取存储的token,这个可以做到持久化存储
|
||||
String get _userToken => _box.read('userToken') ?? '';
|
||||
String get _userSig =>
|
||||
_box.read('userSig') ??
|
||||
'eJwtzEELgjAYxvHvsnPIu7VNJ3ToIIKsIAp2ljbrRYylJs3ou2fq8fk98P*Qiz5Hg2tJSlgEZDNvtO7RY4UzixU7W5feoyUp5QAxVVzR5XFvj62bXAjBAGDRHpu-SSnZlseKrxW8TU1p9sV4v3YJoyFrDrV*QYu5yrQ*2cqUzyHkPpixOMbJjnx-EqUv9A__';
|
||||
String get _userId => _box.read('userId') ?? '5';
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
if (_userToken.isNotEmpty) {
|
||||
if (_userSig.isNotEmpty) {
|
||||
isLogin.value = true;
|
||||
userToken = _userToken;
|
||||
userSig = _userSig;
|
||||
userId = _userId;
|
||||
}
|
||||
|
||||
// ever(_isLogin, (_) {
|
||||
@@ -37,10 +44,21 @@ class AuthService extends GetxService {
|
||||
}
|
||||
|
||||
Future<bool> login(String address) async {
|
||||
_box.write('userToken', address);
|
||||
userToken = address;
|
||||
_box.write('userId', '5');
|
||||
userId = '5';
|
||||
isLogin.value = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 退出登录
|
||||
void logout() async {
|
||||
await TencentImSDKPlugin.v2TIMManager.logout();
|
||||
_box.remove('userSig');
|
||||
_box.remove('userId');
|
||||
userSig = '';
|
||||
userId = '';
|
||||
isLogin.value = false;
|
||||
Get.offAllNamed(AuthRoutes.index);
|
||||
}
|
||||
}
|
||||
|
||||
50
lib/services/tim/apply_service.dart
Normal file
50
lib/services/tim/apply_service.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:chat/services/tim_service.dart';
|
||||
import 'package:chat/utils/ui_tools.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/friend_application_type_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/friend_response_type_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/manager/v2_tim_friendship_manager.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_application.dart';
|
||||
|
||||
class TimApplyService extends GetxService {
|
||||
static TimApplyService get to => Get.find<TimApplyService>();
|
||||
|
||||
/// 好友申请
|
||||
RxList<V2TimFriendApplication?> applies =
|
||||
List<V2TimFriendApplication?>.empty(growable: true).obs;
|
||||
|
||||
/// 好友关系
|
||||
V2TIMFriendshipManager get friendshipManager =>
|
||||
TimService.to.instance.v2TIMFriendshipManager;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
await fetchList();
|
||||
}
|
||||
|
||||
/// 获取申请列表
|
||||
Future<void> fetchList() async {
|
||||
var applyList = await friendshipManager.getFriendApplicationList();
|
||||
if (applyList.code == 0) {
|
||||
applies.value = applyList.data!.friendApplicationList!;
|
||||
}
|
||||
}
|
||||
|
||||
/// 接受好友请求
|
||||
Future<bool> accept(String userID) async {
|
||||
var result = await friendshipManager.acceptFriendApplication(
|
||||
responseType: FriendResponseTypeEnum.V2TIM_FRIEND_ACCEPT_AGREE_AND_ADD,
|
||||
type: FriendApplicationTypeEnum.V2TIM_FRIEND_APPLICATION_COME_IN,
|
||||
userID: userID,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
await fetchList();
|
||||
return true;
|
||||
} else {
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
lib/services/tim/block_service.dart
Normal file
56
lib/services/tim/block_service.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
import 'package:chat/services/tim_service.dart';
|
||||
import 'package:chat/utils/ui_tools.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/manager/v2_tim_friendship_manager.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info.dart';
|
||||
|
||||
class TimBlockService extends GetxService {
|
||||
static TimBlockService get to => Get.find<TimBlockService>();
|
||||
|
||||
/// 好友关系
|
||||
V2TIMFriendshipManager get friendshipManager =>
|
||||
TimService.to.instance.v2TIMFriendshipManager;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
await fetchList();
|
||||
}
|
||||
|
||||
/// 黑名单列表
|
||||
RxList<V2TimFriendInfo> blocks =
|
||||
List<V2TimFriendInfo>.empty(growable: true).obs;
|
||||
|
||||
/// 拉取黑名单列表
|
||||
Future<void> fetchList() async {
|
||||
var blacklist = await friendshipManager.getBlackList();
|
||||
if (blacklist.code == 0) {
|
||||
blocks.value = blacklist.data!;
|
||||
}
|
||||
}
|
||||
|
||||
/// 拉黑某人
|
||||
Future<bool> add(String userID) async {
|
||||
var result = await friendshipManager.addToBlackList(userIDList: [userID]);
|
||||
|
||||
if (result.code == 0) {
|
||||
return result.data!.first.resultCode == 0;
|
||||
} else {
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 解除拉黑
|
||||
Future<bool> remove(String userID) async {
|
||||
var result =
|
||||
await friendshipManager.deleteFromBlackList(userIDList: [userID]);
|
||||
|
||||
if (result.code == 0) {
|
||||
return result.data!.first.resultCode == 0;
|
||||
} else {
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
379
lib/services/tim/conversation_service.dart
Normal file
379
lib/services/tim/conversation_service.dart
Normal file
@@ -0,0 +1,379 @@
|
||||
import 'package:chat/models/im/custom_message_model.dart';
|
||||
import 'package:chat/models/im/location_model.dart';
|
||||
import 'package:chat/services/tim_service.dart';
|
||||
import 'package:chat/utils/ui_tools.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/V2TimConversationListener.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/conversation_type.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/receive_message_opt_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/manager/v2_tim_conversation_manager.dart';
|
||||
import 'package:tencent_im_sdk_plugin/manager/v2_tim_message_manager.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_callback.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_msg_create_info_result.dart';
|
||||
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
||||
|
||||
class TimConversationService extends GetxService {
|
||||
static TimConversationService get to => Get.find<TimConversationService>();
|
||||
|
||||
/// 消息管理实例
|
||||
V2TIMMessageManager get messageManager =>
|
||||
TimService.to.instance.v2TIMMessageManager;
|
||||
|
||||
/// 会话管理
|
||||
V2TIMConversationManager get conversationManager =>
|
||||
TimService.to.instance.v2ConversationManager;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
await fetchList();
|
||||
_addListener();
|
||||
}
|
||||
|
||||
_addListener() {
|
||||
conversationManager.addConversationListener(
|
||||
listener: V2TimConversationListener(
|
||||
|
||||
/// 未读消息总数监听
|
||||
onTotalUnreadMessageCountChanged: (_) {
|
||||
unreadCount.value = _;
|
||||
}));
|
||||
}
|
||||
|
||||
/// 会话列表
|
||||
RxList<V2TimConversation?> conversationList =
|
||||
List<V2TimConversation?>.empty(growable: true).obs;
|
||||
|
||||
/// 未读消息总数
|
||||
var unreadCount = 0.obs;
|
||||
|
||||
Future<void> fetchList() async {
|
||||
var data = await conversationManager.getConversationList(
|
||||
count: 100,
|
||||
nextSeq: '0',
|
||||
);
|
||||
|
||||
if (data.code == 0) {
|
||||
conversationList.value = data.data!.conversationList!;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取未读消息数
|
||||
Future<void> getUnreadCount() async {
|
||||
var result = await conversationManager.getTotalUnreadMessageCount();
|
||||
unreadCount.value = result.data ?? 0;
|
||||
}
|
||||
|
||||
/// 获取会话信息
|
||||
Future<V2TimConversation> getById(
|
||||
String conversationID,
|
||||
) async {
|
||||
var result = await conversationManager.getConversation(
|
||||
conversationID: conversationID,
|
||||
);
|
||||
return result.data!;
|
||||
}
|
||||
|
||||
/// 标记会话已读
|
||||
Future<void> markAsRead(V2TimConversation conversation) async {
|
||||
/// 标记会话内消息已读
|
||||
if (conversation.type == ConversationType.V2TIM_GROUP) {
|
||||
await messageManager.markGroupMessageAsRead(
|
||||
groupID: conversation.groupID!,
|
||||
);
|
||||
} else {
|
||||
await messageManager.markC2CMessageAsRead(
|
||||
userID: conversation.userID!,
|
||||
);
|
||||
}
|
||||
fetchList();
|
||||
}
|
||||
|
||||
/// 从会话列表移除会话
|
||||
Future<void> delete(V2TimConversation conversation) async {
|
||||
await deleteById(conversation.conversationID);
|
||||
fetchList();
|
||||
}
|
||||
|
||||
Future<void> deleteById(String conversationID) async {
|
||||
await conversationManager.deleteConversation(
|
||||
conversationID: conversationID,
|
||||
);
|
||||
fetchList();
|
||||
}
|
||||
|
||||
/// 清空会话历史消息
|
||||
Future<void> clearHistoryMessage(V2TimConversation conversation) async {
|
||||
if (conversation.type == ConversationType.V2TIM_GROUP) {
|
||||
await messageManager.clearGroupHistoryMessage(
|
||||
groupID: conversation.groupID!,
|
||||
);
|
||||
} else {
|
||||
await messageManager.clearC2CHistoryMessage(
|
||||
userID: conversation.userID!,
|
||||
);
|
||||
}
|
||||
fetchList();
|
||||
}
|
||||
|
||||
/// 设置会话置顶/取消置顶
|
||||
Future<bool> setOnTop(V2TimConversation conversation) async {
|
||||
var result = await conversationManager.pinConversation(
|
||||
conversationID: conversation.conversationID,
|
||||
isPinned: !conversation.isPinned!,
|
||||
);
|
||||
fetchList();
|
||||
|
||||
if (result.code != 0) {
|
||||
UiTools.toast(result.desc);
|
||||
}
|
||||
|
||||
return result.code == 0;
|
||||
}
|
||||
|
||||
/// 开启/关闭消息免打扰
|
||||
Future<bool> setReceiveOpt(
|
||||
V2TimConversation conversation,
|
||||
) async {
|
||||
V2TimCallback result;
|
||||
|
||||
if (conversation.type == ConversationType.V2TIM_GROUP) {
|
||||
result = await messageManager.setGroupReceiveMessageOpt(
|
||||
groupID: conversation.groupID!,
|
||||
opt: (conversation.recvOpt == 0)
|
||||
? ReceiveMsgOptEnum.V2TIM_NOT_RECEIVE_MESSAGE
|
||||
: ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE,
|
||||
);
|
||||
} else {
|
||||
result = await messageManager.setC2CReceiveMessageOpt(
|
||||
userIDList: [
|
||||
conversation.userID!,
|
||||
],
|
||||
opt: (conversation.recvOpt == 0)
|
||||
? ReceiveMsgOptEnum.V2TIM_NOT_RECEIVE_MESSAGE
|
||||
: ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE,
|
||||
);
|
||||
}
|
||||
|
||||
fetchList();
|
||||
|
||||
return result.code == 0;
|
||||
}
|
||||
|
||||
/// 发送消息
|
||||
Future<bool> sendTextMessage(
|
||||
V2TimConversation conversation,
|
||||
String text,
|
||||
) async {
|
||||
var msg = await messageManager.createTextAtMessage(
|
||||
text: text,
|
||||
atUserList: [],
|
||||
);
|
||||
if (msg.code == 0) {
|
||||
return await _sendMessage(conversation, msg.data!);
|
||||
} else {
|
||||
UiTools.toast(msg.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送图片消息
|
||||
Future<bool> sendImageMessage(
|
||||
V2TimConversation conversation,
|
||||
AssetEntity asset,
|
||||
) async {
|
||||
var msg = await messageManager.createImageMessage(
|
||||
imagePath: (await asset.file)!.path,
|
||||
);
|
||||
if (msg.code == 0) {
|
||||
return await _sendMessage(conversation, msg.data!);
|
||||
} else {
|
||||
UiTools.toast(msg.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送语音消息
|
||||
Future<bool> sendSoundMessage(
|
||||
V2TimConversation conversation,
|
||||
String soundPath,
|
||||
int duration,
|
||||
) async {
|
||||
var msg = await messageManager.createSoundMessage(
|
||||
soundPath: soundPath, duration: duration);
|
||||
if (msg.code == 0) {
|
||||
return await _sendMessage(conversation, msg.data!);
|
||||
} else {
|
||||
UiTools.toast(msg.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送视频消息
|
||||
Future<bool> sendVideoMessage(
|
||||
V2TimConversation conversation,
|
||||
AssetEntity asset,
|
||||
) async {
|
||||
return false;
|
||||
// final originFile = await asset.originFile;
|
||||
// var size = await originFile!.length();
|
||||
|
||||
// if (size >= 104857600) {
|
||||
// UiTools.toast('视频文件不能超过100M');
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// final duration = asset.videoDuration.inSeconds;
|
||||
|
||||
// String tempPath = (await getTemporaryDirectory()).path;
|
||||
// String? thumbnail = await VideoThumbnail.thumbnailFile(
|
||||
// video: originFile.path,
|
||||
// thumbnailPath: tempPath,
|
||||
// imageFormat: ImageFormat.JPEG,
|
||||
// maxWidth: 256,
|
||||
// quality: 25,
|
||||
// );
|
||||
|
||||
// var msg = await messageManager.createVideoMessage(
|
||||
// videoFilePath: originFile.path,
|
||||
// type: asset.mimeType!.replaceFirst('video/', ''),
|
||||
// duration: duration,
|
||||
// snapshotPath: thumbnail ?? '',
|
||||
// );
|
||||
// if (msg.code == 0) {
|
||||
// return await _sendMessage(conversation, msg.data!);
|
||||
// } else {
|
||||
// UiTools.toast(msg.desc);
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
/// 发送文件消息
|
||||
Future<bool> sendFileMessage(
|
||||
V2TimConversation conversation,
|
||||
String fileName,
|
||||
String filePath,
|
||||
) async {
|
||||
var msg = await messageManager.createFileMessage(
|
||||
fileName: fileName,
|
||||
filePath: filePath,
|
||||
);
|
||||
if (msg.code == 0) {
|
||||
return await _sendMessage(conversation, msg.data!);
|
||||
} else {
|
||||
UiTools.toast(msg.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送位置消息
|
||||
Future<bool> sendLocationMessage(
|
||||
V2TimConversation conversation,
|
||||
LocationModel messageModel,
|
||||
) async {
|
||||
var msg = await messageManager.createLocationMessage(
|
||||
desc: messageModel.name,
|
||||
latitude: messageModel.latitude,
|
||||
longitude: messageModel.longitude,
|
||||
);
|
||||
if (msg.code == 0) {
|
||||
return await _sendMessage(conversation, msg.data!);
|
||||
} else {
|
||||
UiTools.toast(msg.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送表情消息
|
||||
Future<bool> sendFaceMessage(
|
||||
V2TimConversation conversation,
|
||||
) async {
|
||||
var msg = await messageManager.createFaceMessage(
|
||||
data: '',
|
||||
index: 0,
|
||||
);
|
||||
if (msg.code == 0) {
|
||||
return await _sendMessage(conversation, msg.data!);
|
||||
} else {
|
||||
UiTools.toast(msg.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送聊天记录消息
|
||||
Future<bool> sendMergerMessage(
|
||||
V2TimConversation conversation,
|
||||
) async {
|
||||
var msg = await messageManager.createMergerMessage(
|
||||
abstractList: [],
|
||||
compatibleText: '',
|
||||
msgIDList: [],
|
||||
title: '',
|
||||
);
|
||||
if (msg.code == 0) {
|
||||
return await _sendMessage(conversation, msg.data!);
|
||||
} else {
|
||||
UiTools.toast(msg.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送自定义消息
|
||||
Future<bool> sendCustomMessage(
|
||||
V2TimConversation conversation,
|
||||
CustomMessageModel customMessageModel,
|
||||
String desc,
|
||||
) async {
|
||||
var msg = await messageManager.createCustomMessage(
|
||||
data: customMessageModel.toJson(),
|
||||
desc: desc,
|
||||
);
|
||||
if (msg.code == 0) {
|
||||
return await _sendMessage(conversation, msg.data!);
|
||||
} else {
|
||||
UiTools.toast(msg.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _sendMessage(
|
||||
V2TimConversation conversation,
|
||||
V2TimMsgCreateInfoResult result,
|
||||
) async {
|
||||
var sendMessageRes = await messageManager.sendMessage(
|
||||
id: result.id!,
|
||||
receiver: conversation.type == ConversationType.V2TIM_C2C
|
||||
? conversation.userID!
|
||||
: '',
|
||||
groupID: conversation.type == ConversationType.V2TIM_GROUP
|
||||
? conversation.groupID!
|
||||
: '',
|
||||
// isExcludedFromUnreadCount: true,
|
||||
// isExcludedFromLastMessage: true,
|
||||
// needReadReceipt: true,
|
||||
);
|
||||
|
||||
if (sendMessageRes.code == 0) {
|
||||
// TimMessageService.to
|
||||
// .add(conversation.conversationID, result.messageInfo!);
|
||||
// eventBus.fire(result.messageInfo!);
|
||||
fetchList();
|
||||
return true;
|
||||
} else {
|
||||
UiTools.toast(sendMessageRes.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置会话草稿
|
||||
Future<void> draft(
|
||||
V2TimConversation conversation, {
|
||||
String? draftText = "",
|
||||
}) async {
|
||||
await conversationManager.setConversationDraft(
|
||||
conversationID: conversation.conversationID,
|
||||
draftText: draftText,
|
||||
);
|
||||
}
|
||||
}
|
||||
241
lib/services/tim/friend_service.dart
Normal file
241
lib/services/tim/friend_service.dart
Normal file
@@ -0,0 +1,241 @@
|
||||
import 'package:azlistview/azlistview.dart';
|
||||
import 'package:chat/models/im/contact_info_model.dart';
|
||||
import 'package:chat/models/im/search_user_model.dart';
|
||||
import 'package:chat/services/tim_service.dart';
|
||||
import 'package:chat/utils/im_tools.dart';
|
||||
import 'package:chat/utils/request/http.dart';
|
||||
import 'package:chat/utils/ui_tools.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/friend_type_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/manager/v2_tim_friendship_manager.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_check_result.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info_result.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_user_full_info.dart';
|
||||
|
||||
class TimFriendService extends GetxService {
|
||||
static TimFriendService get to => Get.find<TimFriendService>();
|
||||
|
||||
/// 好友关系
|
||||
V2TIMFriendshipManager get friendshipManager =>
|
||||
TimService.to.instance.v2TIMFriendshipManager;
|
||||
|
||||
/// 好友列表
|
||||
var friends = List<V2TimFriendInfo>.empty(growable: true).obs;
|
||||
|
||||
/// 格式化后的联系人信息
|
||||
var contacts = List<ContactInfoModel>.empty(growable: true).obs;
|
||||
|
||||
Future<void> fetchList() async {
|
||||
var result = await friendshipManager.getFriendList();
|
||||
if (result.code == 0) {
|
||||
friends.value = result.data!;
|
||||
contacts.clear();
|
||||
for (var element in result.data!) {
|
||||
String name = ImTools.parseNicknameFromInfo(element);
|
||||
String pinyin = PinyinHelper.getPinyinE(name);
|
||||
String tag = pinyin.substring(0, 1).toUpperCase();
|
||||
|
||||
if (!RegExp('[A-Z]').hasMatch(tag)) {
|
||||
tag = '#';
|
||||
}
|
||||
|
||||
contacts.add(ContactInfoModel(
|
||||
name: name,
|
||||
userID: element.userID,
|
||||
tagIndex: tag,
|
||||
namePinyin: pinyin,
|
||||
friendInfo: element,
|
||||
));
|
||||
}
|
||||
|
||||
SuspensionUtil.sortListBySuspensionTag(contacts);
|
||||
SuspensionUtil.setShowSuspensionStatus(contacts);
|
||||
}
|
||||
}
|
||||
|
||||
/// 添加好友
|
||||
Future<bool> add(
|
||||
String userID, {
|
||||
String? remark,
|
||||
String? addWording,
|
||||
String? addSource,
|
||||
}) async {
|
||||
var result = await friendshipManager.addFriend(
|
||||
userID: userID,
|
||||
remark: remark,
|
||||
addWording: addWording,
|
||||
addSource: addSource,
|
||||
addType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH,
|
||||
);
|
||||
if (result.code == 0) {
|
||||
if (result.data!.resultCode == 0) {
|
||||
fetchList();
|
||||
return true;
|
||||
} else if (result.data!.resultCode == 30539) {
|
||||
return true;
|
||||
} else if (result.data!.resultCode == 30010) {
|
||||
UiTools.toast('好友数量已达上限');
|
||||
return false;
|
||||
} else {
|
||||
UiTools.toast(
|
||||
result.data!.resultInfo! + result.data!.resultCode.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 删除双向好友
|
||||
Future<bool> delete(String userID) async {
|
||||
var result = await friendshipManager.deleteFromFriendList(
|
||||
deleteType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH,
|
||||
userIDList: [userID],
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
fetchList();
|
||||
return result.data!.first.resultCode == 0;
|
||||
} else {
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 检测好友是否有双向(单向)好友关系。
|
||||
Future<V2TimFriendCheckResult?> check(String userID) async {
|
||||
var result = await friendshipManager.checkFriend(
|
||||
checkType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH,
|
||||
userIDList: [userID],
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return result.data!.first;
|
||||
} else {
|
||||
UiTools.toast(result.desc);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取用户资料
|
||||
Future<V2TimUserFullInfo?> userInfo(String userID) async {
|
||||
var result = await TimService.to.instance.getUsersInfo(
|
||||
userIDList: [userID],
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return result.data!.first;
|
||||
}
|
||||
|
||||
UiTools.toast(result.desc);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 获取好友资料
|
||||
Future<V2TimFriendInfoResult?> friendInfo(String userID) async {
|
||||
var result = await friendshipManager.getFriendsInfo(userIDList: [userID]);
|
||||
|
||||
if (result.code == 0) {
|
||||
if (result.data!.isNotEmpty) {
|
||||
return result.data!.first;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
UiTools.toast(result.desc);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 修改个人资料
|
||||
Future<bool> setSelfInfo({
|
||||
String? nickname,
|
||||
String? avatar,
|
||||
}) async {
|
||||
var result = await TimService.to.instance.setSelfInfo(
|
||||
userFullInfo: V2TimUserFullInfo(
|
||||
nickName: nickname,
|
||||
faceUrl: avatar,
|
||||
),
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
// TimService.to.fetchSelfInfo();
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 设置是否允许陌生人消息
|
||||
Future<bool> allowStrangerMessage() async {
|
||||
var result = await TimService.to.instance.setSelfInfo(
|
||||
userFullInfo: V2TimUserFullInfo(
|
||||
customInfo: {
|
||||
'Stranger': 'TRUE',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 禁止陌生人消息
|
||||
Future<bool> forbidStrangerMessage() async {
|
||||
var result = await TimService.to.instance.setSelfInfo(
|
||||
userFullInfo: V2TimUserFullInfo(
|
||||
customInfo: {
|
||||
'Stranger': '',
|
||||
},
|
||||
),
|
||||
);
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 修改好友备注信息
|
||||
Future<bool> setFriendRemark(
|
||||
String userID,
|
||||
String friendRemark,
|
||||
) async {
|
||||
var result = await friendshipManager.setFriendInfo(
|
||||
userID: userID,
|
||||
friendRemark: friendRemark,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 从服务器查找用户
|
||||
Future<List<SearchUserModel>?> searchUser(String keyword) async {
|
||||
try {
|
||||
var json = await Http.get(
|
||||
'user/search',
|
||||
params: {
|
||||
'keyword': keyword,
|
||||
},
|
||||
);
|
||||
|
||||
return List<SearchUserModel>.from(
|
||||
json.map(
|
||||
(x) => SearchUserModel.fromJson(x),
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
UiTools.toast(err.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
403
lib/services/tim/group_service.dart
Normal file
403
lib/services/tim/group_service.dart
Normal file
@@ -0,0 +1,403 @@
|
||||
import 'package:chat/services/tim/conversation_service.dart';
|
||||
import 'package:chat/services/tim_service.dart';
|
||||
import 'package:chat/utils/ui_tools.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_add_opt_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_application_type_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_member_filter_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_member_role_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_type.dart';
|
||||
import 'package:tencent_im_sdk_plugin/manager/v2_tim_group_manager.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_application.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member_full_info.dart';
|
||||
|
||||
class TimGroupService extends GetxService {
|
||||
static TimGroupService get to => Get.find<TimGroupService>();
|
||||
|
||||
/// 群管理实例
|
||||
V2TIMGroupManager get groupManager =>
|
||||
TimService.to.instance.v2TIMGroupManager;
|
||||
|
||||
/// 我的群组列表
|
||||
RxList<V2TimGroupInfo> groups =
|
||||
List<V2TimGroupInfo>.empty(growable: true).obs;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
await fetchList();
|
||||
}
|
||||
|
||||
Future<void> fetchList() async {
|
||||
var result = await groupManager.getJoinedGroupList();
|
||||
|
||||
if (result.code == 0) {
|
||||
groups.value = result.data!;
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建群
|
||||
Future<String?> create(
|
||||
String groupName,
|
||||
List<V2TimFriendInfo> memberList, {
|
||||
String? groupType,
|
||||
}) async {
|
||||
var result = await groupManager.createGroup(
|
||||
groupType: groupType ?? GroupType.Public,
|
||||
groupName: groupName,
|
||||
notification: '',
|
||||
introduction: '',
|
||||
faceUrl: '',
|
||||
isAllMuted: false,
|
||||
isSupportTopic: false,
|
||||
addOpt: GroupAddOptTypeEnum.V2TIM_GROUP_ADD_AUTH,
|
||||
memberList: memberList.map((e) {
|
||||
return V2TimGroupMember(
|
||||
role: GroupMemberRoleTypeEnum.V2TIM_GROUP_MEMBER_ROLE_MEMBER,
|
||||
userID: e.userID,
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
|
||||
if (result.code != 0) {
|
||||
UiTools.toast(result.desc);
|
||||
}
|
||||
|
||||
await fetchList();
|
||||
await TimConversationService.to.fetchList();
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
/// 加群申请列表
|
||||
Future<List<V2TimGroupApplication?>?> applies(String groupID) async {
|
||||
var result = await groupManager.getGroupApplicationList();
|
||||
|
||||
if (result.code == 0) {
|
||||
return result.data?.groupApplicationList;
|
||||
} else {
|
||||
UiTools.toast(result.desc);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 同意加群
|
||||
Future<void> accept() async {
|
||||
await groupManager.acceptGroupApplication(
|
||||
fromUser: '',
|
||||
groupID: '',
|
||||
toUser: '',
|
||||
);
|
||||
}
|
||||
|
||||
/// 拒绝加群请求
|
||||
Future<void> refuse() async {
|
||||
await groupManager.refuseGroupApplication(
|
||||
addTime: 0,
|
||||
fromUser: '',
|
||||
groupID: '',
|
||||
toUser: '',
|
||||
type: GroupApplicationTypeEnum.V2TIM_GROUP_APPLICATION_GET_TYPE_JOIN,
|
||||
);
|
||||
}
|
||||
|
||||
/// 加入群组
|
||||
Future<bool> join(
|
||||
V2TimGroupInfo group,
|
||||
String message,
|
||||
) async {
|
||||
var result = await TimService.to.instance.joinGroup(
|
||||
groupID: group.groupID,
|
||||
groupType: group.groupType,
|
||||
message: message,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
await fetchList();
|
||||
return true;
|
||||
} else {
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 邀请加群
|
||||
Future<bool> invite(
|
||||
V2TimGroupInfo group,
|
||||
List<String> userList,
|
||||
) async {
|
||||
var result = await groupManager.inviteUserToGroup(
|
||||
groupID: group.groupID,
|
||||
userList: userList,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
} else {
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 退出群组
|
||||
Future<void> quit(
|
||||
V2TimGroupInfo group,
|
||||
) async {
|
||||
await TimService.to.instance.quitGroup(
|
||||
groupID: group.groupID,
|
||||
);
|
||||
await fetchList();
|
||||
await TimConversationService.to.deleteById(
|
||||
'group_' + group.groupID,
|
||||
);
|
||||
}
|
||||
|
||||
/// 解散群组
|
||||
Future<bool> dismiss(
|
||||
V2TimGroupInfo group,
|
||||
V2TimConversation conversation,
|
||||
) async {
|
||||
var result = await TimService.to.instance.dismissGroup(
|
||||
groupID: group.groupID,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 获取群资料
|
||||
Future<V2TimGroupInfo?> info(String groupId) async {
|
||||
var result = await groupManager.getGroupsInfo(groupIDList: [groupId]);
|
||||
|
||||
if (result.code == 0) {
|
||||
return result.data![0].groupInfo!;
|
||||
} else {
|
||||
UiTools.toast(result.desc);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 更新群资料
|
||||
Future<bool> updateName(
|
||||
V2TimGroupInfo group,
|
||||
String groupName,
|
||||
) async {
|
||||
var result = await groupManager.setGroupInfo(
|
||||
info: V2TimGroupInfo.fromJson(
|
||||
{
|
||||
'groupID': group.groupID,
|
||||
'groupType': group.groupType,
|
||||
'groupName': groupName,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 更新群公告
|
||||
Future<bool> updateNotification(
|
||||
V2TimGroupInfo group,
|
||||
String notification,
|
||||
) async {
|
||||
var result = await groupManager.setGroupInfo(
|
||||
info: V2TimGroupInfo.fromJson(
|
||||
{
|
||||
'groupID': group.groupID,
|
||||
'groupType': group.groupType,
|
||||
'notification': notification,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
await TimConversationService.to.fetchList();
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 修改加群方式
|
||||
Future<bool> updateAddOpt(
|
||||
V2TimGroupInfo group,
|
||||
String notification,
|
||||
) async {
|
||||
var result = await groupManager.setGroupInfo(
|
||||
info: V2TimGroupInfo.fromJson(
|
||||
{
|
||||
'groupID': group.groupID,
|
||||
'groupType': group.groupType,
|
||||
'groupAddOpt': group.groupAddOpt,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
await TimConversationService.to.fetchList();
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 获取群组成员
|
||||
Future<List<V2TimGroupMemberFullInfo?>?> members(
|
||||
String groupID, {
|
||||
int count = 10,
|
||||
GroupMemberFilterTypeEnum filter =
|
||||
GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_ALL,
|
||||
}) async {
|
||||
var result = await groupManager.getGroupMemberList(
|
||||
count: count,
|
||||
filter: filter,
|
||||
nextSeq: '0',
|
||||
offset: 0,
|
||||
groupID: groupID,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return result.data!.memberInfoList!;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 获取群成员资料
|
||||
Future<V2TimGroupMemberFullInfo?> getMemberInfo(
|
||||
V2TimGroupInfo group,
|
||||
String userID,
|
||||
) async {
|
||||
var result = await groupManager.getGroupMembersInfo(
|
||||
groupID: group.groupID,
|
||||
memberList: [userID],
|
||||
);
|
||||
|
||||
if (result.data != null) {
|
||||
return result.data!.first;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置群成员资料
|
||||
Future<bool> setMemberInfo(
|
||||
V2TimGroupInfo group,
|
||||
String userID,
|
||||
String nameCard,
|
||||
) async {
|
||||
var result = await groupManager.setGroupMemberInfo(
|
||||
groupID: group.groupID,
|
||||
userID: userID,
|
||||
nameCard: nameCard,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 禁言群成员, 时间单位秒,如果是0,应该就解除禁言了
|
||||
Future<bool> muteMember(
|
||||
V2TimGroupInfo group,
|
||||
String userID, {
|
||||
int seconds = 60,
|
||||
}) async {
|
||||
var result = await groupManager.muteGroupMember(
|
||||
groupID: group.groupID,
|
||||
userID: userID,
|
||||
seconds: seconds,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 全员禁言/解除全员禁言
|
||||
Future<bool> mute(
|
||||
V2TimGroupInfo group,
|
||||
bool isAllMuted,
|
||||
) async {
|
||||
var result = await groupManager.setGroupInfo(
|
||||
info: V2TimGroupInfo(
|
||||
isAllMuted: isAllMuted,
|
||||
groupID: group.groupID,
|
||||
groupType: group.groupType,
|
||||
),
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 踢人
|
||||
Future<bool> kickMember(
|
||||
V2TimGroupInfo group,
|
||||
List<String> memberList,
|
||||
) async {
|
||||
var result = await groupManager.kickGroupMember(
|
||||
groupID: group.groupID,
|
||||
memberList: memberList,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 设置管理员
|
||||
Future<bool> setMemberRole(
|
||||
V2TimGroupInfo group,
|
||||
String userID,
|
||||
GroupMemberRoleTypeEnum role,
|
||||
) async {
|
||||
var result = await groupManager.setGroupMemberRole(
|
||||
groupID: group.groupID,
|
||||
userID: userID,
|
||||
role: role,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 转让群主
|
||||
Future<bool> transfer(
|
||||
V2TimGroupInfo group,
|
||||
String userID,
|
||||
) async {
|
||||
var result = await groupManager.transferGroupOwner(
|
||||
groupID: group.groupID,
|
||||
userID: userID,
|
||||
);
|
||||
|
||||
if (result.code == 0) {
|
||||
return true;
|
||||
}
|
||||
UiTools.toast(result.desc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
77
lib/services/tim/message_service.dart
Normal file
77
lib/services/tim/message_service.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/conversation_type.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_value_callback.dart';
|
||||
import 'conversation_service.dart';
|
||||
|
||||
class LoadMessageResult {
|
||||
String? lastMsgId;
|
||||
bool noMore;
|
||||
|
||||
LoadMessageResult({
|
||||
this.lastMsgId,
|
||||
required this.noMore,
|
||||
});
|
||||
}
|
||||
|
||||
class TimMessageService extends GetxService {
|
||||
static TimMessageService get to => Get.find<TimMessageService>();
|
||||
|
||||
RxList<V2TimMessage> messages = List<V2TimMessage>.empty(growable: true).obs;
|
||||
|
||||
var curConversationId = '';
|
||||
|
||||
Future<String?> loadMessagesFromService(
|
||||
V2TimConversation conversation,
|
||||
String? lastMsgId,
|
||||
AutoScrollController _scrollController,
|
||||
) async {
|
||||
if (lastMsgId == null && curConversationId != conversation.conversationID) {
|
||||
messages.clear();
|
||||
curConversationId = conversation.conversationID;
|
||||
}
|
||||
|
||||
V2TimValueCallback<List<V2TimMessage>> result;
|
||||
if (conversation.type == ConversationType.V2TIM_GROUP) {
|
||||
result = await TimConversationService.to.messageManager
|
||||
.getGroupHistoryMessageList(
|
||||
groupID: conversation.groupID!,
|
||||
count: 20,
|
||||
lastMsgID: lastMsgId,
|
||||
);
|
||||
} else {
|
||||
result = await TimConversationService.to.messageManager
|
||||
.getC2CHistoryMessageList(
|
||||
userID: conversation.userID!,
|
||||
count: 20,
|
||||
lastMsgID: lastMsgId,
|
||||
);
|
||||
}
|
||||
var list = result.data ?? [];
|
||||
if (lastMsgId == null) {
|
||||
messages.value = list.reversed.toList();
|
||||
_scrollController.scrollToIndex(
|
||||
messages.length - 1,
|
||||
preferPosition: AutoScrollPosition.begin,
|
||||
);
|
||||
} else {
|
||||
//加载更多
|
||||
if (list.isNotEmpty) {
|
||||
messages.value = messages.reversed.toList(); //正常顺序
|
||||
messages.addAll(list);
|
||||
messages.value = messages.reversed.toList(); //反转一次
|
||||
}
|
||||
}
|
||||
if (messages.isNotEmpty) {
|
||||
return messages.first.msgID;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
addMessage(V2TimMessage message) {
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,395 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:chat/controllers/group_controller.dart';
|
||||
import 'package:chat/models/im/custom_message_model.dart';
|
||||
import 'package:chat/services/auth_service.dart';
|
||||
import 'package:chat/services/tim/apply_service.dart';
|
||||
import 'package:chat/services/tim/block_service.dart';
|
||||
import 'package:chat/services/tim/conversation_service.dart';
|
||||
import 'package:chat/services/tim/friend_service.dart';
|
||||
import 'package:chat/services/tim/group_service.dart';
|
||||
import 'package:chat/services/tim/message_service.dart';
|
||||
import 'package:chat/utils/ui_tools.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/V2TimAdvancedMsgListener.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/V2TimFriendshipListener.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/V2TimGroupListener.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/V2TimSDKListener.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/log_level_enum.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/message_elem_type.dart';
|
||||
import 'package:tencent_im_sdk_plugin/manager/v2_tim_manager.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_application.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_change_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member_change_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_message_receipt.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_topic_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_user_full_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/tencent_im_sdk_plugin.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
|
||||
class TimService extends GetxService {
|
||||
Future<TimService> init() async {
|
||||
return this;
|
||||
static TimService get to => Get.find<TimService>();
|
||||
|
||||
/// 获取实例
|
||||
V2TIMManager get instance => _getInstance();
|
||||
|
||||
/// 获取TIM实例
|
||||
V2TIMManager _getInstance() {
|
||||
return TencentImSDKPlugin.v2TIMManager;
|
||||
}
|
||||
|
||||
int sdkAppID = 1400719491;
|
||||
|
||||
String get _userSig => AuthService.to.userSig;
|
||||
|
||||
String get _userId => AuthService.to.userId;
|
||||
|
||||
RxString currentConversationId = ''.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
if (AuthService.to.isLogin.value) {
|
||||
initSdk();
|
||||
}
|
||||
}
|
||||
|
||||
Future initSdk() async {
|
||||
/// 初始化TIMSDK
|
||||
await instance.initSDK(
|
||||
sdkAppID: sdkAppID,
|
||||
loglevel: LogLevelEnum.V2TIM_LOG_INFO,
|
||||
listener: V2TimSDKListener(
|
||||
onConnectFailed: (
|
||||
int code,
|
||||
String error,
|
||||
) {},
|
||||
onConnectSuccess: () async {},
|
||||
onConnecting: () {},
|
||||
onKickedOffline: () {
|
||||
UiTools.toast('该账号在其他设备登录被迫下线');
|
||||
AuthService.to.logout();
|
||||
},
|
||||
onSelfInfoUpdated: (
|
||||
V2TimUserFullInfo info,
|
||||
) {},
|
||||
onUserSigExpired: () {},
|
||||
),
|
||||
);
|
||||
|
||||
/// 登录
|
||||
await instance.login(
|
||||
userID: _userId,
|
||||
userSig: _userSig,
|
||||
);
|
||||
|
||||
Get.put(TimConversationService());
|
||||
Get.put(TimFriendService());
|
||||
Get.put(TimGroupService());
|
||||
Get.put(TimBlockService());
|
||||
Get.put(TimApplyService());
|
||||
Get.put(TimMessageService());
|
||||
|
||||
/// 消息监听器
|
||||
await TimConversationService.to.messageManager.addAdvancedMsgListener(
|
||||
listener: V2TimAdvancedMsgListener(
|
||||
/// 收到新消息
|
||||
onRecvNewMessage: (
|
||||
V2TimMessage msg,
|
||||
) {
|
||||
onRecvNewMessage(msg);
|
||||
},
|
||||
|
||||
/// 收到C2C已读回执
|
||||
onRecvC2CReadReceipt: (
|
||||
List<V2TimMessageReceipt> receiptList,
|
||||
) {},
|
||||
|
||||
/// 消息撤回
|
||||
onRecvMessageRevoked: (
|
||||
String msgID,
|
||||
) {},
|
||||
|
||||
/// 发送消息进度
|
||||
onSendMessageProgress: (
|
||||
V2TimMessage message,
|
||||
int progress,
|
||||
) {},
|
||||
|
||||
/// 消息更改
|
||||
onRecvMessageModified: (
|
||||
V2TimMessage msg,
|
||||
) {},
|
||||
|
||||
/// 消息已读回执
|
||||
onRecvMessageReadReceipts: (
|
||||
List<V2TimMessageReceipt> receiptList,
|
||||
) {},
|
||||
),
|
||||
);
|
||||
|
||||
TimFriendService.to.friendshipManager.setFriendListener(
|
||||
listener: V2TimFriendshipListener(
|
||||
/// 有新的好友请求
|
||||
onFriendApplicationListAdded: (
|
||||
List<V2TimFriendApplication> applicationList,
|
||||
) {
|
||||
TimApplyService.to.fetchList();
|
||||
},
|
||||
onFriendApplicationListDeleted: (
|
||||
List<String> userIDList,
|
||||
) {
|
||||
TimApplyService.to.fetchList();
|
||||
},
|
||||
onFriendApplicationListRead: () {
|
||||
// UiTools.toast('onFriendApplicationListRead');
|
||||
},
|
||||
onFriendListAdded: (
|
||||
List<V2TimFriendInfo> users,
|
||||
) {
|
||||
TimFriendService.to.fetchList();
|
||||
},
|
||||
onFriendListDeleted: (
|
||||
List<String> userList,
|
||||
) {
|
||||
TimFriendService.to.fetchList();
|
||||
},
|
||||
|
||||
/// 有黑名单
|
||||
onBlackListAdd: (
|
||||
List<V2TimFriendInfo> infoList,
|
||||
) {
|
||||
TimFriendService.to.fetchList();
|
||||
TimBlockService.to.fetchList();
|
||||
},
|
||||
|
||||
/// 移除黑名单
|
||||
onBlackListDeleted: (
|
||||
List<String> userList,
|
||||
) {
|
||||
TimFriendService.to.fetchList();
|
||||
TimBlockService.to.fetchList();
|
||||
},
|
||||
|
||||
/// 好友资料修改
|
||||
onFriendInfoChanged: (
|
||||
List<V2TimFriendInfo> infoList,
|
||||
) {
|
||||
TimFriendService.to.fetchList();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
instance.setGroupListener(
|
||||
listener: V2TimGroupListener(
|
||||
/// 群组解散
|
||||
onGroupDismissed: (
|
||||
String groupID,
|
||||
V2TimGroupMemberInfo opUser,
|
||||
) async {
|
||||
await TimConversationService.to.deleteById(
|
||||
'group_$groupID',
|
||||
);
|
||||
await TimGroupService.to.fetchList();
|
||||
|
||||
/// 如果是在当前的会话中,关闭会话内容,返回主页面
|
||||
if (currentConversationId.value == 'group_' + groupID) {
|
||||
UiTools.toast('群已解散');
|
||||
Navigator.popUntil(Get.context!, (route) => route.isFirst);
|
||||
}
|
||||
},
|
||||
|
||||
/// 群资料修改
|
||||
onGroupInfoChanged: (
|
||||
String groupID,
|
||||
List<V2TimGroupChangeInfo?> changeInfos,
|
||||
) {
|
||||
TimConversationService.to.fetchList();
|
||||
TimGroupService.to.fetchList();
|
||||
},
|
||||
onMemberEnter: (
|
||||
String groupID,
|
||||
List<V2TimGroupMemberInfo> memberList,
|
||||
) {
|
||||
UiTools.toast('onMemberEnter');
|
||||
},
|
||||
onMemberLeave: (
|
||||
String groupID,
|
||||
V2TimGroupMemberInfo member,
|
||||
) {
|
||||
UiTools.toast('onMemberLeave');
|
||||
},
|
||||
onMemberInvited: (
|
||||
String groupID,
|
||||
V2TimGroupMemberInfo opUser,
|
||||
List<V2TimGroupMemberInfo> memberList,
|
||||
) {
|
||||
UiTools.toast('onMemberInvited');
|
||||
},
|
||||
|
||||
/// 有用户被移出群聊,判断是否当前用户,当前会话,关闭会话
|
||||
onMemberKicked: (
|
||||
String groupID,
|
||||
V2TimGroupMemberInfo opUser,
|
||||
List<V2TimGroupMemberInfo> memberList,
|
||||
) {
|
||||
bool isYou = memberList
|
||||
.where((e) => true
|
||||
// e.userID ==
|
||||
// UserController.to.userInfo.value!.userId.toString(),
|
||||
)
|
||||
.isNotEmpty;
|
||||
if (isYou) {
|
||||
if (currentConversationId.value == 'group_' + groupID) {
|
||||
UiTools.toast('您已被${opUser.nickName}移出当前群聊');
|
||||
Navigator.popUntil(Get.context!, (route) => route.isFirst);
|
||||
}
|
||||
TimConversationService.to.deleteById(
|
||||
'group_' + groupID,
|
||||
);
|
||||
TimGroupService.to.fetchList();
|
||||
}
|
||||
},
|
||||
onMemberInfoChanged: (
|
||||
String groupID,
|
||||
List<V2TimGroupMemberChangeInfo> v2TIMGroupMemberChangeInfoList,
|
||||
) {
|
||||
UiTools.toast('onMemberInfoChanged');
|
||||
},
|
||||
onGroupRecycled: (
|
||||
String groupID,
|
||||
V2TimGroupMemberInfo opUser,
|
||||
) {
|
||||
UiTools.toast('onGroupRecycled');
|
||||
},
|
||||
onReceiveJoinApplication: (
|
||||
String groupID,
|
||||
V2TimGroupMemberInfo member,
|
||||
String opReason,
|
||||
) {
|
||||
UiTools.toast('onReceiveJoinApplication');
|
||||
},
|
||||
onApplicationProcessed: (
|
||||
String groupID,
|
||||
V2TimGroupMemberInfo opUser,
|
||||
bool isAgreeJoin,
|
||||
String opReason,
|
||||
) {
|
||||
UiTools.toast('onApplicationProcessed');
|
||||
},
|
||||
|
||||
/// 有新的管理被授权
|
||||
onGrantAdministrator: (
|
||||
String groupID,
|
||||
V2TimGroupMemberInfo opUser,
|
||||
List<V2TimGroupMemberInfo> memberList,
|
||||
) {
|
||||
if (currentConversationId.value == 'group_' + groupID) {
|
||||
GroupController.to.fetchGroupMemberList();
|
||||
}
|
||||
},
|
||||
|
||||
/// 取消管理员
|
||||
onRevokeAdministrator: (
|
||||
String groupID,
|
||||
V2TimGroupMemberInfo opUser,
|
||||
List<V2TimGroupMemberInfo> memberList,
|
||||
) {
|
||||
if (currentConversationId.value == 'group_' + groupID) {
|
||||
GroupController.to.fetchGroupMemberList();
|
||||
}
|
||||
},
|
||||
onQuitFromGroup: (
|
||||
String groupID,
|
||||
) {
|
||||
UiTools.toast('onQuitFromGroup');
|
||||
},
|
||||
onReceiveRESTCustomData: (
|
||||
String groupID,
|
||||
String customData,
|
||||
) {
|
||||
UiTools.toast('onReceiveRESTCustomData');
|
||||
},
|
||||
onGroupAttributeChanged: (
|
||||
String groupID,
|
||||
Map<String, String> groupAttributeMap,
|
||||
) {
|
||||
UiTools.toast('onGroupAttributeChanged');
|
||||
},
|
||||
onTopicCreated: (
|
||||
String groupID,
|
||||
String topicID,
|
||||
) {
|
||||
UiTools.toast('onTopicCreated');
|
||||
},
|
||||
onTopicDeleted: (
|
||||
String groupID,
|
||||
List<String> topicIDList,
|
||||
) {
|
||||
UiTools.toast('onTopicDeleted');
|
||||
},
|
||||
onTopicInfoChanged: (
|
||||
String groupID,
|
||||
V2TimTopicInfo topicInfo,
|
||||
) {
|
||||
UiTools.toast('onTopicInfoChanged');
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 有新消息的事件处理
|
||||
Future<void> onRecvNewMessage(V2TimMessage message) async {
|
||||
/// 过滤自定义消息中,用户输入状态的消息
|
||||
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) {
|
||||
var msgData = json.decode(message.customElem!.data!);
|
||||
|
||||
if (msgData['businessID'] == CustomMessageType.TYPING_STATUS) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var conversation = await TimConversationService.to.getById(
|
||||
getConversationIdByMessage(message),
|
||||
);
|
||||
if (conversation.recvOpt == 0) {
|
||||
/// 振动提醒
|
||||
Vibration.vibrate(duration: 100);
|
||||
}
|
||||
|
||||
if (_isMessageInCurrentConversation(message)) {
|
||||
TimConversationService.to.markAsRead(conversation);
|
||||
} else {
|
||||
await TimConversationService.to.getUnreadCount();
|
||||
}
|
||||
|
||||
/// 更新会话列表做了个延迟,要不然列表中的未读消息数量,不正确
|
||||
Future.delayed(const Duration(milliseconds: 500), () async {
|
||||
await TimConversationService.to.fetchList();
|
||||
});
|
||||
|
||||
// eventBus.fire(message);
|
||||
}
|
||||
|
||||
/// 通过消息判断是否是当前会话
|
||||
bool _isMessageInCurrentConversation(V2TimMessage message) {
|
||||
return getConversationIdByMessage(message) == currentConversationId.value;
|
||||
}
|
||||
|
||||
/// 通过消息获取会话ID
|
||||
String getConversationIdByMessage(V2TimMessage message) {
|
||||
String conId;
|
||||
if (message.groupID != null) {
|
||||
conId = 'group_' + message.groupID!;
|
||||
} else {
|
||||
conId = 'c2c_' + message.userID!;
|
||||
}
|
||||
|
||||
return conId;
|
||||
}
|
||||
}
|
||||
|
||||
30
lib/utils/convert.dart
Normal file
30
lib/utils/convert.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:dart_date/dart_date.dart';
|
||||
|
||||
class Convert {
|
||||
/// 隐藏字符串中间位
|
||||
///
|
||||
/// * [str] 要处理的字符串
|
||||
/// * [start] 字符串从0开始保留位数
|
||||
/// * [end] 末尾保留的长度
|
||||
static String hideCenterStr(
|
||||
String str, {
|
||||
int start = 8,
|
||||
int end = 6,
|
||||
}) {
|
||||
if (str.length <= start + end) {
|
||||
return str;
|
||||
}
|
||||
return '${str.substring(0, start)}...${str.substring(str.length - end)}';
|
||||
}
|
||||
|
||||
/// 时间戳转日期
|
||||
///
|
||||
/// [timestamp] 要转换的时间戳
|
||||
/// [format] 日期格式
|
||||
static String timeFormat(
|
||||
int timestamp, {
|
||||
String format = 'yyyy-MM-dd HH:mm:ss',
|
||||
}) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(timestamp * 1000).format(format);
|
||||
}
|
||||
}
|
||||
251
lib/utils/im_tools.dart
Normal file
251
lib/utils/im_tools.dart
Normal file
@@ -0,0 +1,251 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:azlistview/azlistview.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/models/im/custom_message_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_change_info_type.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_tips_elem_type.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/message_elem_type.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info_result.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
|
||||
|
||||
class ImTools {
|
||||
static String parseNicknameFromResult(V2TimFriendInfoResult? infoResult) {
|
||||
if (infoResult == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (infoResult.friendInfo!.friendRemark != null) {
|
||||
if (infoResult.friendInfo!.friendRemark! == '') {
|
||||
return infoResult.friendInfo!.userProfile!.nickName!;
|
||||
} else {
|
||||
return infoResult.friendInfo!.friendRemark!;
|
||||
}
|
||||
}
|
||||
|
||||
return infoResult.friendInfo!.userProfile!.nickName!;
|
||||
}
|
||||
|
||||
static String parseNicknameFromInfo(V2TimFriendInfo infoResult) {
|
||||
if (infoResult.friendRemark != '') {
|
||||
return infoResult.friendRemark!;
|
||||
}
|
||||
|
||||
return infoResult.userProfile!.nickName!;
|
||||
}
|
||||
|
||||
static parseMessage(V2TimMessage? message) {
|
||||
String text = '';
|
||||
switch (message?.elemType) {
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
|
||||
text = message!.textElem!.text!;
|
||||
break;
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
|
||||
text = '[图片]';
|
||||
break;
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
|
||||
text = '[语音]';
|
||||
break;
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
|
||||
text = '[视频]';
|
||||
break;
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
|
||||
text = '[文件]';
|
||||
break;
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
|
||||
text = '[位置]';
|
||||
break;
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
|
||||
text = '[表情]';
|
||||
break;
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
|
||||
text = '[聊天记录]';
|
||||
break;
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
|
||||
var data = json.decode(message!.customElem!.data!);
|
||||
|
||||
switch (data['businessID']) {
|
||||
case CustomMessageType.CALL:
|
||||
text = '通话';
|
||||
break;
|
||||
case CustomMessageType.DT_TRANSFER:
|
||||
text = '[转账]';
|
||||
break;
|
||||
case CustomMessageType.NAME_CARD:
|
||||
text = '[名片]';
|
||||
break;
|
||||
case CustomMessageType.GROUP_CARD:
|
||||
text = '[群名片]';
|
||||
break;
|
||||
case CustomMessageType.EVALUATION:
|
||||
text = '[评价]';
|
||||
break;
|
||||
default:
|
||||
text = '[${data['businessID']}]';
|
||||
}
|
||||
|
||||
break;
|
||||
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
|
||||
switch (message?.groupTipsElem!.type) {
|
||||
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_JOIN:
|
||||
text = '加入群聊';
|
||||
break;
|
||||
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_INVITE:
|
||||
text = '邀请入群';
|
||||
break;
|
||||
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_QUIT:
|
||||
text = '退出群聊';
|
||||
break;
|
||||
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_KICKED:
|
||||
text = '踢出群聊';
|
||||
break;
|
||||
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_SET_ADMIN:
|
||||
text = '设置管理';
|
||||
break;
|
||||
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_CANCEL_ADMIN:
|
||||
text = '取消管理';
|
||||
break;
|
||||
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_GROUP_INFO_CHANGE:
|
||||
// (opMember 修改群资料:groupName & introduction & notification & faceUrl & owner & custom)
|
||||
switch (message!.groupTipsElem!.groupChangeInfoList![0]!.type) {
|
||||
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_NAME:
|
||||
text = '修改群名称';
|
||||
break;
|
||||
case GroupChangeInfoType
|
||||
.V2TIM_GROUP_INFO_CHANGE_TYPE_INTRODUCTION:
|
||||
text = '群简介修改';
|
||||
break;
|
||||
case GroupChangeInfoType
|
||||
.V2TIM_GROUP_INFO_CHANGE_TYPE_NOTIFICATION:
|
||||
text = '修改群公告';
|
||||
break;
|
||||
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_FACE_URL:
|
||||
text = '群头像修改';
|
||||
break;
|
||||
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER:
|
||||
text = '群主变更';
|
||||
break;
|
||||
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_CUSTOM:
|
||||
text = '群自定义字段变更';
|
||||
break;
|
||||
default:
|
||||
text = message.groupTipsElem!.groupChangeInfoList![0]!.type
|
||||
.toString();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_MEMBER_INFO_CHANGE:
|
||||
text = '群成员资料变更';
|
||||
break;
|
||||
default:
|
||||
text = message!.groupTipsElem!.type.toString();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
text = message!.elemType.toString();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/// 右侧索引栏样式
|
||||
static const IndexBarOptions indexBarOptions = IndexBarOptions(
|
||||
needRebuild: true,
|
||||
ignoreDragCancel: true,
|
||||
downTextStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
downItemDecoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
indexHintWidth: 120 / 2,
|
||||
indexHintHeight: 100 / 2,
|
||||
indexHintDecoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'assets/chats/index_bar.png',
|
||||
),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
indexHintAlignment: Alignment.centerRight,
|
||||
indexHintChildAlignment: Alignment(
|
||||
-0.25,
|
||||
0.0,
|
||||
),
|
||||
indexHintOffset: Offset(
|
||||
-20,
|
||||
0,
|
||||
),
|
||||
);
|
||||
|
||||
/// 悬浮导航,显示ABCD~Z
|
||||
static Widget susItem(
|
||||
BuildContext context,
|
||||
String tag, {
|
||||
double susHeight = 40,
|
||||
}) {
|
||||
return Container(
|
||||
height: susHeight,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16.0,
|
||||
),
|
||||
color: Colors.grey[200],
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
tag,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static showTrtcMessage(String userID) {
|
||||
return showModalBottomSheet(
|
||||
context: Get.context!,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: AppColors.white,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
|
||||
),
|
||||
builder: (context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// PopMenuItem(
|
||||
// '语音通话',
|
||||
// onTap: () {
|
||||
// TimService.to.tuiCalling.call(userID, CallingScenes.Audio);
|
||||
// Get.back();
|
||||
// },
|
||||
// ),
|
||||
// const Divider(height: 0),
|
||||
// PopMenuItem(
|
||||
// '视频通话',
|
||||
// onTap: () {
|
||||
// TimService.to.tuiCalling.call(userID, CallingScenes.Video);
|
||||
// Get.back();
|
||||
// },
|
||||
// ),
|
||||
const Divider(height: 0),
|
||||
Container(
|
||||
height: 8,
|
||||
color: AppColors.page,
|
||||
),
|
||||
const Divider(height: 0),
|
||||
// PopMenuItem(
|
||||
// '取消',
|
||||
// onTap: () {
|
||||
// Get.back();
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
121
lib/utils/request/http.dart
Normal file
121
lib/utils/request/http.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'package:chat/utils/request/http_request.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class Http {
|
||||
/// 取消请求
|
||||
static void cancelRequests({
|
||||
required CancelToken token,
|
||||
}) {
|
||||
HttpRequest().cancelRequests(
|
||||
token: token,
|
||||
);
|
||||
}
|
||||
|
||||
/// GET 请求
|
||||
static Future<T> get<T>(
|
||||
String path, {
|
||||
Map<String, dynamic>? params,
|
||||
Options? options,
|
||||
CancelToken? cancelToken,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
}) async {
|
||||
return await HttpRequest().request(
|
||||
path,
|
||||
method: HttpMethod.get,
|
||||
params: params,
|
||||
options: options,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
}
|
||||
|
||||
/// POST 请求
|
||||
static Future<T> post<T>(
|
||||
String path, {
|
||||
Map<String, dynamic>? params,
|
||||
dynamic data,
|
||||
Options? options,
|
||||
CancelToken? cancelToken,
|
||||
ProgressCallback? onSendProgress,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
}) async {
|
||||
return await HttpRequest().request(
|
||||
path,
|
||||
method: HttpMethod.post,
|
||||
params: params,
|
||||
data: data,
|
||||
options: options,
|
||||
cancelToken: cancelToken,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
);
|
||||
}
|
||||
|
||||
/// PUT
|
||||
static Future<T> put<T>(
|
||||
String path, {
|
||||
Map<String, dynamic>? params,
|
||||
dynamic data,
|
||||
Options? options,
|
||||
CancelToken? cancelToken,
|
||||
ProgressCallback? onSendProgress,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
}) async {
|
||||
return await HttpRequest().request(
|
||||
path,
|
||||
method: HttpMethod.put,
|
||||
params: params,
|
||||
data: data,
|
||||
options: options,
|
||||
cancelToken: cancelToken,
|
||||
onSendProgress: onSendProgress,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
);
|
||||
}
|
||||
|
||||
/// DELETE
|
||||
static Future<T> delete<T>(
|
||||
String path, {
|
||||
Map<String, dynamic>? params,
|
||||
dynamic data,
|
||||
Options? options,
|
||||
CancelToken? cancelToken,
|
||||
ProgressCallback? onSendProgress,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
}) async {
|
||||
return await HttpRequest().request(
|
||||
path,
|
||||
method: HttpMethod.delete,
|
||||
params: params,
|
||||
data: data,
|
||||
options: options,
|
||||
cancelToken: cancelToken,
|
||||
onSendProgress: onSendProgress,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
);
|
||||
}
|
||||
|
||||
/// 上传文件
|
||||
static Future<T> upload<T>(
|
||||
String path, {
|
||||
required String filePath,
|
||||
Map<String, dynamic>? params,
|
||||
Options? options,
|
||||
CancelToken? cancelToken,
|
||||
ProgressCallback? onSendProgress,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
}) async {
|
||||
var formData = FormData.fromMap({
|
||||
'upload': await MultipartFile.fromFile(filePath),
|
||||
});
|
||||
|
||||
return await HttpRequest().request(
|
||||
path,
|
||||
method: HttpMethod.post,
|
||||
params: params,
|
||||
data: formData,
|
||||
options: options,
|
||||
cancelToken: cancelToken,
|
||||
onSendProgress: onSendProgress,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
);
|
||||
}
|
||||
}
|
||||
97
lib/utils/request/http_interceptor.dart
Normal file
97
lib/utils/request/http_interceptor.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:chat/services/auth_service.dart';
|
||||
import 'package:chat/utils/ui_tools.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class HttpInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler handler,
|
||||
) {
|
||||
// 头部添加token
|
||||
// options.headers['Authorization'] = AuthService.to.userToken;
|
||||
options.headers['Accept'] = 'application/json';
|
||||
|
||||
super.onRequest(
|
||||
options,
|
||||
handler,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(
|
||||
Response response,
|
||||
ResponseInterceptorHandler handler,
|
||||
) {
|
||||
final apiStatusCode = response.data['status_code'];
|
||||
|
||||
if (apiStatusCode == 200) {
|
||||
response.data = response.data['data'];
|
||||
} else if (apiStatusCode == 401) {
|
||||
throw DioError(
|
||||
response: response,
|
||||
error: "登录超时",
|
||||
requestOptions: response.requestOptions,
|
||||
);
|
||||
} else if (apiStatusCode == 404) {
|
||||
throw DioError(
|
||||
response: response,
|
||||
error: "请求的接口不存在",
|
||||
requestOptions: response.requestOptions,
|
||||
);
|
||||
} else if (apiStatusCode == 0) {
|
||||
throw DioError(
|
||||
response: response,
|
||||
error: response.data['message'],
|
||||
requestOptions: response.requestOptions,
|
||||
);
|
||||
} else {
|
||||
throw DioError(
|
||||
response: response,
|
||||
error: response.data['message'],
|
||||
requestOptions: response.requestOptions,
|
||||
);
|
||||
}
|
||||
super.onResponse(
|
||||
response,
|
||||
handler,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(
|
||||
DioError err,
|
||||
ErrorInterceptorHandler handler,
|
||||
) {
|
||||
switch (err.type) {
|
||||
// 连接服务器超时
|
||||
case DioErrorType.connectTimeout:
|
||||
UiTools.toast('网络传输超时');
|
||||
break;
|
||||
// 响应超时
|
||||
case DioErrorType.receiveTimeout:
|
||||
UiTools.toast('网络传输超时');
|
||||
break;
|
||||
// 发送超时
|
||||
case DioErrorType.sendTimeout:
|
||||
UiTools.toast('网络传输超时');
|
||||
break;
|
||||
// 请求取消
|
||||
case DioErrorType.cancel:
|
||||
break;
|
||||
// 404/503错误
|
||||
case DioErrorType.response:
|
||||
break;
|
||||
// other 其他错误类型
|
||||
case DioErrorType.other:
|
||||
if (err.response?.data['status_code'] == 401) {
|
||||
AuthService.to.logout();
|
||||
}
|
||||
break;
|
||||
}
|
||||
super.onError(
|
||||
err,
|
||||
handler,
|
||||
);
|
||||
}
|
||||
}
|
||||
5
lib/utils/request/http_options.dart
Normal file
5
lib/utils/request/http_options.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
class HttpOptions {
|
||||
static const String baseUrl = 'http://api.gl.shangkelian.cn/api/';
|
||||
static const int connectTimeout = 15000;
|
||||
static const int receiveTimeout = 15000;
|
||||
}
|
||||
91
lib/utils/request/http_request.dart
Normal file
91
lib/utils/request/http_request.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'package:chat/utils/request/http_interceptor.dart';
|
||||
import 'package:chat/utils/request/http_options.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
enum HttpMethod {
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
delete,
|
||||
patch,
|
||||
head,
|
||||
}
|
||||
|
||||
class HttpRequest {
|
||||
static HttpRequest? _instance;
|
||||
static Dio _dio = Dio();
|
||||
Dio get dio => _dio;
|
||||
|
||||
HttpRequest._internal() {
|
||||
_instance = this;
|
||||
_instance!._init();
|
||||
}
|
||||
|
||||
factory HttpRequest() => _instance ?? HttpRequest._internal();
|
||||
|
||||
static HttpRequest? getInstance() {
|
||||
return _instance ?? HttpRequest._internal();
|
||||
}
|
||||
|
||||
/// 取消请求token
|
||||
final CancelToken _cancelToken = CancelToken();
|
||||
|
||||
_init() {
|
||||
BaseOptions options = BaseOptions(
|
||||
baseUrl: HttpOptions.baseUrl,
|
||||
connectTimeout: HttpOptions.connectTimeout,
|
||||
receiveTimeout: HttpOptions.receiveTimeout,
|
||||
);
|
||||
_dio = Dio(options);
|
||||
|
||||
/// 添加拦截器
|
||||
_dio.interceptors.add(HttpInterceptor());
|
||||
}
|
||||
|
||||
/// 请求
|
||||
Future request(
|
||||
String path, {
|
||||
HttpMethod method = HttpMethod.get,
|
||||
Map<String, dynamic>? params,
|
||||
dynamic data,
|
||||
Options? options,
|
||||
CancelToken? cancelToken,
|
||||
ProgressCallback? onSendProgress,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
}) async {
|
||||
const methodValues = {
|
||||
HttpMethod.get: 'get',
|
||||
HttpMethod.post: 'post',
|
||||
HttpMethod.put: 'put',
|
||||
HttpMethod.delete: 'delete',
|
||||
HttpMethod.patch: 'patch',
|
||||
HttpMethod.head: 'head'
|
||||
};
|
||||
|
||||
options ??= Options(method: methodValues[method]);
|
||||
|
||||
try {
|
||||
Response response = await _dio.request(
|
||||
path,
|
||||
data: data,
|
||||
queryParameters: params,
|
||||
cancelToken: cancelToken ?? _cancelToken,
|
||||
options: options,
|
||||
onSendProgress: onSendProgress,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
);
|
||||
// ignore: avoid_print
|
||||
print('请求地址:${response.requestOptions.uri}');
|
||||
// ignore: avoid_print
|
||||
print('响应数据:${response.data}');
|
||||
return response.data;
|
||||
} on DioError catch (e) {
|
||||
throw Exception(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消网络请求
|
||||
void cancelRequests({CancelToken? token}) {
|
||||
token ?? _cancelToken.cancel('CANCELED');
|
||||
}
|
||||
}
|
||||
15
lib/views/contact/group/create/index_page.dart
Normal file
15
lib/views/contact/group/create/index_page.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ContactGroupCreatePage extends StatefulWidget {
|
||||
const ContactGroupCreatePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ContactGroupCreatePageState createState() => _ContactGroupCreatePageState();
|
||||
}
|
||||
|
||||
class _ContactGroupCreatePageState extends State<ContactGroupCreatePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
15
lib/views/contact/group/manage/index_page.dart
Normal file
15
lib/views/contact/group/manage/index_page.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ContactGroupManagePage extends StatefulWidget {
|
||||
const ContactGroupManagePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ContactGroupManagePage> createState() => _ContactGroupManagePageState();
|
||||
}
|
||||
|
||||
class _ContactGroupManagePageState extends State<ContactGroupManagePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
17
lib/views/contact/group/notification/index_page.dart
Normal file
17
lib/views/contact/group/notification/index_page.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ContactGroupNotificationPage extends StatefulWidget {
|
||||
const ContactGroupNotificationPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ContactGroupNotificationPageState createState() =>
|
||||
_ContactGroupNotificationPageState();
|
||||
}
|
||||
|
||||
class _ContactGroupNotificationPageState
|
||||
extends State<ContactGroupNotificationPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
82
lib/views/conversation/index_page.dart
Normal file
82
lib/views/conversation/index_page.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:chat/controllers/group_controller.dart';
|
||||
import 'package:chat/controllers/private_controller.dart';
|
||||
import 'package:chat/routes/conversation_routes.dart';
|
||||
import 'package:chat/services/tim/conversation_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/conversation_type.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
|
||||
|
||||
class ConversationPage extends StatefulWidget {
|
||||
const ConversationPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ConversationPageState createState() => _ConversationPageState();
|
||||
}
|
||||
|
||||
class _ConversationPageState extends State<ConversationPage> {
|
||||
late final V2TimConversation conversation;
|
||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
final GlobalKey<dynamic> inputextField = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
conversation = Get.arguments['conversation'];
|
||||
|
||||
/// 标记会话内消息已读
|
||||
TimConversationService.to.markAsRead(conversation);
|
||||
if (conversation.type == ConversationType.V2TIM_GROUP) {
|
||||
GroupController.to.setCurrentGroup(conversation.groupID!);
|
||||
} else {
|
||||
PrivateController.to.setCurrentFriend(conversation.userID!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
inputextField.currentState.hideAllPanel();
|
||||
},
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: AppBar(
|
||||
title: conversation.type == ConversationType.V2TIM_GROUP
|
||||
? GetX<GroupController>(
|
||||
builder: (_) {
|
||||
return Text(
|
||||
_.currentGroup.value.conversation?.showName ?? '',
|
||||
);
|
||||
},
|
||||
)
|
||||
: GetX<PrivateController>(
|
||||
builder: (_) {
|
||||
return Text(
|
||||
_.currentFriend.value.conversation?.showName ?? '',
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
_topRightAction(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _topRightAction() {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
onPressed: () {
|
||||
conversation.type == ConversationType.V2TIM_GROUP
|
||||
? Get.toNamed(
|
||||
ConversationRoutes.infoGroup,
|
||||
)
|
||||
: Get.toNamed(
|
||||
ConversationRoutes.infoPrivate,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
235
lib/views/conversation/info/group_page.dart
Normal file
235
lib/views/conversation/info/group_page.dart
Normal file
@@ -0,0 +1,235 @@
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/controllers/group_controller.dart';
|
||||
import 'package:chat/routes/contact_routes.dart';
|
||||
import 'package:chat/services/tim/conversation_service.dart';
|
||||
import 'package:chat/services/tim/group_service.dart';
|
||||
import 'package:chat/views/conversation/info/widgets/group_member_preview.dart';
|
||||
import 'package:chat/views/home/widgets/action_button.dart';
|
||||
import 'package:chat/views/home/widgets/action_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ConversationInfoGroupPage extends StatelessWidget {
|
||||
const ConversationInfoGroupPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
GroupController.to.fetchGroupMemberList();
|
||||
|
||||
return GetX<GroupController>(builder: (_) {
|
||||
var currentGroup = _.currentGroup.value;
|
||||
var group = _.currentGroup.value.group;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('聊天信息(${group?.memberCount})'),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
const GroupMemberPreview(),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
color: AppColors.white,
|
||||
child: Column(
|
||||
children: [
|
||||
ActionItem(
|
||||
'群聊名称',
|
||||
extend: group?.groupName,
|
||||
onTap: () {
|
||||
if (currentGroup.isAdmin || currentGroup.isOwner) {
|
||||
// Get.toNamed(
|
||||
// ImRoutes.groupName,
|
||||
// );
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(height: 0, indent: 16),
|
||||
ActionItem(
|
||||
'群二维码',
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
ContactRoutes.groupQrCode,
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(height: 0, indent: 16),
|
||||
ActionItem(
|
||||
'群公告',
|
||||
bottom: group?.notification,
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
ContactRoutes.groupNotification,
|
||||
);
|
||||
},
|
||||
),
|
||||
Visibility(
|
||||
visible: currentGroup.isAdmin || currentGroup.isOwner,
|
||||
child: const Divider(
|
||||
height: 0,
|
||||
indent: 16,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: currentGroup.isAdmin || currentGroup.isOwner,
|
||||
child: ActionItem(
|
||||
'群管理',
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
ContactRoutes.groupManage,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: currentGroup.isAdmin || currentGroup.isOwner,
|
||||
child: const Divider(
|
||||
height: 0,
|
||||
indent: 16,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: currentGroup.isAdmin || currentGroup.isOwner,
|
||||
child: ActionItem(
|
||||
'加群申请',
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
ContactRoutes.groupApprove,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
color: AppColors.white,
|
||||
child: Column(
|
||||
children: [
|
||||
ActionItem(
|
||||
'查找聊天记录',
|
||||
onTap: () {
|
||||
// Get.toNamed(
|
||||
// ImRoutes.conversationSearch,
|
||||
// );
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
color: AppColors.white,
|
||||
child: Column(
|
||||
children: [
|
||||
ActionItem(
|
||||
'消息免打扰',
|
||||
rightWidget: SizedBox(
|
||||
height: 24,
|
||||
child: Switch(
|
||||
value:
|
||||
_.currentGroup.value.conversation!.recvOpt == 1,
|
||||
onChanged: (e) async {
|
||||
_.toggleReceiveOpt();
|
||||
},
|
||||
materialTapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
height: 0,
|
||||
indent: 16,
|
||||
),
|
||||
ActionItem(
|
||||
'置顶聊天',
|
||||
rightWidget: SizedBox(
|
||||
height: 24,
|
||||
child: Switch(
|
||||
value: _.currentGroup.value.conversation!.isPinned!,
|
||||
onChanged: (e) async {
|
||||
_.togglePinned();
|
||||
},
|
||||
materialTapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
color: AppColors.white,
|
||||
child: Column(
|
||||
children: [
|
||||
ActionItem(
|
||||
'我在群里的昵称',
|
||||
extend: _.currentGroup.value.selfInfo?.nameCard ?? '',
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
ContactRoutes.groupNickname,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Column(
|
||||
children: [
|
||||
ActionButton(
|
||||
'清空聊天记录',
|
||||
onTap: () async {
|
||||
OkCancelResult result = await showOkCancelAlertDialog(
|
||||
style: AdaptiveStyle.iOS,
|
||||
context: Get.context!,
|
||||
title: '系统提示',
|
||||
message: '将删除该聊天记录,是否继续?',
|
||||
okLabel: '确定',
|
||||
cancelLabel: '取消',
|
||||
defaultType: OkCancelAlertDefaultType.ok,
|
||||
);
|
||||
|
||||
if (result == OkCancelResult.ok) {
|
||||
TimConversationService.to.clearHistoryMessage(
|
||||
_.currentGroup.value.conversation!,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!currentGroup.isOwner) const Divider(height: 0),
|
||||
ActionButton(
|
||||
'删除并退出',
|
||||
onTap: () async {
|
||||
OkCancelResult result = await showOkCancelAlertDialog(
|
||||
style: AdaptiveStyle.iOS,
|
||||
context: Get.context!,
|
||||
title: '系统提示',
|
||||
message: '将删除并退出该群组,是否继续?',
|
||||
okLabel: '确定',
|
||||
cancelLabel: '取消',
|
||||
defaultType: OkCancelAlertDefaultType.ok,
|
||||
);
|
||||
|
||||
if (result == OkCancelResult.ok) {
|
||||
TimGroupService.to.quit(
|
||||
group!,
|
||||
);
|
||||
Navigator.popUntil(context, (route) => route.isFirst);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
157
lib/views/conversation/info/private_page.dart
Normal file
157
lib/views/conversation/info/private_page.dart
Normal file
@@ -0,0 +1,157 @@
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/controllers/private_controller.dart';
|
||||
import 'package:chat/routes/contact_routes.dart';
|
||||
import 'package:chat/services/tim/conversation_service.dart';
|
||||
import 'package:chat/views/home/widgets/action_item.dart';
|
||||
import 'package:chat/widgets/custom_avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ConversationInfoPrivatePage extends StatelessWidget {
|
||||
const ConversationInfoPrivatePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetX<PrivateController>(builder: (_) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('聊天信息'),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: const BoxDecoration(color: AppColors.white),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
ContactRoutes.friendProfile,
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
CustomAvatar(
|
||||
_.currentFriend.value.userProfile?.faceUrl,
|
||||
size: 54,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_.currentFriend.value.conversation!.showName!,
|
||||
style: const TextStyle(
|
||||
color: AppColors.unactive,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
ContactRoutes.groupCreate,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 54,
|
||||
height: 54,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.unactive.withOpacity(0.1),
|
||||
border: Border.all(
|
||||
color: AppColors.unactive.withOpacity(0.3),
|
||||
width: 0.4,
|
||||
style: BorderStyle.solid,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 0),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 0),
|
||||
ActionItem(
|
||||
'查找聊天记录',
|
||||
onTap: () {
|
||||
// Get.toNamed(
|
||||
// ImRoutes.conversationSearch,
|
||||
// );
|
||||
},
|
||||
),
|
||||
const Divider(height: 0),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 0),
|
||||
ActionItem(
|
||||
'消息免打扰',
|
||||
rightWidget: SizedBox(
|
||||
height: 24,
|
||||
child: Switch(
|
||||
value: _.currentFriend.value.conversation!.recvOpt == 1,
|
||||
onChanged: (e) async {
|
||||
_.changeReceiveOpt();
|
||||
},
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
height: 0,
|
||||
indent: 16,
|
||||
),
|
||||
ActionItem(
|
||||
'置顶聊天',
|
||||
rightWidget: SizedBox(
|
||||
height: 24,
|
||||
child: Switch(
|
||||
value: _.currentFriend.value.conversation!.isPinned!,
|
||||
onChanged: (e) async {
|
||||
_.togglePinned();
|
||||
},
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(height: 0),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 0),
|
||||
ActionItem(
|
||||
'清空聊天记录',
|
||||
onTap: () async {
|
||||
OkCancelResult result = await showOkCancelAlertDialog(
|
||||
style: AdaptiveStyle.iOS,
|
||||
context: Get.context!,
|
||||
title: '系统提示',
|
||||
message: '将删除该聊天记录,是否继续?',
|
||||
okLabel: '确定',
|
||||
cancelLabel: '取消',
|
||||
defaultType: OkCancelAlertDefaultType.ok,
|
||||
);
|
||||
|
||||
if (result == OkCancelResult.ok) {
|
||||
TimConversationService.to.clearHistoryMessage(
|
||||
_.currentFriend.value.conversation!,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(height: 0),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
209
lib/views/conversation/info/widgets/group_member_preview.dart
Normal file
209
lib/views/conversation/info/widgets/group_member_preview.dart
Normal file
@@ -0,0 +1,209 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/controllers/group_controller.dart';
|
||||
import 'package:chat/controllers/private_controller.dart';
|
||||
import 'package:chat/routes/contact_routes.dart';
|
||||
import 'package:chat/widgets/custom_avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/group_member_role.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member_full_info.dart';
|
||||
|
||||
class GroupMemberPreview extends StatelessWidget {
|
||||
const GroupMemberPreview({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetX<GroupController>(builder: (_) {
|
||||
List<Widget> items = List<Widget>.empty(growable: true);
|
||||
|
||||
if (_.currentGroup.value.memberList != null) {
|
||||
var members = _.currentGroup.value.memberList!;
|
||||
|
||||
if (members.length > 13) {
|
||||
members = members.sublist(0, 13);
|
||||
}
|
||||
|
||||
for (var item in members) {
|
||||
items.add(_memberItem(item!));
|
||||
}
|
||||
}
|
||||
|
||||
/// 因为 Public 类型的群,不支持邀请功能,用户只能主动申请加群
|
||||
items.add(
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
OkCancelResult result = await showOkCancelAlertDialog(
|
||||
style: AdaptiveStyle.iOS,
|
||||
context: Get.context!,
|
||||
title: '系统提示',
|
||||
message: '当前群聊不支持邀请用户,请分享群二维码至您要邀请的好友。',
|
||||
okLabel: '去分享',
|
||||
cancelLabel: '取消',
|
||||
defaultType: OkCancelAlertDefaultType.ok,
|
||||
);
|
||||
|
||||
if (result == OkCancelResult.ok) {
|
||||
Get.toNamed(ContactRoutes.groupQrCode);
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.unactive.withOpacity(0.1),
|
||||
border: Border.all(
|
||||
color: AppColors.unactive.withOpacity(0.3),
|
||||
width: 0.4,
|
||||
style: BorderStyle.solid,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'邀请',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (_.currentGroup.value.isAdmin || _.currentGroup.value.isOwner) {
|
||||
items.add(
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
ContactRoutes.groupKick,
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.unactive.withOpacity(0.1),
|
||||
border: Border.all(
|
||||
color: AppColors.unactive.withOpacity(0.3),
|
||||
width: 0.4,
|
||||
style: BorderStyle.solid,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.remove,
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'移除',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
color: AppColors.white,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: GridView.count(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 5,
|
||||
childAspectRatio: 1 / 1.1,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
children: items,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _memberItem(V2TimGroupMemberFullInfo member) {
|
||||
double w = 40;
|
||||
double h = 12;
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
await PrivateController.to.setCurrentFriend(member.userID);
|
||||
Get.toNamed(
|
||||
ContactRoutes.friendProfile,
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomAvatar(member.faceUrl),
|
||||
if (member.role ==
|
||||
GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN ||
|
||||
member.role ==
|
||||
GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER)
|
||||
Positioned(
|
||||
left: 0,
|
||||
top: sqrt(w * w / 2 - sqrt2 * w * h + h * h),
|
||||
child: Transform.rotate(
|
||||
angle: -0.25 * pi,
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Container(
|
||||
color: member.role ==
|
||||
GroupMemberRoleType
|
||||
.V2TIM_GROUP_MEMBER_ROLE_OWNER
|
||||
? AppColors.red
|
||||
: AppColors.golden,
|
||||
width: w,
|
||||
height: h,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
member.role ==
|
||||
GroupMemberRoleType
|
||||
.V2TIM_GROUP_MEMBER_ROLE_OWNER
|
||||
? '群主'
|
||||
: '管理员',
|
||||
style: const TextStyle(
|
||||
color: AppColors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
member.nameCard!.isNotEmpty ? member.nameCard! : member.nickName!,
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,219 @@
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/routes/app_routes.dart';
|
||||
import 'package:chat/routes/contact_routes.dart';
|
||||
import 'package:chat/routes/user_routes.dart';
|
||||
import 'package:chat/services/tim/conversation_service.dart';
|
||||
import 'package:chat/views/home/widgets/conversation_item.dart';
|
||||
import 'package:chat/widgets/custom_easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('消息'),
|
||||
backgroundColor: AppColors.white,
|
||||
appBar: _appBar(),
|
||||
body: EasyRefresh(
|
||||
header: CustomEasyRefresh.header,
|
||||
// firstRefresh: true,
|
||||
onRefresh: () async {
|
||||
await TimConversationService.to.fetchList();
|
||||
},
|
||||
child: GetX<TimConversationService>(
|
||||
builder: (_) {
|
||||
return _.conversationList.isEmpty
|
||||
? CustomEasyRefresh.empty()
|
||||
: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemCount: _.conversationList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ConversationItem(_.conversationList[index]!);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider(
|
||||
height: 0,
|
||||
indent: 72,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
PreferredSizeWidget _appBar() {
|
||||
return AppBar(
|
||||
title: const Text('聊聊'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Get.toNamed(AppRoutes.search);
|
||||
},
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (String value) {
|
||||
switch (value) {
|
||||
case 'A':
|
||||
Get.toNamed(UserRoutes.qrCode);
|
||||
break;
|
||||
case 'B':
|
||||
Get.toNamed(ContactRoutes.groupCreate);
|
||||
break;
|
||||
case 'C':
|
||||
Get.toNamed(ContactRoutes.friendSearch);
|
||||
break;
|
||||
case 'D':
|
||||
Permission.camera.request().isGranted.then((value) {
|
||||
if (value) {
|
||||
Get.toNamed(AppRoutes.scan);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
tooltip: '',
|
||||
offset: const Offset(0, 56),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
),
|
||||
itemBuilder: (_) {
|
||||
return [
|
||||
_popupMenuItem('我的二维码', 'A', Icons.qr_code_outlined),
|
||||
_popupMenuItem('发起群聊', 'B', Icons.textsms),
|
||||
_popupMenuItem('添加朋友', 'C', Icons.person_add_alt),
|
||||
_popupMenuItem('扫一扫', 'D', Icons.photo_camera),
|
||||
];
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 右上角弹出菜单
|
||||
PopupMenuItem<String> _popupMenuItem(
|
||||
String text,
|
||||
String value,
|
||||
IconData icon,
|
||||
) {
|
||||
return PopupMenuItem(
|
||||
value: value,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: AppColors.primary,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// /// 左侧抽题
|
||||
// Widget _drawer() {
|
||||
// return Drawer(
|
||||
// child: ListView(
|
||||
// children: [
|
||||
// GetX<UserController>(builder: (_) {
|
||||
// return DrawerHeader(
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// CustomCircleAvatar(
|
||||
// _.userInfo.value!.avatar,
|
||||
// size: 72,
|
||||
// ),
|
||||
// const SizedBox(height: 8),
|
||||
// Text(
|
||||
// _.userInfo.value!.nickname,
|
||||
// style: const TextStyle(
|
||||
// fontSize: 24,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }),
|
||||
// ListTile(
|
||||
// onTap: () {
|
||||
// Get.back();
|
||||
// Get.toNamed(UserRoutes.info);
|
||||
// },
|
||||
// leading: const Icon(Icons.info_outlined),
|
||||
// title: const Text('修改资料'),
|
||||
// ),
|
||||
// const Divider(height: 0),
|
||||
// ListTile(
|
||||
// onTap: () {
|
||||
// Get.back();
|
||||
// Get.toNamed(
|
||||
// ImRoutes.friend,
|
||||
// arguments: {
|
||||
// 'name_card': false,
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// leading: const Icon(Icons.person_outlined),
|
||||
// title: const Text('我的好友'),
|
||||
// ),
|
||||
// const Divider(height: 0),
|
||||
// ListTile(
|
||||
// onTap: () {
|
||||
// Get.back();
|
||||
// Get.toNamed(ImRoutes.group);
|
||||
// },
|
||||
// leading: const Icon(Icons.group_outlined),
|
||||
// title: const Text('我的群组'),
|
||||
// ),
|
||||
// const Divider(height: 0),
|
||||
// ListTile(
|
||||
// onTap: () {
|
||||
// Get.back();
|
||||
// Get.toNamed(ImRoutes.blcok);
|
||||
// },
|
||||
// leading: const Icon(Icons.block_flipped),
|
||||
// title: const Text('黑名单'),
|
||||
// ),
|
||||
// const Divider(height: 0),
|
||||
// ListTile(
|
||||
// onTap: () {
|
||||
// Get.back();
|
||||
// Get.toNamed(ImRoutes.friendRequest);
|
||||
// },
|
||||
// leading: const Icon(Icons.person_add_alt_outlined),
|
||||
// title: const Text('好友申请'),
|
||||
// ),
|
||||
// const Divider(height: 0),
|
||||
// ListTile(
|
||||
// onTap: () {
|
||||
// Get.back();
|
||||
// Get.toNamed(ImRoutes.setting);
|
||||
// },
|
||||
// leading: const Icon(Icons.settings_outlined),
|
||||
// title: const Text('消息设置'),
|
||||
// ),
|
||||
// const Divider(height: 0),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
39
lib/views/home/widgets/action_button.dart
Normal file
39
lib/views/home/widgets/action_button.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ActionButton extends StatelessWidget {
|
||||
final String text;
|
||||
final Color color;
|
||||
final VoidCallback? onTap;
|
||||
const ActionButton(
|
||||
this.text, {
|
||||
this.color = AppColors.red,
|
||||
this.onTap,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
onTap?.call();
|
||||
},
|
||||
child: Container(
|
||||
color: AppColors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
76
lib/views/home/widgets/action_item.dart
Normal file
76
lib/views/home/widgets/action_item.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ActionItem extends StatelessWidget {
|
||||
final String title;
|
||||
final String? extend;
|
||||
final Widget? rightWidget;
|
||||
final String? bottom;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const ActionItem(
|
||||
this.title, {
|
||||
this.extend,
|
||||
this.rightWidget,
|
||||
this.bottom,
|
||||
this.onTap,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
onTap?.call();
|
||||
},
|
||||
child: Container(
|
||||
color: AppColors.white,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Expanded(child: Container()),
|
||||
if (extend != null)
|
||||
Text(
|
||||
extend!,
|
||||
style: const TextStyle(
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
),
|
||||
rightWidget ??
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (bottom != null && bottom!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
bottom!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.unactive,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
212
lib/views/home/widgets/conversation_item.dart
Normal file
212
lib/views/home/widgets/conversation_item.dart
Normal file
@@ -0,0 +1,212 @@
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/routes/conversation_routes.dart';
|
||||
import 'package:chat/services/tim/conversation_service.dart';
|
||||
import 'package:chat/utils/convert.dart';
|
||||
import 'package:chat/views/home/widgets/group_avatar.dart';
|
||||
import 'package:chat/views/home/widgets/message_preview_widget.dart';
|
||||
import 'package:chat/views/home/widgets/pop_menu_item.dart';
|
||||
import 'package:chat/widgets/custom_avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/enum/conversation_type.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_conversation.dart';
|
||||
|
||||
class ConversationItem extends StatelessWidget {
|
||||
final V2TimConversation conversation;
|
||||
const ConversationItem(this.conversation, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 68,
|
||||
decoration: BoxDecoration(
|
||||
color: conversation.isPinned! ? AppColors.page : null,
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 12,
|
||||
bottom: 12,
|
||||
),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
ConversationRoutes.index,
|
||||
arguments: {
|
||||
'conversation': conversation,
|
||||
},
|
||||
);
|
||||
},
|
||||
onLongPress: () async {
|
||||
await _showLongPressMenu();
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
conversation.type == ConversationType.V2TIM_C2C
|
||||
? CustomAvatar(
|
||||
conversation.faceUrl,
|
||||
)
|
||||
: GroupAvatar(conversation.groupID!),
|
||||
Visibility(
|
||||
visible: conversation.recvOpt == 0 &&
|
||||
conversation.unreadCount! > 0,
|
||||
child: Positioned(
|
||||
right: -5,
|
||||
top: -5,
|
||||
child: Container(
|
||||
width: 18,
|
||||
height: 18,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: AppColors.red,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Center(
|
||||
child: Text(
|
||||
conversation.unreadCount! > 99
|
||||
? '99+'
|
||||
: conversation.unreadCount.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: AppColors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: conversation.recvOpt == 1 &&
|
||||
conversation.unreadCount! > 0,
|
||||
child: const Positioned(
|
||||
right: -3,
|
||||
top: -3,
|
||||
child: Icon(
|
||||
Icons.circle_rounded,
|
||||
color: AppColors.red,
|
||||
size: 8,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
conversation.showName!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
MessagePreviewWidget(conversation.lastMessage),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
conversation.lastMessage == null
|
||||
? ''
|
||||
: Convert.timeFormat(
|
||||
conversation.lastMessage!.timestamp!,
|
||||
format: 'MM/dd HH:mm',
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
if (conversation.recvOpt == 1)
|
||||
const Icon(
|
||||
Icons.notifications_off_outlined,
|
||||
size: 14,
|
||||
color: AppColors.unactive,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showLongPressMenu() async {
|
||||
showModalBottomSheet(
|
||||
context: Get.context!,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: AppColors.white,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
|
||||
),
|
||||
builder: (context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
PopMenuItem(
|
||||
conversation.isPinned! ? '取消置顶' : '聊天置顶',
|
||||
onTap: () {
|
||||
TimConversationService.to.setOnTop(conversation);
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
const Divider(height: 0),
|
||||
PopMenuItem(
|
||||
conversation.recvOpt == 1 ? '取消免打扰' : '消息免打扰',
|
||||
onTap: () {
|
||||
TimConversationService.to.setReceiveOpt(conversation);
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
const Divider(height: 0),
|
||||
PopMenuItem(
|
||||
'清空聊天记录',
|
||||
onTap: () {
|
||||
TimConversationService.to.clearHistoryMessage(conversation);
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
const Divider(height: 0),
|
||||
PopMenuItem(
|
||||
'标为已读',
|
||||
onTap: () {
|
||||
TimConversationService.to.markAsRead(conversation);
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
const Divider(height: 0),
|
||||
PopMenuItem(
|
||||
'删除该聊天',
|
||||
onTap: () {
|
||||
TimConversationService.to.delete(conversation);
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
const Divider(height: 0.4),
|
||||
Container(
|
||||
color: AppColors.page,
|
||||
height: 8,
|
||||
),
|
||||
const Divider(height: 0.4),
|
||||
PopMenuItem(
|
||||
'取消',
|
||||
onTap: () {
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
140
lib/views/home/widgets/friend_selector.dart
Normal file
140
lib/views/home/widgets/friend_selector.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'package:azlistview/azlistview.dart';
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/models/im/contact_info_model.dart';
|
||||
import 'package:chat/services/tim/friend_service.dart';
|
||||
import 'package:chat/utils/im_tools.dart';
|
||||
import 'package:chat/widgets/custom_avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_friend_info.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member_full_info.dart';
|
||||
|
||||
class FriendSelector extends StatefulWidget {
|
||||
final Function(List<V2TimFriendInfo>) onChanged;
|
||||
final List<V2TimGroupMemberFullInfo?>? lockedUsers;
|
||||
const FriendSelector({
|
||||
required this.onChanged,
|
||||
this.lockedUsers,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<FriendSelector> createState() => _FriendSelectorState();
|
||||
}
|
||||
|
||||
class _FriendSelectorState extends State<FriendSelector> {
|
||||
/// 选中的好友列表
|
||||
List<V2TimFriendInfo> selectList =
|
||||
List<V2TimFriendInfo>.empty(growable: true);
|
||||
|
||||
/// 选择列表改变的事件,更新选中列表
|
||||
_selectListChange(id) {
|
||||
setState(() {
|
||||
if (selectList.contains(id)) {
|
||||
selectList.remove(id);
|
||||
} else {
|
||||
selectList.add(id);
|
||||
}
|
||||
});
|
||||
widget.onChanged(selectList);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetX<TimFriendService>(
|
||||
builder: (_) {
|
||||
return AzListView(
|
||||
data: _.contacts,
|
||||
itemCount: _.contacts.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
ContactInfoModel info = _.contacts[index];
|
||||
return _contactItem(info);
|
||||
},
|
||||
susItemBuilder: (BuildContext context, int index) {
|
||||
ContactInfoModel model = _.contacts[index];
|
||||
return ImTools.susItem(
|
||||
context,
|
||||
model.getSuspensionTag(),
|
||||
susHeight: 32,
|
||||
);
|
||||
},
|
||||
indexBarData: SuspensionUtil.getTagIndexList(_.contacts).toList(),
|
||||
indexBarOptions: ImTools.indexBarOptions,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _contactItem(
|
||||
ContactInfoModel info,
|
||||
) {
|
||||
bool isDisable = widget.lockedUsers == null
|
||||
? false
|
||||
: widget.lockedUsers!
|
||||
.where(
|
||||
(e) => e!.userID == info.friendInfo!.userID,
|
||||
)
|
||||
.isNotEmpty;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
color: AppColors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: isDisable
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_selectListChange(info.friendInfo);
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Radio<V2TimFriendInfo>(
|
||||
groupValue:
|
||||
selectList.contains(info.friendInfo) || isDisable
|
||||
? info.friendInfo
|
||||
: null,
|
||||
onChanged: isDisable
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_selectListChange(info.friendInfo);
|
||||
});
|
||||
},
|
||||
value: info.friendInfo!,
|
||||
toggleable: true,
|
||||
),
|
||||
CustomAvatar(
|
||||
info.friendInfo!.userProfile!.faceUrl,
|
||||
size: 32,
|
||||
radius: 2,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
info.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
height: 0,
|
||||
indent: 92,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
68
lib/views/home/widgets/group_avatar.dart
Normal file
68
lib/views/home/widgets/group_avatar.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/services/tim/group_service.dart';
|
||||
import 'package:chat/widgets/custom_avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member_full_info.dart';
|
||||
|
||||
class GroupAvatar extends StatefulWidget {
|
||||
final String groupID;
|
||||
final double size;
|
||||
const GroupAvatar(
|
||||
this.groupID, {
|
||||
this.size = 44,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<GroupAvatar> createState() => _GroupAvatarState();
|
||||
}
|
||||
|
||||
class _GroupAvatarState extends State<GroupAvatar> {
|
||||
List<V2TimGroupMemberFullInfo?>? members;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
TimGroupService.to.members(widget.groupID, count: 9).then((value) {
|
||||
setState(() {
|
||||
members = value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: AppColors.border,
|
||||
width: 0.4,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: members == null
|
||||
? CustomAvatar('')
|
||||
: GridView.builder(
|
||||
padding: const EdgeInsets.all(1),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount:
|
||||
(members != null && members!.length > 4) ? 3 : 2,
|
||||
childAspectRatio: 1,
|
||||
crossAxisSpacing: 1,
|
||||
mainAxisSpacing: 1,
|
||||
),
|
||||
itemCount: members!.length,
|
||||
itemBuilder: (context, index) {
|
||||
return CustomAvatar(
|
||||
members![index]?.faceUrl,
|
||||
size: widget.size / 3,
|
||||
radius: 2,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
111
lib/views/home/widgets/group_user_selector.dart
Normal file
111
lib/views/home/widgets/group_user_selector.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/widgets/custom_avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_group_member_full_info.dart';
|
||||
|
||||
class GroupUserSelector extends StatefulWidget {
|
||||
final Function(List<V2TimGroupMemberFullInfo>) onChanged;
|
||||
final List<V2TimGroupMemberFullInfo> selectedList;
|
||||
final List<V2TimGroupMemberFullInfo?> originalList;
|
||||
|
||||
const GroupUserSelector({
|
||||
required this.onChanged,
|
||||
required this.selectedList,
|
||||
required this.originalList,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<GroupUserSelector> createState() => _GroupUserSelectorState();
|
||||
}
|
||||
|
||||
class _GroupUserSelectorState extends State<GroupUserSelector> {
|
||||
/// 选中的好友列表
|
||||
List<V2TimGroupMemberFullInfo> selectList =
|
||||
List<V2TimGroupMemberFullInfo>.empty(growable: true);
|
||||
|
||||
/// 选择列表改变的事件,更新选中列表
|
||||
_selectListChange(id) {
|
||||
setState(() {
|
||||
if (selectList.contains(id)) {
|
||||
selectList.remove(id);
|
||||
} else {
|
||||
selectList.add(id);
|
||||
}
|
||||
});
|
||||
widget.onChanged(selectList);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.separated(
|
||||
itemBuilder: (_, index) {
|
||||
return _contactItem(widget.originalList[index]!);
|
||||
},
|
||||
separatorBuilder: (_, index) {
|
||||
return const Divider(
|
||||
height: 0,
|
||||
);
|
||||
},
|
||||
itemCount: widget.originalList.length,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _contactItem(
|
||||
V2TimGroupMemberFullInfo info,
|
||||
) {
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
color: AppColors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectListChange(info);
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Radio<V2TimGroupMemberFullInfo>(
|
||||
groupValue: selectList.contains(info) ? info : null,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_selectListChange(info);
|
||||
});
|
||||
},
|
||||
value: info,
|
||||
toggleable: true,
|
||||
),
|
||||
CustomAvatar(
|
||||
info.faceUrl,
|
||||
size: 32,
|
||||
radius: 2,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
info.nickName!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
height: 0,
|
||||
indent: 92,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/views/home/widgets/message_preview_widget.dart
Normal file
26
lib/views/home/widgets/message_preview_widget.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:chat/configs/app_colors.dart';
|
||||
import 'package:chat/utils/im_tools.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tencent_im_sdk_plugin/models/v2_tim_message.dart';
|
||||
|
||||
class MessagePreviewWidget extends StatelessWidget {
|
||||
final V2TimMessage? message;
|
||||
const MessagePreviewWidget(this.message, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (message == null) {
|
||||
return const Text('');
|
||||
}
|
||||
|
||||
return Text(
|
||||
ImTools.parseMessage(message),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.unactive,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
29
lib/views/home/widgets/pop_menu_item.dart
Normal file
29
lib/views/home/widgets/pop_menu_item.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class PopMenuItem extends StatelessWidget {
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const PopMenuItem(
|
||||
this.text, {
|
||||
this.onTap,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
onTap?.call();
|
||||
},
|
||||
child: Container(
|
||||
height: 52,
|
||||
width: Get.width,
|
||||
alignment: Alignment.center,
|
||||
child: Text(text),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -20,19 +20,23 @@ class AppPage extends StatelessWidget {
|
||||
|
||||
final List<Map> _tabBarList = [
|
||||
{
|
||||
'icon': 'tabBar_03.png',
|
||||
'icon': Icons.message_outlined,
|
||||
'active_icon': Icons.message,
|
||||
'label': '消息',
|
||||
},
|
||||
{
|
||||
'icon': 'tabBar_03.png',
|
||||
'icon': Icons.contact_page_outlined,
|
||||
'active_icon': Icons.contact_page,
|
||||
'label': '通讯录',
|
||||
},
|
||||
{
|
||||
'icon': 'tabBar_03.png',
|
||||
'icon': Icons.explore_outlined,
|
||||
'active_icon': Icons.explore,
|
||||
'label': '发现',
|
||||
},
|
||||
{
|
||||
'icon': 'tabBar_03.png',
|
||||
'icon': Icons.person_outline,
|
||||
'active_icon': Icons.person,
|
||||
'label': '我的',
|
||||
},
|
||||
];
|
||||
@@ -47,16 +51,14 @@ class AppPage extends StatelessWidget {
|
||||
},
|
||||
items: _tabBarList.map((item) {
|
||||
return BottomNavigationBarItem(
|
||||
icon: Image.asset(
|
||||
'assets/icons/${item['icon']}',
|
||||
width: 20,
|
||||
height: 20,
|
||||
icon: Icon(
|
||||
item['icon'],
|
||||
size: 24,
|
||||
),
|
||||
activeIcon: Image.asset(
|
||||
'assets/icons/${item['icon']}',
|
||||
activeIcon: Icon(
|
||||
item['active_icon'],
|
||||
size: 24,
|
||||
color: AppColors.primary,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
label: item['label'],
|
||||
tooltip: '',
|
||||
|
||||
15
lib/views/user/qr_code/index_page.dart
Normal file
15
lib/views/user/qr_code/index_page.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UserQrCodePage extends StatefulWidget {
|
||||
const UserQrCodePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_UserQrCodePageState createState() => _UserQrCodePageState();
|
||||
}
|
||||
|
||||
class _UserQrCodePageState extends State<UserQrCodePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
177
pubspec.lock
177
pubspec.lock
@@ -122,6 +122,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csslib
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.17.2"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -129,6 +136,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
dart_date:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dart_date
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
device_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -143,6 +157,20 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.0.6"
|
||||
extended_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: extended_image
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
extended_image_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: extended_image_library
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -277,6 +305,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.15.1"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -284,6 +319,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.13.5"
|
||||
http_client_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_client_helper
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -319,6 +361,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -375,6 +424,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -473,6 +529,48 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "9.0.7"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.9.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
photo_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: photo_manager
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
pinput:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -515,6 +613,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.4"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.4"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -543,6 +648,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
scroll_to_index:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: scroll_to_index
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
scrollable_positioned_list:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -617,7 +729,14 @@ packages:
|
||||
name: tencent_im_sdk_plugin
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.5.1"
|
||||
version: "4.1.9"
|
||||
tencent_im_sdk_plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tencent_im_sdk_plugin_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.5"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -632,6 +751,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.4.8"
|
||||
timeago:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timeago
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -639,13 +765,6 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
unorm_dart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: unorm_dart
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -667,6 +786,48 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.7.6"
|
||||
video_player:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: video_player
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.7"
|
||||
video_player_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_android
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.9"
|
||||
video_player_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_avfoundation
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
video_player_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.1.4"
|
||||
video_player_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_web
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.12"
|
||||
wechat_assets_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: wechat_assets_picker
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.2.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
10
pubspec.yaml
10
pubspec.yaml
@@ -1,7 +1,7 @@
|
||||
name: chat
|
||||
description: A new Flutter project.
|
||||
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
version: 1.0.0+1
|
||||
version: 1.0.0
|
||||
environment:
|
||||
sdk: ">=2.16.2 <3.0.0"
|
||||
dependencies:
|
||||
@@ -15,7 +15,6 @@ dependencies:
|
||||
azlistview: ^2.0.0
|
||||
lpinyin: ^2.0.3
|
||||
vibration: ^1.7.6
|
||||
tencent_im_sdk_plugin: ^3.5.1
|
||||
scan: ^1.6.0
|
||||
package_info_plus: ^0.0.1
|
||||
device_info_plus: ^0.0.1
|
||||
@@ -39,7 +38,12 @@ dependencies:
|
||||
ref: master
|
||||
fast_base58: ^0.2.1
|
||||
hash: ^1.0.4
|
||||
unorm_dart: ^0.2.0
|
||||
tencent_im_sdk_plugin: ^4.1.9
|
||||
wechat_assets_picker: ^7.2.0
|
||||
video_player: ^2.4.7
|
||||
scroll_to_index: ^2.1.1
|
||||
dart_date: ^1.1.1
|
||||
permission_handler: ^10.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <smart_auth/smart_auth_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
SmartAuthPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SmartAuthPlugin"));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
permission_handler_windows
|
||||
smart_auth
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user