289 lines
12 KiB
Dart
289 lines
12 KiB
Dart
import 'package:abawo_bt_app/controller/bluetooth.dart';
|
|
import 'package:abawo_bt_app/util/constants.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
|
import 'package:abawo_bt_app/widgets/device_listitem.dart';
|
|
import 'package:abawo_bt_app/widgets/scanning_animation.dart'; // Import the new animation widget
|
|
|
|
const Duration _scanDuration = Duration(seconds: 10);
|
|
|
|
class ConnectDevicePage extends ConsumerStatefulWidget {
|
|
const ConnectDevicePage({super.key});
|
|
|
|
@override
|
|
ConsumerState<ConnectDevicePage> createState() => _ConnectDevicePageState();
|
|
}
|
|
|
|
class _ConnectDevicePageState extends ConsumerState<ConnectDevicePage>
|
|
with TickerProviderStateMixin {
|
|
// Use TickerProviderStateMixin for multiple controllers if needed later, good practice
|
|
bool _initialScanStarted = false;
|
|
bool _showOnlyAbawoDevices = true; // State for filtering devices
|
|
late AnimationController
|
|
_progressController; // Controller for scan duration progress
|
|
late AnimationController
|
|
_waveAnimationController; // Controller for wave animation
|
|
|
|
// Function to start scan safely after controller is ready
|
|
void _startScanIfNeeded(BluetoothController controller) {
|
|
// Use WidgetsBinding to schedule the scan start after the build phase
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (!_initialScanStarted && mounted) {
|
|
controller.startScan(timeout: _scanDuration);
|
|
_startScanProgressAnimation(); // Start scan duration progress animation
|
|
_startWaveAnimation(); // Start the wave animation
|
|
if (mounted) {
|
|
setState(() {
|
|
_initialScanStarted = true;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Initialize scan progress controller
|
|
_progressController = AnimationController(
|
|
vsync: this,
|
|
duration: _scanDuration,
|
|
)..addListener(() {
|
|
// Trigger rebuild when animation value changes for the progress indicator
|
|
if (mounted) {
|
|
setState(() {});
|
|
}
|
|
});
|
|
|
|
// Initialize wave animation controller
|
|
_waveAnimationController = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 1500), // New duration: 1.5 seconds
|
|
)..repeat(); // Make the wave animation repeat
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// Stop animations before disposing
|
|
_progressController.stop();
|
|
_waveAnimationController.stop();
|
|
_progressController.dispose();
|
|
_waveAnimationController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
// Helper method to start/reset scan progress animation
|
|
void _startScanProgressAnimation() {
|
|
if (mounted) {
|
|
_progressController.reset();
|
|
_progressController.forward().whenCompleteOrCancel(() {
|
|
// Optional: Add logic if needed when scan progress completes or cancels
|
|
});
|
|
}
|
|
}
|
|
|
|
// Helper method to start the wave animation
|
|
void _startWaveAnimation() {
|
|
if (mounted && !_waveAnimationController.isAnimating) {
|
|
_waveAnimationController.reset();
|
|
_waveAnimationController.repeat();
|
|
}
|
|
}
|
|
|
|
// Helper method to stop animations when scan finishes/cancels
|
|
void _stopAnimations() {
|
|
if (mounted) {
|
|
if (_progressController.isAnimating) {
|
|
_progressController.stop();
|
|
}
|
|
if (_waveAnimationController.isAnimating) {
|
|
_waveAnimationController.stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Connect Device'),
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () => context.go('/'),
|
|
),
|
|
actions: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 12.0),
|
|
child: Row(
|
|
children: [
|
|
const Text('abawo only'), // Label for the switch
|
|
Switch(
|
|
value: _showOnlyAbawoDevices,
|
|
onChanged: (value) {
|
|
if (mounted) {
|
|
setState(() {
|
|
_showOnlyAbawoDevices = value;
|
|
});
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
body: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
'Available Devices',
|
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 20),
|
|
// Use Consumer to react to bluetoothProvider changes
|
|
Expanded(
|
|
// Allow the Consumer content to expand
|
|
child: Consumer(builder: (context, ref, child) {
|
|
final btAsyncValue = ref.watch(bluetoothProvider);
|
|
|
|
return btAsyncValue.when(
|
|
loading: () => const Center(
|
|
child:
|
|
CircularProgressIndicator()), // Center loading indicator
|
|
error: (err, stack) => Center(
|
|
child: Text(
|
|
'Error loading Bluetooth: $err')), // Center error
|
|
data: (controller) {
|
|
// Start the initial scan once the controller is ready
|
|
// Start initial scan and animation
|
|
_startScanIfNeeded(controller);
|
|
|
|
// Use StreamBuilder to watch the scanning state
|
|
return StreamBuilder<bool>(
|
|
stream: FlutterBluePlus.isScanning,
|
|
initialData:
|
|
false, // Default to not scanning before check
|
|
builder: (context, snapshot) {
|
|
final isScanning = snapshot.data ?? false;
|
|
|
|
if (isScanning && _initialScanStarted) {
|
|
_startWaveAnimation(); // Ensure wave animation is running
|
|
// Show the new scanning wave animation with the progress indicator value
|
|
return Center(
|
|
// Pass the wave animation controller. The progress value will be added
|
|
// to the ScanningWaveAnimation widget itself later.
|
|
child: ScanningWaveAnimation(
|
|
animation: _waveAnimationController,
|
|
progressValue: _progressController
|
|
.value, // Pass the scan progress
|
|
),
|
|
);
|
|
} else if (!_initialScanStarted) {
|
|
// Show placeholder or button to start initial scan if needed, or just empty space
|
|
return const SizedBox(
|
|
height: 50); // Placeholder before scan starts
|
|
} else {
|
|
// Scan finished, stop animations and show results
|
|
_stopAnimations();
|
|
final results = controller.scanResults;
|
|
// Filter results based on the toggle state
|
|
final filteredResults = _showOnlyAbawoDevices
|
|
? results
|
|
.where((device) => device
|
|
.advertisementData.serviceUuids
|
|
.contains(Guid(abawoServiceBtUUID)))
|
|
.toList()
|
|
: results;
|
|
|
|
// Use Column + Expanded for ListView + Button layout
|
|
return Column(
|
|
children: [
|
|
Expanded(
|
|
// Allow ListView to take available space
|
|
child: filteredResults
|
|
.isEmpty // Use filtered list check
|
|
? const Center(
|
|
child: Text(
|
|
'No devices found.')) // Center empty text
|
|
: ListView.builder(
|
|
itemCount: filteredResults
|
|
.length, // Use filtered list length
|
|
itemBuilder: (context, index) {
|
|
final device = filteredResults[
|
|
index]; // Use filtered list
|
|
final isAbawoDevice = device
|
|
.advertisementData.serviceUuids
|
|
.contains(
|
|
Guid(abawoServiceBtUUID));
|
|
final deviceName =
|
|
device.device.advName.isEmpty
|
|
? 'Unknown Device'
|
|
: device.device.advName;
|
|
// Use the custom DeviceListItem widget
|
|
return InkWell(
|
|
// Wrap with InkWell for tap feedback
|
|
onTap: () {
|
|
if (!isAbawoDevice) {
|
|
// Show a snackbar for non-Abawo devices
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(const SnackBar(
|
|
content: Text(
|
|
'This app can only connect to abawo devices.')));
|
|
return;
|
|
}
|
|
// TODO: Implement connect logic
|
|
// controller.connectToDevice(device.device); // Pass the BluetoothDevice
|
|
// context.go('/control/${device.device.remoteId.str}');
|
|
print(
|
|
'Tapped on ${device.device.remoteId.str}');
|
|
},
|
|
child: DeviceListItem(
|
|
deviceName: deviceName,
|
|
deviceId:
|
|
device.device.remoteId.str,
|
|
isUnknownDevice:
|
|
device.device.advName.isEmpty,
|
|
),
|
|
);
|
|
} // End of itemBuilder
|
|
),
|
|
),
|
|
Padding(
|
|
// Add padding around the button
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
// Retry scan by calling startScan on the controller
|
|
// Ensure _initialScanStarted is true so indicator shows
|
|
if (mounted) {
|
|
setState(() {
|
|
_initialScanStarted = true;
|
|
});
|
|
}
|
|
controller.startScan(
|
|
timeout: _scanDuration);
|
|
_startScanProgressAnimation(); // Restart scan progress animation
|
|
_startWaveAnimation(); // Ensure wave animation runs on retry
|
|
},
|
|
child: const Text('Retry Scan'),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|