diff --git a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift new file mode 100644 index 00000000..a30d77de --- /dev/null +++ b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "FlutterGeneratedPluginSwiftPackage", + platforms: [ + .iOS("13.0") + ], + products: [ + .library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"]) + ], + dependencies: [ + .package(name: "file_picker", path: "../.packages/file_picker-12.0.0-beta.7"), + .package(name: "FlutterFramework", path: "../.packages/FlutterFramework") + ], + targets: [ + .target( + name: "FlutterGeneratedPluginSwiftPackage", + dependencies: [ + .product(name: "file-picker", package: "file_picker"), + .product(name: "FlutterFramework", package: "FlutterFramework") + ] + ) + ] +) diff --git a/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift new file mode 100644 index 00000000..a30d95a8 --- /dev/null +++ b/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift @@ -0,0 +1,3 @@ +// +// Generated file. Do not edit. +// diff --git a/example/ios/Podfile b/example/ios/Podfile index 2c068c40..10f3c9b4 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index bf757d56..92c1fc03 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,21 +2,28 @@ PODS: - Flutter (1.0.0) - flutter_midi_command (0.4.2): - Flutter + - universal_ble (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - Flutter (from `Flutter`) - flutter_midi_command (from `.symlinks/plugins/flutter_midi_command/ios`) + - universal_ble (from `.symlinks/plugins/universal_ble/darwin`) EXTERNAL SOURCES: Flutter: :path: Flutter flutter_midi_command: :path: ".symlinks/plugins/flutter_midi_command/ios" + universal_ble: + :path: ".symlinks/plugins/universal_ble/darwin" SPEC CHECKSUMS: - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_midi_command: 760f0ec4b7c6d3034982f6045e878d9b7d01674e + universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 -PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 +PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/lib/controller.dart b/example/lib/controller.dart index 0d0ff0f3..9d7c3e28 100644 --- a/example/lib/controller.dart +++ b/example/lib/controller.dart @@ -1,324 +1,324 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_midi_command/flutter_midi_command.dart'; -import 'package:flutter_midi_command/flutter_midi_command_messages.dart'; -import 'package:flutter_midi_command_example/recorder.dart'; -import 'package:flutter_virtual_piano/flutter_virtual_piano.dart'; - -class ControllerPage extends StatelessWidget { - final MidiDevice device; - - const ControllerPage(this.device, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(device.name), - ), - body: MidiControls(device), - ); - } -} - -class MidiControls extends StatefulWidget { - final MidiDevice device; - - const MidiControls(this.device, {Key? key}) : super(key: key); - - @override - MidiControlsState createState() { - return MidiControlsState(); - } -} - -class MidiControlsState extends State { - var _channel = 0; - var _controller = 0; - var _ccValue = 0; - var _pcValue = 0; - var _pitchValue = 0.0; - var _nrpnValue = 0; - var _nrpnCtrl = 0; - - // StreamSubscription _setupSubscription; - StreamSubscription? _rxSubscription; - final MidiCommand _midiCommand = MidiCommand(); - - final MidiRecorder _recorder = MidiRecorder(); - - @override - void initState() { - if (kDebugMode) { - print('init controller'); - } - _rxSubscription = _midiCommand.onMidiDataReceived?.listen((packet) { - var data = packet.data; - var timestamp = packet.timestamp; - var device = packet.device; - // if (kDebugMode) { - // print("data $data @ time $timestamp from device ${device.name}:${device.id}"); - // } - - var status = data[0]; - - if (status == 0xF8) { - // Beat - return; - } - - if (status == 0xFE) { - // Active sense; - return; - } - - if (data.length >= 2) { - var rawStatus = status & 0xF0; // without channel - var channel = (status & 0x0F); - if (channel == _channel) { - var d1 = data[1]; - switch (rawStatus) { - case 0xB0: // CC - if (d1 == _controller) { - // CC - var d2 = data[2]; - setState(() { - _ccValue = d2; - }); - } - break; - case 0xC0: // PC - setState(() { - _pcValue = d1; - }); - break; - case 0xE0: // Pitch Bend - setState(() { - var rawPitch = d1 + (data[2] << 7); - _pitchValue = (((rawPitch) / 0x3FFF) * 2.0) - 1; - }); - break; - } - } - } - }); - - super.initState(); - } - - @override - void dispose() { - // _setupSubscription?.cancel(); - _rxSubscription?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ListView( - padding: const EdgeInsets.all(12), - children: [ - Text("Channel", style: Theme.of(context).textTheme.titleLarge), - SteppedSelector('Channel', _channel + 1, 1, 16, _onChannelChanged), - const Divider(), - Text("CC", style: Theme.of(context).textTheme.titleLarge), - SteppedSelector('Controller', _controller, 0, 127, _onControllerChanged), - SlidingSelector('Value', _ccValue, 0, 127, _onValueChanged), - const Divider(), - Text("NRPN", style: Theme.of(context).textTheme.titleLarge), - SteppedSelector('Parameter', _nrpnCtrl, 0, 16383, _onNRPNCtrlChanged), - SlidingSelector('Parameter', _nrpnCtrl, 0, 16383, _onNRPNCtrlChanged), - SlidingSelector('Value', _nrpnValue, 0, 16383, _onNRPNValueChanged), - const Divider(), - Text("PC", style: Theme.of(context).textTheme.titleLarge), - SteppedSelector('Program', _pcValue, 0, 127, _onProgramChanged), - const Divider(), - Text("Pitch Bend", style: Theme.of(context).textTheme.titleLarge), - Slider( - value: _pitchValue, - max: 1, - min: -1, - onChanged: _onPitchChanged, - onChangeEnd: (_) { - _onPitchChanged(0); - }), - const Divider(), - SizedBox( - height: 80, - child: VirtualPiano( - noteRange: const RangeValues(48, 76), - onNotePressed: (note, vel) { - NoteOnMessage(channel: _channel, note: note, velocity: 100).send(); - }, - onNoteReleased: (note) { - NoteOffMessage(channel: _channel, note: note).send(); - }, - ), - ), - const Divider(), - Text("SysEx", style: Theme.of(context).textTheme.titleLarge), - ...[64, 128, 256, 512, 768, 1024] - .map( - (e) => Padding( - padding: const EdgeInsets.all(8.0), - child: ElevatedButton( - onPressed: () => _sendSysex(e), - child: Text('Send $e bytes'), - ), - ), - ) - , - const Divider(), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Text("Recorder", style: Theme.of(context).textTheme.titleLarge), - Expanded(child: Container()), - Switch(value: _recorder.recording, onChanged: (newValue){ - setState(() { - if (newValue) { - _recorder.startRecording(); - } else { - _recorder.stopRecording(); - } - }); - }), - TextButton(onPressed: (){ - _recorder.exportRecording(); - }, child: const Text("Export CSV")), - TextButton(onPressed: (){ - _recorder.clearRecording(); - }, child: const Text("Clear")) - ], - - ), - ) - ], - ); - } - - _onChannelChanged(int newValue) { - setState(() { - _channel = newValue - 1; - }); - } - - _onControllerChanged(int newValue) { - setState(() { - _controller = newValue; - }); - } - - _onProgramChanged(int newValue) { - setState(() { - _pcValue = newValue; - }); - PCMessage(channel: _channel, program: _pcValue).send(); - } - - _onValueChanged(int newValue) { - setState(() { - _ccValue = newValue; - }); - CCMessage(channel: _channel, controller: _controller, value: _ccValue).send(); - } - - _onNRPNValueChanged(int newValue) { - setState(() { - _nrpnValue = newValue; - }); - NRPN4Message(channel: _channel, parameter: _nrpnCtrl, value: _nrpnValue).send(); - } - - _onNRPNCtrlChanged(int newValue) { - setState(() { - _nrpnCtrl = newValue; - }); - } - - _onPitchChanged(double newValue) { - setState(() { - _pitchValue = newValue; - }); - PitchBendMessage(channel: _channel, bend: _pitchValue).send(); - } - - void _sendSysex(int length) { - print("Send $length SysEx bytes"); - final data = Uint8List(length); - data[0] = 0xF0; - for (int i = 0; i < length -1; i++) { - data[i+1] = i % 0x80; - } - data[length - 1] = 0xF7; - _midiCommand.sendData(data); - } -} - -class SteppedSelector extends StatelessWidget { - final String label; - final int minValue; - final int maxValue; - final int value; - final Function(int) callback; - - const SteppedSelector(this.label, this.value, this.minValue, this.maxValue, this.callback, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(label), - IconButton( - icon: const Icon(Icons.remove_circle), - onPressed: (value > minValue) - ? () { - callback(value - 1); - } - : null), - Text(value.toString()), - IconButton( - icon: const Icon(Icons.add_circle), - onPressed: (value < maxValue) - ? () { - callback(value + 1); - } - : null) - ], - ); - } -} - -class SlidingSelector extends StatelessWidget { - final String label; - final int minValue; - final int maxValue; - final int value; - final Function(int) callback; - - const SlidingSelector(this.label, this.value, this.minValue, this.maxValue, this.callback, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(label), - Slider( - value: value.toDouble(), - divisions: maxValue, - min: minValue.toDouble(), - max: maxValue.toDouble(), - onChanged: (v) { - callback(v.toInt()); - }, - ), - Text(value.toString()), - ], - ); - } -} +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_midi_command/flutter_midi_command.dart'; +import 'package:flutter_midi_command/flutter_midi_command_messages.dart'; +import 'package:flutter_midi_command_example/recorder.dart'; +import 'package:flutter_virtual_piano/flutter_virtual_piano.dart'; + +class ControllerPage extends StatelessWidget { + final MidiDevice device; + + const ControllerPage(this.device, {super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(device.name), + ), + body: MidiControls(device), + ); + } +} + +class MidiControls extends StatefulWidget { + final MidiDevice device; + + const MidiControls(this.device, {super.key}); + + @override + MidiControlsState createState() { + return MidiControlsState(); + } +} + +class MidiControlsState extends State { + var _channel = 0; + var _controller = 0; + var _ccValue = 0; + var _pcValue = 0; + var _pitchValue = 0.0; + var _nrpnValue = 0; + var _nrpnCtrl = 0; + + // StreamSubscription _setupSubscription; + StreamSubscription? _rxSubscription; + final MidiCommand _midiCommand = MidiCommand(); + + final MidiRecorder _recorder = MidiRecorder(); + + @override + void initState() { + if (kDebugMode) { + print('init controller'); + } + _rxSubscription = _midiCommand.onMidiDataReceived?.listen((packet) { + var data = packet.data; + var timestamp = packet.timestamp; + var device = packet.device; + // if (kDebugMode) { + // print("data $data @ time $timestamp from device ${device.name}:${device.id}"); + // } + + var status = data[0]; + + if (status == 0xF8) { + // Beat + return; + } + + if (status == 0xFE) { + // Active sense; + return; + } + + if (data.length >= 2) { + var rawStatus = status & 0xF0; // without channel + var channel = (status & 0x0F); + if (channel == _channel) { + var d1 = data[1]; + switch (rawStatus) { + case 0xB0: // CC + if (d1 == _controller) { + // CC + var d2 = data[2]; + setState(() { + _ccValue = d2; + }); + } + break; + case 0xC0: // PC + setState(() { + _pcValue = d1; + }); + break; + case 0xE0: // Pitch Bend + setState(() { + var rawPitch = d1 + (data[2] << 7); + _pitchValue = (((rawPitch) / 0x3FFF) * 2.0) - 1; + }); + break; + } + } + } + }); + + super.initState(); + } + + @override + void dispose() { + // _setupSubscription?.cancel(); + _rxSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ListView( + padding: const EdgeInsets.all(12), + children: [ + Text("Channel", style: Theme.of(context).textTheme.titleLarge), + SteppedSelector('Channel', _channel + 1, 1, 16, _onChannelChanged), + const Divider(), + Text("CC", style: Theme.of(context).textTheme.titleLarge), + SteppedSelector('Controller', _controller, 0, 127, _onControllerChanged), + SlidingSelector('Value', _ccValue, 0, 127, _onValueChanged), + const Divider(), + Text("NRPN", style: Theme.of(context).textTheme.titleLarge), + SteppedSelector('Parameter', _nrpnCtrl, 0, 16383, _onNRPNCtrlChanged), + SlidingSelector('Parameter', _nrpnCtrl, 0, 16383, _onNRPNCtrlChanged), + SlidingSelector('Value', _nrpnValue, 0, 16383, _onNRPNValueChanged), + const Divider(), + Text("PC", style: Theme.of(context).textTheme.titleLarge), + SteppedSelector('Program', _pcValue, 0, 127, _onProgramChanged), + const Divider(), + Text("Pitch Bend", style: Theme.of(context).textTheme.titleLarge), + Slider( + value: _pitchValue, + max: 1, + min: -1, + onChanged: _onPitchChanged, + onChangeEnd: (_) { + _onPitchChanged(0); + }), + const Divider(), + SizedBox( + height: 80, + child: VirtualPiano( + noteRange: const RangeValues(48, 76), + onNotePressed: (note, vel) { + NoteOnMessage(channel: _channel, note: note, velocity: 100).send(); + }, + onNoteReleased: (note) { + NoteOffMessage(channel: _channel, note: note).send(); + }, + ), + ), + const Divider(), + Text("SysEx", style: Theme.of(context).textTheme.titleLarge), + ...[64, 128, 256, 512, 768, 1024] + .map( + (e) => Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () => _sendSysex(e), + child: Text('Send $e bytes'), + ), + ), + ) + , + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Text("Recorder", style: Theme.of(context).textTheme.titleLarge), + Expanded(child: Container()), + Switch(value: _recorder.recording, onChanged: (newValue){ + setState(() { + if (newValue) { + _recorder.startRecording(); + } else { + _recorder.stopRecording(); + } + }); + }), + TextButton(onPressed: (){ + _recorder.exportRecording(); + }, child: const Text("Export CSV")), + TextButton(onPressed: (){ + _recorder.clearRecording(); + }, child: const Text("Clear")) + ], + + ), + ) + ], + ); + } + + void _onChannelChanged(int newValue) { + setState(() { + _channel = newValue - 1; + }); + } + + void _onControllerChanged(int newValue) { + setState(() { + _controller = newValue; + }); + } + + void _onProgramChanged(int newValue) { + setState(() { + _pcValue = newValue; + }); + PCMessage(channel: _channel, program: _pcValue).send(); + } + + void _onValueChanged(int newValue) { + setState(() { + _ccValue = newValue; + }); + CCMessage(channel: _channel, controller: _controller, value: _ccValue).send(); + } + + void _onNRPNValueChanged(int newValue) { + setState(() { + _nrpnValue = newValue; + }); + NRPN4Message(channel: _channel, parameter: _nrpnCtrl, value: _nrpnValue).send(); + } + + void _onNRPNCtrlChanged(int newValue) { + setState(() { + _nrpnCtrl = newValue; + }); + } + + void _onPitchChanged(double newValue) { + setState(() { + _pitchValue = newValue; + }); + PitchBendMessage(channel: _channel, bend: _pitchValue).send(); + } + + void _sendSysex(int length) { + print("Send $length SysEx bytes"); + final data = Uint8List(length); + data[0] = 0xF0; + for (int i = 0; i < length -1; i++) { + data[i+1] = i % 0x80; + } + data[length - 1] = 0xF7; + _midiCommand.sendData(data); + } +} + +class SteppedSelector extends StatelessWidget { + final String label; + final int minValue; + final int maxValue; + final int value; + final Function(int) callback; + + const SteppedSelector(this.label, this.value, this.minValue, this.maxValue, this.callback, {super.key}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(label), + IconButton( + icon: const Icon(Icons.remove_circle), + onPressed: (value > minValue) + ? () { + callback(value - 1); + } + : null), + Text(value.toString()), + IconButton( + icon: const Icon(Icons.add_circle), + onPressed: (value < maxValue) + ? () { + callback(value + 1); + } + : null) + ], + ); + } +} + +class SlidingSelector extends StatelessWidget { + final String label; + final int minValue; + final int maxValue; + final int value; + final Function(int) callback; + + const SlidingSelector(this.label, this.value, this.minValue, this.maxValue, this.callback, {super.key}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(label), + Slider( + value: value.toDouble(), + divisions: maxValue, + min: minValue.toDouble(), + max: maxValue.toDouble(), + onChanged: (v) { + callback(v.toInt()); + }, + ), + Text(value.toString()), + ], + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 5260b2fb..1905b086 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,293 +1,296 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_midi_command/flutter_midi_command.dart'; - -import 'controller.dart'; - -void main() => runApp(const MyApp()); - -class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); - - @override - MyAppState createState() => MyAppState(); -} - -class MyAppState extends State { - StreamSubscription? _setupSubscription; - StreamSubscription? _bluetoothStateSubscription; - final MidiCommand _midiCommand = MidiCommand(); - - bool _virtualDeviceActivated = false; - bool _iOSNetworkSessionEnabled = false; - - bool _didAskForBluetoothPermissions = false; - - @override - void initState() { - super.initState(); - - _setupSubscription = _midiCommand.onMidiSetupChanged?.listen((data) async { - if (kDebugMode) { - print("setup changed $data"); - } - setState(() {}); - }); - - _bluetoothStateSubscription = - _midiCommand.onBluetoothStateChanged.listen((data) { - if (kDebugMode) { - print("bluetooth state change $data"); - } - setState(() {}); - }); - - _updateNetworkSessionState(); - } - - @override - void dispose() { - _setupSubscription?.cancel(); - _bluetoothStateSubscription?.cancel(); - super.dispose(); - } - - _updateNetworkSessionState() async { - var nse = await _midiCommand.isNetworkSessionEnabled; - if (nse != null) { - setState(() { - _iOSNetworkSessionEnabled = nse; - }); - } - } - - IconData _deviceIconForType(String type) { - switch (type) { - case "native": - return Icons.devices; - case "network": - return Icons.language; - case "BLE": - return Icons.bluetooth; - default: - return Icons.device_unknown; - } - } - - Future _informUserAboutBluetoothPermissions( - BuildContext context) async { - if (_didAskForBluetoothPermissions) { - return; - } - - await showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return AlertDialog( - title: const Text( - 'Please Grant Bluetooth Permissions to discover BLE MIDI Devices.'), - content: const Text( - 'In the next dialog we might ask you for bluetooth permissions.\n' - 'Please grant permissions to make bluetooth MIDI possible.'), - actions: [ - TextButton( - child: const Text('Ok. I got it!'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - - _didAskForBluetoothPermissions = true; - - return; - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('FlutterMidiCommand Example'), - actions: [ - Switch( - value: _iOSNetworkSessionEnabled, - onChanged: (newValue) { - _midiCommand.setNetworkSessionEnabled(newValue); - setState(() { - _iOSNetworkSessionEnabled = newValue; - }); - }), - Switch( - value: _virtualDeviceActivated, - onChanged: (newValue) { - setState(() { - _virtualDeviceActivated = newValue; - }); - if (newValue) { - _midiCommand.addVirtualDevice(name: "Flutter MIDI Command"); - } else { - _midiCommand.removeVirtualDevice( - name: "Flutter MIDI Command"); - } - }), - Builder(builder: (context) { - return IconButton( - onPressed: () async { - // Ask for bluetooth permissions - await _informUserAboutBluetoothPermissions(context); - - // Start bluetooth - if (kDebugMode) { - print("start ble central"); - } - await _midiCommand - .startBluetoothCentral() - .catchError((err) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(err), - )); - }); - - if (kDebugMode) { - print("wait for init"); - } - await _midiCommand - .waitUntilBluetoothIsInitialized() - .timeout(const Duration(seconds: 5), onTimeout: () { - if (kDebugMode) { - print("Failed to initialize Bluetooth"); - } - }); - - // If bluetooth is powered on, start scanning - if (_midiCommand.bluetoothState == - BluetoothState.poweredOn) { - _midiCommand - .startScanningForBluetoothDevices() - .catchError((err) { - if (kDebugMode) { - print("Error $err"); - } - }); - if (context.mounted) { - ScaffoldMessenger.of(context) - .showSnackBar(const SnackBar( - content: Text('Scanning for bluetooth devices ...'), - )); - } - } else { - final messages = { - BluetoothState.unsupported: - 'Bluetooth is not supported on this device.', - BluetoothState.poweredOff: - 'Please switch on bluetooth and try again.', - BluetoothState.poweredOn: 'Everything is fine.', - BluetoothState.resetting: - 'Currently resetting. Try again later.', - BluetoothState.unauthorized: - 'This app needs bluetooth permissions. Please open settings, find your app and assign bluetooth access rights and start your app again.', - BluetoothState.unknown: - 'Bluetooth is not ready yet. Try again later.', - BluetoothState.other: - 'This should never happen. Please inform the developer of your app.', - }; - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - backgroundColor: Colors.red, - content: Text(messages[_midiCommand.bluetoothState] ?? - 'Unknown bluetooth state: ${_midiCommand.bluetoothState}'), - )); - } - } - - if (kDebugMode) { - print("done"); - } - // If not show a message telling users what to do - setState(() {}); - }, - icon: const Icon(Icons.refresh)); - }), - ], - ), - bottomNavigationBar: Container( - padding: const EdgeInsets.all(24.0), - child: const Text( - "Tap to connnect/disconnect, long press to control.", - textAlign: TextAlign.center, - ), - ), - body: Center( - child: FutureBuilder( - future: _midiCommand.devices, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData && snapshot.data != null) { - var devices = snapshot.data as List; - return ListView.builder( - itemCount: devices.length, - itemBuilder: (context, index) { - MidiDevice device = devices[index]; - - return ListTile( - title: Text( - device.name, - style: Theme.of(context).textTheme.headlineSmall, - ), - subtitle: Text( - "ins:${device.inputPorts.length} outs:${device.outputPorts.length}, ${device.id}, ${device.type}"), - leading: Icon(device.connected - ? Icons.radio_button_on - : Icons.radio_button_off), - trailing: Icon(_deviceIconForType(device.type)), - onLongPress: () { - _midiCommand.stopScanningForBluetoothDevices(); - Navigator.of(context) - .push(MaterialPageRoute( - builder: (_) => ControllerPage(device), - )) - .then((value) { - setState(() {}); - }); - }, - onTap: () { - if (device.connected) { - if (kDebugMode) { - print("disconnect"); - } - _midiCommand.disconnectDevice(device); - } else { - if (kDebugMode) { - print("connect"); - } - _midiCommand.connectToDevice(device).then((_) { - if (kDebugMode) { - print("device connected async"); - } - }).catchError((err) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - "Error: ${(err as PlatformException?)?.message}"))); - }); - } - }, - ); - }, - ); - } else { - return const CircularProgressIndicator(); - } - }, - ), - ), - ), - ); - } -} +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_midi_command/flutter_midi_command.dart'; + +import 'controller.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + MyAppState createState() => MyAppState(); +} + +class MyAppState extends State { + StreamSubscription? _setupSubscription; + StreamSubscription? _bluetoothStateSubscription; + final MidiCommand _midiCommand = MidiCommand(); + + bool _virtualDeviceActivated = false; + bool _iOSNetworkSessionEnabled = false; + + bool _didAskForBluetoothPermissions = false; + + @override + void initState() { + super.initState(); + + _setupSubscription = _midiCommand.onMidiSetupChanged?.listen((data) async { + if (kDebugMode) { + print("setup changed $data"); + } + setState(() {}); + }); + + _bluetoothStateSubscription = + _midiCommand.onBluetoothStateChanged.listen((data) { + if (kDebugMode) { + print("bluetooth state change $data"); + } + setState(() {}); + }); + + _updateNetworkSessionState(); + } + + @override + void dispose() { + _setupSubscription?.cancel(); + _bluetoothStateSubscription?.cancel(); + super.dispose(); + } + + void _updateNetworkSessionState() async { + var nse = await _midiCommand.isNetworkSessionEnabled; + if (nse != null) { + setState(() { + _iOSNetworkSessionEnabled = nse; + }); + } + } + + IconData _deviceIconForType(String type) { + switch (type) { + case "native": + return Icons.devices; + case "network": + return Icons.language; + case "BLE": + return Icons.bluetooth; + default: + return Icons.device_unknown; + } + } + + Future _informUserAboutBluetoothPermissions( + BuildContext context) async { + if (_didAskForBluetoothPermissions) { + return; + } + + await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: const Text( + 'Please Grant Bluetooth Permissions to discover BLE MIDI Devices.'), + content: const Text( + 'In the next dialog we might ask you for bluetooth permissions.\n' + 'Please grant permissions to make bluetooth MIDI possible.'), + actions: [ + TextButton( + child: const Text('Ok. I got it!'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + + _didAskForBluetoothPermissions = true; + + return; + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('FlutterMidiCommand Example'), + actions: [ + Switch( + value: _iOSNetworkSessionEnabled, + onChanged: (newValue) { + _midiCommand.setNetworkSessionEnabled(newValue); + setState(() { + _iOSNetworkSessionEnabled = newValue; + }); + }), + Switch( + value: _virtualDeviceActivated, + onChanged: (newValue) { + setState(() { + _virtualDeviceActivated = newValue; + }); + if (newValue) { + _midiCommand.addVirtualDevice(name: "Flutter MIDI Command"); + } else { + _midiCommand.removeVirtualDevice( + name: "Flutter MIDI Command"); + } + }), + Builder(builder: (context) { + return IconButton( + onPressed: () async { + // Ask for bluetooth permissions + await _informUserAboutBluetoothPermissions(context); + + // Start bluetooth + if (kDebugMode) { + print("start ble central"); + } + await _midiCommand + .startBluetoothCentral() + .catchError((err) { + if (kDebugMode) { + print("BLE Central Error $err"); + } + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(err), + )); + }); + + if (kDebugMode) { + print("wait for init"); + } + await _midiCommand + .waitUntilBluetoothIsInitialized() + .timeout(const Duration(seconds: 5), onTimeout: () { + if (kDebugMode) { + print("Failed to initialize Bluetooth"); + } + }); + + // If bluetooth is powered on, start scanning + if (_midiCommand.bluetoothState == + BluetoothState.poweredOn) { + _midiCommand + .startScanningForBluetoothDevices() + .catchError((err) { + if (kDebugMode) { + print("Error $err"); + } + }); + if (context.mounted) { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar( + content: Text('Scanning for bluetooth devices ...'), + )); + } + } else { + final messages = { + BluetoothState.unsupported: + 'Bluetooth is not supported on this device.', + BluetoothState.poweredOff: + 'Please switch on bluetooth and try again.', + BluetoothState.poweredOn: 'Everything is fine.', + BluetoothState.resetting: + 'Currently resetting. Try again later.', + BluetoothState.unauthorized: + 'This app needs bluetooth permissions. Please open settings, find your app and assign bluetooth access rights and start your app again.', + BluetoothState.unknown: + 'Bluetooth is not ready yet. Try again later.', + BluetoothState.other: + 'This should never happen. Please inform the developer of your app.', + }; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + backgroundColor: Colors.red, + content: Text(messages[_midiCommand.bluetoothState] ?? + 'Unknown bluetooth state: ${_midiCommand.bluetoothState}'), + )); + } + } + + if (kDebugMode) { + print("done"); + } + // If not show a message telling users what to do + setState(() {}); + }, + icon: const Icon(Icons.refresh)); + }), + ], + ), + bottomNavigationBar: Container( + padding: const EdgeInsets.all(24.0), + child: const Text( + "Tap to connnect/disconnect, long press to control.", + textAlign: TextAlign.center, + ), + ), + body: Center( + child: FutureBuilder( + future: _midiCommand.devices, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData && snapshot.data != null) { + var devices = snapshot.data as List; + return ListView.builder( + itemCount: devices.length, + itemBuilder: (context, index) { + MidiDevice device = devices[index]; + + return ListTile( + title: Text( + device.name, + style: Theme.of(context).textTheme.headlineSmall, + ), + subtitle: Text( + "ins:${device.inputPorts.length} outs:${device.outputPorts.length}, ${device.id}, ${device.type}"), + leading: Icon(device.connected + ? Icons.radio_button_on + : Icons.radio_button_off), + trailing: Icon(_deviceIconForType(device.type)), + onLongPress: () { + _midiCommand.stopScanningForBluetoothDevices(); + Navigator.of(context) + .push(MaterialPageRoute( + builder: (_) => ControllerPage(device), + )) + .then((value) { + setState(() {}); + }); + }, + onTap: () { + if (device.connected) { + if (kDebugMode) { + print("disconnect"); + } + _midiCommand.disconnectDevice(device); + } else { + if (kDebugMode) { + print("connect"); + } + _midiCommand.connectToDevice(device).then((_) { + if (kDebugMode) { + print("device connected async"); + } + }).catchError((err) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + "Error: ${(err as PlatformException?)?.message}"))); + }); + } + }, + ); + }, + ); + } else { + return const CircularProgressIndicator(); + } + }, + ), + ), + ), + ); + } +} diff --git a/example/lib/recorder.dart b/example/lib/recorder.dart index 52bf7849..d91a2c1e 100644 --- a/example/lib/recorder.dart +++ b/example/lib/recorder.dart @@ -1,66 +1,70 @@ - -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter_midi_command/flutter_midi_command.dart'; -import 'package:csv/csv.dart'; -import 'package:file_picker/file_picker.dart'; - -class MidiRecorder { - - factory MidiRecorder() { - _instance ??= MidiRecorder._(); - return _instance!; - } - - static MidiRecorder? _instance; - - MidiRecorder._(); - - bool _recording = false; - - bool get recording => _recording; - - final List _messages = []; - - StreamSubscription? _midiSub; - - startRecording() { - _recording = true; - _midiSub = MidiCommand().onMidiDataReceived?.listen((packet) { - _messages.add(packet); - }); - } - - stopRecording() { - _recording = false; - _midiSub?.cancel(); - } - - - exportRecording() async { - var rows = _messages.map((e) => [e.timestamp, ...e.data.map((e) => e.toString())]).toList(); - - var csv = const ListToCsvConverter().convert(rows); - - String? outputFile = await FilePicker.platform.saveFile( - dialogTitle: 'Please select an output file:', - fileName: 'midi_recording.csv', - type: FileType.custom, - allowedExtensions: ['csv'], - ); - - if (outputFile == null) { - // User canceled the picker - } else { - await File(outputFile).writeAsString(csv); - } - - print("recording exported"); - } - - clearRecording() { - _messages.clear(); - } -} - + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter_midi_command/flutter_midi_command.dart'; +import 'package:csv/csv.dart'; +import 'package:file_picker/file_picker.dart'; + +class MidiRecorder { + + factory MidiRecorder() { + _instance ??= MidiRecorder._(); + return _instance!; + } + + static MidiRecorder? _instance; + + MidiRecorder._(); + + bool _recording = false; + + bool get recording => _recording; + + final List _messages = []; + + StreamSubscription? _midiSub; + + void startRecording() { + print("Starting recording"); + _recording = true; + _midiSub = MidiCommand().onMidiDataReceived?.listen((packet) { + print("Recording packet: ${packet.data.length} bytes"); + _messages.add(packet); + }); + } + + void stopRecording() { + print("Stopping recording"); + _recording = false; + _midiSub?.cancel(); + } + + + void exportRecording() async { + print("Writing ${_messages.length} messages to CSV file"); + List> rows = _messages.map>((e) => [e.timestamp.toString(), ...e.data.map((e) => e.toString())]).toList(); + + var data = csv.encode(rows); + Uint8List bytes = Uint8List.fromList(data.codeUnits); + + String? outputFile = await FilePicker.saveFile( + dialogTitle: 'Please select an output file:', + fileName: 'midi_recording.csv', + type: FileType.custom, + allowedExtensions: ['csv'], + bytes: bytes, + ); + + if (outputFile == null) { + print("The user canceled the picker"); + } else { + print("Recorded ${bytes.length} bytes exported to $outputFile"); + } + } + + void clearRecording() { + _messages.clear(); + } +} + diff --git a/example/lib/test.dart b/example/lib/test.dart index ff01f017..69f9b9ef 100644 --- a/example/lib/test.dart +++ b/example/lib/test.dart @@ -1,76 +1,76 @@ -import 'dart:typed_data'; -import 'package:flutter/material.dart'; -import 'package:flutter_midi_command/flutter_midi_command.dart'; - -void main() { - runApp(const MainApp()); -} - -class MainApp extends StatelessWidget { - const MainApp({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold(body: HomeView()), - ); - } -} - -class HomeView extends StatefulWidget { - const HomeView({Key? key}) : super(key: key); - - @override - State createState() => _HomeViewState(); -} - -class _HomeViewState extends State { - final midi = MidiCommand(); - - @override - void initState() { - super.initState(); - midi.onMidiSetupChanged?.listen((_) => _updateSetup()); - _updateSetup(); - } - - void _updateSetup() async { - final devices = await midi.devices ?? []; - for (final device in devices) { - if (!device.connected) { - try { - await midi.connectToDevice(device); - } catch (_) { - debugPrint('Could not connect to device: $device'); - } - } - } - } - - void _sendEmptySysex(int length) { - final data = Uint8List(length); - data[0] = 0xF0; - data[length - 1] = 0xF7; - midi.sendData(data); - } - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [256, 512, 768, 1024] - .map( - (e) => Padding( - padding: const EdgeInsets.all(8.0), - child: ElevatedButton( - onPressed: () => _sendEmptySysex(e), - child: Text('Send $e bytes'), - ), - ), - ) - .toList(), - ), - ); - } -} +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:flutter_midi_command/flutter_midi_command.dart'; + +void main() { + runApp(const MainApp()); +} + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold(body: HomeView()), + ); + } +} + +class HomeView extends StatefulWidget { + const HomeView({super.key}); + + @override + State createState() => _HomeViewState(); +} + +class _HomeViewState extends State { + final midi = MidiCommand(); + + @override + void initState() { + super.initState(); + midi.onMidiSetupChanged?.listen((_) => _updateSetup()); + _updateSetup(); + } + + void _updateSetup() async { + final devices = await midi.devices ?? []; + for (final device in devices) { + if (!device.connected) { + try { + await midi.connectToDevice(device); + } catch (_) { + debugPrint('Could not connect to device: $device'); + } + } + } + } + + void _sendEmptySysex(int length) { + final data = Uint8List(length); + data[0] = 0xF0; + data[length - 1] = 0xF7; + midi.sendData(data); + } + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [256, 512, 768, 1024] + .map( + (e) => Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () => _sendEmptySysex(e), + child: Text('Send $e bytes'), + ), + ), + ) + .toList(), + ), + ); + } +} diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 7555e258..246e12cf 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -1,6 +1,4 @@ PODS: - - file_picker (0.0.1): - - FlutterMacOS - flutter_midi_command (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) @@ -9,14 +7,11 @@ PODS: - FlutterMacOS DEPENDENCIES: - - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - flutter_midi_command (from `Flutter/ephemeral/.symlinks/plugins/flutter_midi_command/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - universal_ble (from `Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin`) EXTERNAL SOURCES: - file_picker: - :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos flutter_midi_command: :path: Flutter/ephemeral/.symlinks/plugins/flutter_midi_command/macos FlutterMacOS: @@ -25,7 +20,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin SPEC CHECKSUMS: - file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af flutter_midi_command: c1a74de594e48e8fffd195a2ed5746161d507a48 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 935f6b37..2e279977 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; F6088CB5AD8C4CAEF0253909 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 303361F6BA59A431BF1858B8 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ @@ -71,6 +72,9 @@ 3A3AF070747DDC637905AF95 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 5AF7B4DC2518D333002A84B8 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 68D531B7B01B16EBD789AF1A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; }; + 78DABEA22ED26510000E7860 /* flutter_midi_command */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = flutter_midi_command; path = ../../../macos/flutter_midi_command; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; E65AFE4C7315E93A33532327 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -81,6 +85,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, F6088CB5AD8C4CAEF0253909 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -132,6 +137,9 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + 78DABEA22ED26510000E7860 /* flutter_midi_command */, + 784666492D4C4C64000A1A5F /* FlutterFramework */, + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -193,6 +201,9 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; @@ -232,6 +243,9 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -322,13 +336,11 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", "${BUILT_PRODUCTS_DIR}/flutter_midi_command/flutter_midi_command.framework", "${BUILT_PRODUCTS_DIR}/universal_ble/universal_ble.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_midi_command.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/universal_ble.framework", ); @@ -649,6 +661,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 7685b01e..e61e3cb5 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,43 +1,43 @@ -name: flutter_midi_command_example -description: Demonstrates how to use the flutter_midi_command plugin. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: "none" # Remove this line if you wish to publish to pub.dev - -environment: - sdk: ">=3.1.0 <4.0.0" - -dependencies: - flutter: - sdk: flutter - - flutter_midi_command: - # When depending on this package from a real application you should use: - # fluttermidicommand: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.5 - flutter_virtual_piano: ^0.0.8 - csv: ^6.0.0 - file_picker: ^8.0.6 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^5.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true +name: flutter_midi_command_example +description: Demonstrates how to use the flutter_midi_command plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=3.10.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + + flutter_midi_command: + # When depending on this package from a real application you should use: + # fluttermidicommand: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.9 + flutter_virtual_piano: ^0.0.10 + csv: ^8.0.0 + file_picker: ^12.0.0-beta.7 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true diff --git a/pubspec.yaml b/pubspec.yaml index 79b87fbf..1659ab66 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,19 +2,24 @@ name: flutter_midi_command description: A Flutter plugin for sending and receiving MIDI messages between Flutter and physical and virtual MIDI devices. Wraps CoreMIDI and android.media.midi in a thin dart/flutter layer. version: 0.5.4 +publish_to: none homepage: https://github.com/InvisibleWrench/FlutterMidiCommand environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=2.11.0" + sdk: ">=3.10.0 <4.0.0" + flutter: ">=3.11.0" dependencies: flutter: sdk: flutter - flutter_midi_command_platform_interface: ^0.4.1 + flutter_midi_command_platform_interface: ^0.4.3 flutter_midi_command_linux: ^0.3.0 flutter_midi_command_windows: ^0.3.0 + # git: + # url: https://github.com/kristofb/flutter_midi_command_windows.git + # ref: 0b326c93e06eada007f43ab96942987bf10d7a57 + dev_dependencies: flutter_test: sdk: flutter @@ -40,4 +45,4 @@ flutter: linux: default_package: flutter_midi_command_linux windows: - default_package: flutter_midi_command_windows \ No newline at end of file + default_package: flutter_midi_command_windows