feat: everything up to bluetooth scanning
This commit is contained in:
288
lib/pages/devices_page.dart
Normal file
288
lib/pages/devices_page.dart
Normal file
@ -0,0 +1,288 @@
|
||||
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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
75
lib/pages/home_page.dart
Normal file
75
lib/pages/home_page.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Abawo BT App'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () => context.go('/settings'),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Welcome to Abawo BT App',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Devices Section
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Devices',
|
||||
style: TextStyle(
|
||||
fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add_circle),
|
||||
onPressed: () => context.go('/connect_device'),
|
||||
tooltip: 'Connect a device',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'No devices connected yet',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
ElevatedButton(
|
||||
onPressed: () => context.go('/settings'),
|
||||
child: const Text('Go to Settings'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
50
lib/pages/settings_page.dart
Normal file
50
lib/pages/settings_page.dart
Normal file
@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Settings'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => context.go('/'),
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.brightness_6),
|
||||
title: const Text('Theme'),
|
||||
subtitle: const Text('Change app theme'),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// Theme settings functionality
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.bluetooth),
|
||||
title: const Text('Bluetooth Settings'),
|
||||
subtitle: const Text('Configure Bluetooth connections'),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// Bluetooth settings functionality
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info),
|
||||
title: const Text('About'),
|
||||
subtitle: const Text('App information'),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// About screen functionality
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user