939 lines
30 KiB
Dart
939 lines
30 KiB
Dart
import 'dart:async';
|
||
import 'dart:developer';
|
||
import 'dart:math' as math;
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:package_info_plus/package_info_plus.dart';
|
||
import 'package:permission_handler/permission_handler.dart';
|
||
import 'package:geolocator/geolocator.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
|
||
import '../../services/update_dialog_service.dart';
|
||
|
||
class HomePage extends StatefulWidget {
|
||
const HomePage({super.key});
|
||
|
||
@override
|
||
State<HomePage> createState() => _HomePageState();
|
||
}
|
||
|
||
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||
final UpdateDialogService _updateDialogService = UpdateDialogService();
|
||
StreamSubscription<Position>? _positionStreamSubscription;
|
||
bool _showLocation = true; // 控制图标和km数显示的布尔值
|
||
bool _showLocationPrompt = false; // 控制定位提示框显示的布尔值
|
||
final double quanchengSquareLat = 36.667763;
|
||
final double quanchengSquareLon = 117.028663;
|
||
final double damingLakeLat = 36.681025;
|
||
final double damingLakeLon = 117.030707;
|
||
final double heihuSpringLat = 36.66844;
|
||
final double heihuSpringLon = 117.040105;
|
||
|
||
final double wulongtanLat = 36.672278; // 五龙潭纬度
|
||
final double wulongtanLon = 117.021282; // 五龙潭经度
|
||
final double jiefangeLat = 36.669113; // 解放阁纬度
|
||
final double jiefangeLon = 117.040088; // 解放阁经度
|
||
final double baotugSpringLat = 36.667419; // 趵突泉纬度
|
||
final double baotugSpringLon = 117.022342; // 趵突泉经度
|
||
|
||
String qcgc = '0km';
|
||
String dmh = '0km';
|
||
String hhq = '0km';
|
||
String wlt = '0km'; // 五龙潭
|
||
String jfg = '0km'; // 解放阁
|
||
String btq = '0km'; // 趵突泉
|
||
|
||
Timer? _debounceTimer;
|
||
bool _isProcessingAR = false; // 防止重复启动AR
|
||
bool _isUnityInitialized = false; // 跟踪Unity是否已初始化
|
||
Position? _lastKnownPosition;
|
||
bool _isPageActive = false;
|
||
|
||
static const platform =
|
||
MethodChannel('com.example.ar_tourism_flutter_unity.unity');
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
print('HomePage初始化');
|
||
WidgetsBinding.instance.addObserver(this);
|
||
|
||
// 初始化时设置页面激活状态
|
||
_isPageActive = true;
|
||
|
||
// 设置Unity通信通道
|
||
_setupUnityChannel();
|
||
|
||
// 延迟初始化
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
if (_isPageActive) {
|
||
_checkPermissionAndGetLocation();
|
||
_checkVersionUpdate(); // 检查更新但不重置状态
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
Future<String?> _getToken() async {
|
||
try {
|
||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||
return prefs.getString('token');
|
||
} catch (e) {
|
||
print('获取token失败: $e');
|
||
return null;
|
||
}
|
||
}
|
||
|
||
|
||
Future<void> _checkVersionUpdate() async {
|
||
try {
|
||
// 判断是否需要检查更新
|
||
bool shouldCheck = await _updateDialogService.shouldCheckUpdate();
|
||
if (!shouldCheck) {
|
||
print('本次会话已检查过更新或今天已经检查过,跳过更新检查');
|
||
return;
|
||
}
|
||
|
||
// 获取当前版本号
|
||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||
String currentVersion = packageInfo.version;
|
||
|
||
final versionInfo = await _updateDialogService.checkForUpdate();
|
||
|
||
// 只有当版本信息存在且需要更新时才显示对话框
|
||
if (versionInfo != null &&
|
||
_updateDialogService.isNeedUpdate(currentVersion, versionInfo.version) &&
|
||
mounted) {
|
||
_updateDialogService.showUpdateDialog(context, versionInfo);
|
||
}
|
||
} catch (e) {
|
||
print('版本检查失败: $e');
|
||
}
|
||
}
|
||
|
||
@override
|
||
void didChangeDependencies() {
|
||
super.didChangeDependencies();
|
||
// 使用Route观察页面是否是当前显示的页面
|
||
final route = ModalRoute.of(context);
|
||
if (route != null) {
|
||
route.addScopedWillPopCallback(() async {
|
||
_isPageActive = false;
|
||
return true;
|
||
});
|
||
}
|
||
}
|
||
|
||
void _onPageResume() {
|
||
if (!_isPageActive) return;
|
||
|
||
print('页面恢复焦点');
|
||
if (_lastKnownPosition != null) {
|
||
print('使用上次已知位置更新距离');
|
||
_updateDistances(_lastKnownPosition!);
|
||
}
|
||
// 重新检查权限并获取最新位置
|
||
_checkPermissionAndGetLocation();
|
||
}
|
||
|
||
void _setupUnityChannel() {
|
||
platform.setMethodCallHandler((call) async {
|
||
switch (call.method) {
|
||
case 'onUnityBackPressed':
|
||
print('收到Unity返回请求');
|
||
await _safeExitUnity();
|
||
return true;
|
||
case 'onUnityClosed':
|
||
print('Unity已关闭');
|
||
_isProcessingAR = false; // 重置处理标志
|
||
await _restartLocationService(); // 重启位置服务
|
||
return true;
|
||
case 'reinitializeLocation':
|
||
print('重新初始化位置服务');
|
||
await _restartLocationService();
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
});
|
||
}
|
||
|
||
Future<void> _safeExitUnity() async {
|
||
try {
|
||
// 分步关闭流程
|
||
await Future.delayed(Duration(milliseconds: 300));
|
||
if (mounted) {
|
||
Navigator.popUntil(context, (route) => route.isFirst);
|
||
}
|
||
await Future.delayed(Duration(milliseconds: 200));
|
||
await SystemChannels.platform.invokeMethod('System.gc');
|
||
} catch (e) {
|
||
print('安全退出异常: $e');
|
||
}
|
||
}
|
||
|
||
Future<void> _initializeUnity() async {
|
||
try {
|
||
print('初始化Unity');
|
||
final result = await platform.invokeMethod('initializeUnity');
|
||
_isUnityInitialized = result ?? false;
|
||
print('Unity初始化结果: $_isUnityInitialized');
|
||
} catch (e) {
|
||
print('Unity初始化错误: $e');
|
||
_isUnityInitialized = false;
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_isPageActive = false;
|
||
_cleanupLocationService();
|
||
_debounceTimer?.cancel();
|
||
WidgetsBinding.instance.removeObserver(this);
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||
print('应用生命周期状态变化: $state');
|
||
if (state == AppLifecycleState.resumed && _isPageActive) {
|
||
// 应用从后台恢复,不主动检查更新
|
||
// 从Unity返回时,需要重新启动Flutter的位置服务
|
||
_onPageResume();
|
||
} else if (state == AppLifecycleState.paused) {
|
||
// 切换到Unity时,确保停止Flutter的位置服务
|
||
_cleanupLocationService();
|
||
} else if (state == AppLifecycleState.detached) {
|
||
// 应用可能被完全关闭,准备下次启动时的状态
|
||
_updateDialogService.resetAllCheckStatus();
|
||
}
|
||
}
|
||
|
||
// 计算两个经纬度之间的距离
|
||
double _calculateDistance(
|
||
double lat1, double lon1, double lat2, double lon2) {
|
||
const double earthRadius = 6371000; // 地球半径,单位为米
|
||
|
||
// 将角度转换为弧度
|
||
final double dLat = _degreesToRadians(lat2 - lat1);
|
||
final double dLon = _degreesToRadians(lon2 - lon1);
|
||
|
||
// 使用 Haversine 公式计算距离
|
||
final double a = math.sin(dLat / 2) * math.sin(dLat / 2) +
|
||
math.cos(_degreesToRadians(lat1)) *
|
||
math.cos(_degreesToRadians(lat2)) *
|
||
math.sin(dLon / 2) *
|
||
math.sin(dLon / 2);
|
||
|
||
final double c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
|
||
return earthRadius * c; // 返回距离,单位为米
|
||
}
|
||
|
||
// 角度转弧度的辅助方法
|
||
double _degreesToRadians(double degrees) {
|
||
return degrees * math.pi / 180;
|
||
}
|
||
|
||
// 防抖动处理
|
||
void _debounce(VoidCallback callback,
|
||
{Duration duration = const Duration(milliseconds: 500)}) {
|
||
if (_debounceTimer?.isActive ?? false) {
|
||
_debounceTimer?.cancel();
|
||
}
|
||
_debounceTimer = Timer(duration, callback);
|
||
}
|
||
|
||
bool _isCheckingLocation = false; // 防止重复检查
|
||
int _locationCheckFailCount = 0; // 记录连续失败次数
|
||
DateTime? _lastLocationPromptTime; // 上次显示提示的时间
|
||
|
||
Future<void> _checkPermissionAndGetLocation() async {
|
||
if (!_isPageActive || _isCheckingLocation) {
|
||
print('页面非激活状态或已有检查正在进行,跳过');
|
||
return;
|
||
}
|
||
|
||
_isCheckingLocation = true;
|
||
|
||
try {
|
||
// 确保旧的订阅被完全取消
|
||
await _cleanupLocationService();
|
||
|
||
final hasPermission = await _getLocationPermission();
|
||
if (!hasPermission) {
|
||
print('没有位置权限');
|
||
_locationCheckFailCount++;
|
||
|
||
// 如果没有权限但有上次的位置数据,继续显示
|
||
if (_lastKnownPosition != null) {
|
||
if (mounted) {
|
||
_updateDistances(_lastKnownPosition!);
|
||
}
|
||
} else if (mounted) {
|
||
setState(() {
|
||
_showLocationPrompt = true;
|
||
_showLocation = false;
|
||
});
|
||
}
|
||
|
||
_isCheckingLocation = false;
|
||
return;
|
||
}
|
||
|
||
// 重置失败计数
|
||
_locationCheckFailCount = 0;
|
||
|
||
// 先显示界面,避免等待位置时的卡顿感
|
||
if (mounted) {
|
||
setState(() {
|
||
_showLocation = true;
|
||
_showLocationPrompt = false;
|
||
});
|
||
}
|
||
|
||
// 异步获取位置,不阻塞UI
|
||
Geolocator.getCurrentPosition(
|
||
desiredAccuracy: LocationAccuracy.high,
|
||
timeLimit: const Duration(seconds: 5), // 添加超时限制
|
||
).then((position) {
|
||
print('获取到当前位置');
|
||
_lastKnownPosition = position; // 更新最后已知位置
|
||
_updateDistances(position);
|
||
_isCheckingLocation = false;
|
||
}).catchError((error) {
|
||
print('获取位置错误: $error');
|
||
// 获取位置失败,但权限已授予,可能是临时问题
|
||
// 如果有上次位置,继续使用
|
||
if (_lastKnownPosition != null && mounted) {
|
||
_updateDistances(_lastKnownPosition!);
|
||
}
|
||
_locationCheckFailCount++;
|
||
|
||
if (mounted && _locationCheckFailCount > 3) {
|
||
setState(() {
|
||
_showLocationPrompt = true;
|
||
_showLocation = false;
|
||
});
|
||
}
|
||
_isCheckingLocation = false;
|
||
});
|
||
|
||
// 使用低频率更新,减少计算负担
|
||
// 使用新的位置流配置
|
||
_positionStreamSubscription = Geolocator.getPositionStream(
|
||
locationSettings: const LocationSettings(
|
||
accuracy: LocationAccuracy.high,
|
||
distanceFilter: 20,
|
||
timeLimit: Duration(seconds: 30), // 增加超时时间
|
||
),
|
||
).listen(
|
||
(Position position) {
|
||
print('位置更新: ${position.latitude}, ${position.longitude}');
|
||
_lastKnownPosition = position; // 更新最后已知位置
|
||
_debounce(() {
|
||
_updateDistances(position);
|
||
_locationCheckFailCount = 0;
|
||
}, duration: const Duration(milliseconds: 300));
|
||
},
|
||
onError: (error) {
|
||
print('位置流错误: $error');
|
||
// 处理错误但不立即重启服务
|
||
if (_lastKnownPosition != null && mounted) {
|
||
_updateDistances(_lastKnownPosition!);
|
||
}
|
||
},
|
||
cancelOnError: false, // 防止因错误自动取消订阅
|
||
);
|
||
} catch (e) {
|
||
print('位置错误: $e');
|
||
} finally {
|
||
_isCheckingLocation = false;
|
||
}
|
||
}
|
||
|
||
final Map<String, double> _distanceCache = {};
|
||
String _getDistanceText(String key, double distance) {
|
||
// 缓存计算结果
|
||
if (!_distanceCache.containsKey(key) ||
|
||
(_distanceCache[key]! - distance).abs() > 10) {
|
||
// 只有距离变化超过10米才更新
|
||
_distanceCache[key] = distance;
|
||
}
|
||
return '${(_distanceCache[key]! / 1000).toStringAsFixed(2)} km';
|
||
}
|
||
|
||
// 更高效的距离计算方法
|
||
double _fastDistanceCalculation(
|
||
double lat1, double lon1, double lat2, double lon2) {
|
||
// 对于较短距离,可以使用更简单的计算方法
|
||
const double earthRadius = 6371000; // 地球半径,单位为米
|
||
|
||
// 将角度转换为弧度
|
||
final double lat1Rad = lat1 * math.pi / 180;
|
||
final double lat2Rad = lat2 * math.pi / 180;
|
||
final double dLat = (lat2 - lat1) * math.pi / 180;
|
||
final double dLon = (lon2 - lon1) * math.pi / 180;
|
||
|
||
// 使用简化的 Haversine 公式
|
||
final double a = math.sin(dLat / 2) * math.sin(dLat / 2) +
|
||
math.cos(lat1Rad) *
|
||
math.cos(lat2Rad) *
|
||
math.sin(dLon / 2) *
|
||
math.sin(dLon / 2);
|
||
|
||
final double c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
|
||
return earthRadius * c;
|
||
}
|
||
|
||
// 更新距离
|
||
void _updateDistances(Position position) {
|
||
if (!mounted) return;
|
||
|
||
// 保存最后一次位置
|
||
_lastKnownPosition = position;
|
||
|
||
// 使用更高效的计算方法
|
||
final distanceToQuancheng = _fastDistanceCalculation(
|
||
position.latitude,
|
||
position.longitude,
|
||
quanchengSquareLat,
|
||
quanchengSquareLon,
|
||
);
|
||
final distanceToDaming = _fastDistanceCalculation(
|
||
position.latitude,
|
||
position.longitude,
|
||
damingLakeLat,
|
||
damingLakeLon,
|
||
);
|
||
final distanceToHeihu = _fastDistanceCalculation(
|
||
position.latitude,
|
||
position.longitude,
|
||
heihuSpringLat,
|
||
heihuSpringLon,
|
||
);
|
||
|
||
// 添加新景点的距离计算
|
||
final distanceToWulongtan = _fastDistanceCalculation(
|
||
position.latitude,
|
||
position.longitude,
|
||
wulongtanLat,
|
||
wulongtanLon,
|
||
);
|
||
final distanceToJiefange = _fastDistanceCalculation(
|
||
position.latitude,
|
||
position.longitude,
|
||
jiefangeLat,
|
||
jiefangeLon,
|
||
);
|
||
final distanceToBaotug = _fastDistanceCalculation(
|
||
position.latitude,
|
||
position.longitude,
|
||
baotugSpringLat,
|
||
baotugSpringLon,
|
||
);
|
||
|
||
if (mounted) {
|
||
setState(() {
|
||
_showLocation = true;
|
||
_showLocationPrompt = false;
|
||
qcgc = _getDistanceText('qcgc', distanceToQuancheng);
|
||
dmh = _getDistanceText('dmh', distanceToDaming);
|
||
hhq = _getDistanceText('hhq', distanceToHeihu);
|
||
|
||
// 新增景点距离
|
||
wlt = _getDistanceText('wlt', distanceToWulongtan);
|
||
jfg = _getDistanceText('jfg', distanceToJiefange);
|
||
btq = _getDistanceText('btq', distanceToBaotug);
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
Future<bool> _getLocationPermission() async {
|
||
print('获取位置权限');
|
||
// 检查定位服务是否开启
|
||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||
if (!serviceEnabled) {
|
||
print('定位服务未开启');
|
||
if (mounted) {
|
||
// 移除弹窗,直接显示顶部提示条
|
||
setState(() {
|
||
_showLocationPrompt = true;
|
||
_showLocation = false;
|
||
});
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 检查定位权限
|
||
LocationPermission permission = await Geolocator.checkPermission();
|
||
if (permission == LocationPermission.denied) {
|
||
print('位置权限被拒绝,请求权限');
|
||
permission = await Geolocator.requestPermission();
|
||
if (permission == LocationPermission.denied) {
|
||
print('位置权限请求被拒绝');
|
||
if (mounted) {
|
||
setState(() {
|
||
_showLocationPrompt = true;
|
||
_showLocation = false;
|
||
});
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (permission == LocationPermission.deniedForever) {
|
||
print('位置权限被永久拒绝');
|
||
if (mounted) {
|
||
setState(() {
|
||
_showLocationPrompt = true;
|
||
_showLocation = false;
|
||
});
|
||
}
|
||
return false;
|
||
}
|
||
|
||
print('位置权限已授予');
|
||
return true;
|
||
}
|
||
|
||
Future<bool> _initializeUnityIfNeeded() async {
|
||
if (_isUnityInitialized) return true;
|
||
|
||
try {
|
||
print('初始化Unity');
|
||
final result = await platform.invokeMethod('initializeUnity');
|
||
_isUnityInitialized = result ?? false;
|
||
print('Unity初始化结果: $_isUnityInitialized');
|
||
return _isUnityInitialized;
|
||
} catch (e) {
|
||
print('Unity初始化错误: $e');
|
||
_isUnityInitialized = false;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
void _pauseLocationUpdates() {
|
||
print('暂停位置更新');
|
||
_positionStreamSubscription?.pause();
|
||
}
|
||
|
||
void _resumeLocationUpdates() {
|
||
print('恢复位置更新');
|
||
if (_positionStreamSubscription?.isPaused ?? false) {
|
||
_positionStreamSubscription?.resume();
|
||
} else {
|
||
_checkPermissionAndGetLocation();
|
||
}
|
||
}
|
||
|
||
void _launchUnityActivity() async {
|
||
if (_isProcessingAR) return;
|
||
|
||
setState(() => _isProcessingAR = true);
|
||
|
||
try {
|
||
final String? token = await _getToken();
|
||
|
||
// 停止位置服务
|
||
await _cleanupLocationService();
|
||
|
||
// 确保Unity初始化
|
||
if (!await platform.invokeMethod('initializeUnity')) {
|
||
throw Exception('Unity初始化失败');
|
||
}
|
||
|
||
// 检查权限
|
||
final bool hasPermissions = await platform.invokeMethod('checkAndRequestPermissions');
|
||
if (!hasPermissions) {
|
||
throw Exception('未获得必要权限');
|
||
}
|
||
|
||
// 启动Unity - 使用相同的方法名和参数,在iOS和Android上都有相应实现
|
||
await platform.invokeMethod('start_unity', {
|
||
'returnToHome': true,
|
||
'token': token ?? '',
|
||
'sceneName': 'Main', // 默认场景,iOS和Android通用
|
||
});
|
||
} catch (e) {
|
||
print('AR启动错误: $e');
|
||
// 显示错误提示
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('启动AR失败: ${e.toString()}'),
|
||
duration: const Duration(seconds: 3),
|
||
),
|
||
);
|
||
}
|
||
} finally {
|
||
if (mounted) {
|
||
setState(() => _isProcessingAR = false);
|
||
}
|
||
// 重启位置服务
|
||
await _restartLocationService();
|
||
}
|
||
}
|
||
|
||
Future<void> _cleanupLocationService() async {
|
||
print('清理位置服务');
|
||
try {
|
||
await _positionStreamSubscription?.cancel();
|
||
_positionStreamSubscription = null;
|
||
_lastKnownPosition = null; // 清除最后已知位置
|
||
} catch (e) {
|
||
print('清理位置服务时出错: $e');
|
||
}
|
||
}
|
||
|
||
// 新增:重启位置服务的方法
|
||
Future<void> _restartLocationService() async {
|
||
print('重启位置服务');
|
||
try {
|
||
// 确保完全清理
|
||
await _cleanupLocationService();
|
||
|
||
// 短暂延迟确保资源释放
|
||
await Future.delayed(const Duration(milliseconds: 300));
|
||
|
||
if (mounted && _isPageActive) {
|
||
await _checkPermissionAndGetLocation();
|
||
}
|
||
} catch (e) {
|
||
print('重启位置服务时出错: $e');
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
print('构建HomePage界面');
|
||
return Stack(
|
||
children: [
|
||
// 允许水平滚动的背景图片容器
|
||
SingleChildScrollView(
|
||
scrollDirection: Axis.horizontal, // 允许水平滚动
|
||
child: Container(
|
||
width: MediaQuery.of(context).size.width * 1.6, // 宽度为屏幕宽度的两倍
|
||
height: MediaQuery.of(context).size.height, // 高度保持为屏幕高度
|
||
decoration: const BoxDecoration(
|
||
image: DecorationImage(
|
||
image: AssetImage('images/page_home.png'), // 背景图路径
|
||
fit: BoxFit.cover, // 背景图适应容器
|
||
),
|
||
),
|
||
|
||
child: Stack(
|
||
children: [
|
||
// 位置标记
|
||
if (_showLocation)
|
||
_buildLocationMarker(
|
||
top: MediaQuery.of(context).size.height * 0.38, // 约295/800
|
||
left: MediaQuery.of(context).size.width * 0.65, // 约365/960
|
||
title: '泉城广场',
|
||
location: qcgc,
|
||
isQuancheng: true,
|
||
),
|
||
if (_showLocation)
|
||
_buildLocationMarker(
|
||
top: MediaQuery.of(context).size.height * 0.14, // 约460/800
|
||
left: MediaQuery.of(context).size.width * 0.50, // 约285/960
|
||
title: '大明湖',
|
||
location: dmh,
|
||
),
|
||
if (_showLocation)
|
||
_buildLocationMarker(
|
||
top: MediaQuery.of(context).size.height * 0.615, // 约470/800
|
||
left: MediaQuery.of(context).size.width * 0.69, // 约435/960
|
||
title: '黑虎泉',
|
||
location: hhq,
|
||
),
|
||
if (_showLocation)
|
||
_buildLocationMarker(
|
||
top: MediaQuery.of(context).size.height * 0.30,
|
||
left: MediaQuery.of(context).size.width * 0.16,
|
||
title: '五龙潭',
|
||
location: wlt,
|
||
),
|
||
if (_showLocation)
|
||
_buildLocationMarker(
|
||
top: MediaQuery.of(context).size.height * 0.32,
|
||
left: MediaQuery.of(context).size.width * 1.1 ,
|
||
title: '解放阁',
|
||
location: jfg,
|
||
),
|
||
if (_showLocation)
|
||
_buildLocationMarker(
|
||
top: MediaQuery.of(context).size.height * 0.46,
|
||
left: MediaQuery.of(context).size.width * 0.26,
|
||
title: '趵突泉',
|
||
location: btq,
|
||
),
|
||
|
||
// 单独添加泉城广场GIF,放在泉城广场标记下方
|
||
if (_showLocation)
|
||
Positioned(
|
||
top: MediaQuery.of(context).size.height * 0.40,
|
||
left: MediaQuery.of(context).size.width * 0.73,
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
print('点击泉城广场GIF');
|
||
_debounce(() => _launchUnityActivity());
|
||
},
|
||
child: Image.asset(
|
||
'images/gif1.gif',
|
||
width: 160,
|
||
height: 120,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
|
||
// 底部渐变阴影
|
||
Positioned(
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
child: Container(
|
||
height: 80,
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
begin: Alignment.bottomCenter,
|
||
end: Alignment.topCenter,
|
||
colors: [
|
||
Colors.black.withOpacity(0.8),
|
||
Colors.black.withOpacity(0),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
|
||
// 固定在屏幕右下角的 more 图标
|
||
Positioned(
|
||
bottom: 10, // 距离底部的距离
|
||
right: 10, // 距离右边的距离
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
// 点击事件处理,跳转到 MorePage
|
||
print('点击更多按钮');
|
||
Navigator.pushNamed(context, '/more');
|
||
},
|
||
child: Image.asset(
|
||
'images/more.png',
|
||
width: 30, // 图标的宽度
|
||
height: 30, // 图标的高度
|
||
),
|
||
),
|
||
),
|
||
|
||
// 添加定位提示框
|
||
if (_showLocationPrompt)
|
||
Positioned(
|
||
top: 50,
|
||
right: 10,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF1F212E),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Text(
|
||
'开启定位服务,进入精彩AR世界',
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
GestureDetector(
|
||
onTap: () async {
|
||
print('点击开启定位服务');
|
||
if (_isCheckingLocation) return;
|
||
|
||
_isCheckingLocation = true;
|
||
try {
|
||
// 直接打开系统定位设置
|
||
await Geolocator.openLocationSettings();
|
||
|
||
// 延迟3秒后重新检查定位状态
|
||
if (mounted) {
|
||
Future.delayed(const Duration(seconds: 3), () {
|
||
_isCheckingLocation = false;
|
||
_checkPermissionAndGetLocation();
|
||
});
|
||
}
|
||
} catch (e) {
|
||
print('打开定位设置失败: $e');
|
||
_isCheckingLocation = false;
|
||
}
|
||
},
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 12,
|
||
vertical: 4,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF96FBE9).withOpacity(0.2),
|
||
borderRadius: BorderRadius.circular(4),
|
||
border: Border.all(
|
||
color: const Color(0xFF96FBE9).withOpacity(0.8)),
|
||
),
|
||
child: const Text(
|
||
'开启',
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
GestureDetector(
|
||
onTap: () {
|
||
print('关闭定位提示');
|
||
setState(() {
|
||
_showLocationPrompt = false;
|
||
});
|
||
},
|
||
child: const Icon(
|
||
Icons.close,
|
||
color: Colors.white,
|
||
size: 20,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
|
||
// 添加AR处理中的加载指示器
|
||
// if (_isProcessingAR)
|
||
// Container(
|
||
// color: Colors.black.withOpacity(0.5),
|
||
// child: const Center(
|
||
// child: CircularProgressIndicator(
|
||
// valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||
// ),
|
||
// ),
|
||
// ),
|
||
],
|
||
);
|
||
}
|
||
|
||
// 抽取位置标记的构建逻辑,避免重复代码
|
||
Widget _buildLocationMarker({
|
||
required double top,
|
||
required double left,
|
||
required String title,
|
||
required String location,
|
||
bool isQuancheng = false,
|
||
}) {
|
||
return Positioned(
|
||
top: top,
|
||
left: left,
|
||
child: Column(
|
||
children: [
|
||
GestureDetector(
|
||
onTap: () {
|
||
if (isQuancheng) {
|
||
print('点击泉城广场标记');
|
||
_debounce(() => _launchUnityActivity());
|
||
}
|
||
},
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
gradient: const LinearGradient(
|
||
colors: [
|
||
Color(0xFF3A9FAF),
|
||
Color(0xFF58C082),
|
||
],
|
||
begin: Alignment.centerLeft,
|
||
end: Alignment.centerRight,
|
||
),
|
||
borderRadius: BorderRadius.circular(20),
|
||
),
|
||
child: Container(
|
||
margin: const EdgeInsets.all(1), // 创建1像素的边框效果
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(19), // 略小于外部圆角
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
// 非泉城广场景点添加 nouse.png 图标
|
||
if (!isQuancheng)
|
||
Padding(
|
||
padding: const EdgeInsets.only(right: 4),
|
||
child: Image.asset(
|
||
'images/nouse.png',
|
||
width: 16,
|
||
height: 16,
|
||
color: const Color(0xFFC1C1C1), // 设置灰色
|
||
),
|
||
),
|
||
Text(
|
||
title,
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
// 泉城广场黑色,其他景点灰色
|
||
color: isQuancheng
|
||
? Colors.black87
|
||
: const Color(0xFFC1C1C1),
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
Image.asset(
|
||
'images/location.png',
|
||
width: 12,
|
||
height: 12,
|
||
// 泉城广场黑色,其他景点灰色
|
||
color: isQuancheng ? Colors.black : const Color(0xFFC1C1C1),
|
||
),
|
||
Text(
|
||
location,
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
// 泉城广场黑色,其他景点灰色
|
||
color: isQuancheng
|
||
? Colors.black87
|
||
: const Color(0xFFC1C1C1),
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (isQuancheng)
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
'点击此处,进入MR',
|
||
style: TextStyle(
|
||
color: Color(0xFF3A9FAF),
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|