fix(mobile-next): 清理悬浮窗、语音文案与位置发送
按 B-273 / B-269 收口 Android 客户端:去掉悬浮窗申请与旧浮窗服务,通话入口统一为语音通话,删除位置发送与定位权限,保留历史位置只读展示。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
092039bc1a
commit
694891c393
@@ -45,7 +45,8 @@ class IntervalDo {
|
||||
|
||||
void run({required Function() fuc, int milliseconds = 0}) {
|
||||
DateTime now = DateTime.now();
|
||||
if (null == last || now.difference(last ?? now).inMilliseconds > milliseconds) {
|
||||
if (null == last ||
|
||||
now.difference(last ?? now).inMilliseconds > milliseconds) {
|
||||
last = now;
|
||||
fuc();
|
||||
}
|
||||
@@ -153,12 +154,14 @@ class IMUtils {
|
||||
}
|
||||
}
|
||||
|
||||
static String? emptyStrToNull(String? str) => (null != str && str.trim().isEmpty) ? null : str;
|
||||
static String? emptyStrToNull(String? str) =>
|
||||
(null != str && str.trim().isEmpty) ? null : str;
|
||||
|
||||
static bool isNotNullEmptyStr(String? str) => null != str && "" != str.trim();
|
||||
|
||||
static bool isChinaMobile(String mobile) {
|
||||
RegExp exp = RegExp(r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\d{8}$');
|
||||
RegExp exp = RegExp(
|
||||
r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\d{8}$');
|
||||
return exp.hasMatch(mobile);
|
||||
}
|
||||
|
||||
@@ -172,14 +175,17 @@ class IMUtils {
|
||||
final directory = await createTempDir(dir: 'video');
|
||||
final targetPath = '$directory/$name';
|
||||
|
||||
final String ffmpegCommand = '-i $path -ss 0 -vframes 1 -q:v 15 -y $targetPath';
|
||||
final String ffmpegCommand =
|
||||
'-i $path -ss 0 -vframes 1 -q:v 15 -y $targetPath';
|
||||
final session = await FFmpegKit.execute(ffmpegCommand);
|
||||
|
||||
final state = FFmpegKitConfig.sessionStateToString(await session.getState());
|
||||
final state =
|
||||
FFmpegKitConfig.sessionStateToString(await session.getState());
|
||||
final returnCode = await session.getReturnCode();
|
||||
|
||||
if (state == SessionState.failed || !ReturnCode.isSuccess(returnCode)) {
|
||||
Logger().printError(info: "Command failed. Please check output for the details.");
|
||||
Logger().printError(
|
||||
info: "Command failed. Please check output for the details.");
|
||||
}
|
||||
|
||||
session.cancel();
|
||||
@@ -195,11 +201,14 @@ class IMUtils {
|
||||
|
||||
final output = await FFprobeKit.getMediaInformation(path);
|
||||
final streams = output.getMediaInformation()?.getStreams();
|
||||
final isH264 = streams?.any((element) => element.getCodec()?.contains('h264') == true) ?? false;
|
||||
final isH264 = streams
|
||||
?.any((element) => element.getCodec()?.contains('h264') == true) ??
|
||||
false;
|
||||
final size = output.getMediaInformation()?.getSize() ?? '0';
|
||||
output.cancel();
|
||||
|
||||
final audioStream = streams?.firstWhereOrNull((e) => e.getType()?.contains('audio') == true);
|
||||
final audioStream = streams
|
||||
?.firstWhereOrNull((e) => e.getType()?.contains('audio') == true);
|
||||
final isAAC = audioStream?.getCodec()?.toLowerCase() != 'aac';
|
||||
|
||||
String ffmpegCommand =
|
||||
@@ -210,7 +219,8 @@ class IMUtils {
|
||||
if (isAAC) {
|
||||
return File(targetPath);
|
||||
} else {
|
||||
ffmpegCommand = '-i $path -c:v copy -c:a aac -q:a 2 -threads 4 $targetPath';
|
||||
ffmpegCommand =
|
||||
'-i $path -c:v copy -c:a aac -q:a 2 -threads 4 $targetPath';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +230,8 @@ class IMUtils {
|
||||
|
||||
return File(targetPath);
|
||||
} else {
|
||||
ffmpegCommand = '-i $path -c:v copy -c:a aac -q:a 2 -threads 4 $targetPath';
|
||||
ffmpegCommand =
|
||||
'-i $path -c:v copy -c:a aac -q:a 2 -threads 4 $targetPath';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +241,8 @@ class IMUtils {
|
||||
final returnCode = await session.getReturnCode();
|
||||
|
||||
if (state == SessionState.failed || !ReturnCode.isSuccess(returnCode)) {
|
||||
Logger().printError(info: "Command failed. Please check output for the details.");
|
||||
Logger().printError(
|
||||
info: "Command failed. Please check output for the details.");
|
||||
file.copySync(targetPath);
|
||||
|
||||
return File(targetPath);
|
||||
@@ -241,7 +253,8 @@ class IMUtils {
|
||||
return File(targetPath);
|
||||
}
|
||||
|
||||
static Future<File?> compressImageAndGetFile(File file, {int quality = 80}) async {
|
||||
static Future<File?> compressImageAndGetFile(File file,
|
||||
{int quality = 80}) async {
|
||||
var path = file.path;
|
||||
var name = path.substring(path.lastIndexOf("/") + 1).toLowerCase();
|
||||
|
||||
@@ -360,14 +373,16 @@ class IMUtils {
|
||||
String? externalStorageDirPath;
|
||||
if (Platform.isAndroid) {
|
||||
try {
|
||||
externalStorageDirPath = await PathProviderPlatform.instance.getDownloadsPath();
|
||||
externalStorageDirPath =
|
||||
await PathProviderPlatform.instance.getDownloadsPath();
|
||||
} catch (err, st) {
|
||||
Logger.print('failed to get downloads path: $err, $st');
|
||||
final directory = await getExternalStorageDirectory();
|
||||
externalStorageDirPath = directory?.path;
|
||||
}
|
||||
} else if (Platform.isIOS) {
|
||||
externalStorageDirPath = (await getApplicationDocumentsDirectory()).absolute.path;
|
||||
externalStorageDirPath =
|
||||
(await getApplicationDocumentsDirectory()).absolute.path;
|
||||
}
|
||||
return externalStorageDirPath!;
|
||||
}
|
||||
@@ -384,7 +399,8 @@ class IMUtils {
|
||||
return path;
|
||||
}
|
||||
|
||||
static List<Message> calChatTimeInterval(List<Message> list, {bool calculate = true}) {
|
||||
static List<Message> calChatTimeInterval(List<Message> list,
|
||||
{bool calculate = true}) {
|
||||
if (!calculate) return list;
|
||||
var milliseconds = list.firstOrNull?.sendTime;
|
||||
if (null == milliseconds) return list;
|
||||
@@ -421,7 +437,9 @@ class IMUtils {
|
||||
final yesterday = now.subtract(Duration(days: 1));
|
||||
|
||||
if (isSameDay(dateTime, yesterday)) {
|
||||
return isChinese ? '昨天 ${formatter.format(dateTime)}' : 'Yesterday ${formatter.format(dateTime)}';
|
||||
return isChinese
|
||||
? '昨天 ${formatter.format(dateTime)}'
|
||||
: 'Yesterday ${formatter.format(dateTime)}';
|
||||
}
|
||||
|
||||
if (isSameWeek(dateTime, now)) {
|
||||
@@ -448,13 +466,16 @@ class IMUtils {
|
||||
}
|
||||
|
||||
static bool isSameDay(DateTime date1, DateTime date2) {
|
||||
return date1.year == date2.year && date1.month == date2.month && date1.day == date2.day;
|
||||
return date1.year == date2.year &&
|
||||
date1.month == date2.month &&
|
||||
date1.day == date2.day;
|
||||
}
|
||||
|
||||
static bool isSameWeek(DateTime date1, DateTime date2) {
|
||||
final weekStart = date2.subtract(Duration(days: date2.weekday - 1));
|
||||
final weekEnd = weekStart.add(Duration(days: 6));
|
||||
return date1.isAfter(weekStart.subtract(Duration(days: 1))) && date1.isBefore(weekEnd.add(Duration(days: 1)));
|
||||
return date1.isAfter(weekStart.subtract(Duration(days: 1))) &&
|
||||
date1.isBefore(weekEnd.add(Duration(days: 1)));
|
||||
}
|
||||
|
||||
static String getCallTimeline(int milliseconds) {
|
||||
@@ -530,7 +551,8 @@ class IMUtils {
|
||||
return "${_combTime(days, StrRes.day)}${_combTime(hours, StrRes.hours)}${_combTime(minutes, StrRes.minute)}${_combTime(seconds, StrRes.seconds)}";
|
||||
}
|
||||
|
||||
static String _combTime(int value, String unit) => value > 0 ? '$value$unit' : '';
|
||||
static String _combTime(int value, String unit) =>
|
||||
value > 0 ? '$value$unit' : '';
|
||||
|
||||
static String calContent({
|
||||
required String content,
|
||||
@@ -566,9 +588,11 @@ class IMUtils {
|
||||
int maxLines = 1,
|
||||
double maxWidth = double.infinity,
|
||||
}) {
|
||||
final TextPainter textPainter =
|
||||
TextPainter(text: TextSpan(text: text, style: style), maxLines: maxLines, textDirection: TextDirection.ltr)
|
||||
..layout(minWidth: 0, maxWidth: maxWidth);
|
||||
final TextPainter textPainter = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
maxLines: maxLines,
|
||||
textDirection: TextDirection.ltr)
|
||||
..layout(minWidth: 0, maxWidth: maxWidth);
|
||||
return textPainter.size;
|
||||
}
|
||||
|
||||
@@ -578,7 +602,10 @@ class IMUtils {
|
||||
int maxLines = 1,
|
||||
double maxWidth = double.infinity,
|
||||
}) =>
|
||||
TextPainter(text: TextSpan(text: text, style: style), maxLines: maxLines, textDirection: TextDirection.ltr)
|
||||
TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
maxLines: maxLines,
|
||||
textDirection: TextDirection.ltr)
|
||||
..layout(minWidth: 0, maxWidth: maxWidth);
|
||||
|
||||
static bool isUrlValid(String? url) {
|
||||
@@ -600,11 +627,16 @@ class IMUtils {
|
||||
}
|
||||
|
||||
static String getGroupMemberShowName(GroupMembersInfo membersInfo) {
|
||||
return membersInfo.userID == OpenIM.iMManager.userID ? StrRes.you : membersInfo.nickname!;
|
||||
return membersInfo.userID == OpenIM.iMManager.userID
|
||||
? StrRes.you
|
||||
: membersInfo.nickname!;
|
||||
}
|
||||
|
||||
static String getShowName(String? userID, String? nickname) {
|
||||
return (userID == OpenIM.iMManager.userID ? OpenIM.iMManager.userInfo.nickname : nickname) ?? '';
|
||||
return (userID == OpenIM.iMManager.userID
|
||||
? OpenIM.iMManager.userInfo.nickname
|
||||
: nickname) ??
|
||||
'';
|
||||
}
|
||||
|
||||
static String? parseNtf(
|
||||
@@ -628,7 +660,8 @@ class IMUtils {
|
||||
case MessageType.groupInfoSetNotification:
|
||||
{
|
||||
final ntf = GroupNotification.fromJson(map);
|
||||
if (ntf.group?.notification != null && ntf.group!.notification!.isNotEmpty) {
|
||||
if (ntf.group?.notification != null &&
|
||||
ntf.group!.notification!.isNotEmpty) {
|
||||
return isConversation ? ntf.group!.notification! : null;
|
||||
}
|
||||
|
||||
@@ -649,8 +682,12 @@ class IMUtils {
|
||||
final ntf = InvitedJoinGroupNotification.fromJson(map);
|
||||
|
||||
final label = StrRes.invitedJoinGroupNtf;
|
||||
final b = ntf.invitedUserList?.map((e) => getGroupMemberShowName(e)).toList().join('、');
|
||||
text = sprintf(label, [getGroupMemberShowName(ntf.opUser!), b ?? '']);
|
||||
final b = ntf.invitedUserList
|
||||
?.map((e) => getGroupMemberShowName(e))
|
||||
.toList()
|
||||
.join('、');
|
||||
text = sprintf(
|
||||
label, [getGroupMemberShowName(ntf.opUser!), b ?? '']);
|
||||
}
|
||||
break;
|
||||
case MessageType.memberKickedNotification:
|
||||
@@ -658,7 +695,10 @@ class IMUtils {
|
||||
final ntf = KickedGroupMemeberNotification.fromJson(map);
|
||||
|
||||
final label = StrRes.kickedGroupNtf;
|
||||
final b = ntf.kickedUserList!.map((e) => getGroupMemberShowName(e)).toList().join('、');
|
||||
final b = ntf.kickedUserList!
|
||||
.map((e) => getGroupMemberShowName(e))
|
||||
.toList()
|
||||
.join('、');
|
||||
text = sprintf(label, [b, getGroupMemberShowName(ntf.opUser!)]);
|
||||
}
|
||||
break;
|
||||
@@ -683,7 +723,10 @@ class IMUtils {
|
||||
final ntf = GroupRightsTransferNoticication.fromJson(map);
|
||||
|
||||
final label = StrRes.transferredGroupNtf;
|
||||
text = sprintf(label, [getGroupMemberShowName(ntf.opUser!), getGroupMemberShowName(ntf.newGroupOwner!)]);
|
||||
text = sprintf(label, [
|
||||
getGroupMemberShowName(ntf.opUser!),
|
||||
getGroupMemberShowName(ntf.newGroupOwner!)
|
||||
]);
|
||||
}
|
||||
break;
|
||||
case MessageType.groupMemberMutedNotification:
|
||||
@@ -692,8 +735,11 @@ class IMUtils {
|
||||
|
||||
final label = StrRes.muteMemberNtf;
|
||||
final c = ntf.mutedSeconds;
|
||||
text = sprintf(
|
||||
label, [getGroupMemberShowName(ntf.mutedUser!), getGroupMemberShowName(ntf.opUser!), mutedTime(c!)]);
|
||||
text = sprintf(label, [
|
||||
getGroupMemberShowName(ntf.mutedUser!),
|
||||
getGroupMemberShowName(ntf.opUser!),
|
||||
mutedTime(c!)
|
||||
]);
|
||||
}
|
||||
break;
|
||||
case MessageType.groupMemberCancelMutedNotification:
|
||||
@@ -701,7 +747,10 @@ class IMUtils {
|
||||
final ntf = MuteMemberNotification.fromJson(map);
|
||||
|
||||
final label = StrRes.muteCancelMemberNtf;
|
||||
text = sprintf(label, [getGroupMemberShowName(ntf.mutedUser!), getGroupMemberShowName(ntf.opUser!)]);
|
||||
text = sprintf(label, [
|
||||
getGroupMemberShowName(ntf.mutedUser!),
|
||||
getGroupMemberShowName(ntf.opUser!)
|
||||
]);
|
||||
}
|
||||
break;
|
||||
case MessageType.groupMutedNotification:
|
||||
@@ -737,7 +786,8 @@ class IMUtils {
|
||||
break;
|
||||
case MessageType.groupMemberInfoChangedNotification:
|
||||
final ntf = GroupMemberInfoChangedNotification.fromJson(map);
|
||||
text = sprintf(StrRes.memberInfoChangedNtf, [getGroupMemberShowName(ntf.opUser!)]);
|
||||
text = sprintf(StrRes.memberInfoChangedNtf,
|
||||
[getGroupMemberShowName(ntf.opUser!)]);
|
||||
break;
|
||||
case MessageType.groupInfoSetAnnouncementNotification:
|
||||
if (isConversation) {
|
||||
@@ -747,7 +797,8 @@ class IMUtils {
|
||||
break;
|
||||
case MessageType.groupInfoSetNameNotification:
|
||||
final ntf = GroupNotification.fromJson(map);
|
||||
text = sprintf(StrRes.whoModifyGroupName, [getGroupMemberShowName(ntf.opUser!), ntf.group?.groupName]);
|
||||
text = sprintf(StrRes.whoModifyGroupName,
|
||||
[getGroupMemberShowName(ntf.opUser!), ntf.group?.groupName]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -851,7 +902,8 @@ class IMUtils {
|
||||
switch (customType) {
|
||||
case CustomMessageType.call:
|
||||
var type = map['data']['type'];
|
||||
content = '[${type == 'video' ? StrRes.callVideo : StrRes.callVoice}]';
|
||||
content =
|
||||
'[${type == 'video' ? StrRes.callVideo : StrRes.callVoice}]';
|
||||
break;
|
||||
case CustomMessageType.emoji:
|
||||
content = '[${StrRes.emoji}]';
|
||||
@@ -924,7 +976,8 @@ class IMUtils {
|
||||
switch (state) {
|
||||
case 'beHangup':
|
||||
case 'hangup':
|
||||
content = sprintf(StrRes.callDuration, [seconds2HMS(duration)]);
|
||||
content =
|
||||
sprintf(StrRes.callDuration, [seconds2HMS(duration)]);
|
||||
break;
|
||||
case 'cancel':
|
||||
content = StrRes.cancelled;
|
||||
@@ -991,8 +1044,11 @@ class IMUtils {
|
||||
final atUserInfos = message.atTextElem!.atUsersInfo!;
|
||||
|
||||
for (final userID in atUserIDs) {
|
||||
final groupNickname =
|
||||
(newMapping[userID] ?? atUserInfos.firstWhere((e) => e.atUserID == userID).groupNickname) ?? userID;
|
||||
final groupNickname = (newMapping[userID] ??
|
||||
atUserInfos
|
||||
.firstWhere((e) => e.atUserID == userID)
|
||||
.groupNickname) ??
|
||||
userID;
|
||||
mapping[userID] = getAtNickname(userID, groupNickname);
|
||||
}
|
||||
}
|
||||
@@ -1065,20 +1121,27 @@ class IMUtils {
|
||||
previewUrlPicture(
|
||||
[
|
||||
MediaSource(
|
||||
url: message.pictureElem!.sourcePicture!.url!, thumbnail: message.pictureElem!.snapshotPicture!.url!)
|
||||
url: message.pictureElem!.sourcePicture!.url!,
|
||||
thumbnail: message.pictureElem!.snapshotPicture!.url!)
|
||||
],
|
||||
currentIndex: 0,
|
||||
);
|
||||
} else {
|
||||
final picList = allList
|
||||
.where((element) => element.contentType == MessageType.picture || element.contentType == MessageType.video)
|
||||
.where((element) =>
|
||||
element.contentType == MessageType.picture ||
|
||||
element.contentType == MessageType.video)
|
||||
.toList();
|
||||
final index = picList.indexOf(message);
|
||||
final urls = picList.map((e) {
|
||||
if (e.contentType == MessageType.picture) {
|
||||
return MediaSource(url: e.pictureElem!.sourcePicture!.url!, thumbnail: e.pictureElem!.snapshotPicture!.url!);
|
||||
return MediaSource(
|
||||
url: e.pictureElem!.sourcePicture!.url!,
|
||||
thumbnail: e.pictureElem!.snapshotPicture!.url!);
|
||||
} else {
|
||||
return MediaSource(url: e.videoElem!.videoUrl!, thumbnail: e.videoElem!.snapshotUrl!);
|
||||
return MediaSource(
|
||||
url: e.videoElem!.videoUrl!,
|
||||
thumbnail: e.videoElem!.snapshotUrl!);
|
||||
}
|
||||
}).toList();
|
||||
previewUrlPicture(urls, currentIndex: index == -1 ? 0 : index);
|
||||
@@ -1106,7 +1169,8 @@ class IMUtils {
|
||||
const begin = Offset(0.0, 1.0);
|
||||
const end = Offset.zero;
|
||||
const curve = Curves.easeOut;
|
||||
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
||||
final tween =
|
||||
Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
|
||||
final offsetAnimation = animation.drive(tween);
|
||||
|
||||
return SlideTransition(
|
||||
@@ -1136,7 +1200,8 @@ class IMUtils {
|
||||
|
||||
final isExitCachePath = await isExitFile(cachePath);
|
||||
|
||||
Logger.print('isExitSourcePath:$isExitSourcePath, isExitCachePath:$isExitCachePath, cachePath:$cachePath');
|
||||
Logger.print(
|
||||
'isExitSourcePath:$isExitSourcePath, isExitCachePath:$isExitCachePath, cachePath:$cachePath');
|
||||
|
||||
final isExitNetwork = isUrlValid(url);
|
||||
String? availablePath;
|
||||
@@ -1145,9 +1210,11 @@ class IMUtils {
|
||||
} else if (isExitCachePath) {
|
||||
availablePath = cachePath;
|
||||
}
|
||||
final isAvailableFileSize =
|
||||
isExitSourcePath || isExitCachePath ? (await File(availablePath!).length() == fileSize) : false;
|
||||
Logger.print('previewFile isAvailableFileSize: $isAvailableFileSize isExitNetwork: $isExitNetwork');
|
||||
final isAvailableFileSize = isExitSourcePath || isExitCachePath
|
||||
? (await File(availablePath!).length() == fileSize)
|
||||
: false;
|
||||
Logger.print(
|
||||
'previewFile isAvailableFileSize: $isAvailableFileSize isExitNetwork: $isExitNetwork');
|
||||
if (isAvailableFileSize) {
|
||||
String? mimeType = lookupMimeType(fileName ?? '');
|
||||
if (null != mimeType && allowVideoType(mimeType)) {
|
||||
@@ -1159,7 +1226,9 @@ class IMUtils {
|
||||
previewPicture(Message()
|
||||
..clientMsgID = message.clientMsgID
|
||||
..contentType = MessageType.picture
|
||||
..pictureElem = PictureElem(sourcePath: availablePath, sourcePicture: PictureInfo(url: url)));
|
||||
..pictureElem = PictureElem(
|
||||
sourcePath: availablePath,
|
||||
sourcePicture: PictureInfo(url: url)));
|
||||
} else {
|
||||
openFileByOtherApp(availablePath);
|
||||
}
|
||||
@@ -1183,7 +1252,8 @@ class IMUtils {
|
||||
bool onlySave = false,
|
||||
ValueChanged<OperateType>? onOperate}) {
|
||||
void saveVideo(BuildContext ctx, String url, {int? length}) async {
|
||||
final cachedVideoControllerService = CachedVideoControllerService(DefaultCacheManager());
|
||||
final cachedVideoControllerService =
|
||||
CachedVideoControllerService(DefaultCacheManager());
|
||||
final cached = await cachedVideoControllerService.getCacheFile(url);
|
||||
|
||||
if (cached != null) {
|
||||
@@ -1196,7 +1266,8 @@ class IMUtils {
|
||||
} else {
|
||||
LoadingView.singleton.show();
|
||||
|
||||
final downloader = MultiThreadDownloader(url: url, fileName: url.split('/').last, length: length);
|
||||
final downloader = MultiThreadDownloader(
|
||||
url: url, fileName: url.split('/').last, length: length);
|
||||
|
||||
callback(EasyLoadingStatus status) {
|
||||
if (status == EasyLoadingStatus.dismiss) {
|
||||
@@ -1235,7 +1306,8 @@ class IMUtils {
|
||||
switch (type) {
|
||||
case OperateType.save:
|
||||
if (msg.videoElem != null) {
|
||||
saveVideo(context, msg.videoElem!.videoUrl!, length: msg.videoElem!.videoSize);
|
||||
saveVideo(context, msg.videoElem!.videoUrl!,
|
||||
length: msg.videoElem!.videoSize);
|
||||
} else {
|
||||
final url = msg.pictureElem?.sourcePicture?.url;
|
||||
if (url?.isNotEmpty == true) {
|
||||
@@ -1254,14 +1326,18 @@ class IMUtils {
|
||||
final sources = message.isVideoType
|
||||
? MediaSource(
|
||||
url: message.videoElem?.videoUrl,
|
||||
thumbnail: message.videoElem!.snapshotUrl?.adjustThumbnailAbsoluteString(960) ?? '',
|
||||
thumbnail: message.videoElem!.snapshotUrl
|
||||
?.adjustThumbnailAbsoluteString(960) ??
|
||||
'',
|
||||
file: File(message.videoElem!.videoPath!),
|
||||
tag: message.clientMsgID,
|
||||
isVideo: true,
|
||||
)
|
||||
: MediaSource(
|
||||
url: message.pictureElem?.sourcePicture?.url,
|
||||
thumbnail: message.pictureElem!.snapshotPicture?.url?.adjustThumbnailAbsoluteString(960) ?? '',
|
||||
thumbnail: message.pictureElem!.snapshotPicture?.url
|
||||
?.adjustThumbnailAbsoluteString(960) ??
|
||||
'',
|
||||
file: File(message.pictureElem!.sourcePath!),
|
||||
tag: message.clientMsgID,
|
||||
);
|
||||
@@ -1326,19 +1402,36 @@ class IMUtils {
|
||||
}
|
||||
|
||||
static void previewLocation(Message message) {
|
||||
var location = message.locationElem;
|
||||
Map detail = json.decode(location!.description!);
|
||||
Logger.print('previewLocation ${location.latitude} ${location.longitude}');
|
||||
Get.to(
|
||||
() => MapView(
|
||||
try {
|
||||
final location = message.locationElem;
|
||||
if (location == null ||
|
||||
location.latitude == null ||
|
||||
location.longitude == null) {
|
||||
IMViews.showToast(StrRes.locationMessage);
|
||||
return;
|
||||
}
|
||||
final data = LocationBubbleData.parse(
|
||||
description: location.description ?? '',
|
||||
latitude: location.latitude!,
|
||||
longitude: location.longitude!,
|
||||
address1: detail['name'],
|
||||
address2: detail['addr'],
|
||||
),
|
||||
transition: Transition.cupertino,
|
||||
popGesture: true,
|
||||
);
|
||||
fallbackTitle: StrRes.locationMessage,
|
||||
);
|
||||
Logger.print(
|
||||
'previewLocation ${location.latitude} ${location.longitude}');
|
||||
Get.to(
|
||||
() => MapView(
|
||||
latitude: location.latitude!,
|
||||
longitude: location.longitude!,
|
||||
address1: data.title,
|
||||
address2: data.address,
|
||||
),
|
||||
transition: Transition.cupertino,
|
||||
popGesture: true,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logger.print('previewLocation failed: $e $s');
|
||||
IMViews.showToast(StrRes.locationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
static void previewCarteMessage(
|
||||
@@ -1353,7 +1446,8 @@ class IMUtils {
|
||||
Function(Message msg)? meetingItemClick,
|
||||
VoidCallback? onForward,
|
||||
}) async {
|
||||
if (message.contentType == MessageType.picture || message.contentType == MessageType.video) {
|
||||
if (message.contentType == MessageType.picture ||
|
||||
message.contentType == MessageType.video) {
|
||||
previewMediaFile(
|
||||
context: Get.context!,
|
||||
message: message,
|
||||
@@ -1464,15 +1558,18 @@ class IMUtils {
|
||||
if (mimeType == 'application/pdf') {
|
||||
return ImageRes.filePdf;
|
||||
} else if (mimeType == 'application/msword' ||
|
||||
mimeType == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
|
||||
mimeType ==
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
|
||||
return ImageRes.fileWord;
|
||||
} else if (mimeType == 'application/vnd.ms-excel' ||
|
||||
mimeType == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
|
||||
mimeType ==
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
|
||||
return ImageRes.fileExcel;
|
||||
} else if (mimeType == 'application/vnd.ms-powerpoint') {
|
||||
return ImageRes.filePpt;
|
||||
} else if (mimeType.startsWith('audio/')) {
|
||||
} else if (mimeType == 'application/zip' || mimeType == 'application/x-rar-compressed') {
|
||||
} else if (mimeType == 'application/zip' ||
|
||||
mimeType == 'application/x-rar-compressed') {
|
||||
return ImageRes.fileZip;
|
||||
}
|
||||
/*else if (mimeType.startsWith('audio/')) {
|
||||
@@ -1516,7 +1613,10 @@ class IMUtils {
|
||||
final checkedList = <String>[];
|
||||
final values = result.values;
|
||||
for (final value in values) {
|
||||
if (value is UserInfo || value is FriendInfo || value is UserFullInfo || value is ISUserInfo) {
|
||||
if (value is UserInfo ||
|
||||
value is FriendInfo ||
|
||||
value is UserFullInfo ||
|
||||
value is ISUserInfo) {
|
||||
checkedList.add(value.userID!);
|
||||
}
|
||||
}
|
||||
@@ -1531,7 +1631,10 @@ class IMUtils {
|
||||
for (var item in checkedList) {
|
||||
if (item is ConversationInfo) {
|
||||
checkedMap[item.isSingleChat ? item.userID! : item.groupID!] = item;
|
||||
} else if (item is UserInfo || item is UserFullInfo || item is ISUserInfo || item is FriendInfo) {
|
||||
} else if (item is UserInfo ||
|
||||
item is UserFullInfo ||
|
||||
item is ISUserInfo ||
|
||||
item is FriendInfo) {
|
||||
checkedMap[item.userID!] = item;
|
||||
} else if (item is GroupInfo) {
|
||||
checkedMap[item.groupID] = item;
|
||||
@@ -1540,10 +1643,14 @@ class IMUtils {
|
||||
return checkedMap;
|
||||
}
|
||||
|
||||
static List<Map<String, String?>> convertCheckedListToForwardObj(List<dynamic> checkedList) {
|
||||
static List<Map<String, String?>> convertCheckedListToForwardObj(
|
||||
List<dynamic> checkedList) {
|
||||
final map = <Map<String, String?>>[];
|
||||
for (var item in checkedList) {
|
||||
if (item is UserInfo || item is UserFullInfo || item is ISUserInfo || item is FriendInfo) {
|
||||
if (item is UserInfo ||
|
||||
item is UserFullInfo ||
|
||||
item is ISUserInfo ||
|
||||
item is FriendInfo) {
|
||||
map.add({'nickname': item.nickname, 'faceURL': item.faceURL});
|
||||
} else if (item is GroupInfo) {
|
||||
map.add({'nickname': item.groupName, 'faceURL': item.faceURL});
|
||||
@@ -1555,7 +1662,10 @@ class IMUtils {
|
||||
}
|
||||
|
||||
static String? convertCheckedToUserID(dynamic info) {
|
||||
if (info is UserInfo || info is UserFullInfo || info is ISUserInfo || info is FriendInfo) {
|
||||
if (info is UserInfo ||
|
||||
info is UserFullInfo ||
|
||||
info is ISUserInfo ||
|
||||
info is FriendInfo) {
|
||||
return info.userID;
|
||||
} else if (info is ConversationInfo) {
|
||||
return info.userID;
|
||||
@@ -1570,14 +1680,18 @@ class IMUtils {
|
||||
} else if (info is ConversationInfo) {
|
||||
return info.groupID;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<Map<String, String?>> convertCheckedListToShare(Iterable<dynamic> checkedList) {
|
||||
static List<Map<String, String?>> convertCheckedListToShare(
|
||||
Iterable<dynamic> checkedList) {
|
||||
final map = <Map<String, String?>>[];
|
||||
for (var item in checkedList) {
|
||||
if (item is UserInfo || item is UserFullInfo || item is ISUserInfo || item is FriendInfo) {
|
||||
if (item is UserInfo ||
|
||||
item is UserFullInfo ||
|
||||
item is ISUserInfo ||
|
||||
item is FriendInfo) {
|
||||
map.add({'userID': item.userID, 'groupID': null});
|
||||
} else if (item is GroupInfo) {
|
||||
map.add({'userID': null, 'groupID': item.groupID});
|
||||
@@ -1612,8 +1726,10 @@ class IMUtils {
|
||||
return formatDateMs(ms, format: isZH ? 'yyyy年MM月dd' : 'yyyy/MM/dd');
|
||||
}
|
||||
|
||||
static Future<bool> checkingBiometric(LocalAuthentication auth) => auth.authenticate(
|
||||
localizedReason: 'Scan your fingerprint (or face or other) to authenticate.',
|
||||
static Future<bool> checkingBiometric(LocalAuthentication auth) =>
|
||||
auth.authenticate(
|
||||
localizedReason:
|
||||
'Scan your fingerprint (or face or other) to authenticate.',
|
||||
options: const AuthenticationOptions(
|
||||
biometricOnly: true,
|
||||
),
|
||||
@@ -1636,7 +1752,8 @@ class IMUtils {
|
||||
goToSettingsButton: 'Go to settings',
|
||||
goToSettingsDescription:
|
||||
'No biometric authentication is set up on your device. Please enable Touch ID or Face ID on your phone.',
|
||||
lockOut: 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.',
|
||||
lockOut:
|
||||
'Biometric authentication is disabled. Please lock and unlock your screen to enable it.',
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -1664,11 +1781,15 @@ class IMUtils {
|
||||
r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d\S]{6,20}$',
|
||||
).hasMatch(password);
|
||||
|
||||
static TextInputFormatter getPasswordFormatter() => FilteringTextInputFormatter.allow(
|
||||
static TextInputFormatter getPasswordFormatter() =>
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'[a-zA-Z0-9\S]'),
|
||||
);
|
||||
|
||||
static Future requestBackgroundPermission({required String title, required String text, bool isRetry = false}) async {
|
||||
static Future requestBackgroundPermission(
|
||||
{required String title,
|
||||
required String text,
|
||||
bool isRetry = false}) async {
|
||||
if (!Platform.isAndroid) {
|
||||
return;
|
||||
}
|
||||
@@ -1680,7 +1801,8 @@ class IMUtils {
|
||||
notificationTitle: title,
|
||||
notificationText: text,
|
||||
notificationImportance: AndroidNotificationImportance.normal,
|
||||
notificationIcon: const AndroidResource(name: 'ic_launcher', defType: 'mipmap'),
|
||||
notificationIcon: const AndroidResource(
|
||||
name: 'ic_launcher', defType: 'mipmap'),
|
||||
shouldRequestBatteryOptimizationsOff: false));
|
||||
}
|
||||
if (hasPermissions && !FlutterBackground.isBackgroundExecutionEnabled) {
|
||||
@@ -1689,7 +1811,9 @@ class IMUtils {
|
||||
} catch (e) {
|
||||
if (!isRetry) {
|
||||
return await Future<void>.delayed(
|
||||
const Duration(seconds: 1), () => requestBackgroundPermission(title: title, text: text, isRetry: true));
|
||||
const Duration(seconds: 1),
|
||||
() => requestBackgroundPermission(
|
||||
title: title, text: text, isRetry: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1698,5 +1822,6 @@ class IMUtils {
|
||||
extension PlatformExt on Platform {
|
||||
static bool get isMobile => Platform.isIOS || Platform.isAndroid;
|
||||
|
||||
static bool get isDesktop => Platform.isLinux || Platform.isMacOS || Platform.isWindows;
|
||||
static bool get isDesktop =>
|
||||
Platform.isLinux || Platform.isMacOS || Platform.isWindows;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user