Init
This commit is contained in:
47
lib/AppStateModel.dart
Normal file
47
lib/AppStateModel.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bluetooth_classic/models/device.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppStateModel extends ChangeNotifier {
|
||||
bool _scanning = false;
|
||||
List<Device> _discoveredDevices = [];
|
||||
int _deviceStatus = 0;
|
||||
|
||||
|
||||
bool get scanning => _scanning;
|
||||
List<Device> get discoveredDevices => _discoveredDevices;
|
||||
int get deviceStatus => _deviceStatus;
|
||||
|
||||
void setScanning(bool value) {
|
||||
_scanning = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setDiscoveredDevices(List<Device> value) {
|
||||
_discoveredDevices = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addDiscoveredDevice(Device value) {
|
||||
_discoveredDevices.add(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clearDiscoveredDevices() {
|
||||
_discoveredDevices.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setDeviceStatus(int value) {
|
||||
_deviceStatus = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// listen?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
10
lib/BluetoothUtils.dart
Normal file
10
lib/BluetoothUtils.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
//
|
||||
// bool isAlreadyConnectedToDevice(Device device, List<Device> connectedDevices) {
|
||||
// for (final connectedDevice in connectedDevices) {
|
||||
// if (connectedDevice.address == device.address) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
18
lib/ConnectionScreenDialogs.dart
Normal file
18
lib/ConnectionScreenDialogs.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showAlreadyConnectedDialog(BuildContext context) {
|
||||
showDialog(context: context, builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Already connected'),
|
||||
content: const Text('This device is already connected'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'))
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
25
lib/CustomBluetoothService.dart
Normal file
25
lib/CustomBluetoothService.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
import 'package:bluetooth_classic/bluetooth_classic.dart';
|
||||
import 'package:bluetooth_classic/models/device.dart';
|
||||
|
||||
class Custombluetoothservice extends BluetoothClassic {
|
||||
// @override
|
||||
// Stream<Uint8List> onDeviceDataReceived() {
|
||||
// return _onDeviceDataReceived ??=
|
||||
// super.onDeviceDataReceived().asBroadcastStream();
|
||||
// }
|
||||
|
||||
// Stream<Uint8List>? _onDeviceDataReceived;
|
||||
|
||||
@override
|
||||
Stream<Device> onDeviceDiscovered() =>
|
||||
_onDeviceDiscovered ??= super.onDeviceDiscovered().asBroadcastStream();
|
||||
Stream<Device>? _onDeviceDiscovered;
|
||||
|
||||
@override
|
||||
Stream<int> onDeviceStatusChanged() =>
|
||||
_onDeviceStatusChanged ??=
|
||||
super.onDeviceStatusChanged().asBroadcastStream();
|
||||
Stream<int>? _onDeviceStatusChanged;
|
||||
|
||||
}
|
||||
119
lib/MainControlScreen.dart
Normal file
119
lib/MainControlScreen.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:xapk_installer/AppStateModel.dart';
|
||||
import 'package:xapk_installer/connectPage.dart';
|
||||
import 'package:xapk_installer/pickers/block_picker.dart';
|
||||
|
||||
class Maincontrolscreen extends StatefulWidget {
|
||||
const Maincontrolscreen({super.key});
|
||||
|
||||
@override
|
||||
State<Maincontrolscreen> createState() => _MaincontrolscreenState();
|
||||
}
|
||||
|
||||
class _MaincontrolscreenState extends State<Maincontrolscreen> {
|
||||
// final _bluetoothClassicPlugin = BluetoothClassic();
|
||||
|
||||
List<List<Color>> gridColors = List.generate(
|
||||
5,
|
||||
(i) => List.generate(5, (j) => Colors.grey), // Initialize with grey color
|
||||
);
|
||||
|
||||
// Method to toggle the color of a button
|
||||
void toggleButtonColor(int i, int j) {
|
||||
setState(() {
|
||||
gridColors[i][j] = currentColor;
|
||||
});
|
||||
}
|
||||
|
||||
bool isConnected = false;
|
||||
|
||||
Color currentColor = Colors.grey;
|
||||
|
||||
// Widget buildStatusIcon(){
|
||||
// if(Provider.of<AppStateModel>(context).name == Device.connected){
|
||||
// return Icon(Icons.bluetooth_connected);
|
||||
// }
|
||||
// else if( == Device.connecting){
|
||||
// return Icon(Icons.bluetooth_searching);
|
||||
// }
|
||||
// else{
|
||||
// return Icon(Icons.bluetooth_disabled);
|
||||
// }
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Connect Page'),
|
||||
actions: [
|
||||
IconButton(onPressed: (){
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => ConnectPage()));
|
||||
}, icon: Icon(Icons.bluetooth)),
|
||||
IconButton(onPressed: (){
|
||||
|
||||
}, icon: Icon(Icons.upload)),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GridView.builder(
|
||||
shrinkWrap: true,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 5,
|
||||
childAspectRatio: 1.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
mainAxisSpacing: 8.0,
|
||||
),
|
||||
itemCount: 25, // 5x5 grid
|
||||
itemBuilder: (context, index) {
|
||||
int row = index ~/ 5;
|
||||
int col = index % 5;
|
||||
return GestureDetector(
|
||||
onTap: () => toggleButtonColor(row, col),
|
||||
child: Container(
|
||||
color: gridColors[row][col],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
// ColorPicker(
|
||||
// hexInputBar: false,
|
||||
// pickerAreaBorderRadius: const BorderRadius.all(
|
||||
// Radius.circular(10)),
|
||||
// portraitOnly: true,
|
||||
// pickerColor: currentColor,
|
||||
// onColorChanged: (color) {
|
||||
// currentColor = color;
|
||||
// },
|
||||
// enableAlpha: false,
|
||||
// colorPickerWidth: 100,
|
||||
// displayThumbColor: true,
|
||||
//
|
||||
//
|
||||
// )
|
||||
Center(
|
||||
child: BlockPicker(
|
||||
pickerColor: currentColor,
|
||||
onColorChanged: (color) {
|
||||
currentColor = color;
|
||||
},
|
||||
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
34
lib/NoDeviceScreen.dart
Normal file
34
lib/NoDeviceScreen.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NoDevicesScreen extends StatelessWidget {
|
||||
const NoDevicesScreen({super.key, required this.onScanPressed});
|
||||
|
||||
final VoidCallback onScanPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.bluetooth_disabled,
|
||||
size: 100,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text('No devices found', style: TextStyle(fontSize: 20)),
|
||||
const SizedBox(height: 10),
|
||||
// Button to scan for devices
|
||||
ElevatedButton(
|
||||
onPressed: onScanPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: const Text('Scan for devices', style: TextStyle(fontSize: 15)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
368
lib/connectPage.dart
Normal file
368
lib/connectPage.dart
Normal file
@@ -0,0 +1,368 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bluetooth_classic/bluetooth_classic.dart';
|
||||
import 'package:bluetooth_classic/models/device.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:xapk_installer/AppStateModel.dart';
|
||||
import 'package:xapk_installer/BluetoothUtils.dart';
|
||||
import 'package:xapk_installer/ConnectionScreenDialogs.dart';
|
||||
import 'package:xapk_installer/CustomBluetoothService.dart';
|
||||
|
||||
import 'NoDeviceScreen.dart';
|
||||
|
||||
class ConnectPage extends StatefulWidget {
|
||||
const ConnectPage({super.key});
|
||||
|
||||
@override
|
||||
State<ConnectPage> createState() => _ConnectPageState();
|
||||
}
|
||||
|
||||
class _ConnectPageState extends State<ConnectPage> {
|
||||
final _bluetoothClassicPlugin = Custombluetoothservice();
|
||||
|
||||
//a bool to hide the no devices found screen when the user presses the scan button
|
||||
bool _hideNoDevicesScreen = false;
|
||||
|
||||
bool _scanning = false;
|
||||
|
||||
Icon _statusIcon = const Icon(Icons.restart_alt);
|
||||
|
||||
int _deviceStatus = Device.disconnected;
|
||||
|
||||
Device _connectedDevice = Device(name: 'Unknown', address: 'Unknown');
|
||||
|
||||
StreamSubscription? deviceListen;
|
||||
StreamSubscription? deviceChangeListen;
|
||||
|
||||
|
||||
|
||||
//Init
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bluetoothClassicPlugin.initPermissions();
|
||||
_bluetoothClassicPlugin.onDeviceStatusChanged().listen((event) {
|
||||
setState(() {
|
||||
Provider.of<AppStateModel>(context, listen: false).setDeviceStatus(event);
|
||||
_deviceStatus = event;
|
||||
});
|
||||
});
|
||||
_bluetoothClassicPlugin.onDeviceDiscovered().listen(
|
||||
(event) {
|
||||
if (kDebugMode) {
|
||||
print('Device discovered: ${event.name}');
|
||||
}
|
||||
if (_scanning) {
|
||||
Provider.of<AppStateModel>(context, listen: false)
|
||||
.addDiscoveredDevice(event);
|
||||
//Sort list put Unknowns at the end
|
||||
Provider.of<AppStateModel>(context, listen: false)
|
||||
.discoveredDevices
|
||||
.sort((a, b) {
|
||||
if (a.name == null) {
|
||||
return 1;
|
||||
}
|
||||
if (b.name == null) {
|
||||
return -1;
|
||||
}
|
||||
return a.name!.compareTo(b.name!);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
//Dispose
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
// Provider.of<AppStateModel>(context, listen: false).listen?.cancel();
|
||||
|
||||
}
|
||||
|
||||
Future<void> _scan() async {
|
||||
if (_scanning) {
|
||||
await _bluetoothClassicPlugin.stopScan();
|
||||
if (kDebugMode) {
|
||||
print('Scanning stopped');
|
||||
}
|
||||
setState(() {
|
||||
_scanning = false;
|
||||
_statusIcon = const Icon(Icons.restart_alt);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
Provider.of<AppStateModel>(context, listen: false).clearDiscoveredDevices();
|
||||
_hideNoDevicesScreen = true;
|
||||
_statusIcon = const Icon(Icons.cancel);
|
||||
});
|
||||
await _bluetoothClassicPlugin.startScan();
|
||||
if (kDebugMode) {
|
||||
print('Scanning started');
|
||||
}
|
||||
setState(() {
|
||||
_scanning = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _stopScan() async {
|
||||
await _bluetoothClassicPlugin.stopScan();
|
||||
if (kDebugMode) {
|
||||
print('Scanning stopped');
|
||||
}
|
||||
await _bluetoothClassicPlugin.disconnect();
|
||||
setState(() {
|
||||
_scanning = false;
|
||||
_statusIcon = const Icon(Icons.restart_alt);
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool?> _showConnectConfirmDialog(
|
||||
BuildContext context, String deviceName, String deviceAddress) {
|
||||
return showDialog<bool?>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Connect'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Do you want to connect to this device?'),
|
||||
Container(height: 10),
|
||||
Text(deviceName,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: 20)),
|
||||
Text(deviceAddress, style: const TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: const Text('No')),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: const Text('Yes')),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _connect(Device device) async {
|
||||
final connect = await _showConnectConfirmDialog(
|
||||
context, device.name ?? 'Unknown', device.address);
|
||||
if (connect == null || !connect) {
|
||||
return;
|
||||
}
|
||||
bool connected = false;
|
||||
bool error = false;
|
||||
List<Device> alreadyConnectedDevices = [];
|
||||
try {
|
||||
alreadyConnectedDevices =
|
||||
await _bluetoothClassicPlugin.getPairedDevices();
|
||||
} catch (e) {
|
||||
error = true;
|
||||
}
|
||||
if(error){
|
||||
if (context.mounted) {
|
||||
if (kDebugMode) {
|
||||
print('Failed to get paired devices');
|
||||
}
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Failed to get paired devices'),
|
||||
content: const Text('Failed to get paired devices'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'))
|
||||
],
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// //If the device is already connected, tell the user
|
||||
// if (alreadyConnectedDevices
|
||||
// .any((element) => element.address == device.address)) {
|
||||
// if (context.mounted) {
|
||||
// if (kDebugMode) {
|
||||
// print('Device already connected');
|
||||
// }
|
||||
// showAlreadyConnectedDialog(context);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
if(_deviceStatus == Device.connected){
|
||||
if (context.mounted) {
|
||||
if (kDebugMode) {
|
||||
print('Device already connected');
|
||||
}
|
||||
showAlreadyConnectedDialog(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
connected = await _bluetoothClassicPlugin.connect(
|
||||
device.address, "00001101-0000-1000-8000-00805f9b34fb");
|
||||
} catch (e) {
|
||||
error = true;
|
||||
}
|
||||
if (error || !connected) {
|
||||
if (context.mounted) {
|
||||
if (kDebugMode) {
|
||||
print('Failed to connect');
|
||||
}
|
||||
}
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Failed to connect'),
|
||||
content: const Text('Failed to connect to the device'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'))
|
||||
],
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_statusIcon = const Icon(Icons.restart_alt);
|
||||
_connectedDevice = device;
|
||||
});
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Connected'),
|
||||
content: const Text('You are now connected to the device'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('OK'))
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
await _bluetoothClassicPlugin.write("setPix 1 1 255 0 0\n"); //TODO: remove this
|
||||
}
|
||||
|
||||
|
||||
Future<void> _disconnect() async {
|
||||
await _bluetoothClassicPlugin.disconnect();
|
||||
setState(() {
|
||||
_statusIcon = const Icon(Icons.restart_alt);
|
||||
_connectedDevice = Device(name: 'Unknown', address: 'Unknown');
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Connect Page'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: _statusIcon,
|
||||
onPressed: () {
|
||||
_scan();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_stopScan();
|
||||
Provider.of<AppStateModel>(context, listen: false)
|
||||
.clearDiscoveredDevices();
|
||||
},
|
||||
icon: const Icon(Icons.star_purple500_outlined))
|
||||
],
|
||||
),
|
||||
body: !_hideNoDevicesScreen
|
||||
? NoDevicesScreen(onScanPressed: () {
|
||||
setState(() {
|
||||
_hideNoDevicesScreen = true;
|
||||
});
|
||||
_scan();
|
||||
})
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Consumer<AppStateModel>(
|
||||
builder: (context, appstate, child) {
|
||||
final List<Device> _discoveredDevices =
|
||||
appstate.discoveredDevices;
|
||||
return ListView.builder(itemBuilder: (context, index) {
|
||||
if (index >= _discoveredDevices.length) {
|
||||
return null;
|
||||
}
|
||||
return ListTile(
|
||||
title: Text(_discoveredDevices[index].name ?? 'Unknown', style: TextStyle(
|
||||
fontWeight: _discoveredDevices[index].name == _connectedDevice.name ? FontWeight.bold : FontWeight.normal,
|
||||
color: _discoveredDevices[index].name == _connectedDevice.name ? Colors.green : Colors.white
|
||||
),),
|
||||
subtitle: Text(_discoveredDevices[index].address),
|
||||
leading: _discoveredDevices[index].name == null ? const Icon(Icons.question_mark) : const Icon(
|
||||
Icons.bluetooth,
|
||||
color: Colors.blueAccent,
|
||||
),
|
||||
style: ListTileStyle.list,
|
||||
onTap: () async {
|
||||
if(_discoveredDevices[index].name == _connectedDevice.name){
|
||||
await _disconnect();
|
||||
} else {
|
||||
await _connect(_discoveredDevices[index]);
|
||||
}
|
||||
},
|
||||
onLongPress: () async {
|
||||
// if (isAlreadyConnectedToDevice(_discoveredDevices[index],
|
||||
// await _bluetoothClassicPlugin.getPairedDevices())) {
|
||||
// _bluetoothClassicPlugin.disconnect();
|
||||
// }
|
||||
Fluttertoast.showToast(
|
||||
msg: Provider.of<AppStateModel>(context, listen: false).deviceStatus.toString(),
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.CENTER,
|
||||
timeInSecForIosWeb: 1,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0
|
||||
);
|
||||
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
39
lib/main.dart
Normal file
39
lib/main.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:xapk_installer/MainControlScreen.dart';
|
||||
import 'package:xapk_installer/connectPage.dart';
|
||||
|
||||
import 'AppStateModel.dart';
|
||||
|
||||
void main() {
|
||||
runApp(ChangeNotifierProvider(
|
||||
create: (context) => AppStateModel(),
|
||||
child: const MyApp(),
|
||||
));
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
darkTheme: ThemeData.dark(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: Maincontrolscreen(),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
419
lib/pickers/block_picker.dart
Normal file
419
lib/pickers/block_picker.dart
Normal file
@@ -0,0 +1,419 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||
|
||||
const List<Color> colors = [
|
||||
Colors.red,
|
||||
Colors.pink,
|
||||
Colors.purple,
|
||||
Colors.deepPurple,
|
||||
Colors.indigo,
|
||||
Colors.blue,
|
||||
Colors.lightBlue,
|
||||
Colors.cyan,
|
||||
Colors.teal,
|
||||
Colors.green,
|
||||
Colors.lightGreen,
|
||||
Colors.lime,
|
||||
Colors.yellow,
|
||||
Colors.amber,
|
||||
Colors.orange,
|
||||
Colors.deepOrange,
|
||||
Colors.brown,
|
||||
Colors.grey,
|
||||
Colors.blueGrey,
|
||||
Colors.black,
|
||||
];
|
||||
|
||||
class BlockColorPickerExample extends StatefulWidget {
|
||||
const BlockColorPickerExample({
|
||||
Key? key,
|
||||
required this.pickerColor,
|
||||
required this.onColorChanged,
|
||||
required this.pickerColors,
|
||||
required this.onColorsChanged,
|
||||
required this.colorHistory,
|
||||
}) : super(key: key);
|
||||
|
||||
final Color pickerColor;
|
||||
final ValueChanged<Color> onColorChanged;
|
||||
final List<Color> pickerColors;
|
||||
final ValueChanged<List<Color>> onColorsChanged;
|
||||
final List<Color> colorHistory;
|
||||
|
||||
@override
|
||||
State<BlockColorPickerExample> createState() => _BlockColorPickerExampleState();
|
||||
}
|
||||
|
||||
class _BlockColorPickerExampleState extends State<BlockColorPickerExample> {
|
||||
int _portraitCrossAxisCount = 4;
|
||||
int _landscapeCrossAxisCount = 5;
|
||||
double _borderRadius = 30;
|
||||
double _blurRadius = 5;
|
||||
double _iconSize = 24;
|
||||
|
||||
Widget pickerLayoutBuilder(BuildContext context, List<Color> colors, PickerItem child) {
|
||||
Orientation orientation = MediaQuery.of(context).orientation;
|
||||
|
||||
return SizedBox(
|
||||
width: 300,
|
||||
height: orientation == Orientation.portrait ? 360 : 240,
|
||||
child: GridView.count(
|
||||
crossAxisCount: orientation == Orientation.portrait ? _portraitCrossAxisCount : _landscapeCrossAxisCount,
|
||||
crossAxisSpacing: 5,
|
||||
mainAxisSpacing: 5,
|
||||
children: [for (Color color in colors) child(color)],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget pickerItemBuilder(Color color, bool isCurrentColor, void Function() changeColor) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(_borderRadius),
|
||||
color: color,
|
||||
boxShadow: [BoxShadow(color: color.withOpacity(0.8), offset: const Offset(1, 2), blurRadius: _blurRadius)],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: changeColor,
|
||||
borderRadius: BorderRadius.circular(_borderRadius),
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
opacity: isCurrentColor ? 1 : 0,
|
||||
child: Icon(
|
||||
Icons.done,
|
||||
size: _iconSize,
|
||||
color: useWhiteForeground(color) ? Colors.white : Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
titlePadding: const EdgeInsets.all(0),
|
||||
contentPadding: const EdgeInsets.all(25),
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'''
|
||||
Widget pickerLayoutBuilder(BuildContext context, List<Color> colors, PickerItem child) {
|
||||
Orientation orientation = MediaQuery.of(context).orientation;
|
||||
|
||||
return SizedBox(
|
||||
width: 300,
|
||||
height: orientation == Orientation.portrait ? 360 : 240,
|
||||
child: GridView.count(
|
||||
crossAxisCount: orientation == Orientation.portrait ? $_portraitCrossAxisCount : $_landscapeCrossAxisCount,
|
||||
crossAxisSpacing: 5,
|
||||
mainAxisSpacing: 5,
|
||||
children: [for (Color color in colors) child(color)],
|
||||
),
|
||||
);
|
||||
}
|
||||
''',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Icon(Icons.code, color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: widget.pickerColor,
|
||||
shadowColor: widget.pickerColor.withOpacity(1),
|
||||
elevation: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Portrait Cross Axis Count'),
|
||||
subtitle: Text(_portraitCrossAxisCount.toString()),
|
||||
trailing: SizedBox(
|
||||
width: 200,
|
||||
child: Slider(
|
||||
value: _portraitCrossAxisCount.toDouble(),
|
||||
min: 1,
|
||||
max: 10,
|
||||
divisions: 9,
|
||||
label: _portraitCrossAxisCount.toString(),
|
||||
onChanged: (double value) => setState(() => _portraitCrossAxisCount = value.round()),
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Landscape Cross Axis Count'),
|
||||
subtitle: Text(_landscapeCrossAxisCount.toString()),
|
||||
trailing: SizedBox(
|
||||
width: 200,
|
||||
child: Slider(
|
||||
value: _landscapeCrossAxisCount.toDouble(),
|
||||
min: 1,
|
||||
max: 10,
|
||||
divisions: 9,
|
||||
label: _landscapeCrossAxisCount.toString(),
|
||||
onChanged: (double value) => setState(() => _landscapeCrossAxisCount = value.round()),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
titlePadding: const EdgeInsets.all(0),
|
||||
contentPadding: const EdgeInsets.all(25),
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'''
|
||||
Widget pickerItemBuilder(Color color, bool isCurrentColor, void Function() changeColor) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular($_borderRadius),
|
||||
color: color,
|
||||
boxShadow: [BoxShadow(color: color.withOpacity(0.8), offset: const Offset(1, 2), blurRadius: $_blurRadius)],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: changeColor,
|
||||
borderRadius: BorderRadius.circular(_borderRadius),
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
opacity: isCurrentColor ? 1 : 0,
|
||||
child: Icon(
|
||||
Icons.done,
|
||||
size: $_iconSize,
|
||||
color: useWhiteForeground(color) ? Colors.white : Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
''',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Icon(Icons.code, color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: widget.pickerColor,
|
||||
shadowColor: widget.pickerColor.withOpacity(1),
|
||||
elevation: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Border Radius'),
|
||||
subtitle: Text(_borderRadius.toString()),
|
||||
trailing: SizedBox(
|
||||
width: 200,
|
||||
child: Slider(
|
||||
value: _borderRadius,
|
||||
min: 0,
|
||||
max: 30,
|
||||
divisions: 30,
|
||||
label: _borderRadius.toString(),
|
||||
onChanged: (double value) => setState(() => _borderRadius = value.round().toDouble()),
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Blur Radius'),
|
||||
subtitle: Text(_blurRadius.toString()),
|
||||
trailing: SizedBox(
|
||||
width: 200,
|
||||
child: Slider(
|
||||
value: _blurRadius,
|
||||
min: 0,
|
||||
max: 5,
|
||||
divisions: 5,
|
||||
label: _blurRadius.toString(),
|
||||
onChanged: (double value) => setState(() => _blurRadius = value.round().toDouble()),
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Icon Size'),
|
||||
subtitle: Text(_iconSize.toString()),
|
||||
trailing: SizedBox(
|
||||
width: 200,
|
||||
child: Slider(
|
||||
value: _iconSize,
|
||||
min: 1,
|
||||
max: 50,
|
||||
divisions: 49,
|
||||
label: _iconSize.toString(),
|
||||
onChanged: (double value) => setState(() => _iconSize = value.round().toDouble()),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Select a color'),
|
||||
content: SingleChildScrollView(
|
||||
child: BlockPicker(
|
||||
pickerColor: widget.pickerColor,
|
||||
onColorChanged: widget.onColorChanged,
|
||||
availableColors: widget.colorHistory.isNotEmpty ? widget.colorHistory : colors,
|
||||
layoutBuilder: pickerLayoutBuilder,
|
||||
itemBuilder: pickerItemBuilder,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'Blocky Color Picker',
|
||||
style: TextStyle(color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: widget.pickerColor,
|
||||
shadowColor: widget.pickerColor.withOpacity(1),
|
||||
elevation: 10,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return const AlertDialog(
|
||||
titlePadding: EdgeInsets.all(0),
|
||||
contentPadding: EdgeInsets.all(25),
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'''
|
||||
BlockPicker(
|
||||
pickerColor: color,
|
||||
onColorChanged: changeColor,
|
||||
availableColors: colors,
|
||||
layoutBuilder: pickerLayoutBuilder,
|
||||
itemBuilder: pickerItemBuilder,
|
||||
)
|
||||
''',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Icon(Icons.code, color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: widget.pickerColor,
|
||||
shadowColor: widget.pickerColor.withOpacity(1),
|
||||
elevation: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Select colors'),
|
||||
content: SingleChildScrollView(
|
||||
child: MultipleChoiceBlockPicker(
|
||||
pickerColors: widget.pickerColors,
|
||||
onColorsChanged: widget.onColorsChanged,
|
||||
availableColors: widget.colorHistory.isNotEmpty ? widget.colorHistory : colors,
|
||||
layoutBuilder: pickerLayoutBuilder,
|
||||
itemBuilder: pickerItemBuilder,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'Multiple selection Blocky Color Picker',
|
||||
style: TextStyle(color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: widget.pickerColor,
|
||||
shadowColor: widget.pickerColor.withOpacity(1),
|
||||
elevation: 10,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return const AlertDialog(
|
||||
titlePadding: EdgeInsets.all(0),
|
||||
contentPadding: EdgeInsets.all(25),
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'''
|
||||
MultipleChoiceBlockPicker(
|
||||
pickerColors: colors,
|
||||
onColorsChanged: changeColors,
|
||||
availableColors: colors,
|
||||
layoutBuilder: pickerLayoutBuilder,
|
||||
itemBuilder: pickerItemBuilder,
|
||||
)
|
||||
''',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Icon(Icons.code, color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: widget.pickerColor,
|
||||
shadowColor: widget.pickerColor.withOpacity(1),
|
||||
elevation: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 80),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
0
lib/widgets.dart
Normal file
0
lib/widgets.dart
Normal file
Reference in New Issue
Block a user