fix(mobile-next): 移除悬浮窗申请与位置发送,通话文案改为语音通话
同步完成后不再索取 SYSTEM_ALERT_WINDOW,去掉旧浮窗服务声明和定位权限/发送入口;历史位置消息仍只读展示。 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
d6fafe7f95
@@ -12,7 +12,7 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
@@ -26,14 +26,14 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
|
||||
<uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
|
||||
@@ -130,10 +130,6 @@
|
||||
android:exported="false"
|
||||
android:stopWithTask="false" />
|
||||
|
||||
<service
|
||||
android:name="io.openim.live.alert.flutter_openim_live_alert.services.CallService"
|
||||
android:exported="false" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileProvider"
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:openim_common/openim_common.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:openim_live/openim_live.dart';
|
||||
|
||||
import '../../company/feature_flags.dart';
|
||||
@@ -236,9 +234,6 @@ class IMController extends GetxController with IMCallback, OpenIMLive {
|
||||
},
|
||||
onSyncServerFinish: (reInstall) {
|
||||
imSdkStatus(IMSdkStatus.syncEnded, reInstall: reInstall ?? false);
|
||||
if (Platform.isAndroid) {
|
||||
Permissions.request([Permission.systemAlertWindow]);
|
||||
}
|
||||
},
|
||||
onSyncServerStart: (reInstall) {
|
||||
imSdkStatus(IMSdkStatus.syncStart, reInstall: reInstall ?? false);
|
||||
|
||||
@@ -446,17 +446,6 @@ class ChatLogic extends SuperController {
|
||||
_sendMessage(message);
|
||||
}
|
||||
|
||||
void sendLocation({
|
||||
required dynamic location,
|
||||
}) async {
|
||||
var message = await OpenIM.iMManager.messageManager.createLocationMessage(
|
||||
latitude: location['latitude'],
|
||||
longitude: location['longitude'],
|
||||
description: location['description'],
|
||||
);
|
||||
_sendMessage(message);
|
||||
}
|
||||
|
||||
sendForwardRemarkMsg(
|
||||
String content, {
|
||||
String? userId,
|
||||
@@ -714,18 +703,6 @@ class ChatLogic extends SuperController {
|
||||
forceCloseToolbox.addSafely(true);
|
||||
}
|
||||
|
||||
void onTapLocation() async {
|
||||
var location = await Get.to(
|
||||
const ChatWebViewMap(host: Config.locationHost, webKey: Config.webKey, webServerKey: Config.webServerKey),
|
||||
transition: Transition.cupertino,
|
||||
popGesture: true,
|
||||
);
|
||||
if (null != location) {
|
||||
Logger.print(location);
|
||||
sendLocation(location: location);
|
||||
}
|
||||
}
|
||||
|
||||
void onTapAlbum() async {
|
||||
final List<AssetEntity>? assets = await AssetPicker.pickAssets(Get.context!,
|
||||
pickerConfig: AssetPickerConfig(
|
||||
|
||||
@@ -247,7 +247,6 @@ class ChatPage extends StatelessWidget {
|
||||
onTapCall: FeatureFlags.livekitCall && logic.isSingleChat ? logic.call : null,
|
||||
onTapCard: logic.onTapCarte,
|
||||
onTapFile: logic.onTapFile,
|
||||
onTapLocation: logic.onTapLocation,
|
||||
),
|
||||
voiceRecordBar: bar,
|
||||
emojiView: ChatEmojiView(
|
||||
|
||||
@@ -76,7 +76,6 @@ export 'src/widgets/chat/chat_voice_record_bar.dart';
|
||||
export 'src/widgets/chat/chat_voice_record_layout.dart';
|
||||
export 'src/widgets/chat/chat_voice_record_view.dart';
|
||||
export 'src/widgets/chat/chat_voice_view.dart';
|
||||
export 'src/widgets/chat/chat_webview_map.dart';
|
||||
export 'src/widgets/chat/new_message_indicator.dart';
|
||||
export 'src/widgets/chat/water_mark_view.dart';
|
||||
export 'src/widgets/custom_pop_up_menu.dart';
|
||||
|
||||
@@ -109,7 +109,7 @@ const Map<String, String> en_US = {
|
||||
'rejectCall': 'Reject',
|
||||
'acceptCall': 'Accept',
|
||||
"callVoice": "Voice Call",
|
||||
"callVideo": "Video Call",
|
||||
"callVideo": "Voice Call",
|
||||
"sentSuccessfully": "Sent Successfully",
|
||||
"copySuccessfully": "Copied Successfully",
|
||||
"day": "day",
|
||||
@@ -120,7 +120,7 @@ const Map<String, String> en_US = {
|
||||
"cancel": "Cancel",
|
||||
"determine": "OK",
|
||||
"toolboxAlbum": "Album",
|
||||
"toolboxCall": "Video Call",
|
||||
"toolboxCall": "Voice Call",
|
||||
"toolboxCamera": "Camera",
|
||||
"toolboxCard": "Card",
|
||||
"toolboxFile": "File",
|
||||
@@ -156,7 +156,7 @@ const Map<String, String> en_US = {
|
||||
'nMessage': '%s New Messages',
|
||||
'plsSelectLocation': 'Please select a location',
|
||||
'groupAudioCallHint': '%s people in a voice call',
|
||||
'groupVideoCallHint': '%s people in a video call',
|
||||
'groupVideoCallHint': '%s people in a voice call',
|
||||
'reEdit': 'Re-edit',
|
||||
'playSpeed': 'Playback Speed',
|
||||
'download': 'Download',
|
||||
@@ -224,7 +224,7 @@ const Map<String, String> en_US = {
|
||||
'position': 'Position',
|
||||
'personalInfo': 'Personal Info',
|
||||
'viewDynamics': 'View Dynamics',
|
||||
'audioAndVideoCall': 'Call',
|
||||
'audioAndVideoCall': 'Voice Call',
|
||||
'sendMessage': 'Message',
|
||||
'avatar': 'Avatar',
|
||||
'name': 'Name',
|
||||
@@ -436,7 +436,7 @@ const Map<String, String> en_US = {
|
||||
"waitingVoiceCallHint": "Waiting for the other party to accept the invitation...",
|
||||
"invitedVoiceCallHint": "Inviting you to a voice call",
|
||||
"waitingVideoCallHint": "Waiting for the other party to accept the invitation",
|
||||
"invitedVideoCallHint": "Inviting you for a video call...",
|
||||
"invitedVideoCallHint": "Inviting you to a voice call",
|
||||
"waitingToAnswer": "Waiting to answer",
|
||||
"invitedYouToCall": "Invited you to a call",
|
||||
"calling": "Calling...",
|
||||
@@ -444,7 +444,7 @@ const Map<String, String> en_US = {
|
||||
'busyVideoCallHint': 'The other party is busy and cannot receive the invitation',
|
||||
'inviterBusyVideoCallHint': 'You are in another call and cannot send an invitation',
|
||||
"whoInvitedVoiceCallHint": "%s invited you for a voice call",
|
||||
"whoInvitedVideoCallHint": "%s invited you for a video call",
|
||||
"whoInvitedVideoCallHint": "%s invited you for a voice call",
|
||||
'plsInputMeetingSubject': 'Please enter meeting subject',
|
||||
'meetingStartTime': 'Start Time',
|
||||
'meetingDuration': 'Meeting Duration',
|
||||
|
||||
@@ -109,7 +109,7 @@ const Map<String, String> zh_CN = {
|
||||
'rejectCall': '拒绝',
|
||||
'acceptCall': '同意',
|
||||
"callVoice": "语音通话",
|
||||
"callVideo": "视频通话",
|
||||
"callVideo": "语音通话",
|
||||
"sentSuccessfully": "发送成功",
|
||||
"copySuccessfully": "复制成功",
|
||||
"day": "天",
|
||||
@@ -120,7 +120,7 @@ const Map<String, String> zh_CN = {
|
||||
"cancel": "取消",
|
||||
"determine": "确定",
|
||||
"toolboxAlbum": "相册",
|
||||
"toolboxCall": "视频通话",
|
||||
"toolboxCall": "语音通话",
|
||||
"toolboxCamera": "拍摄",
|
||||
"toolboxCard": "名片",
|
||||
"toolboxFile": "文件",
|
||||
@@ -156,7 +156,7 @@ const Map<String, String> zh_CN = {
|
||||
'nMessage': '%s条新消息',
|
||||
'plsSelectLocation': '请选择一个位置',
|
||||
'groupAudioCallHint': '%s人正在语音通话',
|
||||
'groupVideoCallHint': '%s人正在视频通话',
|
||||
'groupVideoCallHint': '%s人正在语音通话',
|
||||
'reEdit': '重新编辑',
|
||||
'playSpeed': '播放速度',
|
||||
'download': '下载',
|
||||
@@ -224,7 +224,7 @@ const Map<String, String> zh_CN = {
|
||||
'position': '职位',
|
||||
'personalInfo': '个人资料',
|
||||
'viewDynamics': '查看动态',
|
||||
'audioAndVideoCall': '音视频通话',
|
||||
'audioAndVideoCall': '语音通话',
|
||||
'sendMessage': '发消息',
|
||||
'avatar': '头像',
|
||||
'name': '姓名',
|
||||
@@ -438,7 +438,7 @@ const Map<String, String> zh_CN = {
|
||||
"waitingVoiceCallHint": "正在等待对方接受邀请…",
|
||||
"invitedVoiceCallHint": "邀请你语音通话",
|
||||
"waitingVideoCallHint": "正在等待对方接受邀请",
|
||||
"invitedVideoCallHint": "邀请你进行视频通话…",
|
||||
"invitedVideoCallHint": "邀请你语音通话",
|
||||
"waitingToAnswer": "等待接听",
|
||||
"invitedYouToCall": "邀请你通话",
|
||||
"calling": "通话中",
|
||||
@@ -446,7 +446,7 @@ const Map<String, String> zh_CN = {
|
||||
'busyVideoCallHint': '对方正忙,无法接收邀请',
|
||||
'inviterBusyVideoCallHint': '你在其它通话中,无法发起邀请',
|
||||
"whoInvitedVoiceCallHint": "%s邀请你进行语音通话",
|
||||
"whoInvitedVideoCallHint": "%s邀请你进行视频通话",
|
||||
"whoInvitedVideoCallHint": "%s邀请你进行语音通话",
|
||||
'plsInputMeetingSubject': '请输入会议主题',
|
||||
'meetingStartTime': '开始时间',
|
||||
'meetingDuration': '会议时长',
|
||||
|
||||
@@ -10,10 +10,6 @@ import 'package:sprintf/sprintf.dart';
|
||||
class Permissions {
|
||||
Permissions._();
|
||||
|
||||
static Future<bool> checkSystemAlertWindow() async {
|
||||
return Permission.systemAlertWindow.isGranted;
|
||||
}
|
||||
|
||||
static Future<bool> checkStorage() async {
|
||||
return await Permission.storage.isGranted;
|
||||
}
|
||||
@@ -66,15 +62,6 @@ class Permissions {
|
||||
}
|
||||
}
|
||||
|
||||
static void location(Function()? onGranted) async {
|
||||
if (await Permission.location.request().isGranted) {
|
||||
onGranted?.call();
|
||||
}
|
||||
if (await Permission.location.isPermanentlyDenied || await Permission.location.isDenied) {
|
||||
_showPermissionDeniedDialog(Permission.location.title);
|
||||
}
|
||||
}
|
||||
|
||||
static void speech(Function()? onGranted) async {
|
||||
if (await Permission.speech.request().isGranted) {
|
||||
onGranted?.call();
|
||||
|
||||
@@ -850,8 +850,7 @@ class IMUtils {
|
||||
var customData = map['data'];
|
||||
switch (customType) {
|
||||
case CustomMessageType.call:
|
||||
var type = map['data']['type'];
|
||||
content = '[${type == 'video' ? StrRes.callVideo : StrRes.callVoice}]';
|
||||
content = '[${StrRes.callVoice}]';
|
||||
break;
|
||||
case CustomMessageType.emoji:
|
||||
content = '[${StrRes.emoji}]';
|
||||
@@ -1326,15 +1325,19 @@ class IMUtils {
|
||||
}
|
||||
|
||||
static void previewLocation(Message message) {
|
||||
var location = message.locationElem;
|
||||
Map detail = json.decode(location!.description!);
|
||||
final location = message.locationElem;
|
||||
if (location == null) {
|
||||
IMViews.showToast(StrRes.location);
|
||||
return;
|
||||
}
|
||||
final detail = LocationPreviewData.parse(location.description);
|
||||
Logger.print('previewLocation ${location.latitude} ${location.longitude}');
|
||||
Get.to(
|
||||
() => MapView(
|
||||
latitude: location.latitude!,
|
||||
longitude: location.longitude!,
|
||||
address1: detail['name'],
|
||||
address2: detail['addr'],
|
||||
latitude: location.latitude ?? 0,
|
||||
longitude: location.longitude ?? 0,
|
||||
address1: detail.name,
|
||||
address2: detail.addr,
|
||||
),
|
||||
transition: Transition.cupertino,
|
||||
popGesture: true,
|
||||
|
||||
@@ -78,7 +78,7 @@ class ImageTextButton extends StatelessWidget {
|
||||
final Function()? onTap;
|
||||
|
||||
ImageTextButton.call({super.key, this.onTap})
|
||||
: icon = ImageRes.audioAndVideoCall,
|
||||
: icon = ImageRes.callVoice,
|
||||
text = StrRes.audioAndVideoCall,
|
||||
color = Styles.c_FFFFFF,
|
||||
textStyle = null,
|
||||
|
||||
@@ -276,9 +276,9 @@ class _ChatItemViewState extends State<ChatItemView> {
|
||||
} else if (_message.isLocationType) {
|
||||
final location = _message.locationElem;
|
||||
child = ChatLocationView(
|
||||
description: location!.description!,
|
||||
latitude: location.latitude!,
|
||||
longitude: location.longitude!,
|
||||
description: location?.description ?? '',
|
||||
latitude: location?.latitude ?? 0,
|
||||
longitude: location?.longitude ?? 0,
|
||||
);
|
||||
} else if (_message.isCardType) {
|
||||
child = ChatCarteView(cardElem: _message.cardElem!);
|
||||
|
||||
@@ -4,63 +4,96 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:openim_common/openim_common.dart';
|
||||
|
||||
class LocationPreviewData {
|
||||
const LocationPreviewData({
|
||||
required this.name,
|
||||
required this.addr,
|
||||
required this.url,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String addr;
|
||||
final String url;
|
||||
|
||||
static LocationPreviewData parse(String? description) {
|
||||
var name = '';
|
||||
var addr = '';
|
||||
var url = '';
|
||||
try {
|
||||
final raw = description?.trim() ?? '';
|
||||
if (raw.isNotEmpty) {
|
||||
final decoded = json.decode(raw);
|
||||
if (decoded is Map) {
|
||||
url = '${decoded['url'] ?? ''}';
|
||||
name = '${decoded['name'] ?? ''}';
|
||||
addr = '${decoded['addr'] ?? ''}';
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
if (name.trim().isEmpty) {
|
||||
name = StrRes.location;
|
||||
}
|
||||
return LocationPreviewData(name: name, addr: addr, url: url);
|
||||
}
|
||||
}
|
||||
|
||||
class ChatLocationView extends StatelessWidget {
|
||||
const ChatLocationView({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.description,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
}) : super(key: key);
|
||||
});
|
||||
final String description;
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
final _decoder = const JsonDecoder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
try {
|
||||
final map = _decoder.convert(description);
|
||||
String url = map['url'] ?? '';
|
||||
String name = map['name'] ?? '';
|
||||
String addr = map['addr'] ?? '';
|
||||
return Container(
|
||||
width: locationWidth,
|
||||
height: 130.h,
|
||||
decoration: BoxDecoration(
|
||||
color: Styles.c_FFFFFF,
|
||||
border: Border.all(color: Styles.c_E8EAEF, width: 1),
|
||||
borderRadius: BorderRadius.circular(6.r),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
4.verticalSpace,
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4.w),
|
||||
child: name.toText
|
||||
..style = Styles.ts_0C1C33_14sp
|
||||
..maxLines = 1
|
||||
..overflow = TextOverflow.ellipsis,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4.w),
|
||||
child: addr.toText
|
||||
..style = Styles.ts_8E9AB0_12sp
|
||||
..maxLines = 1
|
||||
..overflow = TextOverflow.ellipsis,
|
||||
),
|
||||
2.verticalSpace,
|
||||
Expanded(
|
||||
child: ImageUtil.networkImage(
|
||||
url: url,
|
||||
width: locationWidth,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} catch (e) {}
|
||||
return Container();
|
||||
final data = LocationPreviewData.parse(description);
|
||||
return Container(
|
||||
width: locationWidth,
|
||||
height: 130.h,
|
||||
decoration: BoxDecoration(
|
||||
color: Styles.c_FFFFFF,
|
||||
border: Border.all(color: Styles.c_E8EAEF, width: 1),
|
||||
borderRadius: BorderRadius.circular(6.r),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
4.verticalSpace,
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4.w),
|
||||
child: data.name.toText
|
||||
..style = Styles.ts_0C1C33_14sp
|
||||
..maxLines = 1
|
||||
..overflow = TextOverflow.ellipsis,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4.w),
|
||||
child: (data.addr.isEmpty ? StrRes.location : data.addr).toText
|
||||
..style = Styles.ts_8E9AB0_12sp
|
||||
..maxLines = 1
|
||||
..overflow = TextOverflow.ellipsis,
|
||||
),
|
||||
2.verticalSpace,
|
||||
Expanded(
|
||||
child: data.url.isEmpty
|
||||
? ColoredBox(
|
||||
color: Styles.c_F0F2F6,
|
||||
child: Center(
|
||||
child: StrRes.location.toText..style = Styles.ts_8E9AB0_12sp,
|
||||
),
|
||||
)
|
||||
: ImageUtil.networkImage(
|
||||
url: data.url,
|
||||
width: locationWidth,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ class ChatToolBox extends StatelessWidget {
|
||||
this.onTapCamera,
|
||||
this.onTapCard,
|
||||
this.onTapFile,
|
||||
this.onTapLocation,
|
||||
this.onTapDirectionalMessage,
|
||||
});
|
||||
final Function()? onTapAlbum;
|
||||
@@ -18,7 +17,6 @@ class ChatToolBox extends StatelessWidget {
|
||||
final Function()? onTapCall;
|
||||
final Function()? onTapFile;
|
||||
final Function()? onTapCard;
|
||||
final Function()? onTapLocation;
|
||||
final VoidCallback? onTapDirectionalMessage;
|
||||
|
||||
@override
|
||||
@@ -50,11 +48,6 @@ class ChatToolBox extends StatelessWidget {
|
||||
icon: ImageRes.toolboxCard,
|
||||
onTap: onTapCard,
|
||||
),
|
||||
ToolboxItemInfo(
|
||||
text: StrRes.toolboxLocation,
|
||||
icon: ImageRes.toolboxLocation,
|
||||
onTap: () => Permissions.location(onTapLocation),
|
||||
),
|
||||
if (onTapDirectionalMessage != null)
|
||||
ToolboxItemInfo(
|
||||
text: StrRes.toolboxDirectionalMessage,
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:openim_common/openim_common.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
import 'package:webview_flutter_android/webview_flutter_android.dart';
|
||||
|
||||
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
|
||||
|
||||
class ChatWebViewMap extends StatefulWidget {
|
||||
const ChatWebViewMap({
|
||||
super.key,
|
||||
required this.host,
|
||||
required this.webKey,
|
||||
required this.webServerKey,
|
||||
this.mapThumbnailSize = "1200*600",
|
||||
this.mapBackUrl = "http://callback",
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
});
|
||||
|
||||
final String host;
|
||||
final String webKey;
|
||||
final String webServerKey;
|
||||
final String mapThumbnailSize;
|
||||
final String mapBackUrl;
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
|
||||
@override
|
||||
State<ChatWebViewMap> createState() => _ChatWebViewMapState();
|
||||
}
|
||||
|
||||
class _ChatWebViewMapState extends State<ChatWebViewMap> {
|
||||
WebViewController? _controller;
|
||||
|
||||
String url = "";
|
||||
double progress = 0;
|
||||
double? latitude;
|
||||
double? longitude;
|
||||
String? description;
|
||||
|
||||
late String locationUrl;
|
||||
late String thumbnailUrl;
|
||||
|
||||
late String previewLocationUrl;
|
||||
|
||||
late String webKey;
|
||||
late String webServerKey;
|
||||
late String host;
|
||||
|
||||
void _configUrl() {
|
||||
locationUrl = "$host?key=$webKey&serverKey=$webServerKey#/";
|
||||
previewLocationUrl = "$host?key=$webKey&serverKey=$webServerKey&location=$longitude,$latitude#/";
|
||||
}
|
||||
|
||||
String getStaticMapURL(double longitude, double latitude) {
|
||||
final url =
|
||||
'https://restapi.amap.com/v3/staticmap?location=$longitude,$latitude&zoom=13&size=200*200&markers=mid,,A:$longitude,$latitude&key=$webServerKey';
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
bool get isPreview => widget.longitude != null && widget.latitude != null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
host = widget.host;
|
||||
webKey = widget.webKey;
|
||||
webServerKey = widget.webServerKey;
|
||||
|
||||
longitude = widget.longitude;
|
||||
latitude = widget.latitude;
|
||||
|
||||
_determinePosition().then((position) {
|
||||
longitude = position.longitude;
|
||||
latitude = position.latitude;
|
||||
|
||||
_configUrl();
|
||||
|
||||
late final PlatformWebViewControllerCreationParams params;
|
||||
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
|
||||
params = WebKitWebViewControllerCreationParams(
|
||||
allowsInlineMediaPlayback: true,
|
||||
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
|
||||
);
|
||||
} else {
|
||||
params = const PlatformWebViewControllerCreationParams();
|
||||
}
|
||||
|
||||
final WebViewController controller = WebViewController.fromPlatformCreationParams(params);
|
||||
|
||||
controller
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onProgress: (int progress) {
|
||||
debugPrint('WebView is loading (progress : $progress%)');
|
||||
setState(() {
|
||||
this.progress = progress / 100;
|
||||
});
|
||||
},
|
||||
onPageStarted: (String url) {
|
||||
debugPrint('Page started loading: $url');
|
||||
},
|
||||
onPageFinished: (String url) {
|
||||
debugPrint('Page finished loading: $url');
|
||||
},
|
||||
onWebResourceError: (WebResourceError error) {
|
||||
debugPrint(
|
||||
'Page resource error: code: ${error.errorCode} description: ${error.description} errorType: ${error.errorType} isForMainFrame: ${error.isForMainFrame}');
|
||||
},
|
||||
onNavigationRequest: (NavigationRequest request) {
|
||||
if (request.url.startsWith('https://www.youtube.com/')) {
|
||||
debugPrint('blocking navigation to ${request.url}');
|
||||
return NavigationDecision.prevent;
|
||||
}
|
||||
debugPrint('allowing navigation to ${request.url}');
|
||||
return NavigationDecision.navigate;
|
||||
},
|
||||
onHttpError: (HttpResponseError error) {
|
||||
debugPrint('Error occurred on page: ${error.response?.statusCode}');
|
||||
},
|
||||
onUrlChange: (UrlChange change) {
|
||||
debugPrint('url change to ${change.url}');
|
||||
},
|
||||
onHttpAuthRequest: (HttpAuthRequest request) {},
|
||||
),
|
||||
)
|
||||
..addJavaScriptChannel(
|
||||
'getLocaltion',
|
||||
onMessageReceived: (JavaScriptMessage message) {
|
||||
final value = message.message;
|
||||
final params = jsonDecode(value);
|
||||
final locationStr = params['location'] as String;
|
||||
final locations = locationStr.split(',');
|
||||
longitude = double.parse(locations[0]);
|
||||
latitude = double.parse(locations[1]);
|
||||
final address = params['address'];
|
||||
final url = getStaticMapURL(longitude!, latitude!);
|
||||
|
||||
final result = {
|
||||
'longitude': longitude,
|
||||
'latitude': latitude,
|
||||
'url': url,
|
||||
'addr': address,
|
||||
'name': '',
|
||||
};
|
||||
|
||||
description = jsonEncode(result);
|
||||
Logger.print('$result');
|
||||
|
||||
_confirm();
|
||||
},
|
||||
)
|
||||
..loadRequest(Uri.parse(previewLocationUrl));
|
||||
|
||||
print('previewLocationUrl: $previewLocationUrl');
|
||||
|
||||
if (!Platform.isMacOS) {
|
||||
controller.setBackgroundColor(const Color(0x80000000));
|
||||
}
|
||||
|
||||
if (controller.platform is AndroidWebViewController) {
|
||||
AndroidWebViewController.enableDebugging(true);
|
||||
|
||||
(controller.platform as AndroidWebViewController).setMediaPlaybackRequiresUserGesture(false);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_controller = controller;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<Position> _determinePosition() async {
|
||||
bool serviceEnabled;
|
||||
LocationPermission permission;
|
||||
|
||||
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
return Future.error('Location services are disabled.');
|
||||
}
|
||||
|
||||
permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
return Future.error('Location permissions are denied');
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
return Future.error('Location permissions are permanently denied, we cannot request permissions.');
|
||||
}
|
||||
|
||||
return await Geolocator.getCurrentPosition();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _confirm() async {
|
||||
if (null == latitude || null == longitude) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: StrRes.plsSelectLocation.toText..style = Styles.ts_0C1C33_17sp_semibold,
|
||||
actions: [
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h),
|
||||
child: StrRes.determine.toText..style = Styles.ts_0089FF_17sp_semibold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
Navigator.pop(context, {
|
||||
'latitude': latitude,
|
||||
'longitude': longitude,
|
||||
'description': description,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: TitleBar.back(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
},
|
||||
title: StrRes.location,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
_controller == null
|
||||
? const Align(
|
||||
child: CupertinoActivityIndicator(),
|
||||
)
|
||||
: WebViewWidget(controller: _controller!),
|
||||
progress < 1.0
|
||||
? LinearProgressIndicator(
|
||||
value: progress,
|
||||
color: Colors.blue,
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,9 @@ class RichTextInputBox extends StatefulWidget {
|
||||
this.showCameraIcon = true,
|
||||
this.showCardIcon = true,
|
||||
this.showFileIcon = true,
|
||||
this.showLocationIcon = true,
|
||||
this.onTapAlbum,
|
||||
this.onTapCard,
|
||||
this.onTapFile,
|
||||
this.onTapLocation,
|
||||
this.onSend,
|
||||
}) : super(key: key);
|
||||
final TextEditingController? controller;
|
||||
@@ -29,12 +27,10 @@ class RichTextInputBox extends StatefulWidget {
|
||||
final bool showCameraIcon;
|
||||
final bool showFileIcon;
|
||||
final bool showCardIcon;
|
||||
final bool showLocationIcon;
|
||||
final Function()? onTapAlbum;
|
||||
final Function()? onTapCamera;
|
||||
final Function()? onTapFile;
|
||||
final Function()? onTapCard;
|
||||
final Function()? onTapLocation;
|
||||
final Function()? onSend;
|
||||
|
||||
@override
|
||||
@@ -113,19 +109,12 @@ class _RichTextInputBoxState extends State<RichTextInputBox> {
|
||||
..height = 22.h
|
||||
..opacity = _opacity
|
||||
..onTap = widget.onTapCard,
|
||||
if (widget.showLocationIcon)
|
||||
ImageRes.toolboxLocation1.toImage
|
||||
..width = 16.w
|
||||
..height = 22.h
|
||||
..opacity = _opacity
|
||||
..onTap = widget.onTapLocation,
|
||||
],
|
||||
),
|
||||
if (widget.showAlbumIcon ||
|
||||
widget.showCameraIcon ||
|
||||
widget.showCardIcon ||
|
||||
widget.showFileIcon ||
|
||||
widget.showLocationIcon)
|
||||
widget.showFileIcon)
|
||||
15.verticalSpace,
|
||||
Row(
|
||||
children: [
|
||||
|
||||
@@ -72,12 +72,6 @@ class IMViews {
|
||||
alignment: MainAxisAlignment.start,
|
||||
onTap: () => onTapSheetItem.call(0),
|
||||
),
|
||||
SheetItem(
|
||||
label: StrRes.callVideo,
|
||||
icon: ImageRes.callVideo,
|
||||
alignment: MainAxisAlignment.start,
|
||||
onTap: () => onTapSheetItem.call(1),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -96,11 +90,6 @@ class IMViews {
|
||||
icon: ImageRes.callVoice,
|
||||
onTap: () => onTapSheetItem.call(0),
|
||||
),
|
||||
SheetItem(
|
||||
label: StrRes.callVideo,
|
||||
icon: ImageRes.callVideo,
|
||||
onTap: () => onTapSheetItem.call(1),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -79,7 +79,6 @@ dependencies:
|
||||
flutter_map: ^6.0.1
|
||||
ffmpeg_kit_flutter_full_gpl: 6.0.3
|
||||
pull_to_refresh_new: ^2.0.5
|
||||
geolocator: ^12.0.0
|
||||
fixnum: ^1.1.0
|
||||
protobuf: ^3.0.0
|
||||
intl: ^0.19.0
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -100,13 +99,6 @@ mixin OpenIMLive {
|
||||
final callType = mediaType == 'audio' ? CallType.audio : CallType.video;
|
||||
final callObj = sessionType == ConversationType.single ? CallObj.single : CallObj.group;
|
||||
|
||||
if (Platform.isAndroid && _isRunningBackground) {
|
||||
_beCalledEvent = event;
|
||||
if (await Permissions.checkSystemAlertWindow()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_beCalledEvent = null;
|
||||
OpenIMLiveClient().start(
|
||||
Get.overlayContext!,
|
||||
callEventSubject: signalingSubject,
|
||||
|
||||
@@ -930,54 +930,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
geolocator:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: "149876cc5207a0f5daf4fdd3bfcf0a0f27258b3fe95108fa084f527ad0568f1b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.0"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.1"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: c4ecead17985ede9634f21500072edfcb3dba0ef7b97f8d7bc556d2d722b3ba3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.9"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: "2ed69328e05cd94e7eb48bb0535f5fc0c0c44d1c4fa1e9737267484d05c29b5e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.1"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
gesture_password_widget:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:openim_common/src/res/lang/zh_CN.dart';
|
||||
import 'package:openim_common/src/widgets/chat/chat_location_view.dart';
|
||||
|
||||
void main() {
|
||||
test('用户可见通话文案不再出现视频通话或音视频通话', () {
|
||||
for (final entry in zh_CN.entries) {
|
||||
expect(entry.value.contains('视频通话'), isFalse, reason: '${entry.key}=${entry.value}');
|
||||
expect(entry.value.contains('音视频通话'), isFalse, reason: '${entry.key}=${entry.value}');
|
||||
}
|
||||
expect(zh_CN['toolboxCall'], '语音通话');
|
||||
expect(zh_CN['audioAndVideoCall'], '语音通话');
|
||||
expect(zh_CN['video'], '视频');
|
||||
});
|
||||
|
||||
test('历史位置描述损坏时仍能解析成只读占位,不抛异常', () {
|
||||
final ok = LocationPreviewData.parse('{"url":"http://x","name":"公司门口","addr":"一楼大厅"}');
|
||||
expect(ok.name, '公司门口');
|
||||
expect(ok.addr, '一楼大厅');
|
||||
expect(ok.url, 'http://x');
|
||||
|
||||
expect(() => LocationPreviewData.parse('{not-json'), returnsNormally);
|
||||
expect(() => LocationPreviewData.parse(null), returnsNormally);
|
||||
expect(() => LocationPreviewData.parse(''), returnsNormally);
|
||||
|
||||
final broken = LocationPreviewData.parse('{not-json');
|
||||
expect(broken.url, isEmpty);
|
||||
expect(broken.name, isNotEmpty);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user