退出登录+机器状态+切换设备+历史作业记录+设备信息
This commit is contained in:
@@ -7,6 +7,16 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- 关键! -->
|
||||
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
|
||||
<application
|
||||
android:label="飒沓机器人"
|
||||
android:name="${applicationName}"
|
||||
@@ -38,11 +48,20 @@
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<!-- 高德 Key -->
|
||||
<meta-data
|
||||
android:name="com.amap.api.v2.apikey"
|
||||
android:value="bbb1f0f20eed6bf679eddf2625630aba" />
|
||||
|
||||
|
||||
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
|
||||
@@ -45,5 +45,11 @@
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>需要访问您的位置以显示地图</string>
|
||||
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>需要访问相机以扫描二维码</string>
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -21,7 +21,10 @@ import '../../features/auth/presentation/bloc/auth_cubit.dart';
|
||||
import '../../features/auth/presentation/bloc/login_bloc.dart';
|
||||
import '../../features/auth/presentation/bloc/login_cubit.dart';
|
||||
import '../../features/devices/data/datasources/device_http_datasource.dart';
|
||||
import '../../features/devices/data/repositories/device_hostriry_work_impl.dart';
|
||||
import '../../features/devices/data/repositories/device_repository_impl.dart';
|
||||
import '../../features/devices/domain/repositories/device_hostrity_work_repository.dart';
|
||||
import '../../features/devices/domain/usecases/device_work_hostrirty_usecase.dart';
|
||||
import '../../features/remote_control/presentation/bloc/remote_control_cubit.dart';
|
||||
import '../app/app_user_cubit.dart';
|
||||
import '../network/dio_client.dart';
|
||||
@@ -74,6 +77,16 @@ Future<void> init() async {
|
||||
localTokenStorage: sl(), // 自动寻找已注册的 TokenStorage
|
||||
),
|
||||
);
|
||||
//工作历史
|
||||
sl.registerLazySingleton<DeviceHostrityWorkRepositoryRepository>(
|
||||
() => DeviceHostrityWorkRepositoryImpl(
|
||||
client: sl<Dio>(), // 使用 Dio 实例
|
||||
userStorage: sl<UserStorage>(),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
|
||||
sl.registerLazySingleton<DeviceRepository>(() => DeviceRepositoryImpl(sl()));
|
||||
sl.registerLazySingleton<RemoteControlRepository>(
|
||||
() => RemoteControlRepositoryImpl(sl(), sl()),
|
||||
@@ -83,6 +96,9 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton(() => LoginUseCase(sl()));
|
||||
sl.registerLazySingleton(() => GetUserDeviceUseCase(sl()));
|
||||
sl.registerLazySingleton(() => DiffSteerUseCase());
|
||||
sl.registerLazySingleton(() => DeviceWorkHostrirty(sl()));
|
||||
|
||||
|
||||
|
||||
/// 5. 状态管理 (Cubit/Bloc)
|
||||
sl.registerLazySingleton(() => AppUserCubit()); // AuthCubit 依赖它,必须先注册
|
||||
@@ -106,4 +122,14 @@ Future<void> init() async {
|
||||
/// 8. 页面级 Bloc/Cubit (Factory)
|
||||
sl.registerFactory(() => LoginBloc(sl()));
|
||||
sl.registerFactory(() => LoginCubit(sl(), sl()));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ class SentryLoggerImpl implements ILoggerService {
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
await SentryFlutter.init((options) {
|
||||
options.dsn = EnvConfig.sentryDsn;
|
||||
options.environment = EnvConfig.environment;
|
||||
// 生产环境只采样 20% 的性能数据,测试环境全开
|
||||
options.tracesSampleRate = EnvConfig.isProduction ? 0.2 : 1.0;
|
||||
});
|
||||
// await SentryFlutter.init((options) {
|
||||
// options.dsn = EnvConfig.sentryDsn;
|
||||
// options.environment = EnvConfig.environment;
|
||||
// // 生产环境只采样 20% 的性能数据,测试环境全开
|
||||
// options.tracesSampleRate = EnvConfig.isProduction ? 0.2 : 1.0;
|
||||
// });
|
||||
await _collectDeviceInfo();
|
||||
|
||||
await _prepareLocalFile();
|
||||
|
||||
@@ -2,13 +2,13 @@ import 'dart:convert';
|
||||
|
||||
import 'protocol_decoder.dart';
|
||||
import 'tcp/tcp_client.dart';
|
||||
// 注意:这里需要导入你具体的 UserModel
|
||||
// import '../../features/auth/data/models/user_model.dart';
|
||||
|
||||
class NetMessageDispatcher {
|
||||
final TcpClient tcpClient;
|
||||
NetMessageDispatcher(this.tcpClient);
|
||||
|
||||
|
||||
/// 过滤特定指令的流
|
||||
Stream<RawPacket> onCommand(int commandCode) {
|
||||
return tcpClient.packetStream.where((p) => p.command == commandCode);
|
||||
@@ -23,4 +23,7 @@ class NetMessageDispatcher {
|
||||
Stream<Map<String, dynamic>> onJsonMessage(int cmd) {
|
||||
return onCommand(cmd).map((p) => jsonDecode(utf8.decode(p.payload)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ class TcpClient {
|
||||
final _controller = StreamController<RawPacket>.broadcast();
|
||||
Stream<RawPacket> get packetStream => _controller.stream;
|
||||
|
||||
|
||||
// 新增:心跳定时器
|
||||
Timer? _heartbeatTimer;
|
||||
|
||||
|
||||
// 通过参数配置,不硬编码
|
||||
Future<void> connect({required String host, required int port}) async {
|
||||
try {
|
||||
@@ -22,10 +27,14 @@ class TcpClient {
|
||||
timeout: const Duration(seconds: 5),
|
||||
);
|
||||
|
||||
_socket!.listen(
|
||||
(data) {
|
||||
_socket!.listen((data) {
|
||||
var packets = _decoder.decode(data);
|
||||
for (var packet in packets) {
|
||||
// 🔥 关键修改:若收到服务端心跳(cmd == 0xFF),立即回复一个心跳包
|
||||
if (packet.command == 0xFF) {
|
||||
debugPrint('收到服务端心跳,自动回复...');
|
||||
sendHeartbeat(); // 回复 AB AA FF AA AB
|
||||
}
|
||||
_controller.add(packet);
|
||||
}
|
||||
},
|
||||
@@ -57,10 +66,47 @@ class TcpClient {
|
||||
_socket!.add(builder.takeBytes());
|
||||
}
|
||||
|
||||
void sendHeartbeat() {
|
||||
if (_socket == null) return;
|
||||
final builder = BytesBuilder()
|
||||
..addByte(0xAB)
|
||||
..addByte(0xAA)
|
||||
..addByte(0xFF)
|
||||
..addByte(0xAA)
|
||||
..addByte(0xAB);
|
||||
_socket!.add(builder.takeBytes());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 新增:启动心跳(每 4 秒发送一次 0xFF 指令)
|
||||
void startHeartbeat({Duration interval = const Duration(seconds: 4)}) {
|
||||
if (_heartbeatTimer != null) return; // 防止重复启动
|
||||
|
||||
_heartbeatTimer = Timer.periodic(interval, (_) {
|
||||
// 发送心跳帧:AB AA FF 00 00 AA AB(与 sendRaw 一致)
|
||||
//sendRaw(0xFF, []);
|
||||
sendHeartbeat();
|
||||
});
|
||||
debugPrint("TCP Connected");
|
||||
}
|
||||
|
||||
// 新增:停止心跳
|
||||
void stopHeartbeat() {
|
||||
_heartbeatTimer?.cancel();
|
||||
_heartbeatTimer = null;
|
||||
}
|
||||
|
||||
|
||||
void disconnect() {
|
||||
stopHeartbeat(); // 先停心跳
|
||||
_socket?.destroy();
|
||||
_socket = null;
|
||||
_controller.close();
|
||||
debugPrint("TCP Disconnected");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
|
||||
import 'package:maibu_satabot_v2/features/ai/presentation/routes/ai_routes.dart';
|
||||
import 'package:maibu_satabot_v2/features/auth/presentation/pages/login_page.dart';
|
||||
import 'package:maibu_satabot_v2/features/auth/presentation/routes/auth_routes.dart';
|
||||
import 'package:maibu_satabot_v2/features/home/presentation/routes/home_routes.dart';
|
||||
import 'package:maibu_satabot_v2/features/my/presentation/routes/my_routes.dart';
|
||||
|
||||
import '../../features/auth/presentation/bloc/auth_cubit.dart';
|
||||
import '../../features/auth/presentation/bloc/auth_state.dart';
|
||||
import '../../features/machine_details/presentation/pages/machine_details_page.dart';
|
||||
import '../../features/main_container/presentation/main_wrapper.dart';
|
||||
import 'go_router_refresh_stream.dart';
|
||||
|
||||
GoRouter createRouter(AuthCubit authCubit) {
|
||||
return GoRouter(
|
||||
|
||||
initialLocation: RoutePaths.login,
|
||||
refreshListenable: GoRouterRefreshStream(authCubit.stream),
|
||||
redirect: (context, state) {
|
||||
@@ -27,6 +30,18 @@ GoRouter createRouter(AuthCubit authCubit) {
|
||||
return null;
|
||||
},
|
||||
routes: [
|
||||
// 注册 machineDetails 路由
|
||||
GoRoute(
|
||||
path: RoutePaths.machineDetails,
|
||||
builder: (context, state) => const MachineDetailsPage(), // 替换为实际页面
|
||||
),
|
||||
|
||||
// 注册登录注册 路由
|
||||
GoRoute(
|
||||
path: RoutePaths.machineDetails,
|
||||
builder: (context, state) => const LoginPage(), // 替换为实际页面
|
||||
),
|
||||
|
||||
// 1. 外部路由(无底部导航栏)
|
||||
...AuthRoutes.routes,
|
||||
...HomeRoutes.routes,
|
||||
|
||||
@@ -13,4 +13,9 @@ class RoutePaths {
|
||||
static const remoteControl = '/home/remote_control';
|
||||
static const routePlan = '/home/route_plan';
|
||||
static const runningStatus = '/home/running_status';
|
||||
|
||||
static const machineDetails = '/machine_details/machine_details';
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -45,13 +45,15 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
/// 当 HTTP 登录/注册成功后调用
|
||||
Future<void> loginSuccess(UserEntity user) async {
|
||||
await storage.saveUser(user);
|
||||
tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
|
||||
await tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
|
||||
tcp.startHeartbeat(interval: const Duration(seconds: 4)); // 👈 启动心跳
|
||||
appCubit.setAuth(user);
|
||||
emit(AuthAuthenticated(user));
|
||||
}
|
||||
|
||||
/// 退出登录 (主动或被动)
|
||||
Future<void> logout() async {
|
||||
print("退出登录");
|
||||
await storage.deleteUser();
|
||||
tcp.disconnect();
|
||||
appCubit.clearAuth();
|
||||
@@ -65,6 +67,7 @@ class AuthCubit extends Cubit<AuthState> {
|
||||
// 假设 0x12 是踢下线或多设备登录提醒
|
||||
_kickOutSub = dispatcher.onJsonMessage(0x12).listen((json) {
|
||||
// 如果后端发来指令确认需要退出
|
||||
// print('>>> [AUTH] 收到 0x12: $json'); //
|
||||
logout();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// device_hostrity_work_repository_impl.dart
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:maibu_satabot_v2/core/storage/user_storage.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_run_hostrity_entity.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/repositories/device_hostrity_work_repository.dart';
|
||||
|
||||
class DeviceHostrityWorkRepositoryImpl implements DeviceHostrityWorkRepositoryRepository {
|
||||
final Dio client;
|
||||
final UserStorage userStorage;
|
||||
|
||||
DeviceHostrityWorkRepositoryImpl({required this.client, required this.userStorage});
|
||||
|
||||
@override
|
||||
Future<Either<DeviceFailure, List<DeviceRunStatisticsEntity>>> getDeviceHRunStatistics({
|
||||
required String deviceId,
|
||||
}) async {
|
||||
try {
|
||||
debugPrint('开始获取用户信息...');
|
||||
final user = await userStorage.getUser();
|
||||
if (user == null || user.token == null) {
|
||||
debugPrint('用户未登录或缺少 token');
|
||||
return Left(DeviceFailure.unauthorized(message: 'User not logged in or token is missing'));
|
||||
}
|
||||
final token = user.token!;
|
||||
debugPrint('用户信息获取成功,Token: $token');
|
||||
|
||||
final response = await client.get(
|
||||
'https://serviceri.satabot.com/iot/device/getDeviceRunStatistics?deviceId=$deviceId',
|
||||
options: Options(
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final jsonBody = response.data;
|
||||
if (jsonBody['code'] == 200) {
|
||||
final List<dynamic> data = jsonBody['data'];
|
||||
final List<DeviceRunStatisticsEntity> statistics = data
|
||||
.map((item) => DeviceRunStatisticsEntity(
|
||||
startTime: item['startTime'] as String, // 开始时间
|
||||
endTime: item['endTime'] as String, // 结束时间
|
||||
workArea: (item['workArea'] as num).toDouble(), // 工作面积
|
||||
distance: item['distance'] != null ? (item['distance'] as num).toDouble() : null, // 工作距离
|
||||
time: item['time'] as int, // 花费时间
|
||||
)).toList();
|
||||
return Right(statistics);
|
||||
} else {
|
||||
debugPrint('服务器返回错误码: ${jsonBody['code']}');
|
||||
return Left(DeviceFailure.serverError(message: '服务器返回错误码: ${jsonBody['code']}'));
|
||||
}
|
||||
} else {
|
||||
debugPrint('网络请求失败,状态码: ${response.statusCode}');
|
||||
return Left(DeviceFailure.networkError(message: '网络请求失败,状态码: ${response.statusCode}'));
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
debugPrint('Dio 请求失败: ${e.message}');
|
||||
return Left(DeviceFailure.networkError(message: e.message ?? '未知错误'));
|
||||
} catch (e) {
|
||||
debugPrint('请求过程中发生异常: $e');
|
||||
return Left(DeviceFailure.unknownError(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class DeviceRunStatisticsEntity extends Equatable {
|
||||
final String startTime; // 开始时间
|
||||
final String endTime; // 结束时间
|
||||
final double workArea; // 工作面积
|
||||
final double? distance; // 工作距离(可为空)
|
||||
final int time; // 花费时间
|
||||
|
||||
const DeviceRunStatisticsEntity({
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.workArea,
|
||||
this.distance,
|
||||
required this.time,
|
||||
});
|
||||
|
||||
factory DeviceRunStatisticsEntity.fromJson(Map<String, dynamic> json) {
|
||||
return DeviceRunStatisticsEntity(
|
||||
startTime: json['startTime'] as String,
|
||||
endTime: json['endTime'] as String,
|
||||
workArea: (json['workArea'] as num).toDouble(),
|
||||
distance: json['distance'] != null ? (json['distance'] as num).toDouble() : null,
|
||||
time: json['time'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [startTime, endTime, workArea, distance, time];
|
||||
}
|
||||
@@ -3,6 +3,26 @@ import '../../../../core/error/failure.dart';
|
||||
class DeviceFailure extends Failure implements Exception {
|
||||
final int? code;
|
||||
DeviceFailure(super.message, {this.code});
|
||||
|
||||
// 新增静态方法:服务器错误
|
||||
static DeviceFailure serverError({required String message}) {
|
||||
return DeviceFailure('服务器错误: $message');
|
||||
}
|
||||
|
||||
|
||||
// 新增静态方法:网络错误
|
||||
static DeviceFailure networkError({required String message}) {
|
||||
return DeviceFailure('网络错误: $message');
|
||||
}
|
||||
|
||||
|
||||
static DeviceFailure unknownError({required String message}) {
|
||||
return DeviceFailure('未知错误: $message');
|
||||
}
|
||||
|
||||
static DeviceFailure unauthorized({required String message}) {
|
||||
return DeviceFailure('未授权: $message');
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceNotConnectedFailure extends DeviceFailure {
|
||||
@@ -16,3 +36,5 @@ class DeviceOfflineFailure extends DeviceFailure {
|
||||
class DeviceNotBoundFailure extends DeviceFailure {
|
||||
DeviceNotBoundFailure(super.message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
|
||||
|
||||
import '../entities/device_run_hostrity_entity.dart';
|
||||
|
||||
abstract class DeviceHostrityWorkRepositoryRepository {
|
||||
//获取设备的历史作业记录
|
||||
|
||||
Future<Either<DeviceFailure, List<DeviceRunStatisticsEntity>>> getDeviceHRunStatistics({
|
||||
required String deviceId,
|
||||
});
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:maibu_satabot_v2/core/domain/usecases/base_usecase.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/repositories/device_repository.dart';
|
||||
|
||||
import '../entities/device_run_hostrity_entity.dart';
|
||||
import '../repositories/device_hostrity_work_repository.dart';
|
||||
|
||||
class DeviceWorkHostrirty implements BaseUseCase<List<DeviceRunStatisticsEntity>, BindDeviceParams> {
|
||||
final DeviceHostrityWorkRepositoryRepository deviceRepository;
|
||||
|
||||
DeviceWorkHostrirty(this.deviceRepository);
|
||||
|
||||
@override
|
||||
Future<Either<DeviceFailure, List<DeviceRunStatisticsEntity>>> call(BindDeviceParams params) async {
|
||||
return await deviceRepository.getDeviceHRunStatistics(
|
||||
deviceId: params.deviceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BindDeviceParams {
|
||||
final String deviceId;
|
||||
final String deviceAlias;
|
||||
|
||||
BindDeviceParams(this.deviceId, this.deviceAlias);
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../core/app/app_user_cubit.dart';
|
||||
import '../../../devices/presentation/bloc/devices_cubit.dart';
|
||||
import '../widgets/map/test_amap_page.dart';
|
||||
import '../widgets/map/testmap_pages.dart';
|
||||
|
||||
class RoutePlanPage extends StatelessWidget {
|
||||
const RoutePlanPage({super.key});
|
||||
@@ -19,10 +21,7 @@ class RoutePlanPage extends StatelessWidget {
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF7F7F7),
|
||||
body: CustomScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: [],
|
||||
),
|
||||
body: MapPageEnterprise(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,280 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../core/app/app_user_cubit.dart';
|
||||
import '../../../../core/di/injection.dart';
|
||||
import '../../../../core/network/net_message_dispatcher.dart';
|
||||
import '../../../../core/network/protocol_decoder.dart';
|
||||
import '../../../devices/presentation/bloc/devices_cubit.dart';
|
||||
|
||||
class RunningStatusPage extends StatelessWidget {
|
||||
class RunningStatusPage extends StatefulWidget {
|
||||
const RunningStatusPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 同时监听用户和设备状态
|
||||
final userState = context.read<AppUserCubit>().state;
|
||||
State<RunningStatusPage> createState() => _RunningStatusPageState();
|
||||
}
|
||||
|
||||
class _RunningStatusPageState extends State<RunningStatusPage> {
|
||||
final _dispatcher = sl<NetMessageDispatcher>();
|
||||
late StreamSubscription<RawPacket> _sub;
|
||||
|
||||
// 实时设备状态数据
|
||||
String _yaw = '--';
|
||||
String _satelliteCnt = '--';
|
||||
String _qual = '--';
|
||||
String _leftSpeed = '--';
|
||||
String _rightSpeed = '--';
|
||||
String _voltage = '--';
|
||||
// 实时设备状态数据
|
||||
String _pitch = '--';
|
||||
String _roll = '--';
|
||||
String _latitude = '--';
|
||||
String _longitude = '--';
|
||||
String _timeStamp = '--';
|
||||
String _knifeCuttingSpeed = '--';
|
||||
String _controlMode = '--';
|
||||
String _workingArea = '--';
|
||||
String _battery = '--';
|
||||
String _obstacleFlag = '--';
|
||||
// 视图切换状态
|
||||
bool _isCardView = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_sub = _dispatcher.onCommand(0x02).listen(_onDeviceData);
|
||||
}
|
||||
|
||||
void _onDeviceData(RawPacket packet) {
|
||||
try {
|
||||
final csv = utf8.decode(packet.payload);
|
||||
final fields = csv.split(',');
|
||||
// 至少需 16 个字段(索引 10=航向角, 13=卫星数, 14=定位质量)
|
||||
if (fields.length >= 16) {
|
||||
setState(() {
|
||||
_voltage = fields[0]; // 电压
|
||||
_leftSpeed = fields[1]; // 左轮目标速度
|
||||
_rightSpeed = fields[2]; // 右轮目标速度
|
||||
_pitch = fields[11]; // 俯仰角
|
||||
_roll = fields[12]; // 翻滚角
|
||||
_latitude = fields[16]; // 纬度
|
||||
_longitude = fields[17]; // 经度
|
||||
_timeStamp = '${fields[16]} ${fields[17]}'; // 时间戳(合并字段)
|
||||
_knifeCuttingSpeed = fields[19]; // 割刀速度
|
||||
_controlMode = fields[20]; // 控制模式
|
||||
_workingArea = fields[21]; // 作业面积
|
||||
_battery = fields[22]; // 电量
|
||||
_obstacleFlag = fields[23]; // 障碍物标志
|
||||
_yaw = fields[10]; // 航向角
|
||||
_satelliteCnt = fields[13]; // 卫星数
|
||||
_qual = fields[14]; // 定位质量
|
||||
});
|
||||
}
|
||||
} catch (_) {
|
||||
// 解析失败时忽略,避免崩溃
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_sub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _refreshDeviceData() {
|
||||
final deviceState = context.read<DevicesCubit>().state;
|
||||
final currentDevice = deviceState.selectedDevice;
|
||||
|
||||
if (currentDevice == null)
|
||||
return const Scaffold(body: Center(child: Text("加载中...")));
|
||||
if (currentDevice != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("数据刷新成功!"), duration: Duration(seconds: 1)),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("暂无设备,无法刷新"), duration: Duration(seconds: 1)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final deviceState = context.read<DevicesCubit>().state;
|
||||
final currentDevice = deviceState.selectedDevice;
|
||||
|
||||
if (currentDevice == null) {
|
||||
return const Scaffold(
|
||||
body: Center(child: Text("加载中...")),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF7F7F7),
|
||||
body: CustomScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: [],
|
||||
slivers: [
|
||||
// 1. 顶部导航栏
|
||||
SliverAppBar(
|
||||
backgroundColor: const Color(0xFF1677FF),
|
||||
pinned: true,
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
title: const Text(
|
||||
"设备数据监控",
|
||||
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
|
||||
// 2. 状态信息行(动态更新)
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("航向角状态: $_yaw°", style: const TextStyle(fontSize: 14)),
|
||||
Text("定位质量: $_qual", style: const TextStyle(fontSize: 14)),
|
||||
Text("卫星数: $_satelliteCnt", style: const TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 3. 选项卡 + 刷新按钮
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
margin: const EdgeInsets.only(top: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// 切换选项卡
|
||||
Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _isCardView = true),
|
||||
child: _buildTab("卡片", isActive: _isCardView),
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _isCardView = false),
|
||||
child: _buildTab("图表", isActive: !_isCardView),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 刷新按钮
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF1677FF),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
onPressed: _refreshDeviceData,
|
||||
child: const Text("刷新数据", style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 4. 内容区域
|
||||
SliverFillRemaining(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: _isCardView ? _buildCardContentView() : _buildChartContentView(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab(String title, {required bool isActive}) {
|
||||
return Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: isActive ? const Color(0xFF1677FF) : const Color(0xFF666666),
|
||||
fontSize: 16,
|
||||
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
|
||||
decoration: isActive ? TextDecoration.underline : null,
|
||||
decorationColor: const Color(0xFF1677FF),
|
||||
decorationThickness: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCardContentView() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text("设备实时状态", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 16),
|
||||
_CardItem(title: "电压", value: "$_voltage V"),
|
||||
_CardItem(title: "左轮速度", value: "$_leftSpeed rpm"),
|
||||
_CardItem(title: "右轮速度", value: "$_rightSpeed rpm"),
|
||||
_CardItem(title: "航向角", value: "$_yaw°"),
|
||||
_CardItem(title: "俯仰角", value: "$_pitch°"),
|
||||
_CardItem(title: "翻滚角", value: "$_roll°"),
|
||||
_CardItem(title: "卫星数", value: _satelliteCnt),
|
||||
_CardItem(title: "定位质量", value: _qual),
|
||||
_CardItem(title: "纬度", value: _latitude),
|
||||
_CardItem(title: "经度", value: _longitude),
|
||||
_CardItem(title: "时间戳", value: _timeStamp),
|
||||
_CardItem(title: "割刀速度", value: "$_knifeCuttingSpeed rpm"),
|
||||
_CardItem(title: "控制模式", value: _controlMode),
|
||||
_CardItem(title: "作业面积", value: "$_workingArea m²"),
|
||||
_CardItem(title: "电量", value: "$_battery%"),
|
||||
_CardItem(title: "障碍物标志", value: _obstacleFlag),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChartContentView() {
|
||||
return const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("图表视图", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 16),
|
||||
Text("可接入 fl_chart / charts_flutter 实现折线图/柱状图", style: TextStyle(color: Color(0xFF999999))),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CardItem extends StatelessWidget {
|
||||
final String title;
|
||||
final String value;
|
||||
|
||||
const _CardItem({required this.title, required this.value});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(title, style: const TextStyle(fontSize: 16, color: Color(0xFF333333))),
|
||||
Text(value, style: const TextStyle(fontSize: 16, color: Color(0xFF666666))),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:cc_ui_kit/cc_ui_kit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
|
||||
|
||||
import '../../../../core/app/app_user_cubit.dart';
|
||||
import '../../../../core/router/route_paths.dart';
|
||||
import '../../../devices/presentation/bloc/devices_cubit.dart';
|
||||
import '../../../devices/presentation/bloc/devices_state.dart';
|
||||
// 导入你的主题文件以获取 offWhite
|
||||
@@ -391,8 +393,10 @@ class ImmersionHeader extends StatelessWidget {
|
||||
const SizedBox(height: 8),
|
||||
CCPrimaryButton(
|
||||
onPressed: () {
|
||||
context.push(RoutePaths.machineDetails);
|
||||
print("通过按钮进入详情");
|
||||
},
|
||||
|
||||
text: "详情",
|
||||
width: 80,
|
||||
height: 30,
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'dart:math';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class CoordTransform {
|
||||
static const double pi = 3.1415926535897932384626;
|
||||
static const double a = 6378245.0;
|
||||
static const double ee = 0.00669342162296594323;
|
||||
|
||||
static bool outOfChina(double lat, double lon) {
|
||||
return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
|
||||
}
|
||||
|
||||
/// WGS84 → GCJ02(GPS → 高德)
|
||||
static LatLng wgs84ToGcj02(double lat, double lon) {
|
||||
if (outOfChina(lat, lon)) {
|
||||
return LatLng(lat, lon);
|
||||
}
|
||||
|
||||
double dLat = _transformLat(lon - 105.0, lat - 35.0);
|
||||
double dLon = _transformLon(lon - 105.0, lat - 35.0);
|
||||
double radLat = lat / 180.0 * pi;
|
||||
double magic = sin(radLat);
|
||||
magic = 1 - ee * magic * magic;
|
||||
double sqrtMagic = sqrt(magic);
|
||||
|
||||
dLat = (dLat * 180.0) /
|
||||
((a * (1 - ee)) / (magic * sqrtMagic) * pi);
|
||||
dLon = (dLon * 180.0) /
|
||||
(a / sqrtMagic * cos(radLat) * pi);
|
||||
|
||||
return LatLng(lat + dLat, lon + dLon);
|
||||
}
|
||||
|
||||
static double _transformLat(double x, double y) {
|
||||
return -100.0 +
|
||||
2.0 * x +
|
||||
3.0 * y +
|
||||
0.2 * y * y +
|
||||
0.1 * x * y +
|
||||
0.2 * sqrt(x.abs());
|
||||
}
|
||||
|
||||
static double _transformLon(double x, double y) {
|
||||
return 300.0 +
|
||||
x +
|
||||
2.0 * y +
|
||||
0.1 * x * x +
|
||||
0.1 * x * y +
|
||||
0.1 * sqrt(x.abs());
|
||||
}
|
||||
}
|
||||
178
lib/features/home/presentation/widgets/map/test_amap_page.dart
Normal file
178
lib/features/home/presentation/widgets/map/test_amap_page.dart
Normal file
@@ -0,0 +1,178 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'CoordTransform/coord_transform.dart';
|
||||
|
||||
class AmapFlutterMapPage extends StatefulWidget {
|
||||
const AmapFlutterMapPage({super.key});
|
||||
|
||||
@override
|
||||
State<AmapFlutterMapPage> createState() => _AmapFlutterMapPageState();
|
||||
}
|
||||
|
||||
class _AmapFlutterMapPageState extends State<AmapFlutterMapPage> {
|
||||
final MapController _mapController = MapController();
|
||||
static const String amapKey = "bbb1f0f20eed6bf679eddf2625630aba";
|
||||
|
||||
LatLng _currentCenter = const LatLng(39.908823, 116.397470);
|
||||
bool _isLoading = true;
|
||||
// 新增:标记页面是否已销毁,防止异步操作访问已释放的控制器
|
||||
bool _isDisposed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkAndGetLocation();
|
||||
});
|
||||
}
|
||||
|
||||
// 新增:页面销毁时标记状态
|
||||
@override
|
||||
void dispose() {
|
||||
_isDisposed = true; // 先标记销毁状态
|
||||
// _mapController.dispose(); // 再销毁控制器
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _checkAndGetLocation() async {
|
||||
var status = await Permission.location.status;
|
||||
if (status.isDenied) {
|
||||
status = await Permission.location.request();
|
||||
if (status.isDenied) {
|
||||
if (!_isDisposed) { // 防护:页面没销毁才弹提示
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('定位权限被拒绝,无法获取当前位置')),
|
||||
);
|
||||
setState(() { _isLoading = false; });
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (status.isPermanentlyDenied) {
|
||||
if (!_isDisposed) { // 防护:页面没销毁才弹提示
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('定位权限被永久拒绝,请去设置开启'),
|
||||
action: SnackBarAction(label: '去设置', onPressed: openAppSettings),
|
||||
),
|
||||
);
|
||||
setState(() { _isLoading = false; });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await _getCurrentLocation();
|
||||
}
|
||||
|
||||
Future<void> _getCurrentLocation() async {
|
||||
if (_isDisposed) return;
|
||||
|
||||
setState(() { _isLoading = true; });
|
||||
print('📍 开始定位...');
|
||||
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
if (!_isDisposed) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('定位服务未启用,请开启定位服务')),
|
||||
);
|
||||
setState(() { _isLoading = false; });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.bestForNavigation,
|
||||
forceAndroidLocationManager: true,
|
||||
timeLimit: const Duration(seconds: 8),
|
||||
);
|
||||
|
||||
if (_isDisposed) return;
|
||||
|
||||
// ⭐⭐⭐ 关键修复点:WGS84 → GCJ02 ⭐⭐⭐
|
||||
final gcjPoint = CoordTransform.wgs84ToGcj02(
|
||||
position.latitude,
|
||||
position.longitude,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_currentCenter = gcjPoint;
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
if (!_isDisposed) {
|
||||
_mapController.move(gcjPoint, 15.0);
|
||||
}
|
||||
|
||||
print('✅ 定位成功(GCJ02): ${gcjPoint.latitude}, ${gcjPoint.longitude}');
|
||||
} catch (e) {
|
||||
if (!_isDisposed) {
|
||||
setState(() { _isLoading = false; });
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('定位失败: $e'),
|
||||
action: SnackBarAction(
|
||||
label: '重试',
|
||||
onPressed: _checkAndGetLocation,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('路径规划'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.my_location),
|
||||
onPressed: _checkAndGetLocation,
|
||||
tooltip: '定位到我的位置',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
center: _currentCenter,
|
||||
zoom: 15.0,
|
||||
minZoom: 3.0,
|
||||
maxZoom: 18.0,
|
||||
interactiveFlags: InteractiveFlag.all,
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate:
|
||||
"https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}&key=$amapKey",
|
||||
subdomains: const ['1', '2', '3', '4'],
|
||||
tileProvider: NetworkTileProvider(),
|
||||
),
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: _currentCenter,
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: const Icon(Icons.location_on, color: Colors.red, size: 40),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
228
lib/features/home/presentation/widgets/map/testmap_pages.dart
Normal file
228
lib/features/home/presentation/widgets/map/testmap_pages.dart
Normal file
@@ -0,0 +1,228 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../path_list_pages.dart';
|
||||
|
||||
class MapPageEnterprise extends StatefulWidget {
|
||||
const MapPageEnterprise({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MapPageEnterprise> createState() => _MapPageEnterpriseState();
|
||||
}
|
||||
|
||||
class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
final MapController _mapController = MapController();
|
||||
|
||||
LatLng? _currentLatLng;
|
||||
StreamSubscription<Position>? _positionSub;
|
||||
bool _hasMovedOnce = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initLocationEnterprise();
|
||||
}
|
||||
|
||||
/// ===============================
|
||||
/// 企业级定位初始化(已修复坐标系)
|
||||
/// ===============================
|
||||
Future<void> _initLocationEnterprise() async {
|
||||
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) return;
|
||||
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
}
|
||||
if (permission != LocationPermission.whileInUse &&
|
||||
permission != LocationPermission.always) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1️⃣ 首次强制获取定位
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.bestForNavigation,
|
||||
);
|
||||
|
||||
final gcj = wgs84ToGcj02(position.latitude, position.longitude);
|
||||
_updateLocation(gcj, moveMap: true);
|
||||
|
||||
// 2️⃣ 实时监听(不再强制移动地图)
|
||||
_positionSub = Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.bestForNavigation,
|
||||
distanceFilter: 2,
|
||||
),
|
||||
).listen((pos) {
|
||||
final gcj = wgs84ToGcj02(pos.latitude, pos.longitude);
|
||||
_updateLocation(gcj, moveMap: false);
|
||||
});
|
||||
}
|
||||
|
||||
/// ===============================
|
||||
/// 更新位置
|
||||
/// ===============================
|
||||
void _updateLocation(LatLng latLng, {bool moveMap = false}) {
|
||||
setState(() {
|
||||
_currentLatLng = latLng;
|
||||
});
|
||||
|
||||
if (moveMap && !_hasMovedOnce) {
|
||||
_hasMovedOnce = true;
|
||||
_mapController.move(latLng, 17);
|
||||
}
|
||||
}
|
||||
|
||||
/// 手动回到当前位置
|
||||
void _moveToCurrentLocation() {
|
||||
if (_currentLatLng == null) return;
|
||||
_mapController.move(_currentLatLng!, 17);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_positionSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
final menuHeight = 16 * 6; // 假设 VerticalFloatMenu 有 6 个选项,每个高度为 56
|
||||
final maxTop = screenHeight - menuHeight;
|
||||
return Scaffold(
|
||||
body:
|
||||
SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter:
|
||||
_currentLatLng ?? const LatLng(39.9042, 116.4074),
|
||||
initialZoom: 15,
|
||||
maxZoom: 18,
|
||||
),
|
||||
children: [
|
||||
/// 高德瓦片(GCJ-02)
|
||||
TileLayer(
|
||||
urlTemplate:
|
||||
'https://webrd02.is.autonavi.com/appmaptile'
|
||||
'?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1'
|
||||
'&key=bbb1f0f20eed6bf679eddf2625630aba',
|
||||
),
|
||||
|
||||
/// 当前定位 Marker
|
||||
if (_currentLatLng != null)
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
point: _currentLatLng!,
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: const Icon(
|
||||
Icons.my_location,
|
||||
color: Colors.blue,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Positioned(
|
||||
height: 336,
|
||||
right: 16,
|
||||
top: maxTop > 10 ? 11 : maxTop, // 确保不超出屏幕范围
|
||||
child: FloatingActionButton(
|
||||
onPressed: _moveToCurrentLocation,
|
||||
child: new VerticalFloatMenu(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// =======================================================
|
||||
/// 坐标转换:WGS84 -> GCJ02(国内高德/腾讯通用)
|
||||
/// =======================================================
|
||||
|
||||
const double _pi = 3.14159265358979324;
|
||||
const double _a = 6378245.0;
|
||||
const double _ee = 0.00669342162296594323;
|
||||
|
||||
LatLng wgs84ToGcj02(double lat, double lon) {
|
||||
if (_outOfChina(lat, lon)) {
|
||||
return LatLng(lat, lon);
|
||||
}
|
||||
double dLat = _transformLat(lon - 105.0, lat - 35.0);
|
||||
double dLon = _transformLon(lon - 105.0, lat - 35.0);
|
||||
double radLat = lat / 180.0 * _pi;
|
||||
double magic = sin(radLat);
|
||||
magic = 1 - _ee * magic * magic;
|
||||
double sqrtMagic = sqrt(magic);
|
||||
dLat = (dLat * 180.0) /
|
||||
((_a * (1 - _ee)) / (magic * sqrtMagic) * _pi);
|
||||
dLon = (dLon * 180.0) / (_a / sqrtMagic * cos(radLat) * _pi);
|
||||
double mgLat = lat + dLat;
|
||||
double mgLon = lon + dLon;
|
||||
return LatLng(mgLat, mgLon);
|
||||
}
|
||||
|
||||
bool _outOfChina(double lat, double lon) {
|
||||
return lon < 72.004 ||
|
||||
lon > 137.8347 ||
|
||||
lat < 0.8293 ||
|
||||
lat > 55.8271;
|
||||
}
|
||||
|
||||
double _transformLat(double x, double y) {
|
||||
double ret = -100.0 +
|
||||
2.0 * x +
|
||||
3.0 * y +
|
||||
0.2 * y * y +
|
||||
0.1 * x * y +
|
||||
0.2 * sqrt(x.abs());
|
||||
ret += (20.0 * sin(6.0 * x * _pi) +
|
||||
20.0 * sin(2.0 * x * _pi)) *
|
||||
2.0 /
|
||||
3.0;
|
||||
ret += (20.0 * sin(y * _pi) +
|
||||
40.0 * sin(y / 3.0 * _pi)) *
|
||||
2.0 /
|
||||
3.0;
|
||||
ret += (160.0 * sin(y / 12.0 * _pi) +
|
||||
320 * sin(y * _pi / 30.0)) *
|
||||
2.0 /
|
||||
3.0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
double _transformLon(double x, double y) {
|
||||
double ret = 300.0 +
|
||||
x +
|
||||
2.0 * y +
|
||||
0.1 * x * x +
|
||||
0.1 * x * y +
|
||||
0.1 * sqrt(x.abs());
|
||||
ret += (20.0 * sin(6.0 * x * _pi) +
|
||||
20.0 * sin(2.0 * x * _pi)) *
|
||||
2.0 /
|
||||
3.0;
|
||||
ret += (20.0 * sin(x * _pi) +
|
||||
40.0 * sin(x / 3.0 * _pi)) *
|
||||
2.0 /
|
||||
3.0;
|
||||
ret += (150.0 * sin(x / 12.0 * _pi) +
|
||||
300.0 * sin(x / 30.0 * _pi)) *
|
||||
2.0 /
|
||||
3.0;
|
||||
return ret;
|
||||
}
|
||||
86
lib/features/home/presentation/widgets/path_list_pages.dart
Normal file
86
lib/features/home/presentation/widgets/path_list_pages.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 垂直侧边功能菜单组件 - 纯白不透明长方形、独立可复用
|
||||
class VerticalFloatMenu extends StatefulWidget {
|
||||
// 外部可传点击回调,获取选中的索引和名称(可选)
|
||||
final Function(int index, String name)? onItemTap;
|
||||
|
||||
const VerticalFloatMenu({super.key, this.onItemTap});
|
||||
|
||||
@override
|
||||
State<VerticalFloatMenu> createState() => _VerticalFloatMenuState();
|
||||
}
|
||||
|
||||
class _VerticalFloatMenuState extends State<VerticalFloatMenu> {
|
||||
// 默认选中「编辑」项(索引4),与原设计一致
|
||||
int _selectedIndex = 4;
|
||||
|
||||
// 菜单配置:图标+名称,和原截图完全对应
|
||||
final List<Map<String, dynamic>> _menuItems = const [
|
||||
{"icon": Icons.crop_free, "name": "圈地"},
|
||||
{"icon": Icons.format_list_bulleted, "name": "列表"},
|
||||
{"icon": Icons.train, "name": "场站"},
|
||||
{"icon": Icons.navigation, "name": "定位"},
|
||||
{"icon": Icons.edit_note, "name": "编辑"},
|
||||
{"icon": Icons.videocam, "name": "监控"},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 纯白不透明 + 固定长方形宽度,无阴影/圆角(纯基础样式)
|
||||
return Container(
|
||||
width: 56, // 适配悬浮按钮的窄长方形宽度,可自行微调
|
||||
color: Colors.white, // 纯白不透明背景,核心要求
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min, // 高度自适应子项,不占多余空间
|
||||
children: _menuItems
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) => _buildMenuItem(
|
||||
entry.value["icon"],
|
||||
entry.key,
|
||||
entry.value["name"],
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建单个菜单项(含选中高亮、点击事件)
|
||||
Widget _buildMenuItem(IconData icon, int index, String name) {
|
||||
final isSelected = _selectedIndex == index;
|
||||
return InkWell(
|
||||
// 点击切换选中状态+触发外部回调
|
||||
onTap: () {
|
||||
setState(() => _selectedIndex = index);
|
||||
if (widget.onItemTap != null) widget.onItemTap!(index, name);
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity, // 撑满父容器宽度,保证点击区域
|
||||
height: 56, // 单个项固定高度,视觉均匀
|
||||
alignment: Alignment.center,
|
||||
// 选中项高亮背景(浅蓝,可自行改色)
|
||||
color: isSelected ? Colors.lightBlue[50] : Colors.white,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: isSelected ? Colors.lightBlue : Colors.black,
|
||||
size: 22, // 图标大小微调,适配窄宽度
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
name,
|
||||
style: TextStyle(
|
||||
fontSize: 10, // 文字大小适配窄宽度
|
||||
color: isSelected ? Colors.lightBlue : Colors.black,
|
||||
height: 1, // 取消文字默认行高,避免高度过高
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:fpdart/fpdart.dart' hide State;
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/usecases/device_work_hostrirty_usecase.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_run_hostrity_entity.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
|
||||
|
||||
class WorkParamsCard extends StatelessWidget {
|
||||
import '../../../../core/di/injection.dart';
|
||||
import '../../../devices/presentation/bloc/devices_cubit.dart';
|
||||
|
||||
class WorkParamsCard extends StatefulWidget {
|
||||
const WorkParamsCard({super.key});
|
||||
|
||||
@override
|
||||
State<WorkParamsCard> createState() => _WorkParamsCardState();
|
||||
}
|
||||
|
||||
class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
List<DeviceRunStatisticsEntity>? _statistics;
|
||||
String? _errorMessage;
|
||||
bool _isLoading = false; // 默认不加载
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final deviceState = context.watch<DevicesCubit>().state;
|
||||
final currentDevice = deviceState.selectedDevice;
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
// 极淡的阴影
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.02),
|
||||
@@ -23,13 +42,11 @@ class WorkParamsCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题行
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// 左侧蓝色装饰条
|
||||
Container(
|
||||
width: 4,
|
||||
height: 16,
|
||||
@@ -49,27 +66,28 @@ class WorkParamsCard extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
// 刷新状态
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.refresh, size: 14, color: Colors.grey[400]),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'刚刚更新',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
|
||||
),
|
||||
],
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_handleInfoTap(currentDevice); // 包装在同步函数中
|
||||
}, // 点击事件处理
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.info, size: 14, color: Colors.grey[400]),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'查看详情',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 数据展示行
|
||||
IntrinsicHeight(
|
||||
// 关键:使分割线高度自动充满
|
||||
child: Row(
|
||||
children: [
|
||||
_buildParamItem('作业面积', '0', '亩'),
|
||||
_buildParamItem('作业面积', '0', '亩'), // 默认值
|
||||
_buildDivider(),
|
||||
_buildParamItem('作业里程', '0', 'km'),
|
||||
_buildDivider(),
|
||||
@@ -82,6 +100,185 @@ class WorkParamsCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// 点击事件处理
|
||||
Future<void> _handleInfoTap(DeviceEntity? currentDevice) async {
|
||||
print("点击触摸了历史作业");
|
||||
setState(() {
|
||||
_isLoading = true; // 开始加载
|
||||
_errorMessage = null; // 清除错误信息
|
||||
});
|
||||
print("开始历史作业");
|
||||
try {
|
||||
final useCase = DeviceWorkHostrirty(sl());
|
||||
print("发送获取历史作业记录开始");
|
||||
//displayName
|
||||
final deviceId = currentDevice?.deviceName.toString() ?? '';
|
||||
final params = BindDeviceParams(deviceId, 'your_device_alias');
|
||||
final result = await useCase(params);
|
||||
print("发送获取历史作业记录结束");
|
||||
result.fold(
|
||||
(failure) {
|
||||
setState(() {
|
||||
_errorMessage = failure.message;
|
||||
_isLoading = false;
|
||||
});
|
||||
},
|
||||
(data) {
|
||||
setState(() {
|
||||
_statistics = data;
|
||||
_isLoading = false;
|
||||
});
|
||||
_showDetailsDialog(context, _statistics!); // 展示弹窗
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
print("捕获异常: $e");
|
||||
setState(() {
|
||||
_errorMessage = '请求失败: $e';
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗展示详情
|
||||
// 弹窗展示详情(从底部弹出)
|
||||
// 弹窗展示详情(从底部弹出,覆盖底部导航栏)
|
||||
void _showDetailsDialog(BuildContext context, List<DeviceRunStatisticsEntity> data) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true, // 支持全屏高度
|
||||
useRootNavigator: true, // 覆盖底部导航栏
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)), // 顶部圆角
|
||||
),
|
||||
builder: (context) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.75, // 占据屏幕 75% 高度
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 顶部拖拽指示器
|
||||
Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 标题
|
||||
const Text(
|
||||
'历史记录详情',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 内容区域
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: data.map((item) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.05), // 浅蓝背景
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Colors.blue.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildDetailRow(Icons.calendar_today, '开始时间', item.startTime),
|
||||
_buildDetailRow(Icons.calendar_today_outlined, '结束时间', item.endTime),
|
||||
_buildDetailRow(Icons.square_foot, '工作面积', '${item.workArea} 亩'),
|
||||
_buildDetailRow(
|
||||
Icons.directions_car,
|
||||
'工作距离',
|
||||
'${item.distance?.toStringAsFixed(2) ?? '未计算'} km',
|
||||
),
|
||||
_buildDetailRow(Icons.timer, '花费时间', '${item.time} 小时'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 关闭按钮
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'关闭',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 构建详情行
|
||||
Widget _buildDetailRow(IconData icon, String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: Colors.blue),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'$label: ',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 构建单个参数项
|
||||
Widget _buildParamItem(String label, String value, String unit) {
|
||||
return Expanded(
|
||||
@@ -116,7 +313,7 @@ class WorkParamsCard extends StatelessWidget {
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.black,
|
||||
fontFamily: 'Inter', // 建议使用数字显示更漂亮的字体
|
||||
fontFamily: 'Inter',
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MachineDetailsPage extends StatelessWidget {
|
||||
const MachineDetailsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 移除 MaterialApp,直接返回 Scaffold
|
||||
return const DeviceDetailPage();
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceDetailPage extends StatelessWidget {
|
||||
const DeviceDetailPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F5F5),
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: const Text(
|
||||
'设备详情',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () => Navigator.pop(context), // 正确返回上一页
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildDeviceStatusCard(),
|
||||
const SizedBox(height: 16),
|
||||
_buildBasicInfoCard(),
|
||||
const SizedBox(height: 16),
|
||||
_buildResourceCenterCard(),
|
||||
const SizedBox(height: 24),
|
||||
_buildUnbindButton(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 设备状态卡片
|
||||
Widget _buildDeviceStatusCard() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/car.png',
|
||||
width: 380,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'离线',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 基本信息卡片
|
||||
Widget _buildBasicInfoCard() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: Text(
|
||||
'基本信息',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildInfoRow(
|
||||
icon: Icons.info_outline,
|
||||
label: '设备名称',
|
||||
trailing: Row(
|
||||
children: const [
|
||||
Text(''),
|
||||
SizedBox(width: 8),
|
||||
Icon(Icons.edit, size: 16, color: Color(0xFF007AFF)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 32, color: Color(0xFFF0F0F0)),
|
||||
_buildInfoRow(
|
||||
icon: Icons.shield_outlined,
|
||||
label: '设备ID',
|
||||
trailing: Row(
|
||||
children: const [
|
||||
Text('undefined'),
|
||||
SizedBox(width: 8),
|
||||
Icon(Icons.copy, size: 16, color: Color(0xFF999999)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 32, color: Color(0xFFF0F0F0)),
|
||||
_buildInfoRow(
|
||||
icon: Icons.date_range_outlined,
|
||||
label: '激活时间',
|
||||
trailing: const Text(''),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 资源中心卡片
|
||||
Widget _buildResourceCenterCard() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: Text(
|
||||
'资源中心',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildResourceItem('使用说明', Icons.description_outlined),
|
||||
const Divider(height: 32, color: Color(0xFFF0F0F0)),
|
||||
_buildResourceItem('产品手册', Icons.book_outlined),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 信息行组件
|
||||
Widget _buildInfoRow({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required Widget trailing,
|
||||
}) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, size: 18, color: const Color(0xFF999999)),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const Spacer(),
|
||||
trailing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 资源项组件
|
||||
Widget _buildResourceItem(String title, IconData icon) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, size: 18, color: const Color(0xFF007AFF)),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const Spacer(),
|
||||
const Icon(Icons.arrow_forward_ios, size: 16, color: Color(0xFF999999)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 解绑设备按钮
|
||||
Widget _buildUnbindButton(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: const Color(0xFFFF3B30),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
// 实现解绑逻辑
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('确认解绑'),
|
||||
content: const Text('确定要解绑该设备吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
// 执行解绑操作
|
||||
},
|
||||
child: const Text('确认'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.unarchive_outlined, size: 18),
|
||||
SizedBox(width: 12),
|
||||
Text(
|
||||
'解绑设备',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,31 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../core/router/route_paths.dart';
|
||||
import '../../../../core/storage/user_storage.dart';
|
||||
import '../../../auth/presentation/bloc/auth_cubit.dart';
|
||||
import '../../../auth/presentation/bloc/auth_state.dart';
|
||||
import '../../../auth/presentation/pages/login_page.dart';
|
||||
import '../../../web_view/web_view_page.dart';
|
||||
|
||||
|
||||
|
||||
import '../web_view/user_info_pages.dart';
|
||||
class MyPage extends StatelessWidget {
|
||||
const MyPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 假设从 AuthCubit 获取用户信息
|
||||
final authState = context.watch<AuthCubit>().state;
|
||||
String userName = '未知用户';
|
||||
String userId = '未知ID';
|
||||
|
||||
if (authState is AuthAuthenticated) {
|
||||
final user = authState.user;
|
||||
userName = user.username ?? '未知用户';
|
||||
userId = user.userId ?? '未知ID';
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
@@ -20,7 +34,7 @@ class MyPage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 顶部用户信息
|
||||
_buildUserInfo(),
|
||||
UserInfoCard(userName: userName, userId: userId),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
@@ -43,42 +57,6 @@ class MyPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUserInfo() {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
image: DecorationImage(
|
||||
image: NetworkImage('https://pic4.zhimg.com/v2-c34c61a90095abb8713de9d1dca7ec7b_r.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('宋', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
Text('用户id:songchunyi', style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
onPressed: () {},
|
||||
),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBanner(BuildContext context) { // 添加 context 参数
|
||||
return GestureDetector(
|
||||
@@ -132,8 +110,7 @@ class MyPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
|
||||
Widget _buildFunctionList(BuildContext context) { // 添加 context 参数
|
||||
//https://www.satabot.com/
|
||||
Widget _buildFunctionList(BuildContext context) {
|
||||
final items = [
|
||||
{'icon': Icons.play_arrow, 'title': '操作视频', 'url': 'https://www.satabot.com/Support/index.html'},
|
||||
{'icon': Icons.car_rental, 'title': '租赁服务', 'url': 'https://www.satabot.com/Leasing.html'},
|
||||
@@ -141,7 +118,6 @@ class MyPage extends StatelessWidget {
|
||||
{'icon': Icons.policy, 'title': '售后政策', 'url': 'https://www.satabot.com/Aftersales-Service.html'},
|
||||
];
|
||||
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
@@ -150,36 +126,56 @@ class MyPage extends StatelessWidget {
|
||||
BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 4),
|
||||
],
|
||||
),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return ListTile(
|
||||
leading: Icon(item['icon'] as IconData),
|
||||
title: Text(item['title'] as String),
|
||||
trailing: Icon(Icons.arrow_forward),
|
||||
onTap: () {
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => WebViewPage(url: item['url'] as String),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// 功能列表
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(), // 禁止滚动,使其适应父容器高度
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return ListTile(
|
||||
leading: Icon(item['icon'] as IconData),
|
||||
title: Text(item['title'] as String),
|
||||
trailing: Icon(Icons.arrow_forward),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => WebViewPage(url: item['url'] as String),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
// 分割线
|
||||
Divider(height: 1, color: Colors.grey.withOpacity(0.3)),
|
||||
// 退出登录按钮
|
||||
ListTile(
|
||||
title: Text('退出登录', style: TextStyle(color: Colors.black)),
|
||||
trailing: Icon(Icons.exit_to_app, color: Colors.black),
|
||||
onTap: () async {
|
||||
await GetIt.I<UserStorage>().deleteUser();
|
||||
GetIt.I<AuthCubit>().logout();
|
||||
context.go(RoutePaths.login);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildLogoutButton(BuildContext context) {
|
||||
return ElevatedButton(
|
||||
onPressed: () async {
|
||||
await GetIt.I<UserStorage>().deleteUser();
|
||||
context.go('/auth/login');
|
||||
},
|
||||
GetIt.I<AuthCubit>().logout();
|
||||
context.go(RoutePaths.login);
|
||||
}
|
||||
,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.red,
|
||||
@@ -189,9 +185,6 @@ class MyPage extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.exit_to_app, size: 16, color: Colors.red),
|
||||
SizedBox(width: 8),
|
||||
Text('退出登录'),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,11 +2,14 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/features/my/presentation/pages/my_page.dart';
|
||||
|
||||
import '../../../../core/router/route_paths.dart';
|
||||
import '../../../auth/presentation/pages/login_page.dart';
|
||||
|
||||
class MyRoutes {
|
||||
/// 1. 全屏功能页面(不带导航栏)
|
||||
/// 在 createRouter 的根 routes 中使用 ...HomeRoutes.routes 引入
|
||||
static List<RouteBase> get routes => [
|
||||
|
||||
|
||||
// GoRoute(
|
||||
// path: RoutePaths.remoteControl,
|
||||
// builder: (context, state) => const RemoteControlPage(), // 需导入对应 Page
|
||||
|
||||
47
lib/features/my/presentation/web_view/user_info_pages.dart
Normal file
47
lib/features/my/presentation/web_view/user_info_pages.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
// user_info_card.dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UserInfoCard extends StatelessWidget {
|
||||
final String userName;
|
||||
final String userId;
|
||||
|
||||
const UserInfoCard({super.key, required this.userName, required this.userId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
image: DecorationImage(
|
||||
image: NetworkImage('https://pic4.zhimg.com/v2-c34c61a90095abb8713de9d1dca7ec7b_r.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(userName, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
Text('用户id:$userId', style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
onPressed: () {},
|
||||
),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -44,16 +44,26 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 在 runApp 之前调用 appStarted,确保 GoRouter 初始化时能获取到正确的初始状态
|
||||
sl<AuthCubit>().appStarted();
|
||||
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
//BlocProvider<LoginBloc>(create: (_) => sl<LoginBloc>()),
|
||||
// 核心修正:在这里提供 AuthCubit
|
||||
BlocProvider<AuthCubit>(create: (_) => sl<AuthCubit>()),
|
||||
|
||||
BlocProvider<AppUserCubit>(create: (_) => sl<AppUserCubit>()),
|
||||
BlocProvider<LoginCubit>(create: (_) => sl<LoginCubit>()),
|
||||
BlocProvider<DevicesCubit>(
|
||||
create: (_) =>
|
||||
sl<DevicesCubit>()
|
||||
..fetchAllDevices(sl<AppUserCubit>().state.user!.username),
|
||||
create: (_) {
|
||||
// 增加一个健壮性检查
|
||||
final user = sl<AppUserCubit>().state.user;
|
||||
final devicesCubit = sl<DevicesCubit>();
|
||||
if (user != null) {
|
||||
devicesCubit.fetchAllDevices(user.username);
|
||||
}
|
||||
return devicesCubit;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
|
||||
@@ -8,9 +8,9 @@ import Foundation
|
||||
import device_info_plus
|
||||
import file_selector_macos
|
||||
import flutter_webrtc
|
||||
import geolocator_apple
|
||||
import isar_community_flutter_libs
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import sentry_flutter
|
||||
import shared_preferences_foundation
|
||||
import webview_flutter_wkwebview
|
||||
@@ -19,9 +19,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
|
||||
|
||||
252
pubspec.lock
252
pubspec.lock
@@ -117,10 +117,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139"
|
||||
sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "8.12.1"
|
||||
version: "8.12.3"
|
||||
cc_ui_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -160,6 +160,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_assets:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -220,10 +228,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_webrtc
|
||||
sha256: "51bcda4ba5d7dd9e65a309244ce3ac0b58025e6e1f6d7442cee4cd02134ef65f"
|
||||
sha256: "4ed7b9fa9924e5a81eb39271e2c2356739dd1039d60a13b86ba6c5f448625086"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
version: "1.7.0"
|
||||
dartx:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -252,10 +260,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||
sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.9.0"
|
||||
version: "5.9.1"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -365,6 +373,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_map:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_map
|
||||
sha256: "87cc8349b8fa5dccda5af50018c7374b6645334a0d680931c1fe11bce88fa5bb"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.2.1"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -395,10 +411,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_webrtc
|
||||
sha256: "71a38363a5b50603e405c275f30de2eb90f980b0cc94b0e1e9d8b9d6a6b03bf0"
|
||||
sha256: "0f86b518e9349e71a136a96e0ea11294cad8a8531b2bc9ae99e69df332ac898a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.3.0"
|
||||
fpdart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -415,6 +431,54 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: f62bcd90459e63210bbf9c35deb6a51c521f992a78de19a1fe5c11704f9530e2
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "13.0.4"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.6.2"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.13"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.2.6"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.1.3"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.5"
|
||||
get_it:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -435,10 +499,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: eff94d2a6fc79fa8b811dde79c7549808c2346037ee107a1121b4a644c745f2a
|
||||
sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "17.0.1"
|
||||
version: "17.1.0"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -463,6 +527,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -611,18 +683,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
version: "4.10.0"
|
||||
latlong2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: latlong2
|
||||
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.9.1"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -651,10 +731,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
version: "6.1.0"
|
||||
lists:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lists
|
||||
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
logger:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -695,6 +783,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
mgrs_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mgrs_dart
|
||||
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -703,6 +799,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: native_toolchain_c
|
||||
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.17.4"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -711,6 +815,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "983c7fa1501f6dcc0cb7af4e42072e9993cb28d73604d25ebf4dab08165d997e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "9.2.5"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -771,10 +883,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
|
||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "2.6.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -799,6 +911,54 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "12.0.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "13.0.1"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "9.4.7"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.1.3+5"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -823,6 +983,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
polylabel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: polylabel
|
||||
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -839,6 +1007,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
proj4dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: proj4dart
|
||||
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -867,18 +1043,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sentry
|
||||
sha256: fa14b2177d2cd4489ec78817ec4e52affe1be9f595718de970a5e4ed2bccc530
|
||||
sha256: aad20a9073e674c29a0db32bea5f70b4181c059b5a2da4430d67888d349b0eef
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "9.10.0"
|
||||
version: "9.11.0"
|
||||
sentry_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sentry_flutter
|
||||
sha256: "00d168260c9be3777e4f1c9ca7a2e4f2c48566926ec22a64bbacae5a94ff72a3"
|
||||
sha256: cb1c8fb5da69c3e2c207422017b92eb625f5056e80783d052569af9a932e1f20
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "9.10.0"
|
||||
version: "9.11.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -891,10 +1067,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
|
||||
sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.18"
|
||||
version: "2.4.20"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1052,6 +1228,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
unicode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unicode
|
||||
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1080,10 +1264,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
|
||||
sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.19"
|
||||
version: "1.1.20"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1136,10 +1320,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webrtc_interface
|
||||
sha256: "2e604a31703ad26781782fb14fa8a4ee621154ee2c513d2b9938e486fa695233"
|
||||
sha256: ad0e5786b2acd3be72a3219ef1dde9e1cac071cf4604c685f11b61d63cdd6eb3
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
webview_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1188,6 +1372,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
wkt_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wkt_parser
|
||||
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1221,5 +1413,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.10.0 <4.0.0"
|
||||
flutter: ">=3.38.0"
|
||||
dart: ">=3.10.3 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
|
||||
12
pubspec.yaml
12
pubspec.yaml
@@ -102,6 +102,18 @@ dependencies:
|
||||
intl: ^0.20.2
|
||||
webview_flutter: ^4.4.2 # 将此行移到这里
|
||||
|
||||
geolocator: ^13.0.1
|
||||
flutter_map: ^6.1.0 # 建议使用最新稳定版
|
||||
latlong2: ^0.9.0 # 处理经纬度的依赖
|
||||
#coord_convert: ^1.0.0
|
||||
#高德地图
|
||||
#amap_flutter_map: ^3.0.0 # 最新稳定版,适配 Flutter 3.x
|
||||
#amap_flutter_base: ^3.0.0 # 必须与 map 版本一致
|
||||
|
||||
# 高德地图需要的权限
|
||||
permission_handler: ^12.0.1
|
||||
|
||||
#qr_code_scanner: ^1.0.1 # 用于扫描二维码
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||
#include <geolocator_windows/geolocator_windows.h>
|
||||
#include <isar_community_flutter_libs/isar_flutter_libs_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <sentry_flutter/sentry_flutter_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
@@ -16,8 +18,12 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FlutterWebRTCPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
||||
GeolocatorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
||||
IsarFlutterLibsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
SentryFlutterPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
flutter_webrtc
|
||||
geolocator_windows
|
||||
isar_community_flutter_libs
|
||||
permission_handler_windows
|
||||
sentry_flutter
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user