Compare commits
2 Commits
df50fbb8cd
...
3f7b38a59b
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f7b38a59b | |||
|
|
2fabae8738 |
@ -20,7 +20,5 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>13.0</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -2,12 +2,15 @@ import Flutter
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
@ -24,6 +26,42 @@
|
|||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||||
|
<string>需要蓝牙权限来搜索和连接您的设备</string>
|
||||||
|
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||||
|
<string>需要蓝牙权限来搜索和连接您的设备</string>
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
<string>需要位置权限以扫描附近的蓝牙设备</string>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>FlutterSceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchScreen</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIColorName</key>
|
||||||
|
<string>LaunchBackgroundColor</string>
|
||||||
|
<key>UIImageName</key>
|
||||||
|
<string>LaunchImage</string>
|
||||||
|
</dict>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@ -41,22 +79,5 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
|
||||||
<string>需要蓝牙权限来搜索和连接您的设备</string>
|
|
||||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
|
||||||
<string>需要蓝牙权限来搜索和连接您的设备</string>
|
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
|
||||||
<string>需要位置权限以扫描附近的蓝牙设备</string>
|
|
||||||
<key>UILaunchScreen</key>
|
|
||||||
<dict>
|
|
||||||
<key>UIColorName</key>
|
|
||||||
<string>LaunchBackgroundColor</string>
|
|
||||||
<key>UIImageName</key>
|
|
||||||
<string>LaunchImage</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import '../../features/auth/presentation/pages/login_page.dart';
|
|||||||
import '../../pages/bluetooth_page.dart';
|
import '../../pages/bluetooth_page.dart';
|
||||||
import '../../pages/device_control_page.dart';
|
import '../../pages/device_control_page.dart';
|
||||||
import '../../pages/home_page.dart';
|
import '../../pages/home_page.dart';
|
||||||
|
import '../../pages/product_selection_page.dart';
|
||||||
import '../../pages/profile/profile_page.dart';
|
import '../../pages/profile/profile_page.dart';
|
||||||
import '../../pages/webview_page.dart';
|
import '../../pages/webview_page.dart';
|
||||||
import '../../pages/wifi_config_page.dart';
|
import '../../pages/wifi_config_page.dart';
|
||||||
@ -48,6 +49,10 @@ GoRouter goRouter(Ref ref) {
|
|||||||
extra: state.extra as Map<String, dynamic>?,
|
extra: state.extra as Map<String, dynamic>?,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/product-selection',
|
||||||
|
builder: (context, state) => const ProductSelectionPage(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/device-control',
|
path: '/device-control',
|
||||||
builder: (context, state) => const DeviceControlPage(),
|
builder: (context, state) => const DeviceControlPage(),
|
||||||
|
|||||||
@ -48,4 +48,4 @@ final class GoRouterProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$goRouterHash() => r'8e620e452bb81f2c6ed87b136283a9e508dca2e9';
|
String _$goRouterHash() => r'9f77a00bcbc90890c4b6594a9709288e5206c7d8';
|
||||||
|
|||||||
@ -30,12 +30,12 @@ Map<String, dynamic> _$DeviceTypeToJson(_DeviceType instance) =>
|
|||||||
_DeviceInfo _$DeviceInfoFromJson(Map<String, dynamic> json) => _DeviceInfo(
|
_DeviceInfo _$DeviceInfoFromJson(Map<String, dynamic> json) => _DeviceInfo(
|
||||||
id: (json['id'] as num).toInt(),
|
id: (json['id'] as num).toInt(),
|
||||||
sn: json['sn'] as String,
|
sn: json['sn'] as String,
|
||||||
deviceType: (json['device_type'] is Map<String, dynamic>)
|
deviceType: json['device_type'] == null
|
||||||
? DeviceType.fromJson(json['device_type'] as Map<String, dynamic>)
|
? null
|
||||||
: null,
|
: DeviceType.fromJson(json['device_type'] as Map<String, dynamic>),
|
||||||
deviceTypeInfo: (json['device_type_info'] is Map<String, dynamic>)
|
deviceTypeInfo: json['device_type_info'] == null
|
||||||
? DeviceType.fromJson(json['device_type_info'] as Map<String, dynamic>)
|
? null
|
||||||
: null,
|
: DeviceType.fromJson(json['device_type_info'] as Map<String, dynamic>),
|
||||||
macAddress: json['mac_address'] as String?,
|
macAddress: json['mac_address'] as String?,
|
||||||
name: json['name'] as String? ?? '',
|
name: json['name'] as String? ?? '',
|
||||||
status: json['status'] as String? ?? 'in_stock',
|
status: json['status'] as String? ?? 'in_stock',
|
||||||
|
|||||||
@ -21,6 +21,7 @@ class DeviceController extends _$DeviceController {
|
|||||||
Future<bool> bindDevice(String sn, {int? spiritId}) async {
|
Future<bool> bindDevice(String sn, {int? spiritId}) async {
|
||||||
final repository = ref.read(deviceRepositoryProvider);
|
final repository = ref.read(deviceRepositoryProvider);
|
||||||
final result = await repository.bindDevice(sn, spiritId: spiritId);
|
final result = await repository.bindDevice(sn, spiritId: spiritId);
|
||||||
|
if (!ref.mounted) return false;
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(failure) => false,
|
(failure) => false,
|
||||||
(bindingId) {
|
(bindingId) {
|
||||||
@ -33,6 +34,7 @@ class DeviceController extends _$DeviceController {
|
|||||||
Future<bool> unbindDevice(int userDeviceId) async {
|
Future<bool> unbindDevice(int userDeviceId) async {
|
||||||
final repository = ref.read(deviceRepositoryProvider);
|
final repository = ref.read(deviceRepositoryProvider);
|
||||||
final result = await repository.unbindDevice(userDeviceId);
|
final result = await repository.unbindDevice(userDeviceId);
|
||||||
|
if (!ref.mounted) return false;
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(failure) => false,
|
(failure) => false,
|
||||||
(_) {
|
(_) {
|
||||||
@ -48,6 +50,7 @@ class DeviceController extends _$DeviceController {
|
|||||||
Future<bool> updateSpirit(int userDeviceId, int spiritId) async {
|
Future<bool> updateSpirit(int userDeviceId, int spiritId) async {
|
||||||
final repository = ref.read(deviceRepositoryProvider);
|
final repository = ref.read(deviceRepositoryProvider);
|
||||||
final result = await repository.updateSpirit(userDeviceId, spiritId);
|
final result = await repository.updateSpirit(userDeviceId, spiritId);
|
||||||
|
if (!ref.mounted) return false;
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(failure) => false,
|
(failure) => false,
|
||||||
(updated) {
|
(updated) {
|
||||||
@ -78,6 +81,7 @@ class DeviceDetailController extends _$DeviceDetailController {
|
|||||||
Future<bool> updateSettings(Map<String, dynamic> settings) async {
|
Future<bool> updateSettings(Map<String, dynamic> settings) async {
|
||||||
final repository = ref.read(deviceRepositoryProvider);
|
final repository = ref.read(deviceRepositoryProvider);
|
||||||
final result = await repository.updateSettings(userDeviceId, settings);
|
final result = await repository.updateSettings(userDeviceId, settings);
|
||||||
|
if (!ref.mounted) return false;
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(failure) => false,
|
(failure) => false,
|
||||||
(_) {
|
(_) {
|
||||||
@ -90,6 +94,7 @@ class DeviceDetailController extends _$DeviceDetailController {
|
|||||||
Future<bool> configWifi(String ssid) async {
|
Future<bool> configWifi(String ssid) async {
|
||||||
final repository = ref.read(deviceRepositoryProvider);
|
final repository = ref.read(deviceRepositoryProvider);
|
||||||
final result = await repository.configWifi(userDeviceId, ssid);
|
final result = await repository.configWifi(userDeviceId, ssid);
|
||||||
|
if (!ref.mounted) return false;
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(failure) => false,
|
(failure) => false,
|
||||||
(_) {
|
(_) {
|
||||||
|
|||||||
@ -36,7 +36,7 @@ final class DeviceControllerProvider
|
|||||||
DeviceController create() => DeviceController();
|
DeviceController create() => DeviceController();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$deviceControllerHash() => r'9b39117bd54964ba0035aad0eca10250454efaa7';
|
String _$deviceControllerHash() => r'3f73a13c7f93fecb9fe781efc4ee305b6186639e';
|
||||||
|
|
||||||
/// 管理用户设备列表
|
/// 管理用户设备列表
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import '../widgets/animated_gradient_background.dart';
|
import '../widgets/animated_gradient_background.dart';
|
||||||
import '../widgets/ios_toast.dart';
|
import '../features/device/presentation/controllers/device_controller.dart';
|
||||||
|
import '../features/device/domain/entities/device.dart';
|
||||||
|
|
||||||
class ProductSelectionPage extends StatefulWidget {
|
class ProductSelectionPage extends ConsumerStatefulWidget {
|
||||||
const ProductSelectionPage({super.key});
|
const ProductSelectionPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ProductSelectionPage> createState() => _ProductSelectionPageState();
|
ConsumerState<ProductSelectionPage> createState() => _ProductSelectionPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
class _ProductSelectionPageState extends ConsumerState<ProductSelectionPage> {
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -28,12 +31,30 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 产品 ID 到后端 product_code 的映射
|
||||||
|
static const Map<String, List<String>> _productCodeMap = {
|
||||||
|
'capybara': ['KPBL-ON'],
|
||||||
|
'badge-ai': ['DZBJ-ON'],
|
||||||
|
'badge-basic': ['DZBJ-OFF'],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 查找用户是否已绑定该产品类型的设备
|
||||||
|
UserDevice? _findBoundDevice(String productId, List<UserDevice> devices) {
|
||||||
|
final codes = _productCodeMap[productId];
|
||||||
|
if (codes == null || codes.isEmpty) return null;
|
||||||
|
for (final device in devices) {
|
||||||
|
final dt = device.device.deviceType ?? device.device.deviceTypeInfo;
|
||||||
|
if (dt != null && codes.contains(dt.productCode)) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
static final List<Map<String, dynamic>> _products = [
|
static final List<Map<String, dynamic>> _products = [
|
||||||
{
|
{
|
||||||
'id': 'capybara',
|
'id': 'capybara',
|
||||||
'name': '毛绒机芯',
|
'name': '毛绒机芯',
|
||||||
'status': '已连接',
|
|
||||||
'statusColor': const Color(0xFF10B981),
|
|
||||||
'icon': 'assets/www/Capybara.png',
|
'icon': 'assets/www/Capybara.png',
|
||||||
'isPng': true,
|
'isPng': true,
|
||||||
'hasTag': true,
|
'hasTag': true,
|
||||||
@ -45,13 +66,10 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
),
|
),
|
||||||
'shadowColor': const Color(0xFFC9A07A),
|
'shadowColor': const Color(0xFFC9A07A),
|
||||||
'selected': true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': 'badge-ai',
|
'id': 'badge-ai',
|
||||||
'name': '电子吧唧 AI',
|
'name': '电子吧唧 AI',
|
||||||
'status': '离线',
|
|
||||||
'statusColor': const Color(0xFFE5E7EB),
|
|
||||||
'icon': 'assets/www/icons/icon-product-badge.svg',
|
'icon': 'assets/www/icons/icon-product-badge.svg',
|
||||||
'isPng': false,
|
'isPng': false,
|
||||||
'hasTag': true,
|
'hasTag': true,
|
||||||
@ -63,13 +81,10 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
),
|
),
|
||||||
'shadowColor': const Color(0xFF6366F1),
|
'shadowColor': const Color(0xFF6366F1),
|
||||||
'selected': false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': 'badge-basic',
|
'id': 'badge-basic',
|
||||||
'name': '普通吧唧',
|
'name': '普通吧唧',
|
||||||
'status': '未配对',
|
|
||||||
'statusColor': const Color(0xFFE5E7EB),
|
|
||||||
'icon': 'assets/www/icons/icon-product-badge.svg',
|
'icon': 'assets/www/icons/icon-product-badge.svg',
|
||||||
'isPng': false,
|
'isPng': false,
|
||||||
'hasTag': false,
|
'hasTag': false,
|
||||||
@ -80,13 +95,10 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
),
|
),
|
||||||
'shadowColor': const Color(0xFFA78BFA),
|
'shadowColor': const Color(0xFFA78BFA),
|
||||||
'selected': false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': 'bracelet',
|
'id': 'bracelet',
|
||||||
'name': 'AI 手链',
|
'name': 'AI 手链',
|
||||||
'status': '点击扫描',
|
|
||||||
'statusColor': const Color(0xFFE5E7EB),
|
|
||||||
'icon': 'assets/www/icons/icon-product-badge.svg',
|
'icon': 'assets/www/icons/icon-product-badge.svg',
|
||||||
'isPng': false,
|
'isPng': false,
|
||||||
'hasTag': true,
|
'hasTag': true,
|
||||||
@ -98,13 +110,10 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
),
|
),
|
||||||
'shadowColor': const Color(0xFFE07B54),
|
'shadowColor': const Color(0xFFE07B54),
|
||||||
'selected': false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': 'vsinger',
|
'id': 'vsinger',
|
||||||
'name': '洛天依',
|
'name': '洛天依',
|
||||||
'status': '去下载专属 APP →',
|
|
||||||
'statusColor': Colors.transparent,
|
|
||||||
'icon': 'assets/www/icons/icon-product-luo.svg',
|
'icon': 'assets/www/icons/icon-product-luo.svg',
|
||||||
'isPng': false,
|
'isPng': false,
|
||||||
'hasTag': false,
|
'hasTag': false,
|
||||||
@ -115,13 +124,10 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
),
|
),
|
||||||
'shadowColor': const Color(0xFF2DD4BF),
|
'shadowColor': const Color(0xFF2DD4BF),
|
||||||
'selected': false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': 'nightlight',
|
'id': 'nightlight',
|
||||||
'name': 'AI 星空夜灯',
|
'name': 'AI 星空夜灯',
|
||||||
'status': '未配对',
|
|
||||||
'statusColor': const Color(0xFFE5E7EB),
|
|
||||||
'icon': 'assets/www/icons/icon-product-badge.svg',
|
'icon': 'assets/www/icons/icon-product-badge.svg',
|
||||||
'isPng': false,
|
'isPng': false,
|
||||||
'hasTag': true,
|
'hasTag': true,
|
||||||
@ -133,13 +139,10 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
),
|
),
|
||||||
'shadowColor': const Color(0xFF7C3AED),
|
'shadowColor': const Color(0xFF7C3AED),
|
||||||
'selected': false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': 'feeder',
|
'id': 'feeder',
|
||||||
'name': '智能喂食器',
|
'name': '智能喂食器',
|
||||||
'status': '点击扫描',
|
|
||||||
'statusColor': const Color(0xFFE5E7EB),
|
|
||||||
'icon': 'assets/www/icons/icon-product-badge.svg',
|
'icon': 'assets/www/icons/icon-product-badge.svg',
|
||||||
'isPng': false,
|
'isPng': false,
|
||||||
'hasTag': false,
|
'hasTag': false,
|
||||||
@ -150,24 +153,24 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
),
|
),
|
||||||
'shadowColor': const Color(0xFFE11D48),
|
'shadowColor': const Color(0xFFE11D48),
|
||||||
'selected': false,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final safeTop = MediaQuery.of(context).padding.top;
|
final safeTop = MediaQuery.of(context).padding.top;
|
||||||
// 头部高度 = safeArea + padding + 按钮高度 + padding
|
|
||||||
final headerHeight = safeTop + 12 + 40 + 12;
|
final headerHeight = safeTop + 12 + 40 + 12;
|
||||||
|
|
||||||
|
// 获取用户已绑定的设备列表
|
||||||
|
final devicesAsync = ref.watch(deviceControllerProvider);
|
||||||
|
final devices = devicesAsync.value ?? [];
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
// 1. 全屏流动背景
|
|
||||||
const AnimatedGradientBackground(),
|
const AnimatedGradientBackground(),
|
||||||
|
|
||||||
// 2. 列表
|
|
||||||
ScrollConfiguration(
|
ScrollConfiguration(
|
||||||
behavior: ScrollConfiguration.of(context).copyWith(
|
behavior: ScrollConfiguration.of(context).copyWith(
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
@ -178,17 +181,28 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
itemCount: _products.length,
|
itemCount: _products.length,
|
||||||
separatorBuilder: (_, __) => const SizedBox(height: 16),
|
separatorBuilder: (_, __) => const SizedBox(height: 16),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
final product = _products[index];
|
||||||
|
final boundDevice = _findBoundDevice(product['id'] as String, devices);
|
||||||
return _FadeOnScrollCard(
|
return _FadeOnScrollCard(
|
||||||
key: ValueKey(_products[index]['id']),
|
key: ValueKey(product['id']),
|
||||||
product: _products[index],
|
product: product,
|
||||||
|
isBound: boundDevice != null,
|
||||||
fadeStartY: headerHeight + 16,
|
fadeStartY: headerHeight + 16,
|
||||||
fadeEndY: safeTop,
|
fadeEndY: safeTop,
|
||||||
|
onTap: () {
|
||||||
|
if (boundDevice != null) {
|
||||||
|
// 已绑定 → 直接进入设备控制页
|
||||||
|
context.go('/device-control');
|
||||||
|
} else {
|
||||||
|
// 未绑定 → 跳转蓝牙搜索页
|
||||||
|
context.go('/bluetooth');
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// 3. 头部 — 统一规范:返回按钮 + 居中标题
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@ -198,7 +212,6 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
// 居中标题
|
|
||||||
Text(
|
Text(
|
||||||
'选择产品',
|
'选择产品',
|
||||||
style: GoogleFonts.outfit(
|
style: GoogleFonts.outfit(
|
||||||
@ -207,7 +220,6 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
color: const Color(0xFF1F2937),
|
color: const Color(0xFF1F2937),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 左侧返回按钮
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@ -238,17 +250,21 @@ class _ProductSelectionPageState extends State<ProductSelectionPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 带滚动渐隐的卡片包装器 — 纯 Opacity,无 ShaderMask,无 widget 切换
|
/// 带滚动渐隐的卡片包装器
|
||||||
class _FadeOnScrollCard extends StatefulWidget {
|
class _FadeOnScrollCard extends StatefulWidget {
|
||||||
final Map<String, dynamic> product;
|
final Map<String, dynamic> product;
|
||||||
final double fadeStartY; // 卡片顶部到此位置开始淡出
|
final bool isBound;
|
||||||
final double fadeEndY; // 卡片顶部到此位置完全消失
|
final double fadeStartY;
|
||||||
|
final double fadeEndY;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
const _FadeOnScrollCard({
|
const _FadeOnScrollCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.product,
|
required this.product,
|
||||||
|
required this.isBound,
|
||||||
required this.fadeStartY,
|
required this.fadeStartY,
|
||||||
required this.fadeEndY,
|
required this.fadeEndY,
|
||||||
|
required this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -279,7 +295,11 @@ class _FadeOnScrollCardState extends State<_FadeOnScrollCard> {
|
|||||||
return Opacity(
|
return Opacity(
|
||||||
key: _posKey,
|
key: _posKey,
|
||||||
opacity: opacity,
|
opacity: opacity,
|
||||||
child: _ProductCard(product: widget.product),
|
child: _ProductCard(
|
||||||
|
product: widget.product,
|
||||||
|
isBound: widget.isBound,
|
||||||
|
onTap: widget.onTap,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,22 +307,20 @@ class _FadeOnScrollCardState extends State<_FadeOnScrollCard> {
|
|||||||
/// 产品卡片
|
/// 产品卡片
|
||||||
class _ProductCard extends StatelessWidget {
|
class _ProductCard extends StatelessWidget {
|
||||||
final Map<String, dynamic> product;
|
final Map<String, dynamic> product;
|
||||||
|
final bool isBound;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
const _ProductCard({super.key, required this.product});
|
const _ProductCard({
|
||||||
|
super.key,
|
||||||
|
required this.product,
|
||||||
|
required this.isBound,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool isSelected = product['selected'] == true;
|
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: onTap,
|
||||||
if (isSelected) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
} else {
|
|
||||||
AppToast.show(
|
|
||||||
context, '${product['name']} 离线或未配对', isError: true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 120,
|
height: 120,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20),
|
||||||
@ -343,18 +361,16 @@ class _ProductCard extends StatelessWidget {
|
|||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
if ((product['statusColor'] as Color) !=
|
|
||||||
Colors.transparent)
|
|
||||||
Container(
|
Container(
|
||||||
width: 7,
|
width: 7,
|
||||||
height: 7,
|
height: 7,
|
||||||
margin: const EdgeInsets.only(right: 6),
|
margin: const EdgeInsets.only(right: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: product['id'] == 'capybara'
|
color: isBound
|
||||||
? const Color(0xFF34D399)
|
? const Color(0xFF34D399)
|
||||||
: Colors.white.withOpacity(0.5),
|
: Colors.white.withOpacity(0.5),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: product['id'] == 'capybara'
|
boxShadow: isBound
|
||||||
? [
|
? [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: const Color(0xFF34D399)
|
color: const Color(0xFF34D399)
|
||||||
@ -365,7 +381,7 @@ class _ProductCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
product['status'],
|
isBound ? '已连接' : '点击配对',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: Colors.white.withOpacity(0.85)),
|
color: Colors.white.withOpacity(0.85)),
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'product_selection_page.dart';
|
|
||||||
import '../widgets/glass_dialog.dart';
|
import '../widgets/glass_dialog.dart';
|
||||||
import '../widgets/animated_gradient_background.dart';
|
import '../widgets/animated_gradient_background.dart';
|
||||||
import '../widgets/ios_toast.dart';
|
import '../widgets/ios_toast.dart';
|
||||||
@ -494,10 +493,7 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
|
|||||||
.unbindDevice(_userDeviceId!);
|
.unbindDevice(_userDeviceId!);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (success) {
|
if (success) {
|
||||||
Navigator.pop(context); // close settings
|
context.go('/product-selection');
|
||||||
Navigator.of(context).pushReplacement(
|
|
||||||
MaterialPageRoute(builder: (context) => const ProductSelectionPage()),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
AppToast.show(context, '解绑失败', isError: true);
|
AppToast.show(context, '解绑失败', isError: true);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -132,10 +132,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -644,14 +644,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
js:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: js
|
|
||||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.7.2"
|
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -736,18 +728,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.18"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.13.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1205,26 +1197,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.26.3"
|
version: "1.29.0"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.9"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.12"
|
version: "0.6.15"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user