feat: 增加弹出气泡、搜索设备页面
This commit is contained in:
parent
64f1a28997
commit
8501b5ea48
@ -11,6 +11,21 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<!-- 系统通知 -->
|
<!-- 系统通知 -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
|
<!-- 蓝牙相关权限 -->
|
||||||
|
<!-- Android 12以下需要的蓝牙权限 -->
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
|
||||||
|
<!-- Android 12及以上需要的蓝牙权限 -->
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||||
|
android:usesPermissionFlags="neverForLocation" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||||
|
|
||||||
|
<!-- 位置权限(Android 12以下蓝牙扫描需要) -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
22
lib/app.dart
22
lib/app.dart
@ -11,7 +11,7 @@ import 'package:starwork_flutter/routes/app_pages.dart';
|
|||||||
import 'package:starwork_flutter/routes/app_routes.dart';
|
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||||
|
|
||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
App({super.key, required this.initialRoute});
|
App({super.key, required this.initialRoute});
|
||||||
|
|
||||||
String initialRoute;
|
String initialRoute;
|
||||||
|
|
||||||
@ -29,15 +29,17 @@ class _AppState extends State<App> {
|
|||||||
builder: (_, child) {
|
builder: (_, child) {
|
||||||
return GetMaterialApp(
|
return GetMaterialApp(
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
scaffoldBackgroundColor: Colors.white,
|
scaffoldBackgroundColor: Colors.white,
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
textSelectionTheme: TextSelectionThemeData(
|
textSelectionTheme: TextSelectionThemeData(
|
||||||
cursorColor: Colors.blue.shade200,
|
cursorColor: Colors.blue.shade200,
|
||||||
selectionColor: Colors.blue.shade100,
|
selectionColor: Colors.blue.shade100,
|
||||||
selectionHandleColor: Colors.blue.shade300,
|
selectionHandleColor: Colors.blue.shade300,
|
||||||
),
|
),
|
||||||
dialogBackgroundColor: Colors.white,
|
dialogBackgroundColor: Colors.white,
|
||||||
),
|
appBarTheme: AppBarTheme(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
)),
|
||||||
// 必须配置以下三个本地化代理
|
// 必须配置以下三个本地化代理
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
GlobalMaterialLocalizations.delegate, // 提供Material组件本地化字符串
|
GlobalMaterialLocalizations.delegate, // 提供Material组件本地化字符串
|
||||||
|
|||||||
@ -46,4 +46,132 @@ class AppPermission {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 请求蓝牙权限(Android 12及以上需要)
|
||||||
|
static Future<bool> requestBluetoothPermissions() async {
|
||||||
|
try {
|
||||||
|
// Android 12及以上需要的蓝牙权限
|
||||||
|
List<Permission> bluetoothPermissions = [
|
||||||
|
Permission.bluetoothScan,
|
||||||
|
Permission.bluetoothConnect,
|
||||||
|
Permission.bluetoothAdvertise,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Android 12以下需要的位置权限
|
||||||
|
List<Permission> locationPermissions = [
|
||||||
|
Permission.location,
|
||||||
|
Permission.locationWhenInUse,
|
||||||
|
];
|
||||||
|
|
||||||
|
bool bluetoothGranted = true;
|
||||||
|
bool locationGranted = true;
|
||||||
|
|
||||||
|
// 检查并请求蓝牙权限(Android 12+)
|
||||||
|
Map<Permission, PermissionStatus> bluetoothStatuses = await bluetoothPermissions.request();
|
||||||
|
for (var status in bluetoothStatuses.values) {
|
||||||
|
if (status != PermissionStatus.granted) {
|
||||||
|
bluetoothGranted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查并请求位置权限(Android 12以下或蓝牙扫描需要)
|
||||||
|
Map<Permission, PermissionStatus> locationStatuses = await locationPermissions.request();
|
||||||
|
for (var status in locationStatuses.values) {
|
||||||
|
if (status != PermissionStatus.granted) {
|
||||||
|
locationGranted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasPermission = bluetoothGranted && locationGranted;
|
||||||
|
|
||||||
|
if (hasPermission) {
|
||||||
|
print("蓝牙权限已授予");
|
||||||
|
} else {
|
||||||
|
print("蓝牙权限被拒绝");
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasPermission;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
print("请求蓝牙权限失败: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查蓝牙扫描权限
|
||||||
|
static Future<bool> checkBluetoothScanPermission() async {
|
||||||
|
try {
|
||||||
|
var status = await Permission.bluetoothScan.status;
|
||||||
|
return status == PermissionStatus.granted;
|
||||||
|
} catch (e) {
|
||||||
|
// 如果权限不存在(Android 12以下),检查位置权限
|
||||||
|
return await checkLocationPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查蓝牙连接权限
|
||||||
|
static Future<bool> checkBluetoothConnectPermission() async {
|
||||||
|
try {
|
||||||
|
var status = await Permission.bluetoothConnect.status;
|
||||||
|
return status == PermissionStatus.granted;
|
||||||
|
} catch (e) {
|
||||||
|
// 如果权限不存在(Android 12以下),默认返回true
|
||||||
|
print("蓝牙连接权限检查失败,可能是Android 12以下版本: $e");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查蓝牙广播权限
|
||||||
|
static Future<bool> checkBluetoothAdvertisePermission() async {
|
||||||
|
try {
|
||||||
|
var status = await Permission.bluetoothAdvertise.status;
|
||||||
|
return status == PermissionStatus.granted;
|
||||||
|
} catch (e) {
|
||||||
|
// 如果权限不存在(Android 12以下),默认返回true
|
||||||
|
print("蓝牙广播权限检查失败,可能是Android 12以下版本: $e");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查位置权限(Android 12以下蓝牙扫描需要)
|
||||||
|
static Future<bool> checkLocationPermission() async {
|
||||||
|
var status = await Permission.location.status;
|
||||||
|
return status == PermissionStatus.granted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求位置权限
|
||||||
|
static Future<bool> requestLocationPermission() async {
|
||||||
|
try {
|
||||||
|
final PermissionStatus status = await Permission.location.request();
|
||||||
|
|
||||||
|
if (status == PermissionStatus.granted) {
|
||||||
|
print("位置权限已授予");
|
||||||
|
return true;
|
||||||
|
} else if (status == PermissionStatus.denied) {
|
||||||
|
print("位置权限被拒绝");
|
||||||
|
return false;
|
||||||
|
} else if (status.isPermanentlyDenied) {
|
||||||
|
print("位置权限被永久拒绝,跳转到设置页面");
|
||||||
|
openAppSettings();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
print("请求位置权限失败: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查所有蓝牙相关权限
|
||||||
|
static Future<bool> checkAllBluetoothPermissions() async {
|
||||||
|
bool scanPermission = await checkBluetoothScanPermission();
|
||||||
|
bool connectPermission = await checkBluetoothConnectPermission();
|
||||||
|
bool advertisePermission = await checkBluetoothAdvertisePermission();
|
||||||
|
bool locationPermission = await checkLocationPermission();
|
||||||
|
|
||||||
|
return scanPermission && connectPermission && advertisePermission && locationPermission;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:starwork_flutter/common/widgets/custom_dialog_widget.dart';
|
||||||
|
|
||||||
class BaseController extends GetxController {
|
class BaseController extends GetxController {
|
||||||
void showToast(String message) {
|
void showToast(String message) {
|
||||||
@ -24,6 +25,24 @@ class BaseController extends GetxController {
|
|||||||
EasyLoading.showError(message.tr);
|
EasyLoading.showError(message.tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showCustomDialog({
|
||||||
|
required String title,
|
||||||
|
required Widget content,
|
||||||
|
required VoidCallback onConfirm,
|
||||||
|
String? confirmText
|
||||||
|
}) {
|
||||||
|
Get.dialog(
|
||||||
|
CustomDialogWidget(
|
||||||
|
title: title,
|
||||||
|
content: content,
|
||||||
|
onConfirm: onConfirm,
|
||||||
|
confirmText: confirmText,
|
||||||
|
),
|
||||||
|
barrierDismissible: false, // 点击遮罩是否关闭
|
||||||
|
useSafeArea: true, // 推荐保持默认
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
if (EasyLoading.isShow) {
|
if (EasyLoading.isShow) {
|
||||||
|
|||||||
110
lib/common/widgets/custom_dialog_widget.dart
Normal file
110
lib/common/widgets/custom_dialog_widget.dart
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class CustomDialogWidget extends StatefulWidget {
|
||||||
|
CustomDialogWidget({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.content,
|
||||||
|
required this.onConfirm,
|
||||||
|
String? confirmText,
|
||||||
|
}) : confirmText = confirmText ?? '确认'.tr;
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final Widget content;
|
||||||
|
final VoidCallback onConfirm;
|
||||||
|
final String confirmText;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CustomDialogWidget> createState() => _CustomDialogWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomDialogWidgetState extends State<CustomDialogWidget> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(14.r), // 圆角
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 18.h),
|
||||||
|
Text(
|
||||||
|
widget.title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.sp,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10.h),
|
||||||
|
widget.content,
|
||||||
|
SizedBox(height: 10.h),
|
||||||
|
Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color: Colors.grey, // 右侧边框颜色
|
||||||
|
width: 0.5, // 右侧边框宽度
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
// 左侧文本占一半
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: 42.h,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
right: BorderSide(
|
||||||
|
color: Colors.grey, // 右侧边框颜色
|
||||||
|
width: 0.5, // 右侧边框宽度
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'取消'.tr,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 14.sp, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
// 右侧文本占一半
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
widget.onConfirm();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: 42.h,
|
||||||
|
child: Text(
|
||||||
|
widget.confirmText,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Colors.blue,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,7 +32,7 @@ class F {
|
|||||||
static String get apiHost {
|
static String get apiHost {
|
||||||
switch (appFlavor) {
|
switch (appFlavor) {
|
||||||
case Flavor.skyDev:
|
case Flavor.skyDev:
|
||||||
return 'http://10.0.2.2/api';
|
return 'http://192.168.1.136/api';
|
||||||
case Flavor.skyPre:
|
case Flavor.skyPre:
|
||||||
return 'https://loacl.work.star-lock.cn/api';
|
return 'https://loacl.work.star-lock.cn/api';
|
||||||
case Flavor.skyRelease:
|
case Flavor.skyRelease:
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:starwork_flutter/routes/app_routes.dart';
|
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||||
|
import 'package:starwork_flutter/views/device/searchDevice/search_device_binding.dart';
|
||||||
|
import 'package:starwork_flutter/views/device/searchDevice/search_device_view.dart';
|
||||||
import 'package:starwork_flutter/views/home/home_binding.dart';
|
import 'package:starwork_flutter/views/home/home_binding.dart';
|
||||||
import 'package:starwork_flutter/views/home/home_view.dart';
|
import 'package:starwork_flutter/views/home/home_view.dart';
|
||||||
import 'package:starwork_flutter/views/login/forgotPassword/forgot_password_binding.dart';
|
import 'package:starwork_flutter/views/login/forgotPassword/forgot_password_binding.dart';
|
||||||
@ -60,5 +62,10 @@ class AppPages {
|
|||||||
page: () => const SetNewPasswordView(),
|
page: () => const SetNewPasswordView(),
|
||||||
binding: SetNewPasswordBinding(),
|
binding: SetNewPasswordBinding(),
|
||||||
),
|
),
|
||||||
|
GetPage(
|
||||||
|
name: AppRoutes.searchDevice,
|
||||||
|
page: () => const SearchDeviceView(),
|
||||||
|
binding: SearchDeviceBinding(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,4 +8,5 @@ class AppRoutes{
|
|||||||
static const String inputVerificationCode = '/inputVerificationCode';
|
static const String inputVerificationCode = '/inputVerificationCode';
|
||||||
static const String forgotPassword = '/forgotPassword';
|
static const String forgotPassword = '/forgotPassword';
|
||||||
static const String setNewPassword = '/setNewPassword';
|
static const String setNewPassword = '/setNewPassword';
|
||||||
|
static const String searchDevice = '/searchDevice';
|
||||||
}
|
}
|
||||||
9
lib/views/device/searchDevice/search_device_binding.dart
Normal file
9
lib/views/device/searchDevice/search_device_binding.dart
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:starwork_flutter/views/device/searchDevice/search_device_controller.dart';
|
||||||
|
|
||||||
|
class SearchDeviceBinding extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
Get.lazyPut<SearchDeviceController>(() => SearchDeviceController());
|
||||||
|
}
|
||||||
|
}
|
||||||
22
lib/views/device/searchDevice/search_device_controller.dart
Normal file
22
lib/views/device/searchDevice/search_device_controller.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:starwork_flutter/base/app_permission.dart';
|
||||||
|
import 'package:starwork_flutter/base/base_controller.dart';
|
||||||
|
|
||||||
|
class SearchDeviceController extends BaseController {
|
||||||
|
// 搜索状态管理
|
||||||
|
final RxBool _isSearching = false.obs;
|
||||||
|
|
||||||
|
// Getter
|
||||||
|
bool get isSearching => _isSearching.value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() async {
|
||||||
|
super.onInit();
|
||||||
|
}
|
||||||
|
}
|
||||||
42
lib/views/device/searchDevice/search_device_view.dart
Normal file
42
lib/views/device/searchDevice/search_device_view.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/src/widgets/framework.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:starwork_flutter/views/device/searchDevice/search_device_controller.dart';
|
||||||
|
import 'package:starwork_flutter/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart';
|
||||||
|
|
||||||
|
class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||||
|
const SearchDeviceView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Obx(() => Text(
|
||||||
|
controller.isSearching ? '搜索设备中'.tr : '搜索设备'.tr,
|
||||||
|
)),
|
||||||
|
SizedBox(
|
||||||
|
width: 8.w,
|
||||||
|
),
|
||||||
|
Obx(
|
||||||
|
() => SearchDeviceRotatingIconWidget(
|
||||||
|
isRotating: controller.isSearching,
|
||||||
|
radius: 10.w,
|
||||||
|
rotationDuration: 1500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
|
// TODO: 添加设备搜索结果列表
|
||||||
|
child: Center(
|
||||||
|
child: Text('设备搜索页面'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
|
class SearchDeviceRotatingIconWidget extends StatefulWidget {
|
||||||
|
/// 是否正在旋转
|
||||||
|
final bool isRotating;
|
||||||
|
/// 旋转速度(毫秒)
|
||||||
|
final int rotationDuration;
|
||||||
|
/// 图标颜色
|
||||||
|
final Color? color;
|
||||||
|
/// 图标半径
|
||||||
|
final double? radius;
|
||||||
|
|
||||||
|
const SearchDeviceRotatingIconWidget({
|
||||||
|
super.key,
|
||||||
|
required this.isRotating,
|
||||||
|
this.rotationDuration = 1000,
|
||||||
|
this.color = Colors.grey,
|
||||||
|
this.radius,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SearchDeviceRotatingIconWidgetState createState() => _SearchDeviceRotatingIconWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchDeviceRotatingIconWidgetState extends State<SearchDeviceRotatingIconWidget>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: Duration(milliseconds: widget.rotationDuration),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 根据初始状态决定是否开始动画
|
||||||
|
if (widget.isRotating) {
|
||||||
|
_animationController.repeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(SearchDeviceRotatingIconWidget oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
// 当isRotating状态发生变化时,控制动画的开始和停止
|
||||||
|
if (widget.isRotating != oldWidget.isRotating) {
|
||||||
|
if (widget.isRotating) {
|
||||||
|
_startRotation();
|
||||||
|
} else {
|
||||||
|
_stopRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当旋转速度发生变化时,更新动画时长
|
||||||
|
if (widget.rotationDuration != oldWidget.rotationDuration) {
|
||||||
|
_animationController.duration = Duration(milliseconds: widget.rotationDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 开始旋转动画
|
||||||
|
void _startRotation() {
|
||||||
|
if (!_animationController.isAnimating) {
|
||||||
|
_animationController.repeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 停止旋转动画
|
||||||
|
void _stopRotation() {
|
||||||
|
if (_animationController.isAnimating) {
|
||||||
|
_animationController.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _animationController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Transform.rotate(
|
||||||
|
angle: _animationController.value * 2 * 3.14159,
|
||||||
|
child: CupertinoActivityIndicator(
|
||||||
|
radius: widget.radius ?? 10.w,
|
||||||
|
color: widget.color,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:starwork_flutter/base/app_permission.dart';
|
import 'package:starwork_flutter/base/app_permission.dart';
|
||||||
|
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||||
import 'package:super_tooltip/super_tooltip.dart';
|
import 'package:super_tooltip/super_tooltip.dart';
|
||||||
import 'package:starwork_flutter/views/home/widget/home_attendance_chart_area_widget.dart';
|
import 'package:starwork_flutter/views/home/widget/home_attendance_chart_area_widget.dart';
|
||||||
import 'package:starwork_flutter/views/home/widget/home_carousel_area_widget.dart';
|
import 'package:starwork_flutter/views/home/widget/home_carousel_area_widget.dart';
|
||||||
@ -175,10 +176,15 @@ class HomeView extends GetView<HomeController> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: 14.w,
|
width: 14.w,
|
||||||
),
|
),
|
||||||
Icon(
|
GestureDetector(
|
||||||
Icons.cancel,
|
onTap: () {
|
||||||
color: const Color(0xFFEE9846),
|
controller.isOpenNotificationPermission.value = true;
|
||||||
size: 18.sp,
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.cancel,
|
||||||
|
color: const Color(0xFFEE9846),
|
||||||
|
size: 18.sp,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -263,12 +269,12 @@ class HomeView extends GetView<HomeController> {
|
|||||||
{
|
{
|
||||||
'title': '搜索设备',
|
'title': '搜索设备',
|
||||||
'icon': Icons.sensors_rounded,
|
'icon': Icons.sensors_rounded,
|
||||||
'onTap': () => _handleMenuTap('添加设备'),
|
'onTap': () => _handleMenuTap(0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': '加入团队',
|
'title': '加入团队',
|
||||||
'icon': Icons.group_add,
|
'icon': Icons.group_add,
|
||||||
'onTap': () => _handleMenuTap('创建群组'),
|
'onTap': () => _handleMenuTap(1),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -329,7 +335,9 @@ class HomeView extends GetView<HomeController> {
|
|||||||
size: 18.sp,
|
size: 18.sp,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
SizedBox(width: 8.w,),
|
SizedBox(
|
||||||
|
width: 8.w,
|
||||||
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title,
|
||||||
@ -348,12 +356,13 @@ class HomeView extends GetView<HomeController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 处理菜单点击事件
|
/// 处理菜单点击事件
|
||||||
void _handleMenuTap(String menuTitle) {
|
void _handleMenuTap(int menuIndex) {
|
||||||
Get.snackbar(
|
switch (menuIndex) {
|
||||||
'提示',
|
case 0:
|
||||||
'点击了:$menuTitle',
|
Get.toNamed(AppRoutes.searchDevice);
|
||||||
snackPosition: SnackPosition.TOP,
|
break;
|
||||||
duration: const Duration(seconds: 2),
|
case 1:
|
||||||
);
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,127 +72,44 @@ class LoginController extends BaseController {
|
|||||||
required String content,
|
required String content,
|
||||||
required VoidCallback onConfirm,
|
required VoidCallback onConfirm,
|
||||||
}) {
|
}) {
|
||||||
Get.dialog(
|
showCustomDialog(
|
||||||
Dialog(
|
title: title,
|
||||||
backgroundColor: Colors.white,
|
content: RichText(
|
||||||
shape: RoundedRectangleBorder(
|
text: TextSpan(
|
||||||
borderRadius: BorderRadius.circular(14.r), // 圆角
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
TextSpan(
|
||||||
padding: EdgeInsets.only(top: 22.h),
|
text: '请你阅读并同意'.tr,
|
||||||
child: Text(
|
style: TextStyle(
|
||||||
title,
|
fontSize: 12.sp,
|
||||||
style: TextStyle(
|
color: Colors.grey,
|
||||||
fontSize: 18.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
TextSpan(
|
||||||
padding: EdgeInsets.all(22.w),
|
text: '《用户协议》'.tr,
|
||||||
child: RichText(
|
style: TextStyle(
|
||||||
text: TextSpan(
|
fontSize: 12.sp,
|
||||||
children: [
|
color: Colors.blue,
|
||||||
TextSpan(
|
|
||||||
text: '请你阅读并同意'.tr,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: '《用户协议》'.tr,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: '和'.tr,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: '《隐私政策》'.tr,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 20.h),
|
TextSpan(
|
||||||
Container(
|
text: '和'.tr,
|
||||||
decoration: const BoxDecoration(
|
style: TextStyle(
|
||||||
border: Border(
|
fontSize: 12.sp,
|
||||||
top: BorderSide(
|
color: Colors.grey,
|
||||||
color: Colors.grey, // 右侧边框颜色
|
|
||||||
width: 0.5, // 右侧边框宽度
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Row(
|
),
|
||||||
children: [
|
TextSpan(
|
||||||
Expanded(
|
text: '《隐私政策》'.tr,
|
||||||
// 左侧文本占一半
|
style: TextStyle(
|
||||||
child: GestureDetector(
|
fontSize: 12.sp,
|
||||||
onTap: () {
|
color: Colors.blue,
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
height: 42.h,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
right: BorderSide(
|
|
||||||
color: Colors.grey, // 右侧边框颜色
|
|
||||||
width: 0.5, // 右侧边框宽度
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'取消'.tr,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(fontSize: 14.sp, color: Colors.grey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
// 右侧文本占一半
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Get.back();
|
|
||||||
onConfirm();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
height: 42.h,
|
|
||||||
child: Text(
|
|
||||||
'同意并继续'.tr,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: Colors.blue,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
onConfirm: onConfirm,
|
||||||
|
confirmText: '同意并继续'.tr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:starwork_flutter/base/app_permission.dart';
|
import 'package:starwork_flutter/base/app_permission.dart';
|
||||||
@ -201,10 +202,16 @@ class MessagesView extends GetView<MessagesController> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: 14.w,
|
width: 14.w,
|
||||||
),
|
),
|
||||||
Icon(
|
GestureDetector(
|
||||||
Icons.cancel,
|
onTap: () {
|
||||||
color: const Color(0xFFEE9846),
|
controller.homeController.isOpenNotificationPermission.value =
|
||||||
size: 18.sp,
|
true;
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.cancel,
|
||||||
|
color: const Color(0xFFEE9846),
|
||||||
|
size: 18.sp,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -217,25 +224,31 @@ class MessagesView extends GetView<MessagesController> {
|
|||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: _onRefresh,
|
onRefresh: _onRefresh,
|
||||||
// 基础样式配置
|
// 基础样式配置
|
||||||
color: const Color(0xFF4A90E2), // 刷新指示器颜色
|
color: const Color(0xFF4A90E2),
|
||||||
backgroundColor: Colors.white, // 背景颜色
|
// 刷新指示器颜色
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
// 背景颜色
|
||||||
|
|
||||||
// 控制下拉触发距离的关键属性
|
// 控制下拉触发距离的关键属性
|
||||||
displacement: 60.0, // 刷新指示器距离顶部的距离(默认40.0)
|
displacement: 60.0,
|
||||||
edgeOffset: 0.0, // 边缘偏移量(默认0.0)
|
// 刷新指示器距离顶部的距离(默认40.0)
|
||||||
|
edgeOffset: 0.0,
|
||||||
|
// 边缘偏移量(默认0.0)
|
||||||
|
|
||||||
// 控制下拉幅度的关键属性
|
// 控制下拉幅度的关键属性
|
||||||
triggerMode: RefreshIndicatorTriggerMode.onEdge, // 触发模式
|
triggerMode: RefreshIndicatorTriggerMode.onEdge,
|
||||||
|
// 触发模式
|
||||||
// RefreshIndicatorTriggerMode.onEdge: 在边缘触发(默认)
|
// RefreshIndicatorTriggerMode.onEdge: 在边缘触发(默认)
|
||||||
// RefreshIndicatorTriggerMode.anywhere: 在任何位置都可以触发
|
// RefreshIndicatorTriggerMode.anywhere: 在任何位置都可以触发
|
||||||
|
|
||||||
// 描边宽度
|
// 描边宽度
|
||||||
strokeWidth: 2.5, // 刷新指示器的描边宽度(默认2.0)
|
strokeWidth: 2.5,
|
||||||
|
// 刷新指示器的描边宽度(默认2.0)
|
||||||
|
|
||||||
// 语义标签(用于无障碍功能)
|
// 语义标签(用于无障碍功能)
|
||||||
semanticsLabel: '下拉刷新消息列表',
|
semanticsLabel: '下拉刷新消息列表',
|
||||||
semanticsValue: '刷新中...',
|
semanticsValue: '刷新中...',
|
||||||
|
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(
|
physics: const AlwaysScrollableScrollPhysics(
|
||||||
// 控制滚动物理特性
|
// 控制滚动物理特性
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user