diff --git a/.vscode/launch.json b/.vscode/launch.json index 88bca8aa..5ba62547 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,7 +4,6 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { "name": "BeatScratch", "program": "lib/main.dart", @@ -54,6 +53,12 @@ "type": "dart", "deviceId": "77171629-E070-4F03-9FD7-0898D08E2DEA" }, + { + "name": "iPad Pro 13-inch (M4) (Simulator)", + "request": "launch", + "type": "dart", + "deviceId": "BC339840-C42C-4177-A2D9-A5BC201DD68A" + }, ], "compounds": [ { diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7f7705b3..fd60bbca 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,12 +1,10 @@ PODS: - - aeyrium_sensor (1.0.0): + - "appcheck (1.5.4+1)": - Flutter - Flutter (1.0.0) - - flutter_appavailability (0.0.1): + - flutter_keyboard_visibility (0.0.1): - Flutter - - flutter_keyboard_visibility (0.7.0): - - Flutter - - flutter_midi_command (0.3.6): + - flutter_midi_command (0.4.2): - Flutter - native_device_orientation (0.0.1): - Flutter @@ -15,46 +13,48 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - sensors (0.0.1): + - sensors_plus (0.0.1): - Flutter - - share (0.0.1): + - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - SwiftProtobuf (1.13.0) + - universal_ble (0.0.1): + - Flutter + - FlutterMacOS - url_launcher_ios (0.0.1): - Flutter - webview_flutter_wkwebview (0.0.1): - Flutter + - FlutterMacOS DEPENDENCIES: - - aeyrium_sensor (from `.symlinks/plugins/aeyrium_sensor/ios`) + - appcheck (from `.symlinks/plugins/appcheck/ios`) - Flutter (from `Flutter`) - - flutter_appavailability (from `.symlinks/plugins/flutter_appavailability/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - flutter_midi_command (from `.symlinks/plugins/flutter_midi_command/ios`) - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) - - sensors (from `.symlinks/plugins/sensors/ios`) - - share (from `.symlinks/plugins/share/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - sensors_plus (from `.symlinks/plugins/sensors_plus/ios`) + - share_plus (from `.symlinks/plugins/share_plus/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - SwiftProtobuf + - universal_ble (from `.symlinks/plugins/universal_ble/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: - SwiftProtobuf EXTERNAL SOURCES: - aeyrium_sensor: - :path: ".symlinks/plugins/aeyrium_sensor/ios" + appcheck: + :path: ".symlinks/plugins/appcheck/ios" Flutter: :path: Flutter - flutter_appavailability: - :path: ".symlinks/plugins/flutter_appavailability/ios" flutter_keyboard_visibility: :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" flutter_midi_command: @@ -64,34 +64,36 @@ EXTERNAL SOURCES: package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/ios" - sensors: - :path: ".symlinks/plugins/sensors/ios" - share: - :path: ".symlinks/plugins/share/ios" + :path: ".symlinks/plugins/path_provider_foundation/darwin" + sensors_plus: + :path: ".symlinks/plugins/sensors_plus/ios" + share_plus: + :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/ios" + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + universal_ble: + :path: ".symlinks/plugins/universal_ble/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" + :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: - aeyrium_sensor: cdaa0bdd7206824ea4fb910c627cad05275362e5 - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_appavailability: ebf8f85abea44436d1262fcde8a4e64b2534b01a - flutter_keyboard_visibility: 6195387fb6d8f46e5cd6dda4a4154e41f800f545 - flutter_midi_command: 4acc18c6391c574d21f1d3a1cc40c7fe06ff461f - native_device_orientation: 3b4cfc9565a7b879cc4fde282b3e27745e852d0d - package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 - sensors: 84eb7a30e47a649e4172b71d6e81be614c280336 - share: 0b2c3e82132f5888bccca3351c504d0003b3b410 - shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca + appcheck: 3c94d0ffc94bd639938cac7427d5b13df2795404 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 + flutter_midi_command: 28d867d5701407d94917c4c35ca0172d362e1611 + native_device_orientation: e3580675687d5034770da198f6839ebf2122ef94 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + sensors_plus: 6a11ed0c2e1d0bd0b20b4029d3bad27d96e0c65b + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 SwiftProtobuf: fd4693388a96c8c2df35d3b063272b0e7c499d00 - url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2 - webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f + universal_ble: ff19787898040d721109c6324472e5dd4bc86adc + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 PODFILE CHECKSUM: 79aecb95524f6b2d9ca54f898d51b01d778cce9b -COCOAPODS: 1.11.3 +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 462402ae..c87c0a22 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -251,7 +251,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -301,6 +301,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a3..9c12df59 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 1b16e3e8..02ba4be4 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -4,7 +4,7 @@ import AVFoundation import Foundation import AudioKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { var flutterViewController: FlutterViewController? diff --git a/lib/animations/size_fade_transition.dart b/lib/animations/size_fade_transition.dart index f9d91ea1..b24e727a 100644 --- a/lib/animations/size_fade_transition.dart +++ b/lib/animations/size_fade_transition.dart @@ -9,7 +9,7 @@ // final Widget child; // const SizeFadeTransition({ // Key key, -// @required this.animation, +// required this.animation, // this.sizeFraction = 0, // this.curve = Curves.linear, // this.axis = Axis.vertical, diff --git a/lib/beatscratch_plugin.dart b/lib/beatscratch_plugin.dart index 60f1d343..88c928e2 100644 --- a/lib/beatscratch_plugin.dart +++ b/lib/beatscratch_plugin.dart @@ -1,15 +1,11 @@ -import 'dart:typed_data'; - -import 'package:dart_midi/dart_midi.dart'; -// ignore: implementation_imports -import 'package:dart_midi/src/byte_writer.dart'; +import 'package:beatscratch_flutter_redux/midi/byte_writer.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_events.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'generated/protos/protos.dart'; import 'messages/messages_ui.dart'; import 'recording/recording.dart'; -import 'settings/settings_panel.dart'; import 'settings/settings_common.dart'; import 'util/fake_js.dart' if (dart.library.js) 'dart:js'; import 'util/music_utils.dart'; @@ -61,7 +57,7 @@ class BeatScratchPlugin { case "notifyCountInInitiated": _playing = false; _countInInitiated = true; - onCountInInitiated?.call(); + onCountInInitiated.call(); return Future.value(null); break; case "notifyCurrentSection": @@ -122,20 +118,14 @@ class BeatScratchPlugin { } static _notifyScoreUrlOpened(String url) { - if (onOpenUrlFromSystem != null) { - onOpenUrlFromSystem(url); - } else { - Future.delayed(Duration(milliseconds: 500), () { - _notifyScoreUrlOpened(url); - }); - } + onOpenUrlFromSystem(url); } static _notifyMidiDevices(MidiDevices devices) { connectedControllers = List.from(devices.controllers .where((it) => !MyPlatform.isIOS || it.name != "Session 1")); _connectedSynthesizers = List.from(devices.synthesizers); - onSynthesizerStatusChange?.call(); + onSynthesizerStatusChange.call(); } static _notifyCurrentSection(String sectionId) { @@ -149,32 +139,31 @@ class BeatScratchPlugin { static _notifyPlayingBeat(int beat) { // In case the user pauses and a beat comes in in a race condition - if (_pausedTime == null || - DateTime.now().difference(_pausedTime).inMilliseconds > 75) { + if (DateTime.now().difference(_pausedTime).inMilliseconds > 75) { _playing = true; } currentBeat.value = beat; - onSynthesizerStatusChange?.call(); + onSynthesizerStatusChange.call(); } static _notifyPaused() { _playing = false; - onSynthesizerStatusChange?.call(); + onSynthesizerStatusChange.call(); } static _notifyBpmMultiplier(double bpmMultiplier) { _bpmMultiplier = bpmMultiplier; - onSynthesizerStatusChange?.call(); + onSynthesizerStatusChange.call(); } static _notifyUnmultipliedBpm(double unmultipliedBpm) { BeatScratchPlugin.unmultipliedBpm = unmultipliedBpm; - onSynthesizerStatusChange?.call(); + onSynthesizerStatusChange.call(); } static _notifyBeatScratchAudioAvailable(bool available) { _isBeatScratchAudioAvailable = available; - onSynthesizerStatusChange?.call(); + onSynthesizerStatusChange.call(); } static bool _metronomeEnabled = true; @@ -202,32 +191,24 @@ class BeatScratchPlugin { } static double unmultipliedBpm = 123; - static bool _playing; + static bool _playing = false; static bool get playing { - if (_playing == null) { - _playing = false; - _doSynthesizerStatusChangeLoop(); - } return _playing; } static final ValueNotifier currentBeat = ValueNotifier(0); - static bool _isBeatScratchAudioAvailable; + static bool _isBeatScratchAudioAvailable = false; static bool get isSynthesizerAvailable { - if (_isBeatScratchAudioAvailable == null) { - _isBeatScratchAudioAvailable = false; - _doSynthesizerStatusChangeLoop(); - } return _isBeatScratchAudioAvailable; } - static VoidCallback onCountInInitiated; - static VoidCallback onSynthesizerStatusChange; - static Function(String) onOpenUrlFromSystem; - static Function(String) onSectionSelected; - static Function(Melody) onRecordingMelodyUpdated; - static MessagesUI messagesUI; + static late VoidCallback onCountInInitiated; + static late VoidCallback onSynthesizerStatusChange; + static late Function(String) onOpenUrlFromSystem; + static late Function(String) onSectionSelected; + static late Function(Melody) onRecordingMelodyUpdated; + static late MessagesUI messagesUI; static _doSynthesizerStatusChangeLoop() { Future.delayed(Duration(seconds: 5), () { getApps(); @@ -289,17 +270,13 @@ class BeatScratchPlugin { } else { resultStatus = await _channel.invokeMethod('checkBeatScratchAudioStatus'); } - if (resultStatus == null) { - print("Failed to retrieve Synthesizer Status from JS/Platform Channel"); - resultStatus = false; - } _isBeatScratchAudioAvailable = resultStatus; - onSynthesizerStatusChange?.call(); + onSynthesizerStatusChange.call(); } static void resetAudioSystem() async { _isBeatScratchAudioAvailable = false; - onSynthesizerStatusChange?.call(); + onSynthesizerStatusChange.call(); if (kIsWeb) { } else { _channel.invokeMethod('resetAudioSystem'); @@ -308,7 +285,7 @@ class BeatScratchPlugin { static void createScore(Score score) async { _isBeatScratchAudioAvailable = false; - onSynthesizerStatusChange?.call(); + onSynthesizerStatusChange.call(); _pushScore(score, 'createScore', includeParts: true, includeSections: true); } @@ -375,18 +352,18 @@ class BeatScratchPlugin { static void setCurrentSection(Section section) async { if (kIsWeb) { - context.callMethod('setCurrentSection', [section?.id]); + context.callMethod('setCurrentSection', [section.id]); } else { - _channel.invokeMethod('setCurrentSection', section?.id); + _channel.invokeMethod('setCurrentSection', section.id); } } /// Assigns all external MIDI controllers to the given part. static void setKeyboardPart(Part part) async { if (kIsWeb) { - context.callMethod('setKeyboardPart', [part?.id]); + context.callMethod('setKeyboardPart', [part.id]); } else { - _channel.invokeMethod('setKeyboardPart', part?.id); + _channel.invokeMethod('setKeyboardPart', part.id); } } @@ -433,7 +410,7 @@ class BeatScratchPlugin { /// [Melody]. Implementation-wise: this is just done by passing the [Melody.id]. /// This applies to notes played either with a physical MIDI controller on /// the native side or from [sendMIDI] in the plugin. - static void setRecordingMelody(Melody melody) async { + static void setRecordingMelody(Melody? melody) async { if (kIsWeb) { context.callMethod('setRecordingMelody', [melody?.id]); } else { @@ -451,10 +428,10 @@ class BeatScratchPlugin { static void _play() async { if (kIsWeb) { context.callMethod('play', []); - messagesUI?.sendMessage( + messagesUI.sendMessage( message: "Web playback isn't great. Download the app!", isError: true); - messagesUI?.sendMessage( + messagesUI.sendMessage( message: "You may have to play the Keyboard to play.", ); } else { @@ -477,7 +454,7 @@ class BeatScratchPlugin { _stopUIPlayback(); } - static DateTime _pausedTime; + static late DateTime _pausedTime; static void _stopUIPlayback() { _playing = false; onSynthesizerStatusChange(); @@ -569,7 +546,7 @@ class BeatScratchPlugin { } } - static Future getScoreId() async => + static Future getScoreId() async => _channel.invokeMethod("getScoreId"); static Future get recordedMelody async { diff --git a/lib/cache_management.dart b/lib/cache_management.dart index 7848d229..1042ebb6 100644 --- a/lib/cache_management.dart +++ b/lib/cache_management.dart @@ -16,10 +16,10 @@ clearMutableCachesForSection(String sectionId) { } clearMutableCachesForMelody(String melodyId, - {String sectionId, - int beat, - int sectionLengthBeats, - double melodyLengthBeats}) { + {String? sectionId, + int? beat, + int? sectionLengthBeats, + double? melodyLengthBeats}) { MelodyTheory.averageToneCache.removeWhere((key, value) => key == melodyId); MelodyTheory.tonesAtCache .removeWhere((key, value) => key.arguments[0] == melodyId); @@ -30,10 +30,7 @@ clearMutableCachesForMelody(String melodyId, (key, value) => (key.arguments[0] as String).contains(melodyId)); NotationMusicRenderer.playbackNoteCache .removeWhere((key, value) => key.arguments[0] == melodyId); - if (sectionId == null || - beat == null || - sectionLengthBeats == null || - melodyLengthBeats == null) { + if (beat == null) { NotationMusicRenderer.notationRenderingCache.removeWhere((key, value) => key.arguments[0] == melodyId || (key.arguments[1] as String).contains(melodyId)); @@ -42,8 +39,8 @@ clearMutableCachesForMelody(String melodyId, String keySectionId = (key.arguments[2] as String); int keyBeat = key.arguments[3] as int; return keySectionId == sectionId && - ((keyBeat % melodyLengthBeats).round() == beat || - (((keyBeat + 1) % sectionLengthBeats) % melodyLengthBeats) + ((keyBeat % melodyLengthBeats!).round() == beat || + (((keyBeat + 1) % sectionLengthBeats!) % melodyLengthBeats) .round() == beat || (((keyBeat - 1 + sectionLengthBeats) % sectionLengthBeats) % diff --git a/lib/colors.dart b/lib/colors.dart index 2f6a874e..3c1e05c7 100644 --- a/lib/colors.dart +++ b/lib/colors.dart @@ -31,6 +31,7 @@ var musicForegroundColor = Colors.white; var melodyColor = Color(0xFFDDDDDD); enum ChordColor { dominant, major, minor, augmented, diminished, tonic, none } + enum SectionColor { major, minor, perfect, augmented, diminished } extension ChordColors on ChordColor { @@ -93,7 +94,8 @@ extension BSColors on Color { BSColors._luminanceCache.putIfAbsent(this, () => computeLuminance()); /// With [this] as the background color, computes the appropriate text color. - Color textColor({Color subBackgroundColor}) { + Color textColor({Color? subBackgroundColor}) { + final luminance = subBackgroundColor?.luminance ?? this.luminance; if (luminance > 0.5) { return Colors.black; } diff --git a/lib/drawing/canvas_tone_drawer.dart b/lib/drawing/canvas_tone_drawer.dart index d52fa7be..388dcc64 100644 --- a/lib/drawing/canvas_tone_drawer.dart +++ b/lib/drawing/canvas_tone_drawer.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import '../generated/protos/music.pb.dart'; import '../util/music_theory.dart'; import '../util/util.dart'; +import 'rect_rendering.dart'; extension PreserveColor on Paint { preserveProperties(VoidCallback callback) { @@ -18,7 +19,7 @@ extension PreserveColor on Paint { class VisiblePitch { int tone = 0; - Rect bounds = Rect.fromLTRB(0, 0, 0, 0); + Rect bounds = RectRendering.fromLTRB(0, 0, 0, 0); VisiblePitch(this.tone, this.bounds); } @@ -30,10 +31,10 @@ class CanvasToneDrawer { Paint alphaDrawerPaint = Paint(); /// Represent the bounds for whatever is to be drawn - Rect bounds; - bool renderVertically; + late Rect bounds; + late bool renderVertically; double get axisLength => renderVertically ? bounds.height : bounds.width; - double halfStepsOnScreen; + late double halfStepsOnScreen; int highestPitch = TOP; @@ -63,14 +64,14 @@ class CanvasToneDrawer { .forEach((tone) { // Tone may not be in chord... Rect visiblePitchBounds = renderVertically - ? Rect.fromLTRB( + ? RectRendering.fromLTRB( this.bounds.left, this.bounds.bottom - (tone - bottomMostPoint) * halfStepPhysicalDistance, this.bounds.right, this.bounds.bottom - (1 + tone - bottomMostPoint) * halfStepPhysicalDistance) - : Rect.fromLTRB( + : RectRendering.fromLTRB( this.bounds.left + (tone - bottomMostPoint) * halfStepPhysicalDistance, this.bounds.top, @@ -122,7 +123,7 @@ class CanvasToneDrawer { leftOffset = -0.415; break; } - Rect visiblePitchBounds = Rect.fromLTRB( + Rect visiblePitchBounds = RectRendering.fromLTRB( leftOffset * diatonicStepDistance + this.bounds.left + (tone - bottomMostPoint) * halfStepPhysicalDistance, diff --git a/lib/drawing/color_guide.dart b/lib/drawing/color_guide.dart index eaedfae5..534ab419 100644 --- a/lib/drawing/color_guide.dart +++ b/lib/drawing/color_guide.dart @@ -1,3 +1,4 @@ +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; import 'package:flutter/material.dart'; import '../colors.dart'; @@ -5,10 +6,10 @@ import '../util/music_theory.dart'; import 'canvas_tone_drawer.dart'; class ColorGuide extends CanvasToneDrawer { - int colorGuideAlpha; - int drawPadding; - int nonRootPadding; - int drawnColorGuideAlpha; + late int colorGuideAlpha; + late int drawPadding; + late int nonRootPadding; + late int drawnColorGuideAlpha; Iterable pressedNotes = []; drawColorGuide(Canvas canvas) { @@ -28,7 +29,7 @@ class ColorGuide extends CanvasToneDrawer { (toneInChord.mod12 == chord.rootNote.tone) ? 0 : nonRootPadding; if (renderVertically) { canvas.drawRect( - Rect.fromLTRB( + RectRendering.fromLTRB( toneBounds.left + drawPadding + extraPadding, toneBounds.top, toneBounds.right - drawPadding - extraPadding, @@ -38,7 +39,7 @@ class ColorGuide extends CanvasToneDrawer { alphaDrawerPaint.color = Color(0x11212121) .withAlpha((drawnColorGuideAlpha * 0.1).toInt()); canvas.drawRect( - Rect.fromLTRB( + RectRendering.fromLTRB( toneBounds.left, toneBounds.top - .183 * halfStepPhysicalDistance, toneBounds.right, @@ -49,7 +50,7 @@ class ColorGuide extends CanvasToneDrawer { alphaDrawerPaint.color = Color(0x11212121) .withAlpha((drawnColorGuideAlpha * 0.3).toInt()); canvas.drawRect( - Rect.fromLTRB( + RectRendering.fromLTRB( toneBounds.left, toneBounds.top - .183 * halfStepPhysicalDistance, toneBounds.right, @@ -59,7 +60,7 @@ class ColorGuide extends CanvasToneDrawer { } else { // Horizontal rendering canvas.drawRect( - Rect.fromLTRB( + RectRendering.fromLTRB( toneBounds.left, toneBounds.top + drawPadding + extraPadding, toneBounds.right, @@ -69,7 +70,7 @@ class ColorGuide extends CanvasToneDrawer { alphaDrawerPaint.color = Color(0x11212121) .withAlpha((drawnColorGuideAlpha * 0.1).toInt()); canvas.drawRect( - Rect.fromLTRB( + RectRendering.fromLTRB( toneBounds.left + .183 * halfStepPhysicalDistance, toneBounds.top + drawPadding + extraPadding, toneBounds.right - .183 * halfStepPhysicalDistance, @@ -80,7 +81,7 @@ class ColorGuide extends CanvasToneDrawer { alphaDrawerPaint.color = Color(0x11212121) .withAlpha((drawnColorGuideAlpha * 0.3).toInt()); canvas.drawRect( - Rect.fromLTRB( + RectRendering.fromLTRB( toneBounds.left + .183 * halfStepPhysicalDistance, toneBounds.top + drawPadding + extraPadding, toneBounds.right - .183 * halfStepPhysicalDistance, diff --git a/lib/drawing/harmony_beat_renderer.dart b/lib/drawing/harmony_beat_renderer.dart index 5c97c704..044be015 100644 --- a/lib/drawing/harmony_beat_renderer.dart +++ b/lib/drawing/harmony_beat_renderer.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; import 'package:flutter/material.dart'; import '../colors.dart'; @@ -9,7 +10,8 @@ import '../util/util.dart'; extension _HarmonyHighlight on Color { // ignore: unused_element - Color withHighlight({bool isPlaying, bool isSelected, bool isFaded}) { + Color withHighlight( + {bool isPlaying = false, bool isSelected = false, bool isFaded = false}) { int alpha = 187; if (isPlaying) { alpha = 255; @@ -65,10 +67,10 @@ extension _HarmonyColor on Chord { } class HarmonyBeatRenderer { - Section section; + late Section section; - Harmony get harmony => section?.harmony; - Meter get meter => section?.meter; + Harmony get harmony => section.harmony; + Meter get meter => section.meter; int beatPosition = 0; Iterable get subdivisionRange => range( @@ -85,14 +87,14 @@ class HarmonyBeatRenderer { draw(Canvas canvas) { // canvas.getClipBounds(overallBounds) double overallWidth = overallBounds.right - overallBounds.left; - bounds = Rect.fromLTRB(overallBounds.left, overallBounds.top, + bounds = RectRendering.fromLTRB(overallBounds.left, overallBounds.top, overallBounds.right, overallBounds.bottom); paint.color = Color(0xFFFFFFFF).withOpacity(opacityFactor); canvas.drawRect(bounds, paint); var elementCount = subdivisionRange.length; subdivisionRange.toList().asMap().forEach((elementIndex, elementPosition) { - bounds = Rect.fromLTRB( + bounds = RectRendering.fromLTRB( overallBounds.left + overallWidth * elementIndex / elementCount, overallBounds.top, overallBounds.left + overallWidth * (elementIndex + 1) / elementCount, @@ -162,12 +164,12 @@ class HarmonyBeatRenderer { } canvas.drawRect( - Rect.fromLTRB(bounds.left + leftOffset, bounds.top, + RectRendering.fromLTRB(bounds.left + leftOffset, bounds.top, bounds.left + leftOffset, bounds.bottom), paint); canvas.drawRect( - Rect.fromLTRB(bounds.right - rightOffset, bounds.top, bounds.right, - bounds.bottom), + RectRendering.fromLTRB(bounds.right - rightOffset, bounds.top, + bounds.right, bounds.bottom), paint); } } diff --git a/lib/drawing/music/base_music_renderer.dart b/lib/drawing/music/base_music_renderer.dart index d7f05fb5..fb45d318 100644 --- a/lib/drawing/music/base_music_renderer.dart +++ b/lib/drawing/music/base_music_renderer.dart @@ -1,6 +1,8 @@ import 'dart:math'; import 'dart:ui'; +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; + import '../../colors.dart'; import '../../generated/protos/music.pb.dart'; import '../../util/music_notation_theory.dart'; @@ -14,14 +16,14 @@ class BaseMusicRenderer extends ColorGuide { final bool renderVertically = true; @override final double normalizedDevicePitch = 0; - Rect overallBounds; - Melody melody; - int elementPosition; - bool isCurrentlyPlayingBeat; - bool isSelectedBeatInHarmony; - Section section; - bool isUserChoosingHarmonyChord; - bool isMelodyReferenceEnabled; + late Rect overallBounds; + late Melody melody; + late int elementPosition; + late bool isCurrentlyPlayingBeat; + late bool isSelectedBeatInHarmony; + late Section section; + late bool isUserChoosingHarmonyChord; + late bool isMelodyReferenceEnabled; int beatPosition = 0; double xScale = 1; double yScale = 1; @@ -49,12 +51,12 @@ class BaseMusicRenderer extends ColorGuide { ///doesn't work for [renderVertically]=false. drawTimewiseLineRelativeToBounds( - {Canvas canvas, + {required Canvas canvas, bool leftSide = true, double alpha = 1, double strokeWidth = 1, - double startY, - double stopY, + required double startY, + required double stopY, double percentThrough = 0, Offset offset = Offset.zero}) { double oldStrokeWidth = alphaDrawerPaint.strokeWidth; @@ -72,7 +74,10 @@ class BaseMusicRenderer extends ColorGuide { } drawPitchwiseLine( - {Canvas canvas, double pointOnToneAxis, double left, double right}) { + {required Canvas canvas, + required double pointOnToneAxis, + double? left, + double? right}) { if (renderVertically) { canvas.drawLine( Offset(left ?? bounds.left, pointOnToneAxis), @@ -109,7 +114,7 @@ class BaseMusicRenderer extends ColorGuide { elementPosition = beatPosition * melody.subdivisionsPerBeat; // print("subdivisionRange=$subdivisionRange"); subdivisionRange.toList().asMap().forEach((elementIndex, elementPosition) { - bounds = Rect.fromLTRB( + bounds = RectRendering.fromLTRB( overallBounds.left + overallWidth * elementIndex / elementCount, overallBounds.top, overallBounds.left + @@ -133,7 +138,7 @@ class BaseMusicRenderer extends ColorGuide { double centerOfTone(int tone) => startPoint - (bottomMostNote + tone - 9.5) * halfStepWidth; - double pointFor({NoteLetter letter, int octave}) { + double pointFor({required NoteLetter letter, required int octave}) { double middleC = centerOfTone(0); double result = middleC - letterStepSize * (((octave - 4) * 7) + letter.value); diff --git a/lib/drawing/music/colorblock_music_renderer.dart b/lib/drawing/music/colorblock_music_renderer.dart index 57f44f5d..952b66ca 100644 --- a/lib/drawing/music/colorblock_music_renderer.dart +++ b/lib/drawing/music/colorblock_music_renderer.dart @@ -1,5 +1,7 @@ import 'dart:math'; +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; + import '../../colors.dart'; import '../../generated/protos/music.pb.dart'; import 'package:flutter/material.dart'; @@ -11,7 +13,7 @@ class ColorblockMusicRenderer extends BaseMusicRenderer { double uiScale = 1; @override double get halfStepsOnScreen => (highestPitch - lowestPitch + 1).toDouble(); - double colorblockAlpha; + double colorblockAlpha = 0.5; static double stepLineScaleThreshold = 0.7; draw(Canvas canvas) { @@ -48,7 +50,7 @@ class ColorblockMusicRenderer extends BaseMusicRenderer { } } - _drawColorblockMelody({Canvas canvas, double alpha}) { + _drawColorblockMelody({required Canvas canvas, required double alpha}) { iterateSubdivisions(() { _drawColorblockNotes( canvas: canvas, elementPosition: elementPosition, alpha: alpha); @@ -56,12 +58,15 @@ class ColorblockMusicRenderer extends BaseMusicRenderer { }); // if (drawRhythm) { // double overallWidth = overallBounds.right - overallBounds.left; -// bounds = Rect.fromLTRB(overallWidth, bounds.top, overallWidth, bounds.bottom); +// bounds = RectRendering.fromLTRB(overallWidth, bounds.top, overallWidth, bounds.bottom); // this.drawRhythm(canvas, alphaSource); // } } - _drawColorblockNotes({Canvas canvas, int elementPosition, double alpha}) { + _drawColorblockNotes( + {required Canvas canvas, + required int elementPosition, + required double alpha}) { alphaDrawerPaint.color = musicForegroundColor.withAlpha((alpha * 255).toInt()); @@ -103,7 +108,7 @@ class ColorblockMusicRenderer extends BaseMusicRenderer { bottom -= extraHeight; } canvas.drawRect( - Rect.fromLTRB(bounds.left + leftMargin, top, + RectRendering.fromLTRB(bounds.left + leftMargin, top, bounds.right - rightMargin, bottom), alphaDrawerPaint); }); @@ -123,8 +128,11 @@ class ColorblockMusicRenderer extends BaseMusicRenderer { double bottom = bounds.height - bounds.height * (realTone - lowestPitch + 1) / 88; canvas.drawRect( - Rect.fromLTRB(bounds.left + leftMargin + xScale, top - xScale, - bounds.left + noteOffWidth * uiScale, bottom + xScale), + RectRendering.fromLTRB( + bounds.left + leftMargin + xScale, + top - xScale, + bounds.left + noteOffWidth * uiScale, + bottom + xScale), Paint() ..strokeWidth = 1.2 * xScale ..style = PaintingStyle.stroke diff --git a/lib/drawing/music/music_clef_renderer.dart b/lib/drawing/music/music_clef_renderer.dart index da225137..35194edd 100644 --- a/lib/drawing/music/music_clef_renderer.dart +++ b/lib/drawing/music/music_clef_renderer.dart @@ -11,7 +11,7 @@ import 'music_staff_lines_renderer.dart'; class MelodyClefRenderer extends BaseMusicRenderer { @override double get halfStepsOnScreen => (highestPitch - lowestPitch + 1).toDouble(); - List clefs; // = [Clef.treble, Clef.bass]; + List clefs = [Clef.treble, Clef.bass]; static Path _trebleClefPath = parseSvgPathData( "M 592.10873,1275.9669 C 461.75172,1268.3902 328.65904,1186.6265 249.0601,1092.783 C 156.77394,983.97782 118.72592,836.04683 128.47199,714.56357 C 157.10277,357.61288 545.27831,146.63848 688.97108,-9.280262 C 785.15294,-113.64625 805.31643,-164.52308 826.79977,-218.19949 C 868.39181,-322.09965 875.09166,-443.8341 792.63375,-452.92251 C 713.90712,-461.59988 649.13737,-337.79201 620.20973,-253.17845 C 594.19587,-177.07331 576.90507,-100.71696 592.5563,13.979673 C 599.58954,65.50958 793.18636,1503.9125 796.45179,1526.2088 C 829.05589,1749.0255 701.63092,1841.2249 571.55248,1857.6251 C 290.65671,1893.038 200.52617,1607.5843 326.4212,1499.1719 C 423.34291,1415.7001 564.35026,1487.3615 556.73245,1624.5919 C 549.98693,1746.1391 430.80546,1749.7197 400.35244,1746.9429 C 447.10065,1830.7846 799.52998,1874.5871 745.41513,1495.7923 C 737.811,1442.5634 558.91549,90.842953 554.53112,60.595454 C 521.71238,-165.84753 516.71147,-345.08557 634.69182,-554.25141 C 678.24767,-631.46637 747.0821,-681.3156 780.87362,-674.7893 C 788.29962,-673.35526 795.69824,-670.62872 801.57144,-664.56827 C 892.07191,-571.31845 919.83494,-364.53202 909.9199,-245.74332 C 899.76736,-124.11391 894.1088,1.7993735 773.16902,148.63428 C 726.36601,205.45738 583.54553,330.63538 501.65851,402.55255 C 386.60107,503.59831 303.14756,591.85179 257.99323,698.31862 C 207.24886,817.97506 198.65826,968.6006 313.27268,1102.2505 C 379.20247,1177.7619 488.59222,1231.3424 580.65459,1232.4842 C 836.63719,1235.6628 911.39048,1109.4801 913.77904,966.58197 C 917.71126,731.28351 633.64596,642.32214 516.85762,804.10953 C 449.14212,897.92109 478.90552,996.66049 524.38411,1043.6371 C 539.99424,1059.7587 557.43121,1072.0395 573.92734,1078.8855 C 579.9056,1081.3654 593.96751,1087.9054 589.97593,1097.4779 C 586.6557,1105.4428 580.20702,1105.8904 574.33381,1105.1871 C 500.68573,1096.3544 419.13667,1025.958 399.0828,904.87212 C 369.86288,728.38801 525.6035,519.0349 747.9133,553.274 C 893.45572,575.68903 1028.5853,700.92182 1016.7338,934.11946 C 1006.5722,1133.9822 840.87996,1290.4262 592.10873,1275.9669 z"); diff --git a/lib/drawing/music/music_measure_lines_renderer.dart b/lib/drawing/music/music_measure_lines_renderer.dart index cc4a9f9f..9610ebca 100644 --- a/lib/drawing/music/music_measure_lines_renderer.dart +++ b/lib/drawing/music/music_measure_lines_renderer.dart @@ -25,7 +25,7 @@ class MelodyMeasureLinesRenderer extends BaseMusicRenderer { try { // print("drawing measure line"); NoteSpecification highestDiatonicNote = - clefs.expand((clef) => clef.notes).maxBy((e) => e.diatonicValue); + clefs.expand((clef) => clef.notes).maxBy((e) => e.diatonicValue)!; NoteSpecification lowestDiatonicNote = clefs.expand((clef) => clef.notes).minBy((e) => e.diatonicValue); drawTimewiseLineRelativeToBounds( diff --git a/lib/drawing/music/music_staff_lines_renderer.dart b/lib/drawing/music/music_staff_lines_renderer.dart index 2ad8344a..109523d2 100644 --- a/lib/drawing/music/music_staff_lines_renderer.dart +++ b/lib/drawing/music/music_staff_lines_renderer.dart @@ -8,7 +8,7 @@ import 'base_music_renderer.dart'; enum Clef { treble, bass, tenor_treble, drum_treble, drum_bass } extension ClefNotes on Clef { - List get notes => clefNotes[this]; + List get notes => clefNotes[this]!; static final Map> clefNotes = { Clef.treble: [ @@ -75,7 +75,7 @@ extension ClefNotes on Clef { static Map diatonicMaxCache = Map(); int get diatonicMax => diatonicMaxCache.putIfAbsent( - this, () => notes.maxBy((it) => it.diatonicValue).diatonicValue); + this, () => notes.maxBy((it) => it.diatonicValue)!.diatonicValue); static Map diatonicMinCache = Map(); int get diatonicMin => diatonicMinCache.putIfAbsent( diff --git a/lib/drawing/music/notation_music_renderer.dart b/lib/drawing/music/notation_music_renderer.dart index 419b763b..1c1b665b 100644 --- a/lib/drawing/music/notation_music_renderer.dart +++ b/lib/drawing/music/notation_music_renderer.dart @@ -1,5 +1,7 @@ import 'dart:math'; +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; + import '../../colors.dart'; import 'package:flutter/material.dart'; import 'package:path_drawing/path_drawing.dart'; @@ -17,26 +19,25 @@ enum Notehead { quarter, half, whole, percussion } class _NoteheadInstruction { final Notehead notehead; - final NoteSign noteSign; + final NoteSign? noteSign; final double noteheadTop; final double noteheadLeft; final bool staggered; - final bool hadStaggeredNotes; + final bool hadStaggeredNotes = false; final List ledgerLines; const _NoteheadInstruction( - {this.notehead, - this.noteSign, - this.noteheadLeft, - this.ledgerLines, - this.noteheadTop, - this.staggered, - this.hadStaggeredNotes}); + {required this.notehead, + required this.noteSign, + required this.noteheadLeft, + required this.ledgerLines, + required this.noteheadTop, + required this.staggered}); } class _StemInstruction { final Offset top; final Offset bottom; - const _StemInstruction({this.top, this.bottom}); + const _StemInstruction({required this.top, required this.bottom}); } class _RenderInstructions { @@ -108,7 +109,7 @@ class NotationMusicRenderer extends BaseMusicRenderer { _renderNoteheadSignAndLedgers( Canvas canvas, _NoteheadInstruction instruction) { - NoteSign noteSign = instruction.noteSign; + NoteSign? noteSign = instruction.noteSign; bool staggered = instruction.staggered; double noteheadWidth = 18 * xScale; double noteheadHeight = noteheadWidth; @@ -116,7 +117,7 @@ class NotationMusicRenderer extends BaseMusicRenderer { double bottom = top + noteheadHeight; double left = overallBounds.left + instruction.noteheadLeft * xScale; double right = left + noteheadWidth; - Rect noteheadRect = Rect.fromLTRB(left, top, right, bottom); + Rect noteheadRect = RectRendering.fromLTRB(left, top, right, bottom); switch (instruction.notehead) { case Notehead.quarter: @@ -138,46 +139,45 @@ class NotationMusicRenderer extends BaseMusicRenderer { Offset(right + 3 * xScale, position * xScale), alphaDrawerPaint); }); - if (noteSign != null) { - double signLeft, signRight, signTop, signBottom; - if (staggered) { - signLeft = left - - 0.7 * - noteheadWidth; //bounds.left - 0.7 * noteheadWidth + xOffset;// bounds.right - 2.3 * noteheadWidth; - } else { - signLeft = left - - 0.5 * - noteheadWidth; //bounds.left - 0.5 * noteheadWidth + xOffset; //bounds.right - 2.8 * noteheadWidth; - } - signRight = signLeft + 0.5 * noteheadWidth; - - switch (noteSign) { - case NoteSign.flat: - case NoteSign.double_flat: - double difference = noteheadHeight * 1.5; - signTop = top - 2 * noteheadHeight / 3 - difference; - signBottom = bottom - difference; - break; - case NoteSign.double_sharp: - signTop = top + noteheadHeight / 4; - signBottom = bottom - noteheadHeight / 4; - break; - case NoteSign.sharp: - case NoteSign.natural: - default: - double difference = noteheadHeight * 1.8; - signTop = top - difference; //- noteheadHeight / 3; - signBottom = bottom - difference; // + noteheadHeight / 3; - } - final signTopOffset = 26 * yScale; - final signLeftOffset = -2 * xScale; - signTop += signTopOffset; - signBottom += signTopOffset; - signLeft += signLeftOffset; - signRight += signLeftOffset; - Rect signRect = Rect.fromLTRB(signLeft, signTop, signRight, signBottom); - _renderSign(canvas, signRect, noteSign); + double signLeft, signRight, signTop, signBottom; + if (staggered) { + signLeft = left - + 0.7 * + noteheadWidth; //bounds.left - 0.7 * noteheadWidth + xOffset;// bounds.right - 2.3 * noteheadWidth; + } else { + signLeft = left - + 0.5 * + noteheadWidth; //bounds.left - 0.5 * noteheadWidth + xOffset; //bounds.right - 2.8 * noteheadWidth; + } + signRight = signLeft + 0.5 * noteheadWidth; + + switch (noteSign) { + case NoteSign.flat: + case NoteSign.double_flat: + double difference = noteheadHeight * 1.5; + signTop = top - 2 * noteheadHeight / 3 - difference; + signBottom = bottom - difference; + break; + case NoteSign.double_sharp: + signTop = top + noteheadHeight / 4; + signBottom = bottom - noteheadHeight / 4; + break; + case NoteSign.sharp: + case NoteSign.natural: + default: + double difference = noteheadHeight * 1.8; + signTop = top - difference; //- noteheadHeight / 3; + signBottom = bottom - difference; // + noteheadHeight / 3; } + final signTopOffset = 26 * yScale; + final signLeftOffset = -2 * xScale; + signTop += signTopOffset; + signBottom += signTopOffset; + signLeft += signLeftOffset; + signRight += signLeftOffset; + Rect signRect = + RectRendering.fromLTRB(signLeft, signTop, signRight, signBottom); + _renderSign(canvas, signRect, noteSign); } _planNoteheadsSignsAndLedgers(_RenderInstructions result) { @@ -225,7 +225,7 @@ class NotationMusicRenderer extends BaseMusicRenderer { } // Draw signs - NoteSign previousSign; + NoteSign? previousSign; Iterable previousSigns = getMostRecentSignsOf( note: note, relevantMelodies: otherMelodiesOnStaff.followedBy([melody]), @@ -238,7 +238,7 @@ class NotationMusicRenderer extends BaseMusicRenderer { forceShowNaturals = true; } // val previousSign = previousSignOf(melody, harmony, note, elementPosition) - NoteSign signToDraw; + NoteSign? signToDraw; switch (note.sign) { case NoteSign.sharp: if (previousSign != NoteSign.sharp) signToDraw = NoteSign.sharp; @@ -256,8 +256,7 @@ class NotationMusicRenderer extends BaseMusicRenderer { break; case NoteSign.natural: default: - if (forceShowNaturals || - (previousSign != null && previousSign != NoteSign.natural)) + if (forceShowNaturals || (previousSign != NoteSign.natural)) signToDraw = NoteSign.natural; break; } @@ -307,8 +306,8 @@ class NotationMusicRenderer extends BaseMusicRenderer { static Map> recentSignCache = Map(); Iterable getMostRecentSignsOf({ - NoteSpecification note, - Iterable relevantMelodies, + required NoteSpecification note, + required Iterable relevantMelodies, }) { final args = ArgumentList([ relevantMelodies.map((e) => e.id).join(), @@ -327,8 +326,8 @@ class NotationMusicRenderer extends BaseMusicRenderer { } Iterable _calculateMostRecentSignsOf({ - NoteSpecification note, - Iterable relevantMelodies, + required NoteSpecification note, + required Iterable relevantMelodies, }) { // final currentBeatPosition = elementPosition.toDouble() / melody.subdivisionsPerBeat; // final lastDownbeat = range(0,1000000).firstWhere((it) { @@ -352,7 +351,7 @@ class NotationMusicRenderer extends BaseMusicRenderer { } relevantMelodyIndex -= 1; } - NoteSign melodyResult; + NoteSign? melodyResult; while (melodyResult == null) { if (relevantMelodyIndex % relevantMelody.subdivisionsPerBeat == 0 && // Beginning of measure, stop searching @@ -394,8 +393,8 @@ class NotationMusicRenderer extends BaseMusicRenderer { return []; } double highestKey = - resultCandidates.keys.toList().maxBy((it) => (it * 1000).toInt()); - return resultCandidates[highestKey]; + resultCandidates.keys.toList().maxBy((it) => (it * 1000).toInt()) ?? -1; + return resultCandidates[highestKey] ?? []; } static Path _sharpPath = parseSvgPathData( @@ -412,12 +411,12 @@ class NotationMusicRenderer extends BaseMusicRenderer { Offset.zero); static Path _naturalPath = parseSvgPathData( "M 26.578125,106.17187 L 22.640625,107.57812 L 22.640625,75.375001 L 0,85.218751 L 0,1.6875 L 3.796875,1.4210855e-014 L 3.796875,32.765625 L 26.578125,22.359375 L 26.578125,106.17187 z M 22.640625,61.171871 L 22.640625,38.671875 L 3.796875,46.96875 L 3.796875,69.468751 L 22.640625,61.171871 z "); - _renderSign(Canvas canvas, Rect signRect, NoteSign sign) { + _renderSign(Canvas canvas, Rect signRect, NoteSign? sign) { // canvas.drawRect(signRect, Paint()..style=PaintingStyle.fill..color=Colors.black26); // print("Rendering sign: $sign"); canvas.save(); canvas.translate(signRect.topLeft.dx, signRect.topLeft.dy); - Path signPath; + Path? signPath; switch (sign) { case NoteSign.sharp: signPath = _sharpPath; @@ -449,6 +448,7 @@ class NotationMusicRenderer extends BaseMusicRenderer { default: break; } + if (signPath != null) { canvas.drawPath(signPath, alphaDrawerPaint); } diff --git a/lib/drawing/rect_rendering.dart b/lib/drawing/rect_rendering.dart new file mode 100644 index 00000000..d23ae42c --- /dev/null +++ b/lib/drawing/rect_rendering.dart @@ -0,0 +1,10 @@ +import 'dart:ui'; + +extension RectRendering on Rect { + // Rect + static Rect fromLTRB(double left, double top, double right, double bottom) { + final realRight = (left == right) ? left + 1 : right; + final realBottom = (top == bottom) ? top + 1 : bottom; + return Rect.fromLTRB(left, top, realRight, realBottom); + } +} diff --git a/lib/drawing/sizeutil.dart b/lib/drawing/sizeutil.dart deleted file mode 100644 index 948a7c5f..00000000 --- a/lib/drawing/sizeutil.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'dart:math'; - -class SizeUtil { - static const _DESIGN_WIDTH = 580; - static const _DESIGN_HEIGHT = 648; - - //logic size in device - static Size _logicSize; - - //device pixel radio. - - static get width { - return _logicSize.width; - } - - static get height { - return _logicSize.height; - } - - static set size(size) { - _logicSize = size; - } - - //@param w is the design w; - static double getAxisX(double w) { - return (w * width) / _DESIGN_WIDTH; - } - -// the y direction - static double getAxisY(double h) { - return (h * height) / _DESIGN_HEIGHT; - } - - // diagonal direction value with design size s. - static double getAxisBoth(double s) { - return s * - sqrt((width * width + height * height) / - (_DESIGN_WIDTH * _DESIGN_WIDTH + _DESIGN_HEIGHT * _DESIGN_HEIGHT)); - } -} diff --git a/lib/edit_menu.dart b/lib/edit_menu.dart index cafdc3fa..917d2f71 100644 --- a/lib/edit_menu.dart +++ b/lib/edit_menu.dart @@ -1,36 +1,31 @@ import 'package:beatscratch_flutter_redux/util/music_theory.dart'; -import 'package:beatscratch_flutter_redux/util/ui_utils.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'beatscratch_plugin.dart'; import 'colors.dart'; -import 'export/export_ui.dart'; import 'generated/protos/protos.dart'; import 'music_preview/melody_preview.dart'; import 'music_preview/part_preview.dart'; import 'music_preview/section_preview.dart'; import 'ui_models.dart'; import 'widget/beats_badge.dart'; -import 'widget/my_platform.dart'; Future showEditMenu( - {@required BuildContext context, - @required RelativeRect position, - @required Score score, - @required Part part, - @required Melody selectedMelody, - @required MusicViewMode musicViewMode, - @required Section section, - @required Function(Object) editObject}) async { + {required BuildContext context, + required RelativeRect position, + required Score score, + required Part? part, + Melody? selectedMelody, + required MusicViewMode musicViewMode, + required Section section, + required Function(Object) editObject}) async { onSelected(Object object) { Navigator.pop(context); editObject(object); } - final melodies = part.melodies.where((m) => section.referenceTo(m).isEnabled); + final melodies = + part?.melodies.where((m) => section.referenceTo(m)?.isEnabled == true) ?? + []; return showMenu( context: context, position: position, @@ -38,6 +33,7 @@ Future showEditMenu( color: (musicBackgroundColor.luminance < 0.5 ? subBackgroundColor : musicBackgroundColor) + // .withAlpha(240) .withOpacity(0.95), items: [ PopupMenuItem( @@ -81,7 +77,7 @@ Future showEditMenu( value: null, child: Column(children: [ Row(children: [ - Text(part.midiName, + Text(part?.midiName ?? '', textAlign: TextAlign.left, // overflow: TextOverflow.ellipsis, softWrap: true, @@ -103,7 +99,7 @@ Future showEditMenu( enabled: false, ), ...melodies.map((m) => melodyMenuItem( - score, m, part, section, onSelected, m == selectedMelody)), + score, m, part!, section, onSelected, m == selectedMelody)), ]); } @@ -195,13 +191,14 @@ Material sectionPreview(Score score, Section section, ); } -PopupMenuItem partMenuItem(Score score, Part part, Section section, +PopupMenuItem partMenuItem(Score score, Part? part, Section section, Function(Object) onSelected, bool isSelected) { return PopupMenuItem( value: part, mouseCursor: SystemMouseCursors.basic, padding: EdgeInsets.zero, - child: partPreview(score, part, section, onSelected, isSelected), + child: + partPreview(score, part ?? Part(), section, onSelected, isSelected), enabled: true); } diff --git a/lib/export/export_manager.dart b/lib/export/export_manager.dart index 608075c8..adec291d 100644 --- a/lib/export/export_manager.dart +++ b/lib/export/export_manager.dart @@ -1,21 +1,23 @@ import 'dart:io'; -import '../widget/my_platform.dart'; - +import 'package:beatscratch_flutter_redux/generated/protos/protos.dart'; +import 'package:beatscratch_flutter_redux/util/music_theory.dart'; import 'package:path_provider/path_provider.dart'; -import '../util/music_theory.dart'; +import '../widget/my_platform.dart'; import 'export_models.dart'; class ExportManager { - Directory exportsDirectory; + late Directory exportsDirectory; File createExportFile(BSExport export) { String path = "${exportsDirectory.path.toString()}/${Uri.encodeComponent(export.score.name).replaceAll("%20", " ")}"; - if (export.exportedSection != null) { - path += "-${Uri.encodeComponent(export.exportedSection.canonicalName)}"; + Section? exportedSection = export.exportedSection; + if (exportedSection != null) { + path += "-${Uri.encodeComponent(exportedSection.canonicalName)}"; } + path += "-${DateTime.now().toString().replaceAll(" ", '-').replaceAll(":", '-').split(".").first}"; path += ".${export.exportType.toString().split(".").last}"; @@ -25,13 +27,10 @@ class ExportManager { } List get exportFiles { - if (exportsDirectory != null) { - List result = exportsDirectory?.listSync(); - result.sort( - (a, b) => b.statSync().modified.compareTo(a.statSync().modified)); - return result; - } - return []; + List result = exportsDirectory.listSync(); + result + .sort((a, b) => b.statSync().modified.compareTo(a.statSync().modified)); + return result; } ExportManager() { diff --git a/lib/export/export_models.dart b/lib/export/export_models.dart index 87960a9b..5500d085 100644 --- a/lib/export/export_models.dart +++ b/lib/export/export_models.dart @@ -1,6 +1,7 @@ import 'dart:io'; -import 'package:dart_midi/dart_midi.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_writer.dart'; +import 'package:collection/collection.dart'; import '../generated/protos/protos.dart'; import 'export_manager.dart'; @@ -12,29 +13,26 @@ class BSExport { Score score; double tempoMultiplier; ExportType exportType; - String sectionId; + String? sectionId; final List partIds = []; - BSExport( - {this.score, - this.exportType = ExportType.midi, + BSExport(this.score, + {this.exportType = ExportType.midi, this.tempoMultiplier = 1, this.sectionId}); File call(ExportManager exportManager) { - final midiFile = score.exportMidi(this); + final midiFile = score.exportMidi(this)!; final fileHandle = exportManager.createExportFile(this); MidiWriter().writeMidiToFile(midiFile, fileHandle); return fileHandle; } - Section get exportedSection => score.sections.isEmpty + Section? get exportedSection => score.sections.isEmpty ? null - : score.sections.firstWhere((s) => s.id == sectionId, orElse: () => null); + : score.sections.firstWhereOrNull((s) => s.id == sectionId); - bool includesSection(Section section) => - sectionId == null || sectionId == section.id; + bool includesSection(Section section) => sectionId == section.id; - bool includesPart(Part part) => - partIds == null || partIds.isEmpty || partIds.contains(part.id); + bool includesPart(Part part) => partIds.isEmpty || partIds.contains(part.id); } diff --git a/lib/export/export_ui.dart b/lib/export/export_ui.dart index 0691876c..8e6688f9 100644 --- a/lib/export/export_ui.dart +++ b/lib/export/export_ui.dart @@ -1,9 +1,11 @@ import 'dart:io'; +import 'package:share_plus/share_plus.dart'; + import '../beatscratch_plugin.dart'; import '../generated/protos/music.pb.dart'; import '../messages/messages_ui.dart'; import '../util/dummydata.dart'; -import 'package:share/share.dart'; +// import 'package:share/share.dart'; import '../util/util.dart'; @@ -24,7 +26,7 @@ class ExportUI { static final double _baseHeight = 220; static final double _progressHeight = 30; - static Widget exportIcon({double size = 24, Color color}) => + static Widget exportIcon({double size = 24, Color? color}) => Transform.translate( offset: Offset(size * 2 / 24, 0), child: Icon(MyPlatform.isAppleOS ? CupertinoIcons.share : Icons.share, @@ -33,9 +35,9 @@ class ExportUI { bool visible = false; bool exporting = false; - final BSExport export = BSExport(score: defaultScore()); + final BSExport export = BSExport(defaultScore()); ExportManager exportManager = ExportManager(); - MessagesUI messagesUI; + MessagesUI? messagesUI; double get baseHeight => visible ? _baseHeight : 0.0; @@ -44,9 +46,9 @@ class ExportUI { double get height => baseHeight + progressHeight; Widget build( - {@required BuildContext context, - @required Function(VoidCallback) setState, - @required Section currentSection}) { + {required BuildContext context, + required Function(VoidCallback) setState, + required Section currentSection}) { return AnimatedContainer( duration: animationDuration, height: height, @@ -104,7 +106,7 @@ class ExportUI { Expanded(child: SizedBox()), Transform.translate( offset: Offset(0, 3), - child: Text(export?.score?.name ?? "null", + child: Text(export.score.name ?? "null", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -193,7 +195,7 @@ class ExportUI { exporting = true; visible = false; Future.microtask(() { - File file; + File? file; bool success = false; try { file = export(exportManager); @@ -204,7 +206,7 @@ class ExportUI { print(e.stackTrace); } - messagesUI.sendMessage( + messagesUI?.sendMessage( message: "MIDI Export failed!", isError: true); } @@ -214,35 +216,45 @@ class ExportUI { }); if (success) { if (MyPlatform.isMacOS) { - messagesUI.sendMessage( + messagesUI?.sendMessage( message: "Opening exports directory in Finder..."); Future.delayed(Duration(seconds: 1), () { launchURL( "file://${exportManager.exportsDirectory.path}"); - messagesUI.sendMessage( + messagesUI?.sendMessage( message: "Export complete!"); }); } else if (MyPlatform.isIOS) { - messagesUI.sendMessage( + messagesUI?.sendMessage( message: "Opening exports directory in Files..."); Future.delayed(Duration(seconds: 1), () { launchURL( "shareddocuments://${exportManager.exportsDirectory.path}"); - messagesUI.sendMessage( + messagesUI?.sendMessage( message: "Export complete!"); }); } else if (MyPlatform.isMobile) { - messagesUI.sendMessage( + messagesUI?.sendMessage( message: "Sharing MIDI file..."); Future.delayed(Duration(seconds: 1), () { - Share.shareFiles([file.path], - text: export.score.name); - messagesUI.sendMessage( + SharePlus.instance + .share(ShareParams( + files: [ + XFile(file!.path), + ], + // sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + // fileNameOverrides: [fileName], + downloadFallbackEnabled: true, + )); + + // [file.path], + // text: export.score.name); + messagesUI?.sendMessage( message: "Export complete!"); }); } @@ -269,9 +281,9 @@ class ExportUI { EdgeInsets.only(left: 5, top: 5, bottom: 5); SingleChildScrollView exportOptions( - {@required BuildContext context, - @required Function(VoidCallback) setState, - @required Section currentSection}) { + {required BuildContext context, + required Function(VoidCallback) setState, + required Section currentSection}) { double width = MediaQuery.of(context).size.width; double scrollContainerWidth = width - 44; double exportTypeWidth = 100; @@ -282,18 +294,15 @@ class ExportUI { exportTypeWidth + sectionWidth + partsWidth + speedWidth; bool usesFlex = scrollContainerWidth > scrollContentsWidth; - Widget exportOption(String label, String value, double size, Color color, - {List values, - List disabledValues, - Widget customValue, - Function(String) selectValue}) { - if (disabledValues == null) { - disabledValues = []; - } - if (values == null || values.isEmpty) { + Widget exportOption(String label, String? value, double size, Color color, + {required List values, + List disabledValues = const [], + Widget? customValue, + required Function(String?) selectValue}) { + if (values.isEmpty) { values = [value]; } - if (value != null && !values.contains(value)) { + if (!values.contains(value)) { values = values + [value]; } values = values.toSet().toList(); @@ -301,12 +310,12 @@ class ExportUI { Widget column = Column(children: [ // if (usesFlex) Expanded(child:SizedBox()), ...values.map((v) { - final clickable = selectValue != null && !disabledValues.contains(v); + final clickable = !disabledValues.contains(v); return MyFlatButton( color: v == value ? Colors.white : Colors.black12, padding: EdgeInsets.all(5), onPressed: clickable ? () => selectValue(v) : null, - child: Text(v, + child: Text(v ?? "", textAlign: TextAlign.center, style: valueStyle( !clickable @@ -316,7 +325,7 @@ class ExportUI { : Colors.white, ))); }).toList(), - if (customValue != null) customValue, + customValue ?? SizedBox(), // if (usesFlex) Expanded(child:SizedBox()) ]); // if (!usesFlex) { @@ -341,8 +350,7 @@ class ExportUI { } } - if (export.score == null || - !export.score.sections.any((s) => s.id == export.sectionId)) { + if (!export.score.sections.any((s) => s.id == export.sectionId)) { export.sectionId = null; } export.partIds.removeWhere( @@ -370,7 +378,8 @@ class ExportUI { "${(BeatScratchPlugin.bpmMultiplier ?? 1).toStringAsFixed(3).replaceAll("1.000", "1")}x" ], selectValue: (v) => setState(() { - export.tempoMultiplier = double.parse(v.replaceAll("x", "")); + export.tempoMultiplier = + double.parse((v ?? "").replaceAll("x", "")); })), exportOption( "Section", @@ -378,7 +387,7 @@ class ExportUI { ? "Entire Score" : export.score.sections .firstWhere((s) => s.id == export.sectionId) - ?.canonicalName ?? + .canonicalName ?? "NULL", sectionWidth, chromaticSteps[3], diff --git a/lib/export/midi_export.dart b/lib/export/midi_export.dart index 2b25ba46..7c9c57f8 100644 --- a/lib/export/midi_export.dart +++ b/lib/export/midi_export.dart @@ -1,15 +1,17 @@ +import 'package:beatscratch_flutter_redux/midi/byte_reader.dart'; +import 'package:beatscratch_flutter_redux/midi/byte_writer.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_events.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_file.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_header.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_parser.dart'; + import '../export/export.dart'; import '../generated/protos/protos.dart'; -import 'package:dart_midi/dart_midi.dart'; -// ignore: implementation_imports -import 'package:dart_midi/src/byte_writer.dart'; -// ignore: implementation_imports -import 'package:dart_midi/src/byte_reader.dart'; import '../util/music_theory.dart'; import '../util/midi_theory.dart'; extension ScoreMidiExport on Score { - MidiFile exportMidi(BSExport export) { + MidiFile? exportMidi(BSExport export) { if (parts.isEmpty || sections.isEmpty) return null; final List track = parts .where((p) => export.includesPart(p)) @@ -35,7 +37,8 @@ extension ScoreMidiExport on Score { } print("Track assembled"); - final result = MidiFile([track], MidiHeader(ticksPerBeat: 24, format: 0)); + final result = MidiFile( + [track], MidiHeader(ticksPerBeat: 24, format: 0, numTracks: 1)); return result; } } @@ -53,6 +56,7 @@ extension SectionMidiExport on Section { r.isEnabled && part.melodies.any((m) => m.id == r.melodyId)) .forEach((ref) { final melody = score.melodyReferencedBy(ref); + if (melody == null) return; if (melody.type == MelodyType.midi) { int startBeat = 0; while (startBeat < beatCount) { @@ -61,8 +65,8 @@ extension SectionMidiExport on Section { final absoluteBeat = (subdivision.toDouble() / melody.subdivisionsPerBeat).floor(); final convertedTickOfBeat = - Base24Conversion.map[melody.subdivisionsPerBeat] - [subdivision % melody.subdivisionsPerBeat]; + Base24Conversion.map[melody.subdivisionsPerBeat]![ + subdivision % melody.subdivisionsPerBeat]; final absoluteTick = 24 * startBeat + startTick + 24 * absoluteBeat + diff --git a/lib/generated/i18n.dart b/lib/generated/i18n.dart deleted file mode 100644 index 30fdb7a4..00000000 --- a/lib/generated/i18n.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -// ignore_for_file: non_constant_identifier_names -// ignore_for_file: camel_case_types -// ignore_for_file: prefer_single_quotes - -// This file is automatically generated. DO NOT EDIT, all your changes would be lost. -class S implements WidgetsLocalizations { - const S(); - - static S current; - - static const GeneratedLocalizationsDelegate delegate = - GeneratedLocalizationsDelegate(); - - static S of(BuildContext context) => Localizations.of(context, S); - - @override - TextDirection get textDirection => TextDirection.ltr; - -} - -class $en extends S { - const $en(); -} - -class GeneratedLocalizationsDelegate extends LocalizationsDelegate { - const GeneratedLocalizationsDelegate(); - - List get supportedLocales { - return const [ - Locale("en", ""), - ]; - } - - LocaleListResolutionCallback listResolution({Locale fallback, bool withCountry = true}) { - return (List locales, Iterable supported) { - if (locales == null || locales.isEmpty) { - return fallback ?? supported.first; - } else { - return _resolve(locales.first, fallback, supported, withCountry); - } - }; - } - - LocaleResolutionCallback resolution({Locale fallback, bool withCountry = true}) { - return (Locale locale, Iterable supported) { - return _resolve(locale, fallback, supported, withCountry); - }; - } - - @override - Future load(Locale locale) { - final String lang = getLang(locale); - if (lang != null) { - switch (lang) { - case "en": - S.current = const $en(); - return SynchronousFuture(S.current); - default: - // NO-OP. - } - } - S.current = const S(); - return SynchronousFuture(S.current); - } - - @override - bool isSupported(Locale locale) => _isSupported(locale, true); - - @override - bool shouldReload(GeneratedLocalizationsDelegate old) => false; - - /// - /// Internal method to resolve a locale from a list of locales. - /// - Locale _resolve(Locale locale, Locale fallback, Iterable supported, bool withCountry) { - if (locale == null || !_isSupported(locale, withCountry)) { - return fallback ?? supported.first; - } - - final Locale languageLocale = Locale(locale.languageCode, ""); - if (supported.contains(locale)) { - return locale; - } else if (supported.contains(languageLocale)) { - return languageLocale; - } else { - final Locale fallbackLocale = fallback ?? supported.first; - return fallbackLocale; - } - } - - /// - /// Returns true if the specified locale is supported, false otherwise. - /// - bool _isSupported(Locale locale, bool withCountry) { - if (locale != null) { - for (Locale supportedLocale in supportedLocales) { - // Language must always match both locales. - if (supportedLocale.languageCode != locale.languageCode) { - continue; - } - - // If country code matches, return this locale. - if (supportedLocale.countryCode == locale.countryCode) { - return true; - } - - // If no country requirement is requested, check if this locale has no country. - if (true != withCountry && (supportedLocale.countryCode == null || supportedLocale.countryCode.isEmpty)) { - return true; - } - } - } - return false; - } -} - -String getLang(Locale l) => l == null - ? null - : l.countryCode != null && l.countryCode.isEmpty - ? l.languageCode - : l.toString(); diff --git a/lib/layers_view/layers_part_view.dart b/lib/layers_view/layers_part_view.dart index b6e72735..44509a06 100644 --- a/lib/layers_view/layers_part_view.dart +++ b/lib/layers_view/layers_part_view.dart @@ -1,8 +1,6 @@ import 'dart:math'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter_reorderable_list/flutter_reorderable_list.dart' as frl; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:animated_list_plus/animated_list_plus.dart'; @@ -29,7 +27,7 @@ class LayersPartView extends StatefulWidget { MelodyReferenceView.columnWidthMicroIncrement; final Score score; - final Axis scrollDirection; + final Axis? scrollDirection; final Function(Melody) selectMelody; final VoidCallback toggleEditingMelody; final VoidCallback hideMelodyView; @@ -39,8 +37,8 @@ class LayersPartView extends StatefulWidget { final Function(Part) selectPart; final Color sectionColor; final Section currentSection; - final Melody selectedMelody; - final Part selectedPart; + final Melody? selectedMelody; + final Part? selectedPart; final Part colorboardPart; final Part keyboardPart; final Function(Part) setKeyboardPart; @@ -60,33 +58,33 @@ class LayersPartView extends StatefulWidget { } LayersPartView({ - this.score, + required this.score, this.scrollDirection, - this.part, - this.sectionColor, - this.currentSection, - this.selectedMelody, - this.selectMelody, - this.colorboardPart, - this.keyboardPart, - this.setKeyboardPart, - this.setColorboardPart, - this.selectPart, - this.toggleMelodyReference, - this.setReferenceVolume, - this.setPartVolume, - this.editingMelody, - this.toggleEditingMelody, - this.hideMelodyView, - this.removePart, - this.selectedPart, - this.enableColorboard, - this.showBeatCounts, - this.height, - this.showMediumDetails, - this.autoScroll, - this.showHighDetails, - this.width, + required this.part, + required this.sectionColor, + required this.currentSection, + required this.selectedMelody, + required this.selectMelody, + required this.colorboardPart, + required this.keyboardPart, + required this.setKeyboardPart, + required this.setColorboardPart, + required this.selectPart, + required this.toggleMelodyReference, + required this.setReferenceVolume, + required this.setPartVolume, + required this.editingMelody, + required this.toggleEditingMelody, + required this.hideMelodyView, + required this.removePart, + required this.selectedPart, + required this.enableColorboard, + required this.showBeatCounts, + required this.height, + required this.showMediumDetails, + required this.autoScroll, + required this.showHighDetails, + required this.width, }); @override @@ -122,7 +120,7 @@ class _LayersPartViewState extends State { Section get currentSection => widget.currentSection; - Melody get selectedMelody => widget.selectedMelody; + Melody? get selectedMelody => widget.selectedMelody; get setReferenceVolume => widget.setReferenceVolume; @@ -143,7 +141,7 @@ class _LayersPartViewState extends State { get toggleEditingMelody => widget.toggleEditingMelody; get hideMelodyView => widget.hideMelodyView; - ScrollController scrollController; + late ScrollController scrollController; int _indexOfKey(Key key) { return widget._items.indexWhere((Melody melody) => Key(melody.id) == key); @@ -174,7 +172,7 @@ class _LayersPartViewState extends State { bool get scrollControllerIsNearTop => scrollController.hasClients && scrollController.offset < (widget.showMediumDetails ? 150 : 165); - DateTime _lastScrollTime; + DateTime? _lastScrollTime; setScrollToTopTimeout() { final v = DateTime.now(); Future.delayed(Duration(seconds: 3), () { @@ -267,8 +265,8 @@ class _LayersPartViewState extends State { ]; int newMelodyBeatCountIndex = 0; - String lastSelectedMelodyId; - double prevWidth; + String? lastSelectedMelodyId; + double? prevWidth; @override Widget build(BuildContext context) { @@ -288,9 +286,8 @@ class _LayersPartViewState extends State { backgroundColor = isSelectedPart ? Colors.white : Colors.grey; textColor = isSelectedPart ? Colors.grey : Colors.white; } - if (lastSelectedMelodyId != null && - selectedMelody != null && - lastSelectedMelodyId != selectedMelody.id && + if (selectedMelody != null && + lastSelectedMelodyId != selectedMelody?.id && widget.autoScroll) { int indexOfMelody = part.melodies.indexWhere((m) => m.id == selectedMelody?.id); @@ -587,7 +584,7 @@ class _LayersPartViewState extends State { expandedHeight: 50.0, flexibleSpace: MyFlatButton( onPressed: () { - final melody = widget.selectedMelody.bsCopy()..id = uuid.v4(); + final melody = widget.selectedMelody!.bsCopy()..id = uuid.v4(); bool melodyExists = melody.name.isNotEmpty && part.melodies.any((it) => it.name == melody.name); if (melodyExists && melody.name.isNotEmpty) { @@ -595,9 +592,9 @@ class _LayersPartViewState extends State { r"^(.*?)(\d*)\s*$", ); final match = expr.allMatches(melody.name).first; - String prefix = match.group(1); + String prefix = match.group(1)!; prefix = prefix.trim(); - int number = int.tryParse(match.group(2)) ?? 1; + int number = int.tryParse(match.group(2)!) ?? 1; int newNumber = number + 1; melody.name = "$prefix $newNumber"; while (part.melodies.any((it) { @@ -622,11 +619,11 @@ class _LayersPartViewState extends State { child: Transform.translate( offset: Offset(5, -1), child: Text( - selectedMelody.canonicalName, + selectedMelody?.canonicalName ?? '', maxLines: 1, overflow: TextOverflow.fade, style: TextStyle( - color: selectedMelody.name?.isNotEmpty ?? false + color: selectedMelody?.name.isNotEmpty ?? false ? textColor : textColor.withOpacity(0.5), fontWeight: FontWeight.w300), @@ -638,7 +635,7 @@ class _LayersPartViewState extends State { child: Transform.translate( offset: Offset(5, 0), child: BeatsBadge( - beats: selectedMelody.beatCount, + beats: selectedMelody?.beatCount ?? 0, show: widget.showBeatCounts, ), )), diff --git a/lib/layers_view/layers_view.dart b/lib/layers_view/layers_view.dart index 16a1e72f..9da0edf4 100644 --- a/lib/layers_view/layers_view.dart +++ b/lib/layers_view/layers_view.dart @@ -1,8 +1,6 @@ import 'dart:math'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:animated_list_plus/animated_list_plus.dart'; import 'package:animated_list_plus/transitions.dart'; @@ -15,6 +13,7 @@ import '../util/dummydata.dart'; import '../util/music_theory.dart'; import '../util/util.dart'; import '../widget/my_buttons.dart'; + import '../widget/scalable_view.dart'; import 'layers_part_view.dart'; @@ -29,7 +28,7 @@ class LayersView extends StatefulWidget { final Score score; final Color sectionColor; final Section currentSection; - final Melody selectedMelody; + final Melody? selectedMelody; final Function(Melody) selectMelody; final VoidCallback toggleEditingMelody; final VoidCallback hideMelodyView; @@ -38,7 +37,7 @@ class LayersView extends StatefulWidget { final Function(Part, double) setPartVolume; final Part colorboardPart; final Part keyboardPart; - final Part selectedPart; + final Part? selectedPart; final Function(Part) setKeyboardPart; final Function(Part) setColorboardPart; final Function(Part) selectPart; @@ -51,34 +50,34 @@ class LayersView extends StatefulWidget { final bool shiftUpZoomControls; LayersView( - {this.musicViewMode, - this.superSetState, - this.appSettings, - this.score, - this.sectionColor, - this.currentSection, - this.selectedMelody, - this.selectMelody, - this.colorboardPart, - this.keyboardPart, - this.setKeyboardPart, - this.setColorboardPart, - this.selectPart, - this.toggleMelodyReference, - this.setReferenceVolume, - this.setPartVolume, - this.editingMelody, - this.toggleEditingMelody, - this.hideMelodyView, - this.availableWidth, - this.selectedPart, - this.enableColorboard, - this.showBeatCounts, - this.height, - Key key, - this.showViewOptions, - this.scoreManager, - this.shiftUpZoomControls}) + {Key? key, + required this.musicViewMode, + required this.superSetState, + required this.appSettings, + required this.score, + required this.sectionColor, + required this.currentSection, + required this.selectedMelody, + required this.selectMelody, + required this.colorboardPart, + required this.keyboardPart, + required this.setKeyboardPart, + required this.setColorboardPart, + required this.selectPart, + required this.toggleMelodyReference, + required this.setReferenceVolume, + required this.setPartVolume, + required this.editingMelody, + required this.toggleEditingMelody, + required this.hideMelodyView, + required this.availableWidth, + required this.selectedPart, + required this.enableColorboard, + required this.showBeatCounts, + required this.height, + required this.showViewOptions, + required this.scoreManager, + required this.shiftUpZoomControls}) : super(key: key); @override @@ -144,10 +143,6 @@ class _LayersViewState extends State { Part part = newDrumPart(); widget.score.parts.add(part); BeatScratchPlugin.createPart(part); -// BeatScratchPlugin.pushPart(part); - if (widget.keyboardPart == null) { - widget.setKeyboardPart(part); - } widget.selectPart(part); }); }); @@ -186,6 +181,7 @@ class _LayersViewState extends State { duration: animationDuration, width: width, child: MyFlatButton( + // borderRadius: 0, color: Colors.grey, onPressed: canAddPart ? () { @@ -200,12 +196,6 @@ class _LayersViewState extends State { widget.score.parts.add(part); } BeatScratchPlugin.createPart(part); - if (widget.keyboardPart == null) { - widget.setKeyboardPart(part); - } - if (widget.colorboardPart == null) { - widget.setColorboardPart(part); - } widget.selectPart(part); }); }); diff --git a/lib/layers_view/melody_menu_browser.dart b/lib/layers_view/melody_menu_browser.dart index 1e898d6f..fe587b90 100644 --- a/lib/layers_view/melody_menu_browser.dart +++ b/lib/layers_view/melody_menu_browser.dart @@ -1,7 +1,6 @@ import 'dart:io'; import '../util/util.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -12,7 +11,6 @@ import '../music_preview/melody_preview.dart'; import '../storage/score_manager.dart'; import '../util/dummydata.dart'; import '../util/music_theory.dart'; -import '../util/bs_methods.dart'; import '../widget/my_popup_menu.dart'; import '../widget/my_popup_menu.dart' as myPopup; import '../widget/beats_badge.dart'; @@ -24,14 +22,14 @@ class MelodyMenuBrowser extends StatefulWidget { final Section currentSection; final Function(Melody) onMelodySelected; final Color textColor; - final Widget child; + final Widget? child; const MelodyMenuBrowser( - {Key key, + {Key? key, this.child, - this.part, - this.currentSection, - this.onMelodySelected, + required this.part, + required this.currentSection, + required this.onMelodySelected, this.textColor = Colors.white}) : super(key: key); @@ -79,15 +77,14 @@ final Map> _sampleDrumMelodies = { final _manager = ScoreManager(); class _MelodyMenuBrowserState extends State { - Score selectedScore; - Part selectedPart; - BSMethod updatedMenu; - String selectedSamples; + Score? selectedScore; + Part? selectedPart; + late BSMethod updatedMenu; + String? selectedSamples; Iterable get sampleLists => - (widget.part?.isDrum == true ? _sampleDrumMelodies : _sampleMelodies) - .keys; + (widget.part.isDrum == true ? _sampleDrumMelodies : _sampleMelodies).keys; List get samples => - (widget.part?.isDrum == true + (widget.part.isDrum == true ? _sampleDrumMelodies[selectedSamples] : _sampleMelodies[selectedSamples]) ?? []; @@ -95,7 +92,7 @@ class _MelodyMenuBrowserState extends State { List findDuplicatedMelodies(List input) => input .where((it) => it.name.isNotEmpty && - (widget.part?.melodies?.any((m) => m.name == it.name) ?? false)) + (widget.part.melodies.any((m) => m.name == it.name) ?? false)) .toList(); List> menuEntriesByDuplicateStatus( @@ -124,12 +121,11 @@ class _MelodyMenuBrowserState extends State { @override Widget build(BuildContext context) { - if (selectedScore == null) {} - return new MyPopupMenuButton( + return new MyPopupMenuButton( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), color: musicBackgroundColor.withOpacity(0.95), padding: EdgeInsets.zero, - tooltip: "Import Melody | ${widget.part?.midiName}", + tooltip: "Import Melody | ${widget.part.midiName}", child: widget.child ?? Column(children: [ Expanded( @@ -149,11 +145,9 @@ class _MelodyMenuBrowserState extends State { ]), updatedMenu: updatedMenu, itemBuilder: (ctx) { + late List> result; if (selectedScore == null && selectedSamples == null) { - List> result = [ - mainHeader(), - samplesSectionHeader() - ]; + result = [mainHeader(), samplesSectionHeader()]; result.addAll( sampleLists.map((listName) => sampleListMenuItem(listName))); // result.addAll(samples.map((m) => melodyMenuItem(m, isSample: true))); @@ -162,40 +156,19 @@ class _MelodyMenuBrowserState extends State { result.addAll(_scoreDataCache.map(scoreMenuItem)); } return result; - } else if (selectedSamples != null) { - List> result = [ - backMenuItem(), - sampleListHeader() - ]; - result - .addAll(menuEntriesByDuplicateStatus(samples, isSample: true)); - // result.addAll(samples.map((m) => melodyMenuItem(m, isSample: true))); - return result; - } else if (selectedPart == null) { - return [backMenuItem(), partListHeader()] + - selectedScore.parts.map(partMenuItem).toList(); - } else { - List> result = [ - backMenuItem(), - melodyListHeader(selectedPart) - ]; - result.addAll(menuEntriesByDuplicateStatus(selectedPart.melodies)); - return result; - } + } else + result = [backMenuItem(), sampleListHeader()]; + result.addAll(menuEntriesByDuplicateStatus(samples, isSample: true)); + // result.addAll(samples.map((m) => melodyMenuItem(m, isSample: true))); + return result; }, onSelected: (value) { switch (value) { case "back": - if (selectedPart != null) { - if (selectedPart.isDrum) { - selectedScore = null; - } - selectedPart = null; - } else if (selectedScore != null) { + if (selectedPart?.isDrum ?? false) { selectedScore = null; - } else if (selectedSamples != null) { - selectedSamples = null; } + selectedPart = null; updatedMenu(); break; default: @@ -205,31 +178,19 @@ class _MelodyMenuBrowserState extends State { } else { selectedScore = _scoreDataCache.firstWhere((s) => s.id == value); - if (widget.part?.isDrum == true) { - selectedPart = selectedScore.parts + if (widget.part.isDrum == true) { + selectedPart = selectedScore?.parts .firstWhere((p) => p.isDrum, orElse: null); } } updatedMenu(); - } else if (selectedSamples != null) { - if (value.startsWith("sample-")) { - final melody = - samples.firstWhere((m) => "sample-${m.id}" == value); - widget.onMelodySelected(melody.bsRebuild((m) { - m.id = uuid.v4(); - })); - } // else - ??? - } else if (selectedPart == null) { - selectedPart = - selectedScore.parts.firstWhere((p) => p.id == value); - updatedMenu(); - } else { + } else if (value.startsWith("sample-")) { final melody = - selectedPart.melodies.firstWhere((m) => m.id == value); + samples.firstWhere((m) => "sample-${m.id}" == value); widget.onMelodySelected(melody.bsRebuild((m) { m.id = uuid.v4(); })); - } + } // else - ??? } }); } @@ -245,7 +206,7 @@ class _MelodyMenuBrowserState extends State { child: Text( selectedSamples != null ? "Import" - : selectedPart != null && !selectedPart.isDrum + : !(selectedPart?.isDrum ?? false) ? "Parts" : "Import", style: TextStyle(color: musicForegroundColor), @@ -273,9 +234,9 @@ class _MelodyMenuBrowserState extends State { TextStyle instrumentStyle(Part part, {bool header = false}) { return TextStyle( fontWeight: FontWeight.w800, - color: part?.instrument?.type == InstrumentType.drum - ? Colors.brown.withOpacity(widget.part?.isDrum == true ? 1 : 0.5) - : widget.part?.isDrum == true || header + color: part.instrument.type == InstrumentType.drum + ? Colors.brown.withOpacity(widget.part.isDrum == true ? 1 : 0.5) + : widget.part.isDrum == true || header ? Colors.grey : musicForegroundColor, ); @@ -290,7 +251,7 @@ class _MelodyMenuBrowserState extends State { padding: EdgeInsets.symmetric(vertical: 2, horizontal: 5), child: Icon(Icons.chevron_right, color: musicForegroundColor)) ]), - enabled: part?.instrument?.type == widget.part?.instrument?.type, + enabled: part.instrument.type == widget.part.instrument.type, ); } @@ -315,7 +276,7 @@ class _MelodyMenuBrowserState extends State { padding: EdgeInsets.symmetric(vertical: 2), child: Icon(Icons.add, color: musicForegroundColor)), if (!isDuplicate && - widget.part?.instrument?.type == melody.instrumentType) + widget.part.instrument.type == melody.instrumentType) SizedBox(width: 5), if (isDuplicate) Transform.scale( @@ -359,7 +320,7 @@ class _MelodyMenuBrowserState extends State { SizedBox(width: 5) ])), ), - if (widget.part?.instrument?.type != melody.instrumentType) + if (widget.part.instrument.type != melody.instrumentType) Transform.scale( scale: 0.8, child: Container( @@ -411,7 +372,7 @@ class _MelodyMenuBrowserState extends State { )), ], ), - enabled: melody.instrumentType == widget.part?.instrument?.type, + enabled: melody.instrumentType == widget.part.instrument.type, ); } @@ -444,7 +405,7 @@ class _MelodyMenuBrowserState extends State { fontSize: 10, color: musicForegroundColor) /*style: instrumentStyle(part, header: true)*/), - Text(widget.part?.midiName ?? "", + Text(widget.part.midiName ?? "", textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, @@ -501,7 +462,7 @@ class _MelodyMenuBrowserState extends State { maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 10, color: musicForegroundColor)), - Text(selectedSamples, + Text(selectedSamples!, textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, @@ -516,7 +477,7 @@ class _MelodyMenuBrowserState extends State { maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 10, color: musicForegroundColor)), - Text(selectedScore.name, + Text(selectedScore?.name ?? "", textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, @@ -526,12 +487,12 @@ class _MelodyMenuBrowserState extends State { MyPopupMenuItem melodyListHeader(Part part) { return _splitListHeader( - Text(selectedScore.name, + Text(selectedScore?.name ?? "", textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 10, color: musicForegroundColor)), - Text(selectedPart.midiName, + Text(selectedPart?.midiName ?? "", textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, diff --git a/lib/layers_view/melody_reference_view.dart b/lib/layers_view/melody_reference_view.dart index 39264866..7ab532e6 100644 --- a/lib/layers_view/melody_reference_view.dart +++ b/lib/layers_view/melody_reference_view.dart @@ -1,7 +1,4 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_reorderable_list/flutter_reorderable_list.dart' as frl; import 'package:animated_list_plus/transitions.dart'; @@ -21,14 +18,14 @@ class MelodyReferenceView extends StatefulWidget { static const double maxColumnWidth = 250; static const double columnWidthIncrement = 12; static const double columnWidthMicroIncrement = 1.4; - static Melody lastAddedMelody; + static Melody? lastAddedMelody; final Melody melody; final bool isFirst; final bool isLast; final Color sectionColor; final Section currentSection; final Part part; - final Melody selectedMelody; + final Melody? selectedMelody; final Function(Melody) selectMelody; final VoidCallback toggleEditingMelody; final VoidCallback hideMelodyView; @@ -44,26 +41,26 @@ class MelodyReferenceView extends StatefulWidget { final double width; MelodyReferenceView({ - this.melody, - this.isFirst, - this.isLast, - this.sectionColor, - this.currentSection, - this.selectedMelody, - this.selectMelody, - this.colorboardPart, - this.keyboardPart, - this.toggleMelodyReference, - this.setReferenceVolume, - this.editingMelody, - this.toggleEditingMelody, - this.hideMelodyView, - this.showBeatsBadge, - this.requestScrollToTop, - this.showMediumDetails, - this.showHighDetails, - this.part, - this.width, + required this.melody, + required this.isFirst, + required this.isLast, + required this.sectionColor, + required this.currentSection, + required this.selectedMelody, + required this.selectMelody, + required this.colorboardPart, + required this.keyboardPart, + required this.toggleMelodyReference, + required this.setReferenceVolume, + required this.editingMelody, + required this.toggleEditingMelody, + required this.hideMelodyView, + required this.showBeatsBadge, + required this.requestScrollToTop, + required this.showMediumDetails, + required this.showHighDetails, + required this.part, + required this.width, }); @override @@ -73,10 +70,10 @@ class MelodyReferenceView extends StatefulWidget { class _MelodyReferenceViewState extends State with TickerProviderStateMixin { MelodyReference get reference => - widget.currentSection.referenceTo(widget.melody); + widget.currentSection.referenceTo(widget.melody)!; bool get isSelectedMelody => widget.melody.id == widget.selectedMelody?.id; - AnimationController animationController; + late AnimationController animationController; TextEditingController nameController = TextEditingController(); bool get allowEditName => widget.showMediumDetails; bool get showVolume => @@ -285,7 +282,7 @@ class _MelodyReferenceViewState extends State }, style: TextStyle( color: melodyColor.textColor().withOpacity( - reference?.isEnabled == true ? 1 : 0.5)), + reference.isEnabled == true ? 1 : 0.5)), onTap: () { if (!context.isTabletOrLandscapey) { widget.hideMelodyView(); @@ -306,7 +303,7 @@ class _MelodyReferenceViewState extends State // padding: EdgeInsets.only(right:0), child: Icon(Icons.reorder, color: melodyColor.textColor().withOpacity( - reference?.isEnabled == true ? 1 : 0.5)))) + reference.isEnabled == true ? 1 : 0.5)))) ])), ), Row( diff --git a/lib/main.dart b/lib/main.dart index e8a8925e..b3ee43ff 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ +import 'dart:async'; import 'dart:math'; import 'package:beatscratch_flutter_redux/storage/universe_manager.dart'; +import 'package:collection/collection.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -9,9 +11,7 @@ import 'universe_view/universe_view.dart'; import 'recording/recording.dart'; import 'package:fluro/fluro.dart' as Fluro; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:native_device_orientation/native_device_orientation.dart'; @@ -32,7 +32,6 @@ import 'storage/score_manager.dart'; import 'storage/score_picker.dart'; import 'storage/url_conversions.dart'; import 'ui_models.dart'; -import 'util/bs_methods.dart'; import 'util/dummydata.dart'; import 'util/music_theory.dart'; import 'util/proto_utils.dart'; @@ -67,17 +66,15 @@ const Map swatch = { ScoreManager _scoreManager = ScoreManager(); UniverseManager _universeManager = UniverseManager(); AppSettings _appSettings = AppSettings(); -var baseHandler = Fluro.Handler( - handlerFunc: (BuildContext context, Map params) { +var baseHandler = Fluro.Handler(handlerFunc: (context, params) { return MyHomePage(title: 'BeatScratch', initialScore: defaultScore()); // return UsersScreen(params["scoreData"][0]); }); -var scoreRouteHandler = Fluro.Handler( - handlerFunc: (BuildContext context, Map params) { - String scoreData = params["scoreData"][0]; +var scoreRouteHandler = Fluro.Handler(handlerFunc: (context, params) { + String scoreData = params["scoreData"]![0]; Score score; try { - score = scoreFromUrlHashValue(scoreData); + score = scoreFromUrlHashValue(scoreData)!; } catch (any) { score = defaultScore(); } @@ -85,9 +82,8 @@ var scoreRouteHandler = Fluro.Handler( return MyHomePage(title: 'BeatScratch', initialScore: score); // return UsersScreen(params["scoreData"][0]); }); -var pastebinRouteHandler = Fluro.Handler( - handlerFunc: (BuildContext context, Map params) { - String pastebinCode = params["pasteBinData"][0]; +var pastebinRouteHandler = Fluro.Handler(handlerFunc: (context, params) { + String pastebinCode = params["pasteBinData"]![0]; return MyHomePage( title: 'BeatScratch', initialScore: defaultScore(), @@ -111,7 +107,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { // debugPaintSizeEnabled = true; - MyHomePage home; + late MyHomePage home; try { home = MyHomePage(title: 'BeatScratch', initialScore: defaultScore()); } catch (e) { @@ -145,21 +141,25 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title, this.initialScore, this.pastebinCode}) + MyHomePage( + {Key? key, + required this.title, + required this.initialScore, + this.pastebinCode}) : super(key: key); final String title; final Score initialScore; - final String pastebinCode; + final String? pastebinCode; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State with TickerProviderStateMixin { - Score score; + late Score score; InteractionMode interactionMode = InteractionMode.view; - SplitMode _splitMode; + SplitMode _splitMode = SplitMode.full; SplitMode get splitMode => _splitMode; @@ -199,7 +199,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { set recordingMelody(value) { _recordingMelody = value; if (value) { - BeatScratchPlugin.setRecordingMelody(selectedMelody); + BeatScratchPlugin.setRecordingMelody(value); _showMusicView(); } else { BeatScratchPlugin.setRecordingMelody(null); @@ -209,7 +209,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { } } - Section _currentSection; // + Section _currentSection = Section(); Section get currentSection => _currentSection; @@ -219,7 +219,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { min(section.beatCount - 1, BeatScratchPlugin.currentBeat.value); _currentSection = section; if (recordingMelody && - section.referenceTo(selectedMelody).playbackType == + section.referenceTo(selectedMelody!).playbackType == MelodyReference_PlaybackType.disabled) { recordingMelody = false; } @@ -230,7 +230,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { } } - int _tapInBeat; + int? _tapInBeat; bool showViewOptions = false; bool _wasKeyboardShowingWhenMidiConfigurationOpened = false; bool _wasColorboardShowingWhenMidiConfigurationOpened = false; @@ -254,7 +254,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { bool _showKeyboardConfiguration = false; bool _enableColorboard = false; - get enableColorboard => _enableColorboard; + bool get enableColorboard => _enableColorboard; set enableColorboard(bool value) { _enableColorboard = value; @@ -264,36 +264,31 @@ class _MyHomePageState extends State with TickerProviderStateMixin { bool showColorboard = false; bool _showColorboardConfiguration = false; - Part _keyboardPart; + Part _keyboardPart = Part(); Part get keyboardPart => _keyboardPart; set keyboardPart(Part part) { _keyboardPart = part; - if (part == null) { - showKeyboard = false; - } + // if (part != null) BeatScratchPlugin.setKeyboardPart(part); } - Part _colorboardPart; + Part _colorboardPart = Part(); Part get colorboardPart => _colorboardPart; set colorboardPart(Part part) { _colorboardPart = part; - if (part == null) { - showColorboard = false; - } -// BeatScratchPlugin.setColorboardPart(part); + // BeatScratchPlugin.setColorboardPart(part); } - ValueNotifier> colorboardNotesNotifier; - ValueNotifier> keyboardNotesNotifier; - ValueNotifier>> bluetoothControllerPressedNotes; - int keyboardChordBase; - Set keyboardChordNotes = Set(); - ValueNotifier keyboardChordNotifier; + late ValueNotifier> colorboardNotesNotifier; + late ValueNotifier> keyboardNotesNotifier; + late ValueNotifier>> bluetoothControllerPressedNotes; + int? keyboardChordBase; + final Set keyboardChordNotes = Set(); + late ValueNotifier keyboardChordNotifier; bool get melodyViewVisible => _musicViewSizeFactor > 0; @@ -326,9 +321,6 @@ class _MyHomePageState extends State with TickerProviderStateMixin { setState(() { _musicViewSizeFactor = 0; _prevSelectedMelody = selectedMelody; - if (_prevSelectedMelody == null) { - _prevSelectedPart = selectedPart; - } selectedMelody = null; recordingMelody = false; selectedPart = null; @@ -356,70 +348,60 @@ class _MyHomePageState extends State with TickerProviderStateMixin { _setKeyboardPart(Part part) { setState(() { - bool wasAssignedByPartCreation = keyboardPart == null; + // bool wasAssignedByPartCreation = keyboardPart == null; keyboardPart = part; - if (part != null && - !wasAssignedByPartCreation && - !hasPrioritizedMIDIController) { - showKeyboard = true; - } + // if (!wasAssignedByPartCreation && !hasPrioritizedMIDIController) { + // showKeyboard = true; + // } }); } _setColorboardPart(Part part) { setState(() { - bool wasAssignedByPartCreation = colorboardPart == null; + // bool wasAssignedByPartCreation = colorboardPart == null; colorboardPart = part; - if (part != null && !wasAssignedByPartCreation) { - showColorboard = true; - } + // if (!wasAssignedByPartCreation) { + // showColorboard = true; + // } }); } - Melody _selectedMelody; + Melody? _selectedMelody; - Melody get selectedMelody => _selectedMelody; + Melody? get selectedMelody => _selectedMelody; - set selectedMelody(Melody selectedMelody) { + set selectedMelody(Melody? selectedMelody) { _selectedMelody = selectedMelody; - if (selectedMelody != null) { - Part part = score.parts - .firstWhere((p) => p.melodies.any((m) => m.id == selectedMelody.id)); - if (part != null) { - keyboardPart = part; - } - } else { - recordingMelody = false; + Part? part = score.parts.firstWhereOrNull( + (p) => p.melodies.any((m) => m.id == selectedMelody?.id)); + if (part != null) { + keyboardPart = part; } } - Part _selectedPart; + Part? _selectedPart; - Part get selectedPart => _selectedPart; + Part? get selectedPart => _selectedPart; - set selectedPart(Part selectedPart) { + set selectedPart(Part? selectedPart) { _selectedPart = selectedPart; - if (selectedPart != null) { - keyboardPart = selectedPart; - } + if (selectedPart != null) keyboardPart = selectedPart; } - Part _viewingPart; + Part? _viewingPart; - Part get viewingPart => _viewingPart; + Part? get viewingPart => _viewingPart; - set viewingPart(Part viewingPart) { + set viewingPart(Part? viewingPart) { _viewingPart = viewingPart; - if (viewingPart != null) { - keyboardPart = viewingPart; - } + if (viewingPart != null) keyboardPart = viewingPart; } List _sectionLists = []; Color get sectionColor => currentSection.color.color; - _selectOrDeselectMelody(Melody melody, {bool hideMusicOnDeselect: true}) { + _selectOrDeselectMelody(Melody? melody, {bool hideMusicOnDeselect = true}) { setState(() { if (selectedMelody != melody) { selectedMelody = melody; @@ -435,7 +417,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { recordingMelody = false; if (hideMusicOnDeselect) { _hideMusicView(); - } else { + } else if (melody != null) { final part = score.parts .firstWhere((p) => p.melodies.any((m) => m.id == melody.id)); _selectOrDeselectPart(part, hideMusicOnDeselect: hideMusicOnDeselect); @@ -444,7 +426,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { }); } - _selectOrDeselectPart(Part part, {bool hideMusicOnDeselect: true}) { + _selectOrDeselectPart(Part part, {bool hideMusicOnDeselect = true}) { setState(() { print("yay"); if (selectedPart != part) { @@ -459,8 +441,10 @@ class _MyHomePageState extends State with TickerProviderStateMixin { _hideMusicView(); } else { if (musicViewMode == MusicViewMode.melody) { - _selectOrDeselectMelody(selectedMelody, - hideMusicOnDeselect: hideMusicOnDeselect); + if (selectedMelody != null) { + _selectOrDeselectMelody(selectedMelody!, + hideMusicOnDeselect: hideMusicOnDeselect); + } } else { selectedPart = null; _prevSelectedPart = null; @@ -479,9 +463,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { } else { setState(() { ref.playbackType = MelodyReference_PlaybackType.disabled; - if (selectedMelody != null && - ref != null && - ref.melodyId == selectedMelody.id) { + if (ref.melodyId == selectedMelody?.id) { recordingMelody = false; } }); @@ -565,8 +547,8 @@ class _MyHomePageState extends State with TickerProviderStateMixin { }); } - Part _prevSelectedPart; - Melody _prevSelectedMelody; + Part? _prevSelectedPart; + Melody? _prevSelectedMelody; _editMode() { BeatScratchPlugin.setPlaybackMode(Playback_Mode.section); @@ -574,27 +556,12 @@ class _MyHomePageState extends State with TickerProviderStateMixin { // _universeManager.currentUniverseScore = ""; universeViewUI.visible = false; if (interactionMode.isEdit) { - if (selectedMelody != null) { - _prevSelectedMelody = selectedMelody; - _prevSelectedPart = null; - _hideMusicView(); - } else if (selectedPart != null) { - _prevSelectedMelody = null; - _prevSelectedPart = selectedPart; - _hideMusicView(); - } else if (musicViewMode == MusicViewMode.section) { - _prevSelectedMelody = null; - _prevSelectedPart = null; + _prevSelectedMelody = selectedMelody; + _prevSelectedPart = null; + if (musicViewMode != MusicViewMode.none) _hideMusicView(); - } else { - if (_prevSelectedMelody != null) { - _selectOrDeselectMelody(_prevSelectedMelody); - } else if (_prevSelectedPart != null) { - _selectOrDeselectPart(_prevSelectedPart); - } else { - _selectSection(currentSection); - } - } + else + _showMusicView(); } else { if (_scoreManager.currentScoreName == ScoreManager.UNIVERSE_SCORE || _scoreManager.currentScoreName == ScoreManager.WEB_SCORE || @@ -622,13 +589,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { // }); // }); } - if (_prevSelectedMelody != null) { - _selectOrDeselectMelody(_prevSelectedMelody); - } else if (_prevSelectedPart != null) { - _selectOrDeselectPart(_prevSelectedPart); - } else { - musicViewMode = MusicViewMode.section; - } + _selectOrDeselectMelody(_prevSelectedMelody); interactionMode = InteractionMode.edit; // showSections = true; _showMusicView(); @@ -699,7 +660,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { bool get _portraitPhoneUI => !_landscapePhoneUI && !_scalableUI; - BuildContext nativeDeviceOrientationReaderContext; + late BuildContext nativeDeviceOrientationReaderContext; NativeDeviceOrientation get _nativeOrientation { try { @@ -841,15 +802,17 @@ class _MyHomePageState extends State with TickerProviderStateMixin { double get horizontalSectionListHeight => showSections && !verticalSectionList ? 36 : 0; - ExportUI exportUI; - MessagesUI messagesUI; - UniverseViewUI universeViewUI; + late KeyboardVisibilityController keyboardVisibilityController; + late StreamSubscription keyboardVisbilitySubscription; + late ExportUI exportUI; + late MessagesUI messagesUI; + late UniverseViewUI universeViewUI; double get universeViewUIHeight => universeViewUI.height(context, keyboardHeight: _keyboardHeight, settingsHeight: _midiSettingsHeight); - BSMethod scrollToCurrentBeat; - BSMethod refreshUniverseData; - BSMethod bluetoothScan; - BSMethod duplicateCurrentScore; + late BSMethod scrollToCurrentBeat; + late BSMethod refreshUniverseData; + late BSMethod bluetoothScan; + late BSMethod duplicateCurrentScore; @override void initState() { @@ -889,19 +852,21 @@ class _MyHomePageState extends State with TickerProviderStateMixin { _currentSection = widget.initialScore.sections[0]; _scoreManager.doOpenScore = doOpenScore; if (widget.pastebinCode != null) { - _scoreManager.loadPastebinScoreIntoUI(widget.pastebinCode, onFail: () { + _scoreManager.loadPastebinScoreIntoUI(widget.pastebinCode!, onFail: () { messagesUI.sendMessage(message: "Failed to load URL!", isError: true); }); - } else if (MyPlatform.isWeb) { - BeatScratchPlugin.createScore(score); } - if (MyPlatform.isMobile) { - KeyboardVisibility.onChange.listen((bool visible) { + keyboardVisibilityController = KeyboardVisibilityController(); + keyboardVisbilitySubscription = + keyboardVisibilityController.onChange.listen((bool visible) { + // print('Keyboard visibility update. Is visible: $visible'); + if (MyPlatform.isMobile) { setState(() { _softKeyboardVisible = visible; }); - }); - } + } + }); + // BeatScratchPlugin.createScore(_score); BeatScratchPlugin.onSectionSelected = (sectionId) { setState(() { @@ -951,10 +916,14 @@ class _MyHomePageState extends State with TickerProviderStateMixin { : null; RecordedSegmentQueue.updateRecordingMelody = BeatScratchPlugin.onRecordingMelodyUpdated; - keyboardPart = score.parts.firstWhere((part) => true, orElse: () => null); - colorboardPart = score.parts.firstWhere( - (part) => part.instrument.type == InstrumentType.harmonic, - orElse: () => null); + keyboardPart = score.parts.firstWhereOrNull( + (part) => true, + ) ?? + Part(); + colorboardPart = score.parts.firstWhereOrNull( + (part) => part.instrument.type == InstrumentType.harmonic, + ) ?? + Part(); colorboardNotesNotifier = ValueNotifier(Set()); keyboardNotesNotifier = ValueNotifier(Set()); @@ -1089,6 +1058,8 @@ class _MyHomePageState extends State with TickerProviderStateMixin { ), )) ?? false; + } else { + return true; } } @@ -1127,10 +1098,6 @@ class _MyHomePageState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - if (splitMode == null) { - splitMode = (context.isTablet) ? SplitMode.half : SplitMode.full; - verticalSectionList = context.isTablet || context.isLandscapePhone; - } if (hasPrioritizedMIDIController && !hadPriotizedMIDIController) { showKeyboard = false; } @@ -1140,9 +1107,10 @@ class _MyHomePageState extends State with TickerProviderStateMixin { // verticalSectionList = context.isLandscape; // } if (context.isLandscape) { - SystemChrome.setEnabledSystemUIOverlays([]); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); } else { - SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); } if (BeatScratchPlugin.playing) { _tapInBeat = null; @@ -1392,19 +1360,22 @@ class _MyHomePageState extends State with TickerProviderStateMixin { padding: EdgeInsets.zero, onPressed: _universeManager.redditUsername.isNotEmpty ? () { - bool oldValue = _universeManager - .currentUniverseScoreFuture?.likes; + if (scoreFuture == null) return; + bool? oldValue = scoreFuture.likes; setState(() { if (oldValue == true) { - scoreFuture?.likes = null; - scoreFuture?.voteCount -= 1; + scoreFuture.likes = null; + if (scoreFuture.voteCount != null) + scoreFuture.voteCount = + scoreFuture.voteCount! - 1; } else { - scoreFuture?.likes = true; - scoreFuture?.voteCount += - oldValue == null ? 1 : 2; + scoreFuture.likes = true; + if (scoreFuture.voteCount != null) + scoreFuture.voteCount = scoreFuture.voteCount! + + (oldValue == null ? 1 : 2); } _universeManager.vote( - scoreFuture?.fullName, scoreFuture?.likes); + scoreFuture.fullName ?? '', scoreFuture.likes); }); } : null, @@ -1428,7 +1399,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { duration: animationDuration, child: Align( alignment: Alignment.center, - child: Text(scoreFuture?.voteCount?.toString() ?? '', + child: Text(scoreFuture?.voteCount.toString() ?? '', textAlign: TextAlign.center, style: TextStyle( color: musicForegroundColor, @@ -1440,18 +1411,20 @@ class _MyHomePageState extends State with TickerProviderStateMixin { padding: EdgeInsets.zero, onPressed: _universeManager.redditUsername.isNotEmpty ? () { - bool oldValue = _universeManager - .currentUniverseScoreFuture?.likes; + if (scoreFuture == null) return; + bool? oldValue = scoreFuture.likes; setState(() { if (oldValue == false) { - scoreFuture?.likes = null; - scoreFuture.voteCount += 1; + scoreFuture.likes = null; + scoreFuture.voteCount = + scoreFuture.voteCount! + 1; } else { - scoreFuture?.likes = false; - scoreFuture.voteCount -= oldValue == null ? 1 : 2; + scoreFuture.likes = false; + scoreFuture.voteCount = scoreFuture.voteCount! - + (oldValue == null ? 1 : 2); } _universeManager.vote( - scoreFuture?.fullName, scoreFuture?.likes); + scoreFuture.fullName ?? '', scoreFuture.likes); }); } : null, @@ -1511,13 +1484,16 @@ class _MyHomePageState extends State with TickerProviderStateMixin { child: MyFlatButton( padding: EdgeInsets.zero, onPressed: () { + if (scoreFuture == null) return; + if (_appSettings.enableApollo) { launchURL( - scoreFuture.commentUrl - .replaceAll("https://", "apollo://"), + scoreFuture?.commentUrl + ?.replaceAll("https://", "apollo://") ?? + '', forceSafariVC: false); - } else { - launchURL(scoreFuture.commentUrl, forceSafariVC: false); + } else if (scoreFuture.commentUrl != null) { + launchURL(scoreFuture.commentUrl!, forceSafariVC: false); } }, child: Align( @@ -1571,7 +1547,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { TextStyle( color: foregroundColor, fontSize: 10, - fontWeight: FontWeight.w100), + fontWeight: FontWeight.w400), score, _scoreManager), maxLines: 3, @@ -1765,16 +1741,13 @@ class _MyHomePageState extends State with TickerProviderStateMixin { bool bottom = false, }) { bool playing = BeatScratchPlugin.playing; - int tapInBeat = _tapInBeat; + final int? tapInBeat = _tapInBeat; bool isDisplayed = (!vertical && !bottom && _tapInBarHeight != 0) || (vertical && _landscapeTapInBarWidth != 0) || (bottom && _bottomTapInBarHeight != 0); - final double tapInFirstSize = - !playing && (vertical || tapInBeat == null) ? 42 : 0; + final double tapInFirstSize = !playing && (vertical) ? 42 : 0; final double tapInSecondSize = - !BeatScratchPlugin.playing && (_tapInBeat == null || _tapInBeat <= -2) - ? 42 - : 0; + !playing && (tapInBeat != null) && (tapInBeat <= -2) ? 42 : 0; final audioButtonColor = BeatScratchPlugin.metronomeEnabled ? sectionColor : Colors.grey; Widget tapInBarInstructions({bool withText = true, bool withIcon = true}) => @@ -2245,12 +2218,10 @@ class _MyHomePageState extends State with TickerProviderStateMixin { showViewOptions; showMidiConfiguration = true; showViewOptions = true; - if (keyboardPart != null && showKeyboard) { + if (showKeyboard) { _showKeyboardConfiguration = true; } - if (_enableColorboard && - colorboardPart != null && - showColorboard) { + if (_enableColorboard && showColorboard) { _showColorboardConfiguration = true; } }); @@ -2263,14 +2234,8 @@ class _MyHomePageState extends State with TickerProviderStateMixin { }, saveCurrentScore: saveCurrentScore, pasteScore: () async { - ClipboardData data = await Clipboard.getData(Clipboard.kTextPlain); - if (data == null) { - setState(() { - pasteFailed = true; - }); - return; - } - String scoreUrl = data.text; + ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); + String scoreUrl = data?.text ?? ''; _scoreManager.loadFromScoreUrl(scoreUrl, currentScoreToSave: this.score, onFail: () { setState(() { @@ -2333,11 +2298,9 @@ class _MyHomePageState extends State with TickerProviderStateMixin { _selectOrDeselectPart(object); } } else if (object is Section) { - if (selectedMelody != null) { - _selectOrDeselectMelody(selectedMelody); - } + _selectOrDeselectMelody(selectedMelody); if (selectedPart != null) { - _selectOrDeselectPart(selectedPart); + _selectOrDeselectPart(selectedPart!); } musicViewMode = MusicViewMode.section; if (!interactionMode.isEdit) { @@ -2407,9 +2370,8 @@ class _MyHomePageState extends State with TickerProviderStateMixin { }); }, showTempoConfiguration: _showTapInBar, - visible: (_secondToolbarHeight != null && _secondToolbarHeight != 0) || - (_landscapePhoneSecondToolbarWidth != null && - _landscapePhoneSecondToolbarWidth != 0) || + visible: (_secondToolbarHeight != 0) || + (_landscapePhoneSecondToolbarWidth != 0) || _scalableUI, tempoLongPress: _landscapePhoneUI ? () { @@ -2453,7 +2415,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { return result; } - saveCurrentScore({Duration delay}) { + saveCurrentScore({Duration? delay}) { final score = this.score; final scoreFile = _scoreManager.currentScoreFile; print("Saving score ${score.name} to ${scoreFile.path.split('/').last}..."); @@ -2474,9 +2436,8 @@ class _MyHomePageState extends State with TickerProviderStateMixin { if (delay == null) { Future.microtask(doSave); - } else { + } else Future.delayed(delay, doSave); - } } double beatScratchToolbarHeight(BuildContext context) => @@ -2698,8 +2659,8 @@ class _MyHomePageState extends State with TickerProviderStateMixin { setPartVolume: _setPartVolume, setColorboardPart: _setColorboardPart, setKeyboardPart: _setKeyboardPart, - colorboardPart: colorboardPart, - keyboardPart: keyboardPart, + colorboardPart: colorboardPart ?? Part(), + keyboardPart: keyboardPart ?? Part(), editingMelody: recordingMelody, hideMelodyView: _hideMusicView, availableWidth: availableWidth, @@ -2761,7 +2722,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { this.keyboardPart = score.parts.first; } if (part == this.colorboardPart) { - this.colorboardPart = null; + this.colorboardPart = score.parts.first; } score.sections.forEach((section) { section.melodies.removeWhere( @@ -2836,8 +2797,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { } : null, cloneCurrentSection: () { - if (currentSection.name == null || - currentSection.name.trim().isEmpty) { + if (currentSection.name.trim().isEmpty) { String prefix = "Section"; while (score.sections.any((s) => s.name.startsWith("$prefix "))) { prefix = "$prefix'"; @@ -2849,9 +2809,9 @@ class _MyHomePageState extends State with TickerProviderStateMixin { final match = RegExp( r"^(.*?)(\d*)\s*$", ).allMatches(section.name).first; - String prefix = match.group(1); + String prefix = match.group(1)!; prefix = prefix.trim(); - int number = int.tryParse(match.group(2)) ?? 1; + int number = int.tryParse(match.group(2)!) ?? 1; while (score.sections.any((s) => s.name == section.name)) { section.name = "$prefix ${++number}"; } @@ -3092,6 +3052,11 @@ class _MyHomePageState extends State with TickerProviderStateMixin { part: keyboardPart, height: _keyboardHeight, showConfiguration: _showKeyboardConfiguration, + hideConfiguration: () { + setState(() { + _showKeyboardConfiguration = false; + }); + }, sectionColor: sectionColor, pressedNotesNotifier: keyboardNotesNotifier, bluetoothControllerPressedNotes: bluetoothControllerPressedNotes, @@ -3119,6 +3084,12 @@ class _MyHomePageState extends State with TickerProviderStateMixin { part: colorboardPart, height: _colorboardHeight, showConfiguration: _showColorboardConfiguration, + hideConfiguration: () { + setState(() { + // showColorboard = false; + _showColorboardConfiguration = false; + }); + }, sectionColor: sectionColor, pressedNotesNotifier: colorboardNotesNotifier, distanceFromBottom: diff --git a/lib/main_menu.dart b/lib/main_menu.dart index 833a67d5..22485510 100644 --- a/lib/main_menu.dart +++ b/lib/main_menu.dart @@ -9,12 +9,12 @@ import 'export/export_ui.dart'; import 'generated/protos/protos.dart'; import 'widget/my_platform.dart'; -Future showMainMenu( - {@required BuildContext context, - @required RelativeRect position, - @required bool showDownloads, - @required Score currentScore, - @required String currentScoreName}) async { +Future showMainMenu( + {required BuildContext context, + required RelativeRect position, + required bool showDownloads, + required Score currentScore, + required String currentScoreName}) async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); return showMenu( @@ -44,21 +44,21 @@ Future showMainMenu( Expanded( child: SizedBox(), ), - if (packageInfo?.version != null) - Text('v${packageInfo?.version}', + if (packageInfo.version != null) + Text('v${packageInfo.version}', style: TextStyle( color: musicForegroundColor.withOpacity(0.5), fontWeight: FontWeight.w500, fontSize: 10)), - if (packageInfo?.version != null) SizedBox(width: 3), - if (packageInfo?.version != null) - Text('(${packageInfo?.buildNumber})', + if (packageInfo.version != null) SizedBox(width: 3), + if (packageInfo.version != null) + Text('(${packageInfo.buildNumber})', style: TextStyle( color: musicForegroundColor.withOpacity(0.5), fontWeight: FontWeight.w100, fontSize: 10)), - if (packageInfo?.version == null) - Text('(build ${packageInfo?.buildNumber})', + if (packageInfo.version == null) + Text('(build ${packageInfo.buildNumber})', style: TextStyle( color: musicForegroundColor.withOpacity(0.5), fontWeight: FontWeight.w100, diff --git a/lib/main_toolbars.dart b/lib/main_toolbars.dart index 438ac127..c7d51f85 100644 --- a/lib/main_toolbars.dart +++ b/lib/main_toolbars.dart @@ -1,22 +1,17 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'dart:ui'; import 'package:beatscratch_flutter_redux/edit_menu.dart'; import 'package:beatscratch_flutter_redux/main_menu.dart'; import 'package:beatscratch_flutter_redux/universe_view/universe_view.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'beatscratch_plugin.dart'; import 'cache_management.dart'; import 'colors.dart'; -import 'export/export.dart'; import 'generated/protos/music.pb.dart'; import 'messages/messages_ui.dart'; import 'settings/app_settings.dart'; @@ -25,7 +20,6 @@ import 'storage/score_picker.dart'; import 'storage/universe_manager.dart'; import 'storage/url_conversions.dart'; import 'ui_models.dart'; -import 'util/bs_methods.dart'; import 'util/music_theory.dart'; import 'util/util.dart'; import 'widget/my_buttons.dart'; @@ -37,7 +31,7 @@ class BeatScratchToolbar extends StatefulWidget { final UniverseManager universeManager; final Score score; final Section currentSection; - final Part currentPart; + final Part? currentPart; final ScoreManager scoreManager; final Function(ScorePickerMode) showScorePicker; final VoidCallback viewMode; @@ -62,10 +56,10 @@ class BeatScratchToolbar extends StatefulWidget { final bool vertical; final bool showSections; final bool verticalSections; - final Melody openMelody; - final Melody prevMelody; - final Part openPart; - final Part prevPart; + final Melody? openMelody; + final Melody? prevMelody; + final Part? openPart; + final Part? prevPart; final bool isMelodyViewOpen; final bool leftHalfOnly; final bool rightHalfOnly; @@ -76,49 +70,49 @@ class BeatScratchToolbar extends StatefulWidget { final BSMethod refreshUniverseData; final Function(Object) editObject; const BeatScratchToolbar( - {Key key, - @required this.appSettings, - @required this.universeManager, - @required this.interactionMode, - @required this.musicViewMode, - @required this.viewMode, - @required this.universeMode, - @required this.editMode, - @required this.toggleViewOptions, - @required this.sectionColor, - @required this.togglePlaying, - @required this.toggleSectionListDisplayMode, - @required this.setRenderingMode, - @required this.renderingMode, - @required this.showMidiInputSettings, - @required this.showBeatCounts, - @required this.toggleShowBeatCounts, - @required this.showScorePicker, - @required this.saveCurrentScore, - @required this.currentScoreName, - @required this.score, - @required this.pasteScore, - @required this.export, - @required this.scoreManager, - @required this.routeToCurrentScore, - @required this.vertical, - @required this.showSections, - @required this.verticalSections, - @required this.openMelody, - @required this.prevMelody, - @required this.openPart, - @required this.prevPart, - @required this.isMelodyViewOpen, - @required this.currentSection, - @required this.currentPart, - @required this.leftHalfOnly, - @required this.rightHalfOnly, - @required this.savingScore, // BeatScratchPlugin.isSynthesizerAvailable - @required this.messagesUI, - @required this.showDownloads, - @required this.toggleShowDownloads, - @required this.refreshUniverseData, - @required this.editObject}) + {Key? key, + required this.appSettings, + required this.universeManager, + required this.interactionMode, + required this.musicViewMode, + required this.viewMode, + required this.universeMode, + required this.editMode, + required this.toggleViewOptions, + required this.sectionColor, + required this.togglePlaying, + required this.toggleSectionListDisplayMode, + required this.setRenderingMode, + required this.renderingMode, + required this.showMidiInputSettings, + required this.showBeatCounts, + required this.toggleShowBeatCounts, + required this.showScorePicker, + required this.saveCurrentScore, + required this.currentScoreName, + required this.score, + required this.pasteScore, + required this.export, + required this.scoreManager, + required this.routeToCurrentScore, + required this.vertical, + required this.showSections, + required this.verticalSections, + required this.openMelody, + required this.prevMelody, + required this.openPart, + required this.prevPart, + required this.isMelodyViewOpen, + required this.currentSection, + required this.currentPart, + required this.leftHalfOnly, + required this.rightHalfOnly, + required this.savingScore, // BeatScratchPlugin.isSynthesizerAvailable + required this.messagesUI, + required this.showDownloads, + required this.toggleShowDownloads, + required this.refreshUniverseData, + required this.editObject}) : super(key: key); @override @@ -127,18 +121,17 @@ class BeatScratchToolbar extends StatefulWidget { class _BeatScratchToolbarState extends State with TickerProviderStateMixin { - AnimationController sectionRotationController; - Animation sectionOrPlayRotation; - AnimationController editController; - Animation editRotation; - Animation editTranslation; - Animation editScale; - AnimationController editRotationOnlyController; - Animation editRotationOnlyRotation; + late AnimationController sectionRotationController; + late Animation sectionOrPlayRotation; + late AnimationController editController; + late Animation editRotation; + late Animation editTranslation; + late Animation editScale; + late AnimationController editRotationOnlyController; + late Animation editRotationOnlyRotation; bool get hasMelody => widget.openMelody != null || widget.prevMelody != null; - bool get hasPart => - !hasMelody && widget.openPart != null || widget.prevPart != null; + bool get hasPart => !hasMelody || widget.prevPart != null; bool get hasDrumPart => hasPart && (widget.openPart?.isDrum ?? widget.prevPart?.isDrum ?? false); @@ -196,7 +189,7 @@ class _BeatScratchToolbarState extends State // clipboardTriggerTime.cancel(); } - Widget columnOrRow(BuildContext context, {List children}) { + Widget columnOrRow(BuildContext context, {required List children}) { if (widget.vertical) { return Column(children: children); } else { @@ -571,8 +564,8 @@ class _BeatScratchToolbarState extends State class _EditButton extends StatelessWidget { final Score score; final Section currentSection; - final Part currentPart, openPart, prevPart; - final Melody openMelody, prevMelody; + final Part? openPart, prevPart, currentPart; + final Melody? openMelody, prevMelody; final InteractionMode interactionMode; final MusicViewMode musicViewMode; final VoidCallback editMode; @@ -587,31 +580,31 @@ class _EditButton extends StatelessWidget { final Function(Object) editObject; const _EditButton({ - Key key, - this.score, - this.currentSection, - this.currentPart, + Key? key, + required this.score, + required this.currentSection, + required this.currentPart, this.openPart, this.prevPart, - this.openMelody, - this.prevMelody, - this.interactionMode, - this.musicViewMode, - this.editMode, - this.sectionColor, - this.editController, - this.vertical, - this.isMelodyViewOpen, - this.editRotation, - this.editTranslation, - this.editScale, - this.editRotationOnlyController, - this.editRotationOnlyRotation, - this.editObject, + required this.openMelody, + required this.prevMelody, + required this.interactionMode, + required this.musicViewMode, + required this.editMode, + required this.sectionColor, + required this.editController, + required this.vertical, + required this.isMelodyViewOpen, + required this.editRotation, + required this.editTranslation, + required this.editScale, + required this.editRotationOnlyController, + required this.editRotationOnlyRotation, + required this.editObject, }) : super(key: key); bool get hasMelody => openMelody != null || prevMelody != null; - bool get hasPart => !hasMelody && openPart != null || prevPart != null; + bool get hasPart => !hasMelody || prevPart != null; bool get hasDrumPart => hasPart && (openPart?.isDrum ?? prevPart?.isDrum ?? false); @@ -734,17 +727,17 @@ class _EditButton extends StatelessWidget { width: vertical ? 44 : 60, child: Text( openMelody != null - ? openMelody.canonicalName + ? openMelody!.canonicalName : openPart != null - ? openPart.midiName + ? openPart!.midiName : isMelodyViewOpen || (prevPart == null && prevMelody == null) ? currentSection.canonicalName : prevMelody != null - ? prevMelody.canonicalName + ? prevMelody!.canonicalName : prevPart != null - ? prevPart.midiName + ? prevPart!.midiName : "Oops", textAlign: TextAlign.center, maxLines: vertical ? 2 : 2, @@ -779,12 +772,12 @@ class _EditButton extends StatelessWidget { class SecondToolbar extends StatefulWidget { final AppSettings appSettings; - final VoidCallback toggleKeyboard; - final VoidCallback toggleColorboard; - final VoidCallback toggleKeyboardConfiguration; - final VoidCallback toggleColorboardConfiguration; + final VoidCallback? toggleKeyboard; + final VoidCallback? toggleColorboard; + final VoidCallback? toggleKeyboardConfiguration; + final VoidCallback? toggleColorboardConfiguration; final VoidCallback toggleTempoConfiguration; - final VoidCallback tempoLongPress; + final VoidCallback? tempoLongPress; final VoidCallback rewind; final bool recordingMelody; final bool showKeyboard; @@ -801,28 +794,28 @@ class SecondToolbar extends StatefulWidget { final Function(VoidCallback) setAppState; const SecondToolbar({ - Key key, - this.appSettings, - this.toggleKeyboard, - this.toggleColorboard, - this.showKeyboard, - this.showColorboard, - this.interactionMode, - this.showViewOptions, - this.showKeyboardConfiguration, - this.showColorboardConfiguration, - this.toggleKeyboardConfiguration, - this.toggleColorboardConfiguration, - this.sectionColor, - this.enableColorboard, - this.recordingMelody, - this.toggleTempoConfiguration, - this.showTempoConfiguration, - this.vertical, - this.visible, - this.tempoLongPress, - this.rewind, - this.setAppState, + Key? key, + required this.appSettings, + required this.toggleKeyboard, + required this.toggleColorboard, + required this.showKeyboard, + required this.showColorboard, + required this.interactionMode, + required this.showViewOptions, + required this.showKeyboardConfiguration, + required this.showColorboardConfiguration, + required this.toggleKeyboardConfiguration, + required this.toggleColorboardConfiguration, + required this.sectionColor, + required this.enableColorboard, + required this.recordingMelody, + required this.toggleTempoConfiguration, + required this.showTempoConfiguration, + required this.vertical, + required this.visible, + required this.tempoLongPress, + required this.rewind, + required this.setAppState, }) : super(key: key); @override @@ -831,9 +824,9 @@ class SecondToolbar extends StatefulWidget { class _SecondToolbarState extends State { DateTime lastMetronomeAudioToggleTime = DateTime(0); - double tempoButtonGestureStartMultiplier; - double tempoButtonGestureStartPosition; - Widget columnOrRow(BuildContext context, {List children}) { + double? tempoButtonGestureStartMultiplier; + double? tempoButtonGestureStartPosition; + Widget columnOrRow(BuildContext context, {List children = const []}) { if (widget.vertical) { return Column(children: children); } else { @@ -855,7 +848,7 @@ class _SecondToolbarState extends State { if (widget.enableColorboard) { numberOfButtons += 1; } - Widget createPlayIcon(IconData icon, {bool visible, Color color}) { + Widget createPlayIcon(IconData icon, {bool visible = true, Color? color}) { return AnimatedOpacity( opacity: visible ? 1 : 0, duration: animationDuration, @@ -1021,14 +1014,14 @@ class _SecondToolbarState extends State { onPressed: widget.toggleKeyboard, onLongPress: () { HapticFeedback.lightImpact(); - widget.toggleKeyboardConfiguration(); + widget.toggleKeyboardConfiguration?.call(); }, color: keyboardBackgroundColor, ))), ])); } - Widget tempoButton(BuildContext context, {Color backgroundColor}) { + Widget tempoButton(BuildContext context, {required Color backgroundColor}) { double sensitivity = 7; tempoDragStart(DragStartDetails details) { tempoButtonGestureStartPosition = @@ -1038,13 +1031,14 @@ class _SecondToolbarState extends State { tempoDragUpdate(DragUpdateDetails details) { final change = widget.vertical - ? -(details.localPosition.dy - tempoButtonGestureStartPosition) - : details.localPosition.dx - tempoButtonGestureStartPosition; + ? -(details.localPosition.dy - tempoButtonGestureStartPosition!) + : details.localPosition.dx - tempoButtonGestureStartPosition!; widget.setAppState(() { var startTempo = (BeatScratchPlugin.unmultipliedBpm * BeatScratchPlugin.bpmMultiplier) .toStringAsFixed(0); - double newMultiplier = tempoButtonGestureStartMultiplier + change / 250; + double newMultiplier = + tempoButtonGestureStartMultiplier! + change / 250; BeatScratchPlugin.bpmMultiplier = max(0.1, min(newMultiplier, 2)); var endTempo = (BeatScratchPlugin.unmultipliedBpm * BeatScratchPlugin.bpmMultiplier) @@ -1092,7 +1086,7 @@ class _SecondToolbarState extends State { final buttonBackgroundColor = (widget.showTempoConfiguration) ? Colors.white : backgroundColor; - final buttonForegroundColor = buttonBackgroundColor.textColor(); + final buttonForegroundColor = buttonBackgroundColor!.textColor(); return GestureDetector( onVerticalDragStart: widget.vertical ? tempoDragStart : null, onVerticalDragUpdate: @@ -1206,7 +1200,7 @@ class _SecondToolbarState extends State { onPressed: widget.toggleTempoConfiguration, onLongPress: () { HapticFeedback.lightImpact(); - widget.tempoLongPress(); + widget.tempoLongPress?.call(); }, )); } diff --git a/lib/messages/bs_message.dart b/lib/messages/bs_message.dart index 2006d312..a9f4f9ec 100644 --- a/lib/messages/bs_message.dart +++ b/lib/messages/bs_message.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; class BSMessage { - final String id; + final String? id; final Icon icon; final String message; final Duration timeout; bool visible = false; BSMessage({ - @required this.id, - @required this.message, + this.id, + required this.message, this.timeout = const Duration(milliseconds: 500), - @required this.icon, + required this.icon, }); } diff --git a/lib/messages/messages_ui.dart b/lib/messages/messages_ui.dart index 5a860c8b..7a91b88c 100644 --- a/lib/messages/messages_ui.dart +++ b/lib/messages/messages_ui.dart @@ -18,12 +18,12 @@ class MessagesUI { messages.where((m) => m.visible).length * _messageHeight(context); BSMessage sendMessage({ - @required message, + required message, icon, color, timeout = const Duration(milliseconds: 5500), bool isError = false, - String messageId, + String? messageId, bool andSetState = false, }) { if (icon == null) { @@ -31,9 +31,6 @@ class MessagesUI { ? Icon(Icons.warning, size: 18, color: color ?? chromaticSteps[7]) : Icon(Icons.info, size: 18, color: color ?? chromaticSteps[0]); } - if (messageId == null) { - messageId = uuid.v4(); - } final bsMessage = BSMessage( id: messageId, message: message, @@ -62,8 +59,8 @@ class MessagesUI { _removeMessage( BSMessage bsMessage, { - @required icon, - @required message, + required icon, + required message, }) { setAppState(() { bsMessage.visible = false; @@ -75,7 +72,7 @@ class MessagesUI { }); } - Widget build({@required BuildContext context}) { + Widget build({required BuildContext context}) { return Column( children: messages .map((m) => buildMessage(m, context: context)) @@ -84,7 +81,7 @@ class MessagesUI { Widget buildMessage( BSMessage message, { - @required BuildContext context, + required BuildContext context, }) { return AnimatedContainer( duration: animationDuration, diff --git a/lib/midi/byte_reader.dart b/lib/midi/byte_reader.dart new file mode 100644 index 00000000..fe13113b --- /dev/null +++ b/lib/midi/byte_reader.dart @@ -0,0 +1,95 @@ +import 'data_chunk.dart'; + +class ByteReader { + final List buffer; + int pos = 0; + bool get eof => this.pos >= buffer.length; + + ByteReader(this.buffer); + + int readUInt8() { + var result = this.buffer[this.pos]; + this.pos += 1; + return result; + } + + int readInt8() { + var u = this.readUInt8(); + if (u & 0x80 != 0) { + return u - 0x100; + } else + return u; + } + + int readUInt16() { + var b0 = this.readUInt8(); + var b1 = this.readUInt8(); + return (b0 << 8) + b1; + } + + int readInt16() { + var u = this.readUInt16(); + if (u & 0x8000 != 0) { + return u - 0x10000; + } else + return u; + } + + int readUInt24() { + var b0 = this.readUInt8(); + var b1 = this.readUInt8(); + var b2 = this.readUInt8(); + return (b0 << 16) + (b1 << 8) + b2; + } + + int readInt24() { + var u = this.readUInt16(); + if (u & 0x800000 != 0) { + return u - 0x1000000; + } else + return u; + } + + int readUInt32() { + var b0 = this.readUInt8(); + var b1 = this.readUInt8(); + var b2 = this.readUInt8(); + var b3 = this.readUInt8(); + + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; + } + + List readBytes(int len) { + var bytes = this.buffer.sublist(this.pos, this.pos + len); + this.pos += len; + return bytes; + } + + String readString(int len) { + var bytes = this.readBytes(len); + return String.fromCharCodes(bytes); + } + + int readVarInt() { + var result = 0; + while (!this.eof) { + var b = this.readUInt8(); + if (b & 0x80 != 0) { + result += (b & 0x7f); + result <<= 7; + } else { + // b is last byte + return result + b; + } + } + // premature eof + return result; + } + + DataChunk readChunk() { + var id = this.readString(4); + var length = this.readUInt32(); + var data = this.readBytes(length); + return DataChunk(id: id, length: length, bytes: data); + } +} diff --git a/lib/midi/byte_writer.dart b/lib/midi/byte_writer.dart new file mode 100644 index 00000000..27f8de32 --- /dev/null +++ b/lib/midi/byte_writer.dart @@ -0,0 +1,85 @@ +import 'data_chunk.dart'; + +class ByteWriter { + final List buffer = []; + int pos = 0; + bool get eof => this.pos >= buffer.length; + + ByteWriter(); + + void writeUInt8(int v) { + this.buffer.add(v & 0xFF); + } + + void writeInt8(int v) => writeUInt8(v); + + void writeUInt16(int v) { + var b0 = (v >> 8) & 0xFF, b1 = v & 0xFF; + + this.writeUInt8(b0); + this.writeUInt8(b1); + } + + void writeInt16(int v) => writeUInt16(v); + + void writeUInt24(int v) { + var b0 = (v >> 16) & 0xFF, b1 = (v >> 8) & 0xFF, b2 = v & 0xFF; + + this.writeUInt8(b0); + this.writeUInt8(b1); + this.writeUInt8(b2); + } + + void writeInt24(int v) => writeUInt24(v); + + void writeUInt32(int v) { + var b0 = (v >> 24) & 0xFF, + b1 = (v >> 16) & 0xFF, + b2 = (v >> 8) & 0xFF, + b3 = v & 0xFF; + + this.writeUInt8(b0); + this.writeUInt8(b1); + this.writeUInt8(b2); + this.writeUInt8(b3); + } + + void writeBytes(List bytes) { + this.buffer.addAll(bytes); + } + + void writeString(String str) { + int i, len = str.length; + List arr = []; + + for (i = 0; i < len; i++) { + arr.add(str.codeUnitAt(i)); + } + this.writeBytes(arr); + } + + void writeVarInt(int v) { + if (v < 0) throw "Cannot write negative variable-length integer"; + + if (v <= 0x7F) { + this.writeUInt8(v); + } else { + var i = v; + List bytes = []; + bytes.add(i & 0x7F); + i >>= 7; + while (i != 0) { + var b = i & 0x7F | 0x80; + bytes.add(b); + i >>= 7; + } + this.writeBytes(bytes.reversed.toList()); + } + } + + void writeChunk(DataChunk chunk) { + this.writeString(chunk.id); + this.writeUInt32(chunk.bytes.length); + this.writeBytes(chunk.bytes); + } +} diff --git a/lib/midi/data_chunk.dart b/lib/midi/data_chunk.dart new file mode 100644 index 00000000..e3b0c099 --- /dev/null +++ b/lib/midi/data_chunk.dart @@ -0,0 +1,6 @@ +class DataChunk { + final String id; + final int? length; + final List bytes; + DataChunk({required this.id, this.length, required this.bytes}); +} \ No newline at end of file diff --git a/lib/midi/midi_events.dart b/lib/midi/midi_events.dart new file mode 100644 index 00000000..b6363cab --- /dev/null +++ b/lib/midi/midi_events.dart @@ -0,0 +1,390 @@ +import 'dart:math'; + +import 'byte_writer.dart'; + +abstract class MidiEvent { + String type = ''; + int deltaTime = 0; + bool meta = false; + bool running = false; + + // ByteWriter stuff + int? lastEventTypeByte; + bool useByte9ForNoteOff = false; + + int writeEvent(ByteWriter w); +} + +class SequenceNumberEvent extends MidiEvent { + int number = 0; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x00); + w.writeVarInt(2); + w.writeUInt16(number); + return -1; + } +} + +class EndOfTrackEvent extends MidiEvent { + bool meta = true; + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x2F); + w.writeVarInt(0); + return -1; + } +} + +class ProgramChangeMidiEvent extends MidiEvent { + int channel = 0; + int programNumber = 0; + int writeEvent(ByteWriter w) { + int eventTypeByte = 0xC0 | channel; + if (eventTypeByte != lastEventTypeByte) w.writeUInt8(eventTypeByte); + w.writeUInt8(programNumber); + return eventTypeByte; + } +} + +class ChannelAfterTouchEvent extends MidiEvent { + int channel = 0; + int amount = 0; + + int writeEvent(ByteWriter w) { + var eventTypeByte = 0xD0 | channel; + if (eventTypeByte != lastEventTypeByte) w.writeUInt8(eventTypeByte); + w.writeUInt8(amount); + return eventTypeByte; + } +} + +class PitchBendEvent extends MidiEvent { + int channel = 0; + int value = + 0; // A pitch bend value from -8192 to 8191. Defaults to 0, or no bend. + + int writeEvent(ByteWriter w) { + var eventTypeByte = 0xE0 | channel; + if (eventTypeByte != lastEventTypeByte) w.writeUInt8(eventTypeByte); + var value14 = 0x2000 + value; + var lsb14 = (value14 & 0x7F); + var msb14 = (value14 >> 7) & 0x7F; + w.writeUInt8(lsb14); + w.writeUInt8(msb14); + return eventTypeByte; + } +} + +class ControllerEvent extends MidiEvent { + int controllerType = 0; + int channel = 0; + int value = 0; + int? number; + + int writeEvent(ByteWriter w) { + var eventTypeByte = 0xB0 | channel; + if (eventTypeByte != lastEventTypeByte) w.writeUInt8(eventTypeByte); + w.writeUInt8(controllerType); + w.writeUInt8(value); + return eventTypeByte; + } +} + +class NoteOnEvent extends MidiEvent { + int noteNumber = 60; + int velocity = 127; + int channel = 0; + bool byte9 = false; + + int writeEvent(ByteWriter w) { + var eventTypeByte = 0x90 | channel; + if (eventTypeByte != lastEventTypeByte) w.writeUInt8(eventTypeByte); + w.writeUInt8(noteNumber); + w.writeUInt8(velocity); + return eventTypeByte; + } +} + +class NoteAfterTouchEvent extends MidiEvent { + int noteNumber = 60; + int amount = 0; + int channel = 0; + + int writeEvent(ByteWriter w) { + var eventTypeByte = 0xA0 | channel; + if (eventTypeByte != lastEventTypeByte) w.writeUInt8(eventTypeByte); + w.writeUInt8(noteNumber); + w.writeUInt8(amount); + return eventTypeByte; + } +} + +class NoteOffEvent extends MidiEvent { + int noteNumber = 60; + int channel = 0; + int velocity = 0; + bool byte9 = false; + + int writeEvent(ByteWriter w) { + // Use 0x90 when opts.useByte9ForNoteOff is set and velocity is zero, or when event.byte9 is explicitly set on it. + // parseMidi will set event.byte9 for each event, so that we can get an exact copy by default. + // Explicitly set opts.useByte9ForNoteOff to false, to override event.byte9 and always use 0x80 for noteOff events. + var noteByte = ((useByte9ForNoteOff != false && byte9) || + (useByte9ForNoteOff && velocity == 0)) + ? 0x90 + : 0x80; + + var eventTypeByte = noteByte | channel; + if (eventTypeByte != lastEventTypeByte) w.writeUInt8(eventTypeByte); + w.writeUInt8(noteNumber); + w.writeUInt8(velocity); + return eventTypeByte; + } +} + +class TextEvent extends MidiEvent { + String text = ''; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x01); + w.writeVarInt(text.length); + w.writeString(text); + return -1; + } +} + +class CopyrightNoticeEvent extends MidiEvent { + String text = ''; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x02); + w.writeVarInt(text.length); + w.writeString(text); + return -1; + } +} + +class LyricsEvent extends MidiEvent { + String text = ''; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x05); + w.writeVarInt(text.length); + w.writeString(text); + return -1; + } +} + +class MarkerEvent extends MidiEvent { + String text = ''; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x06); + w.writeVarInt(text.length); + w.writeString(text); + return -1; + } +} + +class CuePointEvent extends MidiEvent { + String text = ''; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x07); + w.writeVarInt(text.length); + w.writeString(text); + return -1; + } +} + +class InstrumentNameEvent extends MidiEvent { + String text = ''; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x04); + w.writeVarInt(text.length); + w.writeString(text); + return -1; + } +} + +class TrackNameEvent extends MidiEvent { + String text = ''; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x03); + w.writeVarInt(text.length); + w.writeString(text); + return -1; + } +} + +class ChannelPrefixEvent extends MidiEvent { + int channel = 0; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x20); + w.writeVarInt(1); + w.writeUInt8(channel); + return -1; + } +} + +class PortPrefixEvent extends MidiEvent { + int port = 0; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x21); + w.writeVarInt(1); + w.writeUInt8(port); + return -1; + } +} + +class SetTempoEvent extends MidiEvent { + int microsecondsPerBeat = 500000; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x51); + w.writeVarInt(3); + w.writeUInt24(microsecondsPerBeat); + return -1; + } +} + +class SequencerSpecificEvent extends MidiEvent { + List data = []; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x7F); + w.writeVarInt(data.length); + w.writeBytes(data); + + return -1; + } +} + +class SystemExclusiveEvent extends MidiEvent { + List data = []; + int writeEvent(ByteWriter w) { + w.writeUInt8(0xF0); + w.writeVarInt(data.length); + w.writeBytes(data); + + return -1; + } +} + +class EndSystemExclusiveEvent extends MidiEvent { + List data = []; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xF7); + w.writeVarInt(data.length); + w.writeBytes(data); + return -1; + } +} + +class UnknownMetaEvent extends MidiEvent { + bool meta = true; + + List data = []; + int? metatypeByte; + + int writeEvent(ByteWriter w) { + if (metatypeByte != null) { + w.writeUInt8(0xFF); + w.writeUInt8(metatypeByte!); + w.writeVarInt(data.length); + w.writeBytes(data); + } + + return -1; + } +} + +class SmpteOffsetEvent extends MidiEvent { + bool meta = true; + + int frameRate = 24; + int hour = 0; + int min = 0; + int sec = 0; + int frame = 0; + int subFrame = 0; + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x54); + w.writeVarInt(5); + var frameRates = {24: 0x00, 25: 0x20, 29: 0x40, 30: 0x60}; + var hourByte = (hour & 0x1F) | frameRates[frameRate]!; + w.writeUInt8(hourByte); + w.writeUInt8(min); + w.writeUInt8(sec); + w.writeUInt8(frame); + w.writeUInt8(subFrame); + return -1; + } +} + +class TimeSignatureEvent extends MidiEvent { + bool meta = true; + + int numerator = 4; + int denominator = 4; + int metronome = 18; + int thirtyseconds = 8; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x58); + w.writeVarInt(4); + w.writeUInt8(numerator); + var _denominator = (log(denominator) / ln2).floor() & 0xFF; + w.writeUInt8(_denominator); + w.writeUInt8(metronome); + w.writeUInt8(thirtyseconds); + return -1; + } +} + +class KeySignatureEvent extends MidiEvent { + int key = 0; + int scale = 0; + bool meta = true; + + int writeEvent(ByteWriter w) { + w.writeUInt8(0xFF); + w.writeUInt8(0x59); + w.writeVarInt(2); + w.writeInt8(key); + w.writeUInt8(scale); + return -1; + } +} diff --git a/lib/midi/midi_file.dart b/lib/midi/midi_file.dart new file mode 100644 index 00000000..584e9690 --- /dev/null +++ b/lib/midi/midi_file.dart @@ -0,0 +1,8 @@ +import 'midi_events.dart'; +import 'midi_header.dart'; + +class MidiFile { + final List> tracks; + final MidiHeader header; + MidiFile(this.tracks, this.header); +} diff --git a/lib/midi/midi_header.dart b/lib/midi/midi_header.dart new file mode 100644 index 00000000..53180c45 --- /dev/null +++ b/lib/midi/midi_header.dart @@ -0,0 +1,16 @@ +class MidiHeader { + final int? framesPerSecond; + final int? ticksPerBeat; + final int? ticksPerFrame; + final int numTracks; + final int format; + final int? timeDivision; + + MidiHeader( + {this.framesPerSecond, + this.ticksPerBeat, + required this.format, + required this.numTracks, + this.ticksPerFrame, + this.timeDivision}); +} \ No newline at end of file diff --git a/lib/midi/midi_parser.dart b/lib/midi/midi_parser.dart new file mode 100644 index 00000000..12cad9f6 --- /dev/null +++ b/lib/midi/midi_parser.dart @@ -0,0 +1,342 @@ +import 'byte_reader.dart'; +import 'midi_events.dart'; +import 'midi_file.dart'; +import 'midi_header.dart'; +import 'dart:io'; + +/// MidiParser is a class responsible of parsing MIDI data into dart objects +class MidiParser { + int? _lastEventTypeByte; + + MidiParser(); + + /// Reads a midi file from provided [buffer] + /// + /// Returns parsed [MidiFile] + MidiFile parseMidiFromBuffer(List buffer) { + var p = new ByteReader(buffer); + + var headerChunk = p.readChunk(); + if (headerChunk.id != 'MThd') + throw "Bad MIDI file. Expected 'MHdr', got: '${headerChunk.id}'"; + var header = parseHeader(headerChunk.bytes); + + List> tracks = []; + for (var i = 0; !p.eof && i < header.numTracks; i++) { + var trackChunk = p.readChunk(); + if (trackChunk.id != 'MTrk') + throw "Bad MIDI file. Expected 'MTrk', got: '${trackChunk.id}'"; + var track = parseTrack(trackChunk.bytes); + tracks.add(track); + } + + return MidiFile(tracks, header); + } + + /// Reads a provided byte [data] into [MidiHeader] + MidiHeader parseHeader(List data) { + final ByteReader p = ByteReader(data); + + final int format = p.readUInt16(); + final int numTracks = p.readUInt16(); + int? framesPerSecond; + int? ticksPerFrame; + int? ticksPerBeat; + + final int timeDivision = p.readUInt16(); + if (timeDivision & 0x8000 != 0) { + framesPerSecond = 0x100 - (timeDivision >> 8); + ticksPerFrame = timeDivision & 0xFF; + } else { + ticksPerBeat = timeDivision; + } + + return MidiHeader( + format: format, + framesPerSecond: framesPerSecond, + numTracks: numTracks, + ticksPerBeat: ticksPerBeat, + ticksPerFrame: ticksPerFrame, + ); + } + + /// Parses provided [file] and returns [MidiFile] + MidiFile parseMidiFromFile(File file) { + return parseMidiFromBuffer(file.readAsBytesSync()); + } + + /// Reads event from provided [p] and returns parsed [MidiEvent] + MidiEvent readEvent(ByteReader p) { + var deltaTime = p.readVarInt(); + + var eventTypeByte = p.readUInt8(); + + if ((eventTypeByte & 0xf0) == 0xf0) { + // system / meta event + if (eventTypeByte == 0xff) { + // meta event + final int metatypeByte = p.readUInt8(); + final int length = p.readVarInt(); + switch (metatypeByte) { + case 0x00: + final SequenceNumberEvent event = SequenceNumberEvent(); + event.deltaTime = deltaTime; + event.type = 'sequenceNumber'; + if (length != 2) + throw 'Expected length for sequenceNumber event is 2, got ${length.toString()}'; + event.number = p.readUInt16(); + return event; + case 0x01: + var event = TextEvent(); + event.type = 'text'; + event.deltaTime = deltaTime; + event.text = p.readString(length); + return event; + case 0x02: + var event = CopyrightNoticeEvent(); + event.type = 'copyrightNotice'; + event.deltaTime = deltaTime; + event.text = p.readString(length); + return event; + case 0x03: + var event = TrackNameEvent(); + event.type = 'trackName'; + event.deltaTime = deltaTime; + event.text = p.readString(length); + return event; + case 0x04: + var event = InstrumentNameEvent(); + event.type = 'instrumentName'; + event.deltaTime = deltaTime; + event.text = p.readString(length); + return event; + case 0x05: + var event = LyricsEvent(); + event.type = 'lyrics'; + event.deltaTime = deltaTime; + event.text = p.readString(length); + return event; + case 0x06: + var event = MarkerEvent(); + event.type = 'marker'; + event.deltaTime = deltaTime; + event.text = p.readString(length); + return event; + case 0x07: + var event = CuePointEvent(); + event.type = 'cuePoint'; + event.deltaTime = deltaTime; + event.text = p.readString(length); + return event; + case 0x20: + var event = ChannelPrefixEvent(); + event.type = 'channelPrefix'; + event.deltaTime = deltaTime; + if (length != 1) + throw 'Expected length for channelPrefix event is 1, got ${length.toString()}'; + event.deltaTime = deltaTime; + event.channel = p.readUInt8(); + return event; + case 0x21: + var event = PortPrefixEvent(); + event.type = 'portPrefix'; + + event.deltaTime = deltaTime; + if (length != 1) + throw 'Expected length for portPrefix event is 1, got ${length.toString()}'; + event.port = p.readUInt8(); + return event; + case 0x2f: + var event = EndOfTrackEvent(); + event.deltaTime = deltaTime; + event.type = 'endOfTrack'; + if (length != 0) + throw 'Expected length for endOfTrack event is 0, got ${length.toString()}'; + return event; + case 0x51: + final event = SetTempoEvent(); + event.deltaTime = deltaTime; + event.type = 'setTempo'; + ; + if (length != 3) + throw 'Expected length for setTempo event is 3, got ${length.toString()}'; + event.microsecondsPerBeat = p.readUInt24(); + return event; + case 0x54: + var event = SmpteOffsetEvent(); + event.deltaTime = deltaTime; + event.type = 'smpteOffset'; + if (length != 5) + throw 'Expected length for smpteOffset event is 5, got ${length.toString()}'; + var hourByte = p.readUInt8(); + var frameRates = {0x00: 24, 0x20: 25, 0x40: 29, 0x60: 30}; + event.frameRate = frameRates[hourByte & 0x60]!; + event.hour = hourByte & 0x1f; + event.min = p.readUInt8(); + event.sec = p.readUInt8(); + event.frame = p.readUInt8(); + event.subFrame = p.readUInt8(); + return event; + case 0x58: + var event = TimeSignatureEvent(); + event.deltaTime = deltaTime; + event.type = 'timeSignature'; + if (length != 4) + throw 'Expected length for timeSignature event is 4, got ${length.toString()}'; + event.numerator = p.readUInt8(); + event.denominator = (1 << p.readUInt8()); + event.metronome = p.readUInt8(); + event.thirtyseconds = p.readUInt8(); + return event; + case 0x59: + var event = KeySignatureEvent(); + event.deltaTime = deltaTime; + event.type = 'keySignature'; + if (length != 2) + throw 'Expected length for keySignature event is 2, got ${length.toString()}'; + event.key = p.readInt8(); + event.scale = p.readUInt8(); + return event; + case 0x7f: + var event = SequencerSpecificEvent(); + event.deltaTime = deltaTime; + event.type = 'sequencerSpecific'; + event.data = p.readBytes(length); + return event; + default: + var event = UnknownMetaEvent(); + event.deltaTime = deltaTime; + event.type = 'unknownMeta'; + event.data = p.readBytes(length); + event.metatypeByte = metatypeByte; + return event; + } + } else if (eventTypeByte == 0xf0) { + var event = SystemExclusiveEvent(); + event.deltaTime = deltaTime; + event.type = 'sysEx'; + var length = p.readVarInt(); + event.data = p.readBytes(length); + return event; + } else if (eventTypeByte == 0xf7) { + var event = EndSystemExclusiveEvent(); + event.deltaTime = deltaTime; + event.type = 'endSysEx'; + var length = p.readVarInt(); + event.data = p.readBytes(length); + return event; + } else { + throw 'Unrecognised MIDI event type byte: ${eventTypeByte.toString()}'; + } + } else { + // channel event + int param1; + bool running = false; + if ((eventTypeByte & 0x80) == 0) { + // running status - reuse lastEventTypeByte as the event type. + // eventTypeByte is actually the first parameter + if (_lastEventTypeByte == null) + throw "Running status byte encountered before status byte"; + param1 = eventTypeByte; + eventTypeByte = _lastEventTypeByte!; + running = true; + } else { + param1 = p.readUInt8(); + _lastEventTypeByte = eventTypeByte; + } + var eventType = eventTypeByte >> 4; + var channel = eventTypeByte & 0x0f; + switch (eventType) { + case 0x08: + var event = NoteOffEvent(); + event.deltaTime = deltaTime; + event.type = 'noteOff'; + event.running = running; + event.channel = channel; + event.noteNumber = param1; + event.velocity = p.readUInt8(); + return event; + case 0x09: + var velocity = p.readUInt8(); + if (velocity == 0) { + var event = NoteOffEvent(); + event.deltaTime = deltaTime; + event.channel = channel; + event.type = 'noteOff'; + event.noteNumber = param1; + event.velocity = velocity; + event.running = running; + if (velocity == 0) event.byte9 = true; + return event; + } else { + var event = NoteOnEvent(); + event.deltaTime = deltaTime; + event.type = 'noteOn'; + + event.channel = channel; + event.running = running; + event.noteNumber = param1; + event.velocity = velocity; + if (velocity == 0) event.byte9 = true; + return event; + } + case 0x0a: + var event = NoteAfterTouchEvent(); + event.channel = channel; + event.deltaTime = deltaTime; + event.noteNumber = param1; + event.running = running; + event.amount = p.readUInt8(); + return event; + case 0x0b: + var event = ControllerEvent(); + event.channel = channel; + event.running = running; + event.deltaTime = deltaTime; + event.type = 'controller'; + event.controllerType = param1; + event.value = p.readUInt8(); + return event; + case 0x0c: + var event = ProgramChangeMidiEvent(); + event.channel = channel; + event.deltaTime = deltaTime; + event.type = 'programChange'; + event.programNumber = param1; + event.running = running; + return event; + case 0x0d: + var event = ChannelAfterTouchEvent(); + event.channel = channel; + event.deltaTime = deltaTime; + event.type = 'channelAftertouch'; + event.amount = param1; + event.running = running; + return event; + case 0x0e: + var event = PitchBendEvent(); + event.channel = channel; + event.deltaTime = deltaTime; + event.running = running; + event.type = 'pitchBend'; + event.value = (param1 + (p.readUInt8() << 7)) - 0x2000; + return event; + default: + throw 'Unrecognised MIDI event type: ${eventType.toString()}'; + } + } + } + + /// Parses provided [data] and returns a list of [MidiEvent] + List parseTrack(List data) { + var p = new ByteReader(data); + + List events = []; + while (!p.eof) { + var event = readEvent(p); + events.add(event); + } + + return events; + } +} diff --git a/lib/midi/midi_writer.dart b/lib/midi/midi_writer.dart new file mode 100644 index 00000000..e1a03439 --- /dev/null +++ b/lib/midi/midi_writer.dart @@ -0,0 +1,80 @@ +import 'byte_writer.dart'; +import 'data_chunk.dart'; +import 'midi_events.dart'; +import 'midi_file.dart'; +import 'midi_header.dart'; +import 'dart:io'; + +class MidiWriter { + MidiWriter(); + + /// Converts a [MidiFile] to byte buffer represented by [List] + /// + /// [running] reuse previous eventTypeByte when possible, to compress file + /// [useByte9ForNoteOff] use 0x09 for noteOff when velocity is zero + List writeMidiToBuffer(MidiFile file, + {bool running = false, bool useByte9ForNoteOff = false}) { + var w = new ByteWriter(); + writeHeader(w, file.header, file.tracks.length); + + file.tracks.forEach((f) => writeTrack(w, f)); + + return w.buffer; + } + + /// Writes [midiFile] as bytes into a provided [file] + /// + /// [running] reuse previous eventTypeByte when possible, to compress file + /// [useByte9ForNoteOff] use 0x09 for noteOff when velocity is zero + void writeMidiToFile(MidiFile midiFile, File file, + {bool running = false, bool useByte9ForNoteOff = false}) { + var bytes = this.writeMidiToBuffer(midiFile); + file.writeAsBytesSync(bytes); + } + + /// Writes a midi track + void writeTrack(ByteWriter w, List track, + {bool running = false, bool useByte9ForNoteOff = false}) { + var t = new ByteWriter(); + int i, len = track.length; + int? eventTypeByte; + for (i = 0; i < len; i++) { + // Reuse last eventTypeByte when opts.running is set, or event.running is explicitly set on it. + // parseMidi will set event.running for each event, so that we can get an exact copy by default. + // Explicitly set opts.running to false, to override event.running and never reuse last eventTypeByte. + if (running == false || !running && !track[i].running) + eventTypeByte = null; + + var event = track[i]; + event.lastEventTypeByte = eventTypeByte; + event.useByte9ForNoteOff = useByte9ForNoteOff; + + t.writeVarInt(event.deltaTime); + eventTypeByte = event.writeEvent(t); + } + + w.writeChunk(DataChunk(id: 'MTrk', bytes: t.buffer)); + } + + /// Writes provided [header] into [w] + void writeHeader(ByteWriter w, MidiHeader header, int numTracks) { + int format = header.format; + + var timeDivision = 128; + if (header.timeDivision != null) { + timeDivision = header.timeDivision!; + } else if (header.ticksPerFrame != null && header.framesPerSecond != null) { + timeDivision = (-(header.framesPerSecond! & 0xFF) << 8) | + (header.ticksPerFrame! & 0xFF); + } else if (header.ticksPerBeat != null && header.ticksPerBeat != 0) { + timeDivision = header.ticksPerBeat! & 0x7FFF; + } + + var h = new ByteWriter(); + h.writeUInt16(format); + h.writeUInt16(numTracks); + h.writeUInt16(timeDivision); + + w.writeChunk(DataChunk(id: 'MThd', bytes: h.buffer)); + } +} diff --git a/lib/music_preview/melody_preview.dart b/lib/music_preview/melody_preview.dart index f9c14b9d..98420282 100644 --- a/lib/music_preview/melody_preview.dart +++ b/lib/music_preview/melody_preview.dart @@ -1,5 +1,4 @@ import '../util/bs_methods.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../generated/protos/music.pb.dart'; @@ -17,10 +16,10 @@ class MelodyPreview extends StatefulWidget { final double scale; const MelodyPreview({ - Key key, - this.section, - this.melody, - this.part, + Key? key, + required this.section, + required this.melody, + required this.part, this.width = 300, this.height = 100, this.scale = 0.15, @@ -31,9 +30,9 @@ class MelodyPreview extends StatefulWidget { static Gradient generateVolumeDecoration( MelodyReference reference, Section section, - {@required bool isSelectedMelody, - @required Color bgColor, - @required Color sectionColor}) { + {required bool isSelectedMelody, + required Color bgColor, + required Color sectionColor}) { if (reference.isEnabled) { Color volumeColor = isSelectedMelody ? Colors.white @@ -78,27 +77,25 @@ class MelodyPreview extends StatefulWidget { } class _MelodyPreviewState extends State { - String lastPreviewKey; - Score preview; - BSMethod notifyUpdate; + late String lastPreviewKey; + late Score preview; + late BSMethod notifyUpdate; String get previewKey => - "${widget.melody.id}-${widget.melody.hashCode}|${widget.part?.id ?? "null"}|" + + "${widget.melody.id}-${widget.melody.hashCode}|${widget.part.id}|" + "${widget.section.id}-${widget.section.hashCode}"; @override initState() { super.initState(); - preview = melodyPreview(widget.melody ?? Melody(), widget.part ?? Part(), - widget.section ?? Section()); + preview = melodyPreview(widget.melody, widget.part, widget.section); notifyUpdate = BSMethod(); } @override Widget build(BuildContext context) { if (lastPreviewKey != previewKey) { - preview = melodyPreview(widget.melody ?? Melody(), widget.part ?? Part(), - widget.section ?? Section()); + preview = melodyPreview(widget.melody, widget.part, widget.section); lastPreviewKey = previewKey; notifyUpdate(); } diff --git a/lib/music_preview/part_preview.dart b/lib/music_preview/part_preview.dart index 57c25444..e56d18e6 100644 --- a/lib/music_preview/part_preview.dart +++ b/lib/music_preview/part_preview.dart @@ -1,12 +1,9 @@ import '../util/bs_methods.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../generated/protos/music.pb.dart'; import 'score_preview.dart'; import '../util/dummydata.dart'; -import '../util/music_theory.dart'; -import '../colors.dart'; class PartPreview extends StatefulWidget { final Section section; @@ -17,10 +14,10 @@ class PartPreview extends StatefulWidget { final double scale; const PartPreview({ - Key key, - this.section, - this.score, - this.part, + Key? key, + required this.section, + required this.score, + required this.part, this.width = 300, this.height = 100, this.scale = 0.15, @@ -31,9 +28,9 @@ class PartPreview extends StatefulWidget { } class _PartPreviewState extends State { - String lastPreviewKey; - Score preview; - BSMethod notifyUpdate; + String? lastPreviewKey; + late Score preview; + late BSMethod notifyUpdate; String get previewKey => "${widget.section.id}-${widget.section.hashCode}"; diff --git a/lib/music_preview/preview_renderer.dart b/lib/music_preview/preview_renderer.dart index 3732b17d..4b012c6b 100644 --- a/lib/music_preview/preview_renderer.dart +++ b/lib/music_preview/preview_renderer.dart @@ -1,10 +1,9 @@ -import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; import 'package:beatscratch_flutter_redux/settings/app_settings.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../colors.dart'; @@ -25,14 +24,14 @@ class MusicPreviewRenderer { Score get score => Score.fromBuffer(scoreData); MusicPreviewRenderer( - {@required this.scoreData, - @required this.scale, - @required this.width, - @required this.height, - @required this.renderSections, - @required this.renderPartNames, - @required this.musicViewMode, - this.renderColor}); + {required this.scoreData, + required this.scale, + required this.width, + required this.height, + required this.renderSections, + required this.renderPartNames, + required this.musicViewMode, + required this.renderColor}); MusicSystemPainter get painter { final parts = score.parts; @@ -53,15 +52,19 @@ class MusicPreviewRenderer { partTopOffsets: ValueNotifier(partTopOffsets), staffOffsets: ValueNotifier(staffOffsets), colorGuideOpacityNotifier: ValueNotifier(0), + tappedBeat: ValueNotifier(null), + tappedPart: ValueNotifier(null), + bluetoothControllerPressedNotes: ValueNotifier(Map()), colorblockOpacityNotifier: ValueNotifier( AppSettings.globalRenderingMode == RenderingMode.colorblock ? 1 : 0), notationOpacityNotifier: ValueNotifier( AppSettings.globalRenderingMode == RenderingMode.notation ? 1 : 0), colorboardNotesNotifier: ValueNotifier([]), keyboardNotesNotifier: ValueNotifier([]), - visibleRect: () => Rect.fromLTRB(0, 0, width / scale, height / scale), + visibleRect: () => + RectRendering.fromLTRB(0, 0, width / scale, height / scale), verticallyVisibleRect: () => - Rect.fromLTRB(0, 0, width / scale, height / scale), + RectRendering.fromLTRB(0, 0, width / scale, height / scale), keyboardPart: ValueNotifier(null), colorboardPart: ValueNotifier(null), focusedPart: ValueNotifier(null), @@ -78,7 +81,7 @@ class MusicPreviewRenderer { double get maxWidth => (extraBeatsSpaceForClefs + score.beatCount) * beatWidth * scale; double get actualWidth => min(maxWidth, width); - Future get renderedScoreImage async { + Future get renderedScoreImage async { if (height < 1 || width < 1) { return null; } @@ -96,9 +99,7 @@ class MusicPreviewRenderer { canvas.scale(_overSampleScale); // await () async { final originalForegroundColor = musicForegroundColor; - if (renderColor != null) { - musicForegroundColor = renderColor; - } + musicForegroundColor = renderColor; painter.paint(canvas, size); musicForegroundColor = originalForegroundColor; // }; @@ -109,12 +110,9 @@ class MusicPreviewRenderer { return data; } - Future get renderedScoreImageData async { + Future get renderedScoreImageData async { final image = await renderedScoreImage; - if (image == null) { - return null; - } return Uint8List.sublistView( - await image.toByteData(format: ui.ImageByteFormat.png)); + (await image?.toByteData(format: ui.ImageByteFormat.png))!); } } diff --git a/lib/music_preview/score_preview.dart b/lib/music_preview/score_preview.dart index c3d1c5e2..2b3a5a5e 100644 --- a/lib/music_preview/score_preview.dart +++ b/lib/music_preview/score_preview.dart @@ -1,5 +1,4 @@ import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -8,14 +7,11 @@ import 'package:dcache/dcache.dart'; import '../colors.dart'; import '../generated/protos/music.pb.dart'; -import '../music_view/music_system_painter.dart'; import '../settings/app_settings.dart'; import '../ui_models.dart'; -import '../util/bs_methods.dart'; import '../util/music_notation_theory.dart'; import '../util/music_theory.dart'; import '../util/util.dart'; -import '../widget/my_platform.dart'; import 'preview_renderer.dart'; class ScorePreview extends StatefulWidget { @@ -27,17 +23,17 @@ class ScorePreview extends StatefulWidget { final double height; final double scale; final BSMethod notifyUpdate; - final Color renderColor; + final Color? renderColor; const ScorePreview(this.score, - {Key key, + {Key? key, this.width = 300, this.height = 100, this.scale = 0.15, this.musicViewMode = MusicViewMode.score, this.renderPartNames = true, this.renderSections = true, - this.notifyUpdate, + required this.notifyUpdate, this.renderColor}) : super(key: key); @@ -48,22 +44,22 @@ class ScorePreview extends StatefulWidget { enum _Thumbnail { a, b } class _ScorePreviewState extends State { - bool hasBuilt; - String _prevScoreId; - RenderingMode _prevRenderingMode; - double _prevScale, _prevWidth, _prevHeight; - Color _prevRenderColor; - _Thumbnail currentThumbnail; - Uint8List thumbnailA, thumbnailB; - - Uint8List get currentThumbnailData => + bool hasBuilt = false; + String? _prevScoreId; + RenderingMode _prevRenderingMode = RenderingMode.notation; + late double _prevScale, _prevWidth, _prevHeight; + late Color _prevRenderColor; + late _Thumbnail currentThumbnail; + Uint8List? thumbnailA, thumbnailB; + + Uint8List? get currentThumbnailData => currentThumbnail == _Thumbnail.a ? thumbnailA : thumbnailB; - set currentThumbnailData(Uint8List value) => currentThumbnail == _Thumbnail.a + set currentThumbnailData(Uint8List? value) => currentThumbnail == _Thumbnail.a ? thumbnailA = value : thumbnailB = value; - Uint8List get otherThumbnailData => + Uint8List? get otherThumbnailData => currentThumbnail == _Thumbnail.b ? thumbnailA : thumbnailB; - set otherThumbnailData(Uint8List value) => currentThumbnail == _Thumbnail.b + set otherThumbnailData(Uint8List? value) => currentThumbnail == _Thumbnail.b ? thumbnailA = value : thumbnailB = value; @@ -84,13 +80,13 @@ class _ScorePreviewState extends State { : 0 : 0; - double renderableWidth; + double renderableWidth = 1; bool disposed = false; @override initState() { super.initState(); currentThumbnail = _Thumbnail.a; - widget.notifyUpdate?.addListener(_updateScoreImage); + widget.notifyUpdate.addListener(_updateScoreImage); renderableWidth = actualWidth; _prevRenderColor = widget.renderColor ?? musicForegroundColor; _prevRenderingMode = AppSettings.globalRenderingMode; @@ -101,7 +97,7 @@ class _ScorePreviewState extends State { @override dispose() { - widget.notifyUpdate?.removeListener(_updateScoreImage); + widget.notifyUpdate.removeListener(_updateScoreImage); disposed = true; super.dispose(); } @@ -130,11 +126,15 @@ class _ScorePreviewState extends State { AnimatedOpacity( opacity: thumbnailAOpacity, duration: animationDuration, - child: thumbnailA == null ? SizedBox() : Image.memory(thumbnailA)), + child: thumbnailA == null + ? SizedBox() + : Image.memory(thumbnailA ?? Uint8List(0))), AnimatedOpacity( opacity: thumbnailBOpacity, duration: animationDuration, - child: thumbnailB == null ? SizedBox() : Image.memory(thumbnailB)) + child: thumbnailB == null + ? SizedBox() + : Image.memory(thumbnailB ?? Uint8List(0))) ]), ); } @@ -146,7 +146,7 @@ class _ScorePreviewState extends State { storage: new InMemoryStorage(500)) ..loader = (key, oldValue) async => oldValue ?? - await MusicPreviewRenderer( + (await MusicPreviewRenderer( scoreData: key.arguments[0].writeToBuffer(), scale: key.arguments[1], width: key.arguments[2], @@ -155,7 +155,7 @@ class _ScorePreviewState extends State { renderPartNames: key.arguments[5], musicViewMode: key.arguments[6], renderColor: key.arguments[7], - ).renderedScoreImageData; + ).renderedScoreImageData)!; ArgumentList get renderingArguments => ArgumentList([ widget.score, @@ -171,13 +171,6 @@ class _ScorePreviewState extends State { _updateScoreImage() { Future.delayed(animationDuration, () async { final Uint8List data = RENDER_CACHE.get(renderingArguments); - if (data == null) { - // print("Ummm this bad"); - if (widget.width != 0 && widget.height != 0) { - _updateScoreImage(); - } - return; - } if (disposed) return; setState(() { if (actualWidth > renderableWidth) { diff --git a/lib/music_preview/section_preview.dart b/lib/music_preview/section_preview.dart index 1a4f1d10..733fa700 100644 --- a/lib/music_preview/section_preview.dart +++ b/lib/music_preview/section_preview.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import '../generated/protos/music.pb.dart'; import 'score_preview.dart'; import '../util/dummydata.dart'; -import '../util/music_theory.dart'; import '../colors.dart'; class SectionPreview extends StatefulWidget { @@ -16,9 +15,9 @@ class SectionPreview extends StatefulWidget { final double scale; const SectionPreview({ - Key key, - this.section, - this.score, + Key? key, + required this.section, + required this.score, this.width = 300, this.height = 100, this.scale = 0.15, @@ -29,9 +28,9 @@ class SectionPreview extends StatefulWidget { } class _SectionPreviewState extends State { - String lastPreviewKey; - Score preview; - BSMethod notifyUpdate; + String? lastPreviewKey; + late Score preview; + late BSMethod notifyUpdate; String get previewKey => "${widget.section.id}-${widget.section.hashCode}"; diff --git a/lib/music_view/melody_editing_toolbar.dart b/lib/music_view/melody_editing_toolbar.dart index 4969f477..c8732a0c 100644 --- a/lib/music_view/melody_editing_toolbar.dart +++ b/lib/music_view/melody_editing_toolbar.dart @@ -1,3 +1,5 @@ +import 'package:collection/collection.dart'; + import '../cache_management.dart'; import '../colors.dart'; import '../generated/protos/music.pb.dart'; @@ -12,31 +14,31 @@ import '../widget/incrementable_value.dart'; import '../widget/my_buttons.dart'; class MelodyEditingToolbar extends StatefulWidget { - final String melodyId; + final String? melodyId; final Score score; final Color sectionColor; final Section currentSection; final bool recordingMelody; final bool visible; - final ValueNotifier highlightedBeat; + final ValueNotifier highlightedBeat; final Function(MelodyReference, double) setReferenceVolume; final VoidCallback toggleRecording; - Melody get melody => score.parts + Melody? get melody => score.parts .expand((p) => p.melodies) - .firstWhere((m) => m.id == melodyId, orElse: () => null); + .firstWhereOrNull((m) => m.id == melodyId); const MelodyEditingToolbar( - {Key key, - @required this.melodyId, - @required this.sectionColor, - @required this.score, - @required this.currentSection, - @required this.highlightedBeat, - @required this.setReferenceVolume, - @required this.recordingMelody, - @required this.visible, - @required this.toggleRecording}) + {Key? key, + required this.melodyId, + required this.sectionColor, + required this.score, + required this.currentSection, + required this.highlightedBeat, + required this.setReferenceVolume, + required this.recordingMelody, + required this.visible, + required this.toggleRecording}) : super(key: key); @override @@ -45,9 +47,9 @@ class MelodyEditingToolbar extends StatefulWidget { class _MelodyEditingToolbarState extends State with TickerProviderStateMixin { - AnimationController animationController; - Color recordingAnimationColor; - Animation recordingAnimation; + late AnimationController animationController; + Color? recordingAnimationColor; + late Animation recordingAnimation; bool showHoldToClear = false; bool showDataCleared = false; bool animationStarted = false; @@ -89,19 +91,24 @@ class _MelodyEditingToolbarState extends State animationStarted = false; } Color recordingColor; - if (widget.recordingMelody && BeatScratchPlugin.playing) { - recordingColor = recordingAnimationColor; + if (widget.recordingMelody && + BeatScratchPlugin.playing && + recordingAnimationColor != null) { + recordingColor = recordingAnimationColor!; } else { recordingColor = Colors.grey; } int beats; - if (widget.melody != null) { - beats = widget.melody.length ~/ widget.melody.subdivisionsPerBeat; + final melody = widget.melody; + if (melody == null) { + beats = 0; + } else { + beats = melody.length ~/ melody.subdivisionsPerBeat; } - bool hasHighlightedBeat = widget.highlightedBeat.value != null && - BeatScratchPlugin.playing && - widget.recordingMelody; - final melodyReference = widget.currentSection.referenceTo(widget.melody); + bool hasHighlightedBeat = + BeatScratchPlugin.playing && widget.recordingMelody; + final melodyReference = + melody != null ? widget.currentSection.referenceTo(melody) : null; bool playingOrCountingIn = BeatScratchPlugin.playing || BeatScratchPlugin.countInInitiated; bool showGo = widget.recordingMelody && @@ -217,23 +224,25 @@ class _MelodyEditingToolbarState extends State SizedBox(width: 7), IncrementableValue( collapsing: true, - onDecrement: (widget.melody != null && widget.melody.beatCount > 1) + onDecrement: ((melody?.beatCount ?? -1) > 1) ? () { - if (widget.melody != null && widget.melody.beatCount > 1) { - widget.melody.length -= widget.melody.subdivisionsPerBeat; + if (melody == null) return; + if (melody.beatCount > 1) { + melody.length -= melody.subdivisionsPerBeat; BeatScratchPlugin.onSynthesizerStatusChange(); - clearMutableCachesForMelody(widget.melody.id); - BeatScratchPlugin.updateMelody(widget.melody); + clearMutableCachesForMelody(melody.id); + BeatScratchPlugin.updateMelody(melody); } } : null, - onIncrement: (widget.melody != null && widget.melody.beatCount <= 999) + onIncrement: ((melody?.beatCount ?? 1000) <= 999) ? () { - if (widget.melody != null && widget.melody.beatCount <= 999) { - widget.melody.length += widget.melody.subdivisionsPerBeat; + if (melody == null) return; + if (melody.beatCount <= 999) { + melody.length += melody.subdivisionsPerBeat; BeatScratchPlugin.onSynthesizerStatusChange(); - clearMutableCachesForMelody(widget.melody.id); - BeatScratchPlugin.updateMelody(widget.melody); + clearMutableCachesForMelody(melody.id); + BeatScratchPlugin.updateMelody(melody); } } : null, @@ -246,36 +255,36 @@ class _MelodyEditingToolbarState extends State SizedBox(width: 5), IncrementableValue( collapsing: true, - onDecrement: (widget.melody?.subdivisionsPerBeat ?? -1) > 1 + onDecrement: (melody?.subdivisionsPerBeat ?? -1) > 1 ? () { - if ((widget.melody?.subdivisionsPerBeat ?? -1) > 1) { - widget.melody?.subdivisionsPerBeat -= 1; - widget.melody.length = - beats * widget.melody.subdivisionsPerBeat; - clearMutableCachesForMelody(widget.melody.id); + if (melody == null) return; + if ((melody?.subdivisionsPerBeat ?? -1) > 1) { + melody.subdivisionsPerBeat -= 1; + melody.length = beats * melody.subdivisionsPerBeat; + clearMutableCachesForMelody(melody.id); BeatScratchPlugin.onSynthesizerStatusChange(); - clearMutableCachesForMelody(widget.melody.id); - BeatScratchPlugin.updateMelody(widget.melody); + clearMutableCachesForMelody(melody.id); + BeatScratchPlugin.updateMelody(melody); } } : null, - onIncrement: (widget.melody?.subdivisionsPerBeat ?? -1) < 24 + onIncrement: (melody?.subdivisionsPerBeat ?? -1) < 24 ? () { - if ((widget.melody?.subdivisionsPerBeat ?? -1) < 24) { - widget.melody?.subdivisionsPerBeat += 1; - widget.melody.length = - beats * widget.melody.subdivisionsPerBeat; - clearMutableCachesForMelody(widget.melody.id); + if (melody == null) return; + if (melody.subdivisionsPerBeat < 24) { + melody.subdivisionsPerBeat += 1; + melody.length = beats * melody.subdivisionsPerBeat; + clearMutableCachesForMelody(melody.id); BeatScratchPlugin.onSynthesizerStatusChange(); - clearMutableCachesForMelody(widget.melody.id); - BeatScratchPlugin.updateMelody(widget.melody); + clearMutableCachesForMelody(melody.id); + BeatScratchPlugin.updateMelody(melody); } } : null, child: Padding( padding: EdgeInsets.symmetric(vertical: 0, horizontal: 5), child: BeatsBadge( - beats: widget.melody?.subdivisionsPerBeat, + beats: melody?.subdivisionsPerBeat ?? 0, isPerBeat: true, )), ), @@ -283,14 +292,14 @@ class _MelodyEditingToolbarState extends State Expanded( child: AnimatedOpacity( duration: animationDuration, - opacity: widget.melody != null && widget.visible ? 1 : 0, + opacity: widget.visible ? 1 : 0, child: MySlider( value: melodyReference?.volume ?? 0, activeColor: melodyReference?.playbackType == MelodyReference_PlaybackType.playback_indefinitely ? widget.sectionColor : widget.sectionColor.withOpacity(0.5), - onChanged: (widget.melody != null && widget.visible) + onChanged: (widget.visible && melodyReference != null) ? (value) { widget.setReferenceVolume(melodyReference, value); } @@ -303,7 +312,7 @@ class _MelodyEditingToolbarState extends State padding: EdgeInsets.only(left: 5), child: AnimatedOpacity( duration: animationDuration, - opacity: widget.melody != null && showHoldToClear ? 1 : 0, + opacity: showHoldToClear ? 1 : 0, child: Stack(children: [ Transform.translate( offset: Offset(0, -7), @@ -337,7 +346,7 @@ class _MelodyEditingToolbarState extends State padding: EdgeInsets.only(left: 5), child: AnimatedOpacity( duration: animationDuration, - opacity: widget.melody != null && showDataCleared ? 1 : 0, + opacity: showDataCleared ? 1 : 0, child: Stack(children: [ // Transform.translate(offset: Offset(0, -7), child: Align(alignment: Alignment.center, child: // Text("Data", maxLines: 1, overflow: TextOverflow.visible, style: TextStyle(fontSize: 10)))), @@ -358,13 +367,13 @@ class _MelodyEditingToolbarState extends State child: MyRaisedButton( color: Color(0x424242).withOpacity(1), padding: EdgeInsets.zero, - onLongPress: widget.melody != null + onLongPress: melody != null ? () { print("clearing"); - widget.melody.midiData.data.clear(); - clearMutableCachesForMelody(widget.melody.id); + melody.midiData.data.clear(); + clearMutableCachesForMelody(melody.id); BeatScratchPlugin.onSynthesizerStatusChange(); - BeatScratchPlugin.updateMelody(widget.melody); + BeatScratchPlugin.updateMelody(melody); setState(() { showDataCleared = true; showHoldToClear = false; @@ -418,19 +427,17 @@ class _MelodyEditingToolbarState extends State child: MyRaisedButton( color: Color(0x424242).withOpacity(1), padding: EdgeInsets.zero, - onLongPress: widget.melody != null + onLongPress: melody != null ? () { print("clearing single beat"); - int beatToDelete = widget.highlightedBeat.value; - if (beatToDelete == null) { - beatToDelete = BeatScratchPlugin.currentBeat.value; - } else { - beatToDelete -= firstBeatOfSection; - } - widget.melody.deleteBeat(beatToDelete); - clearMutableCachesForMelody(widget.melody.id); + var beatToDelete = widget.highlightedBeat.value; + if (beatToDelete == null) return; + + beatToDelete -= firstBeatOfSection; + melody.deleteBeat(beatToDelete); + clearMutableCachesForMelody(melody.id); BeatScratchPlugin.onSynthesizerStatusChange(); - BeatScratchPlugin.updateMelody(widget.melody); + BeatScratchPlugin.updateMelody(melody); setState(() { showDataCleared = true; showHoldToClear = false; diff --git a/lib/music_view/music_action_button.dart b/lib/music_view/music_action_button.dart index 0688033f..4f32f6b3 100644 --- a/lib/music_view/music_action_button.dart +++ b/lib/music_view/music_action_button.dart @@ -9,47 +9,61 @@ class MusicActionButton extends StatelessWidget { final bool visible; final double width, height; final Widget child; - final VoidCallback onPressed; + final VoidCallback? onPressed; final Color color; MusicActionButton( - {Key key, this.visible = true, this.width = 48, this.height = 48, @required this.child, @required this.onPressed, this.color = Colors.black26}) - : super(key: key); + {Key? key, + this.visible = true, + this.width = 48, + this.height = 48, + required this.child, + required this.onPressed, + this.color = Colors.black26}) + : super(key: key); @override Widget build(BuildContext context) { double width = this.width, height = this.height; - if (!visible && width != null && height != null && width != 0 && height != 0) { + if (!visible && width != 0 && height != 0) { width = 0; height = 0; } return AnimatedOpacity( - duration: animationDuration, - opacity: visible ? 1 : 0, - child: Stack(children: [ - AnimatedContainer( - duration: animationDuration, height: height, width: width, child: SizedBox()), - Positioned( - top: .1, - left: 0, - width: width, - height: height, - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: IgnorePointer( - ignoring: !visible, - child: AnimatedContainer( - duration: animationDuration, - color: color, - height: height, - width: width, - child: SizedBox())))), - AnimatedContainer( - duration: animationDuration, - color: color, - height: height, - width: width, - child: onPressed == null ? child : MyFlatButton(onPressed: onPressed, padding: EdgeInsets.zero, child: child)) - ])); + duration: animationDuration, + opacity: visible ? 1 : 0, + child: Stack(children: [ + AnimatedContainer( + duration: animationDuration, + height: height, + width: width, + child: SizedBox()), + Positioned( + top: .1, + left: 0, + width: width, + height: height, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: IgnorePointer( + ignoring: !visible, + child: AnimatedContainer( + duration: animationDuration, + color: color, + height: height, + width: width, + child: SizedBox())))), + AnimatedContainer( + duration: animationDuration, + color: color, + height: height, + width: width, + child: onPressed == null + ? child + : MyFlatButton( + onPressed: onPressed, + padding: EdgeInsets.zero, + child: child)) + ])); } } diff --git a/lib/music_view/music_scroll_container.dart b/lib/music_view/music_scroll_container.dart index b0b16a6e..17799740 100644 --- a/lib/music_view/music_scroll_container.dart +++ b/lib/music_view/music_scroll_container.dart @@ -1,11 +1,9 @@ import 'dart:math'; +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; import 'package:beatscratch_flutter_redux/settings/settings.dart'; -import 'package:beatscratch_flutter_redux/widget/my_platform.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:matrix4_transform/matrix4_transform.dart'; import '../beatscratch_plugin.dart'; import '../generated/protos/music.pb.dart'; @@ -24,7 +22,7 @@ class MusicScrollContainer extends StatefulWidget { final Score score; final Section currentSection; final Color sectionColor; - final Melody focusedMelody; + final Melody? focusedMelody; final RenderingMode renderingMode; final List staves; final Part focusedPart, keyboardPart, colorboardPart; @@ -35,45 +33,45 @@ class MusicScrollContainer extends StatefulWidget { final ValueNotifier> keyboardNotesNotifier, colorboardNotesNotifier; final ValueNotifier>> bluetoothControllerPressedNotes; - final ValueNotifier highlightedBeat, focusedBeat, tappedBeat; - final ValueNotifier tappedPart; - final ValueNotifier requestedScrollOffsetForScale; + final ValueNotifier highlightedBeat, focusedBeat, tappedBeat; + final ValueNotifier tappedPart; + final ValueNotifier requestedScrollOffsetForScale; final TransformationController transformationController; final ValueNotifier scaleUpdateNotifier; - final ValueNotifier targetScaleNotifier; + final ValueNotifier targetScaleNotifier; final bool showingSectionList; const MusicScrollContainer( - {Key key, - this.score, - this.currentSection, - this.sectionColor, - this.focusedMelody, - this.renderingMode, - this.colorboardNotesNotifier, - this.keyboardNotesNotifier, - this.bluetoothControllerPressedNotes, - this.musicViewMode, - this.staves, - this.keyboardPart, - this.colorboardPart, - this.focusedPart, - this.width, - this.height, - this.isCurrentScore, - this.highlightedBeat, - this.focusedBeat, - this.tappedBeat, - this.tappedPart, - this.requestedScrollOffsetForScale, - this.targetScaleNotifier, - this.scrollToCurrentBeat, - this.centerCurrentSection, - this.appSettings, - this.transformationController, - this.scaleUpdateNotifier, - this.scrollToPart, - this.showingSectionList}) + {Key? key, + required this.score, + required this.currentSection, + required this.sectionColor, + required this.focusedMelody, + required this.renderingMode, + required this.colorboardNotesNotifier, + required this.keyboardNotesNotifier, + required this.bluetoothControllerPressedNotes, + required this.musicViewMode, + required this.staves, + required this.keyboardPart, + required this.colorboardPart, + required this.focusedPart, + required this.width, + required this.height, + required this.isCurrentScore, + required this.highlightedBeat, + required this.focusedBeat, + required this.tappedBeat, + required this.tappedPart, + required this.requestedScrollOffsetForScale, + required this.targetScaleNotifier, + required this.scrollToCurrentBeat, + required this.centerCurrentSection, + required this.appSettings, + required this.transformationController, + required this.scaleUpdateNotifier, + required this.scrollToPart, + required this.showingSectionList}) : super(key: key); @override @@ -87,32 +85,32 @@ class _MusicScrollContainerState extends State with TickerProviderStateMixin { static final double scrollTopMarginPercent = 0.17; static final double scrollLeftMarginPercent = 0.25; - AnimationController animationController; - ValueNotifier colorGuideOpacityNotifier, + late AnimationController animationController; + late ValueNotifier colorGuideOpacityNotifier, colorblockOpacityNotifier, notationOpacityNotifier, sectionScaleNotifier; // partTopOffsets are animated based off the Renderer's StaffConfigurations - ValueNotifier> stavesNotifier; - ValueNotifier> partTopOffsets; - ValueNotifier> staffOffsets; + late ValueNotifier> stavesNotifier; + late ValueNotifier> partTopOffsets; + late ValueNotifier> staffOffsets; - ValueNotifier keyboardPart; - ValueNotifier colorboardPart; - ValueNotifier focusedPart; - ValueNotifier sectionColor; + late ValueNotifier keyboardPart; + late ValueNotifier colorboardPart; + late ValueNotifier focusedPart; + late ValueNotifier sectionColor; - MusicViewMode _prevViewMode; + MusicViewMode? _prevViewMode; bool _hasBuilt = false; DateTime _lastAutoScrollTime = DateTime(0); - double prevWidth; - double _prevBeat; - String _prevSectionOrder; - String _prevSectionId; - Score _prevScore; + double? prevWidth; + double? _prevBeat; + String? _prevSectionOrder; + String? _prevSectionId; + Score? _prevScore; // ignore: unused_field - String _prevPartId; + String? _prevPartId; Rect visibleRect = Rect.zero; bool get isViewingSection => widget.musicViewMode != MusicViewMode.score; @@ -125,14 +123,14 @@ class _MusicScrollContainerState extends State ValueNotifier get scaleUpdateNotifier => widget.scaleUpdateNotifier; double get dx => - MatrixUtils.getAsTranslation(transformationController.value).dx; + MatrixUtils.getAsTranslation(transformationController.value)!.dx; double get dy => - MatrixUtils.getAsTranslation(transformationController.value).dy; + MatrixUtils.getAsTranslation(transformationController.value)!.dy; double get scale => transformationController.value.getMaxScaleOnAxis(); double get scaledStandardBeatWidth => beatWidth * scale; - double get targetScale => widget.targetScaleNotifier.value; + double? get targetScale => widget.targetScaleNotifier.value; double get targetBeatWidth => beatWidth; @@ -151,18 +149,19 @@ class _MusicScrollContainerState extends State ? 9999999 : widget.appSettings.systemsToRender; - double systemRenderAreaWidth({double customScale = null}) => + double systemRenderAreaWidth({double? customScale = null}) => max(0, (widget.width / (customScale ?? scale)) - clefWidth); - double beatsOnScreenPerSystem({double customScale = null}) => + double beatsOnScreenPerSystem({double? customScale = null}) => systemRenderAreaWidth(customScale: customScale) / beatWidth; - int maxSystemsNeeded({double customScale = null}) => (widget.score.beatCount / - max(0.1, beatsOnScreenPerSystem(customScale: customScale))) - .ceil(); + int maxSystemsNeeded({double? customScale = null}) => + (widget.score.beatCount / + max(0.1, beatsOnScreenPerSystem(customScale: customScale))) + .ceil(); - int systemsToRender({double customScale = null}) => + int systemsToRender({double? customScale = null}) => min(maxSystemsNeeded(customScale: customScale), maxSupportedSystems); - double calculatedSystemThingy({double customScale = null}) { + double calculatedSystemThingy({double? customScale = null}) { final systemsToRender = this.systemsToRender(customScale: customScale); return ((systemsToRender) * ((currentBeat - 2) / @@ -172,7 +171,7 @@ class _MusicScrollContainerState extends State // In multi-system mode, we select a "target system" for the // currentBeat based on how far into the score currentBeat is. - int currentBeatTargetSystemIndex({double customScale = null}) { + int currentBeatTargetSystemIndex({double? customScale = null}) { final systemsToRender = this.systemsToRender(customScale: customScale); return max( 0, @@ -183,7 +182,7 @@ class _MusicScrollContainerState extends State : calculatedSystemThingy(customScale: customScale).floor())); } - double currentBeatTargetSystemXOffset({double customScale = null}) => + double currentBeatTargetSystemXOffset({double? customScale = null}) => currentBeatTargetSystemIndex(customScale: customScale) * (systemRenderAreaWidth(customScale: customScale)); double get _systemHeightForScrolling => @@ -200,7 +199,7 @@ class _MusicScrollContainerState extends State max(widget.width / smallerScale, (numberOfBeats + extraBeatsSpaceForClefs) * targetBeatWidth) * 3; - double overallCanvasHeight({double customScale = null}) => + double overallCanvasHeight({double? customScale = null}) => max(widget.height / smallerScale, systemsToRender(customScale: customScale) * systemHeight) * 3; @@ -219,8 +218,8 @@ class _MusicScrollContainerState extends State double get scaledSystemHeight => MusicSystemPainter.calculateSystemHeight( scale, widget.score.parts.length); - Animation interactiveAnimation; - AnimationController interactiveController; + Animation? interactiveAnimation; + late AnimationController interactiveController; void _interactiveAnimationStop() { interactiveController.stop(); interactiveAnimation?.removeListener(_onInteractiveAnimation); @@ -229,13 +228,14 @@ class _MusicScrollContainerState extends State } void _onInteractiveAnimation() { - if (interactiveAnimation != null) { - transformationController.value = interactiveAnimation.value; - if (!interactiveController.isAnimating) { - interactiveAnimation.removeListener(_onInteractiveAnimation); - interactiveAnimation = null; - interactiveController.reset(); - } + final interactiveAnimationValue = interactiveAnimation?.value; + if (interactiveAnimationValue == null) return; + + transformationController.value = interactiveAnimationValue; + if (!interactiveController.isAnimating) { + interactiveAnimation?.removeListener(_onInteractiveAnimation); + interactiveAnimation = null; + interactiveController.reset(); } } @@ -255,6 +255,14 @@ class _MusicScrollContainerState extends State } else { // interactiveController.reset(); final targetedMatrix = transformationController.value.clone(); + final targetScale = widget.targetScaleNotifier.value; + if (targetScale == + null /*|| + targetScale < minScale || + targetScale > maxScale*/ + ) { + return; + } targetedMatrix.scale( targetScale / scale, targetScale / scale, targetScale / scale); @@ -291,7 +299,7 @@ class _MusicScrollContainerState extends State begin: transformationController.value, end: targetedMatrix, ).animate(interactiveController); - interactiveAnimation.addListener(_onInteractiveAnimation); + interactiveAnimation?.addListener(_onInteractiveAnimation); interactiveController.forward(); } } @@ -356,7 +364,7 @@ class _MusicScrollContainerState extends State interactionStartFocal.addListener(_deriveStartSystemFromFocal); } - VoidCallback xScaleUpdateListener, yScaleUpdateListener; + // VoidCallback? xScaleUpdateListener, yScaleUpdateListener; _transformationListener() {} @@ -384,20 +392,16 @@ class _MusicScrollContainerState extends State super.dispose(); } - ValueNotifier interactionStartFocal = ValueNotifier(null); - ValueNotifier interactionStartSystem = ValueNotifier(null); + ValueNotifier interactionStartFocal = ValueNotifier(null); + ValueNotifier interactionStartSystem = ValueNotifier(null); _deriveStartSystemFromFocal() { - if (interactionStartFocal.value == null) { - interactionStartSystem.value = null; - } else { - interactionStartSystem.value = - max(0, (interactingFocal.dy / scaledSystemHeight).floor()); - } + interactionStartSystem.value = + max(0, (interactingFocal.dy / scaledSystemHeight).floor()); } - Offset get interactingFocal => interactionStartFocal.value; + Offset get interactingFocal => interactionStartFocal.value!; int get interactingSystem => - max(0, min(systemsToRender() - 1, interactionStartSystem.value)); + max(0, min(systemsToRender() - 1, interactionStartSystem.value!)); adjustDxForScale(int systemNumber, double scaleChange, Matrix4 target, {bool adjustingAfterChange = true}) { @@ -415,7 +419,7 @@ class _MusicScrollContainerState extends State if (transformedRect.left > -scaledAvailableWidth2) { target.translate(diff, 0.0, 0.0); } else { - interactionStartSystem.value -= 1; + interactionStartSystem.value = interactionStartSystem.value! - 1; target.translate(diff - scaledAvailableWidth2, systemHeight, 0.0); } // } else if (adjustingAfterChange) { @@ -444,21 +448,19 @@ class _MusicScrollContainerState extends State if (autoScroll) { if (_prevViewMode == MusicViewMode.score && widget.musicViewMode != MusicViewMode.score && - _prevBeat > widget.currentSection.beatCount - 2) { + _prevBeat! > widget.currentSection.beatCount - 2) { Future.delayed(slowAnimationDuration, () { scrollToCurrentBeat(); _lastAutoScrollTime = DateTime.now(); }); - } else if (_prevSectionId != null && - _prevSectionId != widget.currentSection.id) { + } else if (_prevSectionId != widget.currentSection.id) { scrollToCurrentBeat(); _lastAutoScrollTime = DateTime.now(); - } else if (prevWidth != null && prevWidth != widget.width) { + } else if (prevWidth != widget.width) { // print("width changed"); scrollToCurrentBeat(); _lastAutoScrollTime = DateTime.now(); - } else if (_prevSectionOrder != null && - _prevSectionOrder != sectionOrder) { + } else if (_prevSectionOrder != sectionOrder) { Future.delayed(animationDuration, () { scrollToCurrentBeat(); _lastAutoScrollTime = DateTime.now(); @@ -489,7 +491,7 @@ class _MusicScrollContainerState extends State _prevBeat = currentBeat; _prevSectionOrder = sectionOrder; _prevSectionId = widget.currentSection.id; - _prevPartId = widget.focusedPart?.id; + _prevPartId = widget.focusedPart.id; _hasBuilt = true; // print( // "InteractiveViewer overallCanvasHeight=$overallCanvasHeight, systemsToRender=$systemsToRender, systemHeight=$systemHeight"); @@ -599,14 +601,14 @@ class _MusicScrollContainerState extends State Rect get transformedRect => MatrixUtils.inverseTransformRect( transformationController.value, - Rect.fromLTRB(0, 0, widget.width, widget.height)); + RectRendering.fromLTRB(0, 0, widget.width, widget.height)); // double get secondSystemOffset => // widget.width - clefWidth; // bool showOnSecondSystem(double animationPos) => // systemsToRender > 1 && animationPos > secondSystemOffset; - double _animationPos(double currentBeat, {double customScale = null}) { + double _animationPos(double currentBeat, {double? customScale = null}) { // print( // "_animationPos: $currentBeat $targetBeatWidth $overallCanvasWidth ${horizontallyVisibleRect.width}!"); @@ -644,7 +646,7 @@ class _MusicScrollContainerState extends State sectionWidth + targetClefWidth <= transformedRect.width; int get staffCount => stavesNotifier.value.length; - void scrollToCurrentBeat({double customScale = null}) { + void scrollToCurrentBeat({double? customScale = null}) { print("scrollToCurrentBeat, sectionCanBeCentered=$sectionCanBeCentered"); if (sectionCanBeCentered) { constrainToSectionBounds(customScale: customScale); @@ -657,7 +659,7 @@ class _MusicScrollContainerState extends State max(0, visibleWidth - 2 * targetClefWidth - targetBeatWidth) / targetBeatWidth; - constrainToSectionBounds({double customScale = null}) { + constrainToSectionBounds({double? customScale = null}) { double ratioScale = (customScale ?? scale) / scale; double marginBeatsForSection = max(0.0, visibleWidth / ratioScale - targetClefWidth - sectionWidth) / @@ -674,7 +676,7 @@ class _MusicScrollContainerState extends State } void scrollToBeat(double targetBeat, - {double customScale = null, + {double? customScale = null, bool includeMarginX = true, bool scrollHorizontally = true}) { final scale = this.scale; @@ -766,7 +768,7 @@ class _MusicScrollContainerState extends State var removedOffsets = staffOffsets.value.keys .where((id) => !widget.staves.any((staff) => staff.id == id)); removedOffsets.forEach((removedStaffId) { - Animation staffAnimation; + late Animation staffAnimation; staffAnimation = Tween( begin: staffOffsets.value[removedStaffId], end: 0) .animate( @@ -781,7 +783,7 @@ class _MusicScrollContainerState extends State double staffPosition = staffIndex * staffHeight; double initialStaffPosition = staffOffsets.value.putIfAbsent(staff.id, () => overallCanvasHeight()); - Animation staffAnimation; + late Animation staffAnimation; staffAnimation = Tween( begin: initialStaffPosition, end: staffPosition) .animate( @@ -795,7 +797,7 @@ class _MusicScrollContainerState extends State double partPosition = staffPosition; double initialPartPosition = partTopOffsets.value .putIfAbsent(part.id, () => overallCanvasHeight()); - Animation partAnimation; + late Animation partAnimation; partAnimation = Tween(begin: initialPartPosition, end: partPosition) .animate(CurvedAnimation( @@ -820,28 +822,25 @@ class _MusicScrollContainerState extends State double notationOpacityValue = (widget.renderingMode == RenderingMode.notation) ? 1 : 0; double sectionScaleValue = sectionsHeight != 0 ? 1 : 0; - Animation animation1; + late Animation animation1, animation2, animation3, animation4; animation1 = Tween( begin: colorblockOpacityNotifier.value, end: colorblockOpacityValue) .animate(animationController) ..addListener(() { colorblockOpacityNotifier.value = animation1.value; }); - Animation animation2; animation2 = Tween( begin: notationOpacityNotifier.value, end: notationOpacityValue) .animate(animationController) ..addListener(() { notationOpacityNotifier.value = animation2.value; }); - Animation animation3; animation3 = Tween(begin: sectionScaleNotifier.value, end: sectionScaleValue) .animate(animationController) ..addListener(() { sectionScaleNotifier.value = animation3.value; }); - Animation animation4; animation4 = Tween( begin: colorGuideOpacityNotifier.value, end: colorGuideOpacityValue) .animate(animationController) @@ -853,7 +852,7 @@ class _MusicScrollContainerState extends State extension CloseComparison on double { bool roughlyEquals(double x, {double precision = 0.005}) => - x != null && (this - x).abs() < precision; + (this - x).abs() < precision; bool notRoughlyEquals(double x, {double precision = 0.005}) => !roughlyEquals(x, precision: precision); diff --git a/lib/music_view/music_system_painter.dart b/lib/music_view/music_system_painter.dart index e620cf25..bc48a3af 100644 --- a/lib/music_view/music_system_painter.dart +++ b/lib/music_view/music_system_painter.dart @@ -1,10 +1,9 @@ import 'dart:math'; +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; import 'package:beatscratch_flutter_redux/widget/my_platform.dart'; -import 'package:flutter/foundation.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/painting.dart'; -import 'package:flutter/rendering.dart'; import '../util/util.dart'; import '../beatscratch_plugin.dart'; @@ -12,8 +11,6 @@ import '../colors.dart'; import '../drawing/color_guide.dart'; import '../drawing/harmony_beat_renderer.dart'; import '../drawing/music/music.dart'; -import '../drawing/music/music_clef_renderer.dart'; -import '../drawing/music/music_staff_lines_renderer.dart'; import '../generated/protos/music.pb.dart'; import '../ui_models.dart'; import '../util/dummydata.dart'; @@ -25,7 +22,7 @@ import '../util/music_theory.dart'; class MusicSystemPainter extends CustomPainter { Paint _tickPaint = Paint()..style = PaintingStyle.fill; - final String focusedMelodyId; + final String? focusedMelodyId; final Score score; final Section section; final TransformationController transformationController; @@ -43,11 +40,9 @@ class MusicSystemPainter extends CustomPainter { final ValueNotifier> staves; final ValueNotifier> partTopOffsets, staffOffsets; final ValueNotifier sectionColor; - final ValueNotifier keyboardPart, - colorboardPart, - focusedPart, - tappedPart; - final ValueNotifier highlightedBeat, focusedBeat, tappedBeat; + final ValueNotifier keyboardPart, colorboardPart, focusedPart; + final ValueNotifier tappedPart; + final ValueNotifier highlightedBeat, focusedBeat, tappedBeat; final bool isCurrentScore, isPreview, renderPartNames; final double firstBeatOfSection; final int systemsToRender; @@ -56,50 +51,50 @@ class MusicSystemPainter extends CustomPainter { double get yScale => 1; double get scale => transformationController.value.getMaxScaleOnAxis(); - Melody get focusedMelody => score.parts + Melody? get focusedMelody => score.parts .expand((p) => p.melodies) - .firstWhere((m) => m.id == focusedMelodyId, orElse: () => null); + .firstWhereOrNull((m) => m.id == focusedMelodyId); - int get numberOfBeats => /*isViewingSection ? section.harmony.beatCount :*/ score - .beatCount; + int get numberOfBeats => /*isViewingSection ? section.harmony.beatCount :*/ + score.beatCount; double get standardClefWidth => clefWidth; int get colorGuideAlpha => (255 * colorGuideOpacityNotifier.value).toInt(); MusicSystemPainter( - {this.isPreview, - this.focusedBeat, - this.tappedBeat, - this.firstBeatOfSection, - this.highlightedBeat, - this.musicViewMode, - this.colorGuideOpacityNotifier, - this.sectionColor, - this.focusedPart, - this.tappedPart, - this.keyboardPart, - this.colorboardPart, - this.staves, - this.partTopOffsets, - this.staffOffsets, - this.sectionScaleNotifier, - this.colorboardNotesNotifier, - this.keyboardNotesNotifier, - this.bluetoothControllerPressedNotes, - this.score, - this.section, - this.transformationController, + {required this.isPreview, + required this.focusedBeat, + required this.tappedBeat, + required this.firstBeatOfSection, + required this.highlightedBeat, + required this.musicViewMode, + required this.colorGuideOpacityNotifier, + required this.sectionColor, + required this.focusedPart, + required this.tappedPart, + required this.keyboardPart, + required this.colorboardPart, + required this.staves, + required this.partTopOffsets, + required this.staffOffsets, + required this.sectionScaleNotifier, + required this.colorboardNotesNotifier, + required this.keyboardNotesNotifier, + required this.bluetoothControllerPressedNotes, + required this.score, + required this.section, + required this.transformationController, this.rescale = false, - this.visibleRect, - this.verticallyVisibleRect, + required this.visibleRect, + required this.verticallyVisibleRect, this.focusedMelodyId, - this.colorblockOpacityNotifier, - this.notationOpacityNotifier, - this.isCurrentScore, - this.renderPartNames, + required this.colorblockOpacityNotifier, + required this.notationOpacityNotifier, + required this.isCurrentScore, + required this.renderPartNames, this.systemsToRender = 1, - List otherListenables = null}) + List otherListenables = const []}) : super( repaint: Listenable.merge([ colorblockOpacityNotifier, @@ -117,7 +112,7 @@ class MusicSystemPainter extends CustomPainter { BeatScratchPlugin.pressedMidiControllerNotes, BeatScratchPlugin.currentBeat, transformationController, - if (otherListenables != null) ...otherListenables + ...otherListenables ])) { _tickPaint.color = musicForegroundColor; _tickPaint.strokeWidth = 2.0; @@ -202,7 +197,7 @@ class MusicSystemPainter extends CustomPainter { double staffOffset = staffOffsets.value.putIfAbsent(staff.id, () => 0); double top = visibleRect().top + harmonyHeight + sectionHeight + staffOffset; - Rect staffLineBounds = Rect.fromLTRB( + Rect staffLineBounds = RectRendering.fromLTRB( max(-offsetStart, visibleRect().left), top, max(-offsetStart, visibleRect().right), @@ -210,7 +205,7 @@ class MusicSystemPainter extends CustomPainter { // canvas.drawRect(staffLineBounds, Paint()..style=PaintingStyle.stroke..strokeWidth=10); _renderStaffLines(canvas, !(staff is DrumStaff) && drawContinuousColorGuide, staffLineBounds); - Rect clefBounds = Rect.fromLTRB( + Rect clefBounds = RectRendering.fromLTRB( max(-offsetStart, visibleRect().left), top, max(-offsetStart, visibleRect().left) + standardClefWidth, @@ -238,14 +233,11 @@ class MusicSystemPainter extends CustomPainter { if (sectionIndex < score.sections.length) { candidate = score.sections[sectionIndex]; } else { - candidate = null; + // candidate = null; break; } } renderingSection = candidate; - if (renderingSection == null) { - break; - } if (renderingSectionBeat >= renderingSection.beatCount) { //TODO do this better... break; @@ -263,7 +255,7 @@ class MusicSystemPainter extends CustomPainter { } // print("fontSize=$fontSize topOffset=$topOffset"); double opacityFactor = - magicOpacityFactor(Rect.fromLTRB(left, 0, left, 0)); + magicOpacityFactor(RectRendering.fromLTRB(left, 0, left, 0)); TextSpan span = TextSpan( text: renderingSection.canonicalName, style: TextStyle( @@ -287,12 +279,12 @@ class MusicSystemPainter extends CustomPainter { right = left + beatWidth; String sectionName = renderingSection.name; - if (sectionName == null || sectionName.isEmpty) { + if (sectionName.isEmpty) { sectionName = renderingSection.id; } - Rect harmonyBounds = - Rect.fromLTRB(left, top, left + beatWidth, top + harmonyHeight); + Rect harmonyBounds = RectRendering.fromLTRB( + left, top, left + beatWidth, top + harmonyHeight); if (isInBounds(harmonyBounds)) { _renderHarmonyBeat( harmonyBounds, renderingSection, renderingSectionBeat, canvas); @@ -322,7 +314,7 @@ class MusicSystemPainter extends CustomPainter { } canvas.drawRect( - Rect.fromLTRB( + RectRendering.fromLTRB( left - extraWidth, translationTotal + visibleRect().top - @@ -334,8 +326,15 @@ class MusicSystemPainter extends CustomPainter { } } - doRenderMelodies(staff, renderingSection, canvas, left, right, top, - renderingSectionBeat, renderingBeat) { + doRenderMelodies( + MusicStaff staff, + Section renderingSection, + Canvas canvas, + double left, + double right, + double top, + int renderingSectionBeat, + int renderingBeat) { staff.getParts(score, staves.value).forEach((part) { double partOffset = partTopOffsets.value.putIfAbsent(part.id, () => 0); List melodiesToRender = renderingSection.melodies @@ -343,11 +342,11 @@ class MusicSystemPainter extends CustomPainter { melodyReference.playbackType != MelodyReference_PlaybackType.disabled) .where((MelodyReference ref) => - part.melodies.any((melody) => melody.id == ref.melodyId) as bool) - .map((it) => score.melodyReferencedBy(it)) + part.melodies.any((melody) => melody.id == ref.melodyId)) + .map((it) => score.melodyReferencedBy(it)!) .toList(growable: false); - Rect melodyBounds = Rect.fromLTRB( + Rect melodyBounds = RectRendering.fromLTRB( left, top + partOffset, right, top + partOffset + melodyHeight); if (isInBounds(melodyBounds)) { _renderMelodies( @@ -433,23 +432,27 @@ class MusicSystemPainter extends CustomPainter { index++; } - if (focusedMelody != null) { - final part = score.parts - .firstWhere((p) => p.melodies.any((m) => m.id == focusedMelodyId)); - final parts = staff.getParts(score, staffConfiguration); - if (parts.any((p) => p.id == part.id)) { - double opacity = 1; - if (!melodiesToRender.contains(focusedMelody)) { - if (renderingSection.id == section.id) { - opacity = 0.6; - } else { - opacity = 0; - } + final part = score.parts.firstWhereOrNull( + (p) => p.melodies.any((m) => m.id == focusedMelodyId)); + + if (part == null) { + // No focused melody, so we don't render the focused melody + return; + } + + final parts = staff.getParts(score, staffConfiguration); + if (parts.any((p) => p.id == part.id)) { + double opacity = 1; + if (!melodiesToRender.contains(focusedMelody)) { + if (renderingSection.id == section.id) { + opacity = 0.6; + } else { + opacity = 0; } - _renderMelodyBeat(canvas, focusedMelody, melodyBounds, renderingSection, - renderingSectionBeat, true, opacity, renderQueue, - renderLoopStarts: true); } + _renderMelodyBeat(canvas, focusedMelody ?? Melody(), melodyBounds, + renderingSection, renderingSectionBeat, true, opacity, renderQueue, + renderLoopStarts: true); } try { @@ -484,15 +487,13 @@ class MusicSystemPainter extends CustomPainter { melodyBounds, Paint() ..style = PaintingStyle.fill - ..color = part != null && - tappedPart.value?.id == part.id && + ..color = tappedPart.value?.id == part.id && renderingBeat == tappedBeat.value ? sectionColor.value.withOpacity(0.12) : Colors.black12); } if (isCurrentScore && (renderingBeat == tappedBeat.value) && - part != null && tappedPart.value?.id == part.id) { canvas.drawRect( melodyBounds, @@ -611,7 +612,7 @@ class MusicSystemPainter extends CustomPainter { int renderingSectionBeat, Iterable otherMelodiesOnStaff, MusicStaff staff, - {Paint backgroundPaint}) { + {Paint? backgroundPaint}) { canvas.drawRect( melodyBounds, backgroundPaint ?? Paint() @@ -642,7 +643,7 @@ class MusicSystemPainter extends CustomPainter { keyboardNotes.length.toDouble(); _keyboardDummyMelody.instrumentType = - keyboardPart?.value?.instrument?.type ?? InstrumentType.harmonic; + keyboardPart.value?.instrument.type ?? InstrumentType.harmonic; if (hasColorboardPart) { _renderMelodyBeat( canvas, @@ -755,58 +756,56 @@ class MusicSystemPainter extends CustomPainter { renderingSection.id != section.id) { opacityFactor *= 0.25; } - if (melody != null) { - if (renderLoopStarts && - renderingSectionBeat % (melody.length / melody.subdivisionsPerBeat) == - 0 && - renderingSection.id == section.id) { - Rect highlight = Rect.fromPoints( - melodyBounds.topLeft.translate(-melodyBounds.width / 13, 0), - melodyBounds.bottomLeft.translate(melodyBounds.width / 13, 0)); - canvas.drawRect( - highlight, Paint()..color = sectionColor.value.withAlpha(127)); - } - try { - if (colorblockOpacityNotifier.value > 0) { - ColorblockMusicRenderer() - ..uiScale = scale - ..overallBounds = melodyBounds - ..section = renderingSection - ..beatPosition = renderingSectionBeat - ..colorblockAlpha = - colorblockOpacityNotifier.value * alpha * opacityFactor - ..drawPadding = 3 - ..nonRootPadding = 3 - ..isUserChoosingHarmonyChord = false - ..isMelodyReferenceEnabled = true - ..melody = melody - ..draw(canvas); - } - } catch (e, s) { - print("exception rendering colorblock: $e: \n$s"); + if (renderLoopStarts && + renderingSectionBeat % (melody.length / melody.subdivisionsPerBeat) == + 0 && + renderingSection.id == section.id) { + Rect highlight = Rect.fromPoints( + melodyBounds.topLeft.translate(-melodyBounds.width / 13, 0), + melodyBounds.bottomLeft.translate(melodyBounds.width / 13, 0)); + canvas.drawRect( + highlight, Paint()..color = sectionColor.value.withAlpha(127)); + } + try { + if (colorblockOpacityNotifier.value > 0) { + ColorblockMusicRenderer() + ..uiScale = scale + ..overallBounds = melodyBounds + ..section = renderingSection + ..beatPosition = renderingSectionBeat + ..colorblockAlpha = + colorblockOpacityNotifier.value * alpha * opacityFactor + ..drawPadding = 3 + ..nonRootPadding = 3 + ..isUserChoosingHarmonyChord = false + ..isMelodyReferenceEnabled = true + ..melody = melody + ..draw(canvas); } - try { - if (notationOpacityNotifier.value > 0) { - NotationMusicRenderer() - ..otherMelodiesOnStaff = otherMelodiesOnStaff - ..xScale = xScale - ..yScale = yScale - ..overallBounds = melodyBounds - ..section = renderingSection - ..beatPosition = renderingSectionBeat - ..notationAlpha = - notationOpacityNotifier.value * alpha * opacityFactor - ..drawPadding = 3 - ..nonRootPadding = 3 - ..stemsUp = stemsUp - ..isUserChoosingHarmonyChord = false - ..isMelodyReferenceEnabled = true - ..melody = melody - ..draw(canvas); - } - } catch (e, s) { - print("exception rendering notation: $e: \n$s"); + } catch (e, s) { + print("exception rendering colorblock: $e: \n$s"); + } + try { + if (notationOpacityNotifier.value > 0) { + NotationMusicRenderer() + ..otherMelodiesOnStaff = otherMelodiesOnStaff + ..xScale = xScale + ..yScale = yScale + ..overallBounds = melodyBounds + ..section = renderingSection + ..beatPosition = renderingSectionBeat + ..notationAlpha = + notationOpacityNotifier.value * alpha * opacityFactor + ..drawPadding = 3 + ..nonRootPadding = 3 + ..stemsUp = stemsUp + ..isUserChoosingHarmonyChord = false + ..isMelodyReferenceEnabled = true + ..melody = melody + ..draw(canvas); } + } catch (e, s) { + print("exception rendering notation: $e: \n$s"); } } @@ -818,7 +817,7 @@ class MusicSystemPainter extends CustomPainter { final double startOffset = renderingBeat * beatWidth; double left = startOffset; double chordLeft = left; - Chord renderingChord; + Chord? renderingChord; while (left < visibleRect().right + beatWidth) { if (renderingBeat < 0) { @@ -828,15 +827,6 @@ class MusicSystemPainter extends CustomPainter { } int renderingSectionBeat = renderingBeat; Section renderingSection = this.section; - if (renderingSection == null) { - int _beat = 0; - Section candidate = score.sections[0]; - while (_beat + candidate.beatCount <= renderingBeat) { - _beat += candidate.beatCount; - renderingSectionBeat -= candidate.beatCount; - } - renderingSection = candidate; - } Harmony renderingHarmony = renderingSection.harmony; double beatLeft = left; for (int renderingSubdivision in range( @@ -844,13 +834,10 @@ class MusicSystemPainter extends CustomPainter { (renderingSectionBeat + 1) * renderingHarmony.subdivisionsPerBeat - 1)) { Chord chordAtSubdivision = - renderingHarmony.changeBefore(renderingSubdivision) ?? - cChromatic; //TODO Is this default needed? - if (renderingChord == null) { - renderingChord = chordAtSubdivision; - } - if (renderingChord != chordAtSubdivision) { - Rect renderingRect = Rect.fromLTRB(chordLeft, top, left, bottom); + renderingHarmony.changeBefore(renderingSubdivision) ?? cChromatic; + if (renderingChord != null && renderingChord != chordAtSubdivision) { + Rect renderingRect = + RectRendering.fromLTRB(chordLeft, top, left, bottom); try { ColorGuide() ..renderVertically = true @@ -875,7 +862,7 @@ class MusicSystemPainter extends CustomPainter { renderingBeat += 1; } Rect renderingRect = - Rect.fromLTRB(chordLeft, top + harmonyHeight, left, bottom); + RectRendering.fromLTRB(chordLeft, top + harmonyHeight, left, bottom); try { ColorGuide() ..renderVertically = true @@ -883,7 +870,7 @@ class MusicSystemPainter extends CustomPainter { ..halfStepsOnScreen = 88 ..normalizedDevicePitch = 0 ..bounds = renderingRect - ..chord = renderingChord + ..chord = renderingChord! ..drawPadding = 0 ..nonRootPadding = 0 ..drawnColorGuideAlpha = colorGuideAlpha diff --git a/lib/music_view/music_toolbars.dart b/lib/music_view/music_toolbars.dart index 3f133e75..8b8d21ee 100644 --- a/lib/music_view/music_toolbars.dart +++ b/lib/music_view/music_toolbars.dart @@ -13,7 +13,7 @@ import '../widget/my_buttons.dart'; class MelodyToolbar extends StatefulWidget { final MusicViewMode musicViewMode; final bool editingMelody; - final Melody melody; + final Melody? melody; final Section currentSection; final Color sectionColor; final Function(MelodyReference) toggleMelodyReference; @@ -23,18 +23,18 @@ class MelodyToolbar extends StatefulWidget { final Function(Melody) deleteMelody; const MelodyToolbar( - {Key key, - this.melody, - this.currentSection, - this.toggleMelodyReference, - this.setReferenceVolume, - this.editingMelody, - this.sectionColor, - this.toggleRecording, - this.backToPart, - this.setMelodyName, - this.musicViewMode, - this.deleteMelody}) + {Key? key, + required this.melody, + required this.currentSection, + required this.toggleMelodyReference, + required this.setReferenceVolume, + required this.editingMelody, + required this.sectionColor, + required this.toggleRecording, + required this.backToPart, + required this.setMelodyName, + required this.musicViewMode, + required this.deleteMelody}) : super(key: key); @override @@ -42,17 +42,17 @@ class MelodyToolbar extends StatefulWidget { } class MelodyToolbarState extends State { - bool _showVolume; - TextEditingController nameController; + late bool _showVolume; + late TextEditingController nameController; - MelodyReference get melodyReference => - widget.currentSection.referenceTo(widget.melody); + MelodyReference? get melodyReference => widget.melody != null + ? widget.currentSection.referenceTo(widget.melody!) + : null; bool get melodySelected => widget.melody != null; bool get melodyEnabled => melodySelected && (melodyReference?.isEnabled ?? false); - Melody confirmingDeleteFor; - bool get isConfirmingDelete => - confirmingDeleteFor != null && confirmingDeleteFor == widget.melody; + Melody? confirmingDeleteFor; + bool get isConfirmingDelete => confirmingDeleteFor == widget.melody; bool get showVolume => (melodyReference?.isEnabled == true) && (/*context.isTablet ||*/ _showVolume); @@ -76,11 +76,9 @@ class MelodyToolbarState extends State { if (context.isTabletOrLandscapey) { width = width / 2; } - _showVolume &= melodyReference != null && - (melodyReference?.isEnabled == true) && - !isConfirmingDelete; + _showVolume &= (melodyReference?.isEnabled == true) && !isConfirmingDelete; - if (confirmingDeleteFor != null && confirmingDeleteFor != widget.melody) { + if (confirmingDeleteFor != widget.melody) { confirmingDeleteFor = null; } nameController.value = @@ -98,9 +96,11 @@ class MelodyToolbarState extends State { textCapitalization: TextCapitalization.words, onChanged: (melodySelected) ? (value) { - widget.melody.name = value; - widget.setMelodyName( - widget.melody, widget.melody.name); + final melody = widget.melody; + if (melody == null) return; + + melody.name = value; + widget.setMelodyName(melody, melody.name); // BeatScratchPlugin.updateMelody(widget.melody); } : null, @@ -109,7 +109,7 @@ class MelodyToolbarState extends State { // }, decoration: InputDecoration( border: InputBorder.none, - hintText: (melodySelected) ? widget.melody.idName : "", + hintText: (melodySelected) ? widget.melody?.idName : "", )) : Text(""))), // AnimatedContainer( @@ -166,7 +166,7 @@ class MelodyToolbarState extends State { onChanged: (melodyReference?.isEnabled != true) ? null : (value) { - widget.setReferenceVolume(melodyReference, value); + widget.setReferenceVolume(melodyReference!, value); }), ), ), @@ -178,7 +178,7 @@ class MelodyToolbarState extends State { child: MyRaisedButton( onPressed: melodySelected ? () { - widget.toggleMelodyReference(melodyReference); + widget.toggleMelodyReference(melodyReference!); } : null, onLongPress: (melodyReference?.isEnabled == true) @@ -214,7 +214,7 @@ class MelodyToolbarState extends State { child: MyRaisedButton( onPressed: () { setState(() { - widget.deleteMelody(confirmingDeleteFor); + widget.deleteMelody(confirmingDeleteFor!); confirmingDeleteFor = null; }); }, @@ -245,12 +245,14 @@ class MelodyToolbarState extends State { child: MyRaisedButton( onPressed: () { setState(() { - if (widget.melody.name?.isEmpty != false && - (widget.melody.midiData.data.isEmpty || - widget.melody.midiData.data.values - .where((mc) => mc.data.length != 0) - .isEmpty)) { - widget.deleteMelody(widget.melody); + if (widget.melody?.name.isEmpty != false && + (widget.melody?.midiData.data.isEmpty ?? + false || + (widget.melody?.midiData.data.values + .where((mc) => mc.data.length != 0) + .isEmpty ?? + false))) { + widget.deleteMelody(widget.melody!); } else { confirmingDeleteFor = widget.melody; } @@ -269,7 +271,7 @@ class MelodyToolbarState extends State { class PartToolbar extends StatefulWidget { final Color sectionColor; - final Part part; + final Part? part; final Function(Part) setKeyboardPart; final Function(Part) setColorboardPart; final Part colorboardPart; @@ -281,19 +283,19 @@ class PartToolbar extends StatefulWidget { final bool enableColorboard; const PartToolbar( - {Key key, - this.part, - this.setKeyboardPart, - this.setColorboardPart, - this.colorboardPart, - this.keyboardPart, - this.deletePart, - this.configuringPart, - this.browsingPartMelodies, - this.toggleConfiguringPart, - this.sectionColor, - this.enableColorboard, - this.toggleBrowsingPartMelodies}) + {Key? key, + required this.part, + required this.setKeyboardPart, + required this.setColorboardPart, + required this.colorboardPart, + required this.keyboardPart, + required this.deletePart, + required this.configuringPart, + required this.browsingPartMelodies, + required this.toggleConfiguringPart, + required this.sectionColor, + required this.enableColorboard, + required this.toggleBrowsingPartMelodies}) : super(key: key); @override @@ -301,17 +303,16 @@ class PartToolbar extends StatefulWidget { } class PartToolbarState extends State { - Part confirmingDeleteFor; + Part? confirmingDeleteFor; - bool get isConfirmingDelete => - confirmingDeleteFor != null && confirmingDeleteFor == widget.part; + bool get isConfirmingDelete => confirmingDeleteFor == widget.part; Widget build(BuildContext context) { var width = MediaQuery.of(context).size.width; if (context.isTabletOrLandscapey) { width = width / 2; } - if (confirmingDeleteFor != null && confirmingDeleteFor != widget.part) { + if (confirmingDeleteFor != widget.part) { confirmingDeleteFor = null; } return Container( @@ -328,7 +329,7 @@ class PartToolbarState extends State { color: widget.configuringPart ? Colors.white : null, child: AnimatedOpacity( duration: animationDuration, - opacity: widget.part == null || isConfirmingDelete ? 0 : 1, + opacity: isConfirmingDelete ? 0 : 1, child: Icon(Icons.settings, color: widget.configuringPart ? Colors.black @@ -336,7 +337,8 @@ class PartToolbarState extends State { Expanded( child: Padding( padding: EdgeInsets.only(left: 5), - child: Text((widget.part != null) ? widget.part.midiName : "", + child: Text( + (widget.part != null) ? widget.part?.midiName ?? "" : "", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -377,28 +379,25 @@ class PartToolbarState extends State { height: 36, padding: EdgeInsets.only(right: 5), child: MyRaisedButton( - onPressed: (widget.part != null && - widget.part.instrument.type != InstrumentType.drum) + onPressed: (widget.part?.instrument.type != InstrumentType.drum) ? () { - widget.setColorboardPart(widget.part); + final part = widget.part; + if (part == null) return; + widget.setColorboardPart(part); } : null, padding: EdgeInsets.zero, child: AnimatedOpacity( duration: animationDuration, - opacity: widget.part == null || - isConfirmingDelete || - !widget.enableColorboard - ? 0 - : 1, + opacity: + isConfirmingDelete || !widget.enableColorboard ? 0 : 1, child: Stack(children: [ Align( alignment: Alignment.bottomRight, child: AnimatedOpacity( duration: animationDuration, - opacity: (widget.part != null && - widget.part.instrument.type != - InstrumentType.drum) + opacity: (widget.part?.instrument.type != + InstrumentType.drum) ? 1 : 0.25, child: Padding( @@ -431,7 +430,7 @@ class PartToolbarState extends State { padding: EdgeInsets.all(0), child: AnimatedOpacity( duration: animationDuration, - opacity: (widget.part != null && !isConfirmingDelete) ? 0 : 0, + opacity: (!isConfirmingDelete) ? 0 : 0, child: Stack(children: [ Align( alignment: Alignment.topLeft, @@ -463,7 +462,9 @@ class PartToolbarState extends State { child: MyRaisedButton( onPressed: () { setState(() { - widget.deletePart(widget.part); + final part = widget.part; + if (part == null) return; + widget.deletePart(part); confirmingDeleteFor = null; }); }, @@ -517,23 +518,23 @@ class SectionToolbar extends StatefulWidget { final MusicViewMode musicViewMode; final Function(Section, String) setSectionName; final Function(Section) deleteSection; - final Function addPart; + final Function? addPart; final Function cloneCurrentSection; final bool editingSection; final Function(bool) setEditingSection; const SectionToolbar( - {Key key, - this.currentSection, - this.sectionColor, - this.musicViewMode, - this.setSectionName, - this.deleteSection, - this.canDeleteSection, - this.editingSection, - this.addPart, - this.cloneCurrentSection, - this.setEditingSection}) + {Key? key, + required this.currentSection, + required this.sectionColor, + required this.musicViewMode, + required this.setSectionName, + required this.deleteSection, + required this.canDeleteSection, + required this.editingSection, + required this.addPart, + required this.cloneCurrentSection, + required this.setEditingSection}) : super(key: key); @override @@ -541,11 +542,9 @@ class SectionToolbar extends StatefulWidget { } class SectionToolbarState extends State { - Section confirmingDeleteFor; + Section? confirmingDeleteFor; - bool get isConfirmingDelete => - confirmingDeleteFor != null && - confirmingDeleteFor == widget.currentSection; + bool get isConfirmingDelete => confirmingDeleteFor == widget.currentSection; TextEditingController nameController = TextEditingController(); @override @@ -560,8 +559,7 @@ class SectionToolbarState extends State { if (context.isTabletOrLandscapey) { width = width / 2; } - if (confirmingDeleteFor != null && - confirmingDeleteFor != widget.currentSection) { + if (confirmingDeleteFor != widget.currentSection) { confirmingDeleteFor = null; } nameController.value = @@ -619,17 +617,16 @@ class SectionToolbarState extends State { )), AnimatedContainer( duration: animationDuration, - width: isConfirmingDelete || widget.addPart == null ? 0 : 69, + width: isConfirmingDelete ? 0 : 69, height: 36, padding: EdgeInsets.only(right: 5), child: MyRaisedButton( - onPressed: widget.addPart, + onPressed: widget.addPart as VoidCallback?, padding: EdgeInsets.zero, child: AnimatedOpacity( duration: animationDuration, opacity: widget.musicViewMode != MusicViewMode.section || - isConfirmingDelete || - widget.addPart == null + isConfirmingDelete ? 0 : 1, child: Row(children: [ @@ -648,7 +645,7 @@ class SectionToolbarState extends State { height: 36, padding: EdgeInsets.only(right: 5), child: MyRaisedButton( - onPressed: widget.cloneCurrentSection, + onPressed: widget.cloneCurrentSection as VoidCallback, padding: EdgeInsets.zero, child: AnimatedOpacity( duration: animationDuration, @@ -676,7 +673,7 @@ class SectionToolbarState extends State { child: MyRaisedButton( onPressed: () { setState(() { - widget.deleteSection(confirmingDeleteFor); + widget.deleteSection(confirmingDeleteFor!); confirmingDeleteFor = null; }); }, diff --git a/lib/music_view/music_view.dart b/lib/music_view/music_view.dart index e6a23a4d..cd5bf821 100644 --- a/lib/music_view/music_view.dart +++ b/lib/music_view/music_view.dart @@ -1,7 +1,5 @@ import 'dart:math'; -import 'dart:ui'; -import 'package:beatscratch_flutter_redux/generated/i18n.dart'; import 'package:beatscratch_flutter_redux/widget/color_filtered_image_asset.dart'; import '../colors.dart'; @@ -11,15 +9,16 @@ import '../widget/my_platform.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_icons/flutter_icons.dart'; +// import 'package:flutter_icons/flutter_icons.dart'; import '../beatscratch_plugin.dart'; import '../generated/protos/music.pb.dart'; import '../ui_models.dart'; import '../util/music_notation_theory.dart'; import '../util/music_theory.dart'; -import '../util/bs_methods.dart'; import '../util/util.dart'; +import "package:flutter_feather_icons/flutter_feather_icons.dart"; + import '../widget/incrementable_value.dart'; import '../widget/my_buttons.dart'; import 'part_instrument_picker.dart'; @@ -42,8 +41,8 @@ class MusicView extends StatefulWidget { final ValueNotifier> colorboardNotesNotifier, keyboardNotesNotifier; final ValueNotifier>> bluetoothControllerPressedNotes; - final Melody melody; - final Part part; + final Melody? melody; + final Part? part; final Color sectionColor; final VoidCallback toggleSplitMode, closeMelodyView, toggleRecording; final Function(VoidCallback) superSetState; @@ -64,7 +63,7 @@ class MusicView extends StatefulWidget { final Function(Part) selectOrDeselectPart; final Function(Melody) selectOrDeselectMelody; final Function(Part, Melody, bool) createMelody; - final Function addPart; + final Function? addPart; final Function cloneCurrentSection; final Color backgroundColor; final bool isCurrentScore; @@ -74,53 +73,53 @@ class MusicView extends StatefulWidget { final BSMethod scrollToCurrentBeat; MusicView( - {this.appSettings, - this.selectBeat, - this.selectOrDeselectPart, - this.selectOrDeselectMelody, - this.melodyViewSizeFactor, + {Key? key, + required this.appSettings, + required this.selectBeat, + required this.selectOrDeselectPart, + required this.selectOrDeselectMelody, + required this.melodyViewSizeFactor, this.addPart, - this.cloneCurrentSection, - this.superSetState, - this.musicViewMode, - this.score, - this.currentSection, - this.melody, - this.part, - this.sectionColor, - this.splitMode, - this.toggleSplitMode, - this.closeMelodyView, - this.toggleMelodyReference, - this.setReferenceVolume, - this.setPartVolume, - this.recordingMelody, - this.toggleRecording, - this.setMelodyName, - this.setSectionName, - this.setKeyboardPart, - this.setColorboardPart, - this.colorboardPart, - this.keyboardPart, - this.deletePart, - this.deleteMelody, - this.deleteSection, - this.renderingMode, - this.colorboardNotesNotifier, - this.keyboardNotesNotifier, - this.bluetoothControllerPressedNotes, - this.height, - this.width, - this.enableColorboard, + required this.cloneCurrentSection, + required this.superSetState, + required this.musicViewMode, + required this.score, + required this.currentSection, + required this.melody, + required this.part, + required this.sectionColor, + required this.splitMode, + required this.toggleSplitMode, + required this.closeMelodyView, + required this.toggleMelodyReference, + required this.setReferenceVolume, + required this.setPartVolume, + required this.recordingMelody, + required this.toggleRecording, + required this.setMelodyName, + required this.setSectionName, + required this.setKeyboardPart, + required this.setColorboardPart, + required this.colorboardPart, + required this.keyboardPart, + required this.deletePart, + required this.deleteMelody, + required this.deleteSection, + required this.renderingMode, + required this.colorboardNotesNotifier, + required this.keyboardNotesNotifier, + required this.bluetoothControllerPressedNotes, + required this.height, + required this.width, + required this.enableColorboard, this.isCurrentScore = true, - Key key, - this.requestRenderingMode, + required this.requestRenderingMode, this.showViewOptions = false, this.backgroundColor = Colors.white, - this.showBeatCounts, - this.createMelody, - this.scrollToCurrentBeat, - this.showingSectionList}) + required this.showBeatCounts, + required this.createMelody, + required this.scrollToCurrentBeat, + required this.showingSectionList}) : super(key: key); @override @@ -130,17 +129,17 @@ class MusicView extends StatefulWidget { class _MusicViewState extends State with TickerProviderStateMixin { static const double minScale = MusicScrollContainer.minScale; static const double maxScale = MusicScrollContainer.maxScale; - bool _disposed; + bool _disposed = false; bool get autoScroll => widget.appSettings.autoScrollMusic; set autoScroll(bool v) => widget.appSettings.autoScrollMusic = v; bool get autoSort => widget.appSettings.autoSortMusic; set autoSort(bool v) => widget.appSettings.autoSortMusic = v; bool get autoZoomAlign => widget.appSettings.autoZoomAlignMusic; set autoZoomAlign(bool v) => widget.appSettings.autoZoomAlignMusic = v; - bool isConfiguringPart; - bool isBrowsingPartMelodies; - bool isEditingSection; - TransformationController transformationController; + bool isConfiguringPart = false; + bool isBrowsingPartMelodies = false; + bool isEditingSection = false; + late TransformationController transformationController; ValueNotifier scaleUpdateNotifier = ValueNotifier(ScaleUpdateDetails()); @@ -173,46 +172,46 @@ class _MusicViewState extends State with TickerProviderStateMixin { bool get showViewOptions => widget.showViewOptions || forceShowViewOptions; /// Always immediately updated; the return values of [targetedScale] and [scale]. - ValueNotifier _targetedScale; - double get targetedScale => _targetedScale.value; - set targetedScale(double v) { + late ValueNotifier _targetedScale; + double? get targetedScale => _targetedScale.value; + set targetedScale(double? v) { print("set targetedScale=$v"); _targetedScale.value = v; - widget.appSettings.musicScale = v; + if (v != null) widget.appSettings.musicScale = v; } /// Used to maintain a locking mechanism as we animate from [_xScale] to [targetedScale] in the setter for [targetedScale]. - DateTime _xScaleLock; - List _xScaleAnimationControllers, + late DateTime _xScaleLock; + late List _xScaleAnimationControllers, _yScaleAnimationControllers; /// Used to notify the [MusicScrollContainer] as we animate [_xScale] to [targetedScale] in the setter for [targetedScale]. // BSValueMethod _xScaleUpdate, _yScaleUpdate; - Map> _swipeTutorialsSeen; - SwipeTutorial _currentSwipeTutorial; + late Map> _swipeTutorialsSeen; + SwipeTutorial? _currentSwipeTutorial; - BSMethod centerCurrentSection, scrollToPart; + late BSMethod centerCurrentSection, scrollToPart; // static const double maxScaleDiscrepancy = 1.5; // static const double minScaleDiscrepancy = 1 / maxScaleDiscrepancy; - ValueNotifier highlightedBeat; + late ValueNotifier highlightedBeat; DateTime focusedBeatUpdated = DateTime(0); - ValueNotifier focusedBeat, tappedBeat; - ValueNotifier tappedPart; + late ValueNotifier focusedBeat, tappedBeat; + late ValueNotifier tappedPart; // ValueNotifier _tappedYCoord; - ValueNotifier requestedScrollOffsetForScale; + late ValueNotifier requestedScrollOffsetForScale; - String _lastIgnoreId; + String? _lastIgnoreId; bool _ignoreNextTap = false; // ignore: unused_field - MusicViewMode _previousMusicViewMode; + MusicViewMode? _previousMusicViewMode; // ignore: unused_field - SplitMode _previousSplitMode; + SplitMode? _previousSplitMode; bool get _aligned => widget.appSettings.alignMusic; set _aligned(bool v) => widget.appSettings.alignMusic = v; @@ -222,11 +221,12 @@ class _MusicViewState extends State with TickerProviderStateMixin { AnimationController animationController() => AnimationController(vsync: this, duration: animationDuration); - SwipeTutorial get currentSwipeTutorial => _currentSwipeTutorial; + SwipeTutorial? get currentSwipeTutorial => _currentSwipeTutorial; + + set currentSwipeTutorial(SwipeTutorial? value) { + if (value == null) return; - set currentSwipeTutorial(SwipeTutorial value) { - if (value == null || - _swipeTutorialsSeen[widget.musicViewMode].contains(value)) { + if (_swipeTutorialsSeen[widget.musicViewMode]?.contains(value) == true) { _currentSwipeTutorial = null; return; } @@ -240,11 +240,11 @@ class _MusicViewState extends State with TickerProviderStateMixin { } _startValueAnimation( - {@required double Function() value, - @required double Function() currentValue, - @required Function(double) applyAnimatedValue, - @required List controllers, - VoidCallback onComplete}) { + {required double Function() value, + required double Function() currentValue, + required Function(double) applyAnimatedValue, + required List controllers, + VoidCallback? onComplete}) { if (value() == currentValue()) { // print("skipping scale animation: no change (${currentValue()} to ${value()}"); } else { @@ -257,7 +257,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { AnimationController scaleAnimationController = animationController(); controllers.add(scaleAnimationController); - Animation animation; + late Animation animation; // print("animating targetedScale to $value"); // print("Tween params: begin: $currentValue, end: $value"); animation = Tween(begin: currentValue(), end: value()) @@ -285,19 +285,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { static const focusTimeout = 2500; _updateFocusedBeatValue( - {int value, bool withDelayClear = true, bool force = false}) { - if (value == null) { - value = getBeat( - Offset( - widget.width / - (context.isLandscape && widget.splitMode == SplitMode.half - ? 4 - : 2), - 0), - targeted: false); - } + {int? value, bool withDelayClear = true, bool force = false}) { if (force || - focusedBeat.value == null || DateTime.now().difference(focusedBeatUpdated).inMilliseconds > focusTimeout) { focusedBeat.value = value; @@ -327,12 +316,12 @@ class _MusicViewState extends State with TickerProviderStateMixin { } _animateScaleAtomically({ - @required DateTime Function() getLockTime, - @required Function(DateTime) setLockTime, - @required double Function() value, - @required double Function() currentValue, - @required Function(double) applyAnimatedValue, - @required List controllers, + required DateTime Function() getLockTime, + required Function(DateTime) setLockTime, + required double Function() value, + required double Function() currentValue, + required Function(double) applyAnimatedValue, + required List controllers, }) { if (value() == currentValue()) { return; @@ -371,8 +360,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { // notifyUpdate(ScaleUpdate(currentValue(), value())); } - if (getLockTime() == null || - DateTime.now().difference(getLockTime()).inMilliseconds > 500) { + if (DateTime.now().difference(getLockTime()).inMilliseconds > 500) { if (lock()) { // print("unlocked"); startAnimation(); @@ -395,9 +383,9 @@ class _MusicViewState extends State with TickerProviderStateMixin { } double get dx => - MatrixUtils.getAsTranslation(transformationController.value).dx; + MatrixUtils.getAsTranslation(transformationController.value)!.dx; double get dy => - MatrixUtils.getAsTranslation(transformationController.value).dy; + MatrixUtils.getAsTranslation(transformationController.value)!.dy; double get scale => transformationController.value.getMaxScaleOnAxis(); set scale(value) { print( @@ -419,7 +407,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { _disposed = false; transformationController = TransformationController() ..addListener(_onTransformationChange); - highlightedBeat = new ValueNotifier(null); + highlightedBeat = new ValueNotifier(0); focusedBeat = new ValueNotifier(null); tappedBeat = new ValueNotifier(null); tappedPart = new ValueNotifier(null); @@ -492,7 +480,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { bool get recordingMelody => widget.recordingMelody && - widget.currentSection.referenceTo(widget.melody).isEnabled; + widget.melody != null && + widget.currentSection.referenceTo(widget.melody!).isEnabled; set ignoreDragEvents(value) { _ignoreDragEvents = value; @@ -505,17 +494,9 @@ class _MusicViewState extends State with TickerProviderStateMixin { @override Widget build(context) { - if (scale == null || targetedScale == null) { + if (targetedScale == null) { final musicScale = widget.appSettings.musicScale; - if (musicScale == null) { - if (context.isTablet) { - scale = 0.33; - } else { - scale = 0.22; - } - } else { - scale = musicScale; - } + scale = musicScale; targetedScale = scale; } // if (targetedScale != null && @@ -528,17 +509,15 @@ class _MusicViewState extends State with TickerProviderStateMixin { (widget.musicViewMode == MusicViewMode.part || widget.musicViewMode == MusicViewMode.melody || widget.musicViewMode == MusicViewMode.score)) { - if (targetedScale.notRoughlyEquals(partAlignedScale)) { + if (targetedScale?.notRoughlyEquals(partAlignedScale) == true) { partAlignVertically(); } - } else if (_aligned && targetedScale.notRoughlyEquals(alignedScale)) { + } else if (_aligned && + targetedScale?.notRoughlyEquals(alignedScale) == true) { alignVertically(); } _previousSplitMode = widget.splitMode; _previousMusicViewMode = widget.musicViewMode; - if (widget.part == null) { - // isConfiguringPart = false; - } if (widget.musicViewMode != MusicViewMode.section) { // isEditingSection = false; } @@ -565,9 +544,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { : (widget.musicViewMode == MusicViewMode.melody) ? Colors.white : (widget.musicViewMode == MusicViewMode.part) - ? ((widget.part != null && - widget.part.instrument.type == - InstrumentType.drum) + ? ((widget.part?.instrument.type == + InstrumentType.drum) ? Colors.brown : Colors.grey) : Colors.black, @@ -786,18 +764,13 @@ class _MusicViewState extends State with TickerProviderStateMixin { child: Align( alignment: Alignment.center, child: AnimatedOpacity( - opacity: currentSwipeTutorial == null || - MyPlatform.isMacOS || - MyPlatform.isWeb - ? 0 - : 0.8, + opacity: + MyPlatform.isMacOS || MyPlatform.isWeb + ? 0 + : 0.8, duration: animationDuration, child: AnimatedContainer( - height: - currentSwipeTutorial == null || - _hasSwipedClosed - ? 0 - : 36, + height: _hasSwipedClosed ? 0 : 36, duration: animationDuration, padding: EdgeInsets.symmetric( horizontal: 10), @@ -810,10 +783,12 @@ class _MusicViewState extends State with TickerProviderStateMixin { child: Column(children: [ Expanded(child: SizedBox()), Text( - currentSwipeTutorial.tutorialText( - widget.splitMode, - widget.musicViewMode, - context), + currentSwipeTutorial + ?.tutorialText( + widget.splitMode, + widget.musicViewMode, + context) ?? + "", style: TextStyle(fontSize: 11), overflow: TextOverflow.fade, maxLines: 2, @@ -864,8 +839,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { duration: animationDuration, opacity: widget.musicViewMode == MusicViewMode.part ? 1 : 0, child: AnimatedContainer( - color: widget.part != null && - widget.part.instrument.type == InstrumentType.drum + color: widget.part?.instrument.type == InstrumentType.drum ? Colors.brown : Colors.grey, duration: animationDuration, @@ -876,7 +850,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { sectionColor: widget.sectionColor, score: widget.score, currentSection: widget.currentSection, - part: widget.part, + part: widget.part ?? Part(), browsingMelodies: isBrowsingPartMelodies, selectOrDeselectMelody: widget.selectOrDeselectMelody, createMelody: widget.createMelody, @@ -891,8 +865,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { : 0, child: AnimatedContainer( duration: animationDuration, - color: widget.part != null && - widget.part.instrument.type == InstrumentType.drum + color: widget.part?.instrument.type == InstrumentType.drum ? Colors.brown : Colors.grey, height: (widget.musicViewMode == MusicViewMode.part && @@ -900,7 +873,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { ? partConfigHeight : 0, child: PartConfiguration( - part: widget.part, + part: widget.part ?? Part(), superSetState: widget.superSetState, availableHeight: widget.height, visible: (widget.musicViewMode == MusicViewMode.part && @@ -925,30 +898,30 @@ class _MusicViewState extends State with TickerProviderStateMixin { Expanded(child: _mainMelody(context)) ], ), - if (MyPlatform.isDebug && false) - IgnorePointer( - child: Container( - width: 2 * targetedScale * beatWidth, - height: widget.height, - decoration: BoxDecoration( - color: Colors.black12, - border: Border.all( - color: Colors.red[500], - ), - borderRadius: BorderRadius.all(Radius.circular(5))), - )), - if (MyPlatform.isDebug && false) - IgnorePointer( - child: Container( - width: widget.width, - height: 50, - decoration: BoxDecoration( - color: Colors.black12, - border: Border.all( - color: Colors.blue[500], - ), - borderRadius: BorderRadius.all(Radius.circular(5))), - )) + // if (MyPlatform.isDebug && false) + // IgnorePointer( + // child: Container( + // width: 2 * targetedScale * beatWidth, + // height: widget.height, + // decoration: BoxDecoration( + // color: Colors.black12, + // border: Border.all( + // color: Colors.red[500], + // ), + // borderRadius: BorderRadius.all(Radius.circular(5))), + // )), + // if (MyPlatform.isDebug && false) + // IgnorePointer( + // child: Container( + // width: widget.width, + // height: 50, + // decoration: BoxDecoration( + // color: Colors.black12, + // border: Border.all( + // color: Colors.blue[500], + // ), + // borderRadius: BorderRadius.all(Radius.circular(5))), + // )) ]); } @@ -1016,7 +989,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { final mainPart = this.mainPart(); parts = [mainPart] + widget.score.parts - .where((p) => p.id != mainPart?.id) + .where((p) => p.id != mainPart.id) .toList(growable: false); } final part = parts[max(0, min(parts.length - 1, partIndex))]; @@ -1035,7 +1008,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { mainPart = widget.keyboardPart; break; case MusicViewMode.part: - mainPart = widget.part; + mainPart = widget.part ?? Part(); break; case MusicViewMode.melody: mainPart = widget.keyboardPart; @@ -1068,13 +1041,13 @@ class _MusicViewState extends State with TickerProviderStateMixin { staves = []; // print("mainPart=$mainPart"); - if (mainPart != null && mainPart.isHarmonic) { + if (mainPart.isHarmonic) { staves.add(PartStaff(mainPart)); - } else if (mainPart != null && mainPart.isDrum) { + } else if (mainPart.isDrum) { staves.add(DrumStaff()); } staves.addAll(widget.score.parts - .where((part) => part.id != mainPart?.id) + .where((part) => part.id != mainPart.id) .map((part) => (part.isDrum) ? DrumStaff() : PartStaff(part)) .toList(growable: false)); // if (widget.score.parts.any((part) => part.isHarmonic && part != mainPart)) { @@ -1089,12 +1062,11 @@ class _MusicViewState extends State with TickerProviderStateMixin { .toList(growable: false); } - bool focusedPartIsNotFirst = mainPart != null && + bool focusedPartIsNotFirst = widget.score.parts.indexWhere((it) => it.id == mainPart.id) != 0; - bool focusedMelodyIsNotFirst = widget.melody != null && - widget.score.parts.indexWhere( - (p) => p.melodies.any((m) => m.id == widget.melody.id)) != - 0; + bool focusedMelodyIsNotFirst = widget.score.parts.indexWhere( + (p) => p.melodies.any((m) => m.id == widget.melody?.id)) != + 0; bool showAutoFocusButton = (widget.musicViewMode == MusicViewMode.part || widget.musicViewMode == MusicViewMode.melody || widget.musicViewMode == MusicViewMode.score) && @@ -1110,9 +1082,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { bool isPartOrMelodyView = widget.musicViewMode == MusicViewMode.part || widget.musicViewMode == MusicViewMode.melody; onLongPress() { - if (widget.score.parts.isEmpty || - tappedPart.value == null || - tappedBeat.value == null) return; + if (widget.score.parts.isEmpty) return; final part = tappedPart.value; if (part == null) return; HapticFeedback.lightImpact(); @@ -1184,10 +1154,12 @@ class _MusicViewState extends State with TickerProviderStateMixin { pointerDown(details.localPosition); }, onTapUp: (details) { - if (ignoreNextTap || tappedBeat.value == null) { + if (ignoreNextTap) { return; } - int beat = tappedBeat.value; + final beat = tappedBeat.value; + if (beat == null) return; + print( "onTapUp: ${details.localPosition} -> beat: $beat; x/t: $scale/$targetedScale"); if (BeatScratchPlugin.playing && @@ -1290,19 +1262,19 @@ class _MusicViewState extends State with TickerProviderStateMixin { handleZoomAlign() { return; - if (autoZoomAlign) { - final double x = targetedScale, - // y = targetedScale, - w = widget.width, - numberOfBeatsOnScreen = w / (x * beatWidth), - targetNumberOfBeatsOnScreen = numberOfBeatsOnScreen.roundToDouble(), - newXScale = w / (targetNumberOfBeatsOnScreen * beatWidth); - targetedScale = newXScale; - // scale = newXScale; - } + // if (autoZoomAlign) { + // final double x = targetedScale, + // // y = targetedScale, + // w = widget.width, + // numberOfBeatsOnScreen = w / (x * beatWidth), + // targetNumberOfBeatsOnScreen = numberOfBeatsOnScreen.roundToDouble(), + // newXScale = w / (targetNumberOfBeatsOnScreen * beatWidth); + // targetedScale = newXScale; + // // scale = newXScale; + // } } - Widget autoFocusButton({bool visible}) { + Widget autoFocusButton({required bool visible}) { return MusicActionButton( child: Stack(children: [ Transform.translate( @@ -1362,7 +1334,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { }); } - Widget autoZoomAlignButton({bool visible}) { + Widget autoZoomAlignButton({required bool visible}) { return MusicActionButton( child: Stack(children: [ Transform.translate( @@ -1380,7 +1352,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { duration: animationDuration, opacity: !autoZoomAlign ? 1 : 0, child: Icon( - Feather.move, + FeatherIcons.move, color: Colors.grey, ), ), @@ -1399,7 +1371,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { duration: animationDuration, opacity: autoZoomAlign ? 1 : 0, child: Icon( - Feather.move, + FeatherIcons.move, color: Colors.white, ), ), @@ -1427,11 +1399,11 @@ class _MusicViewState extends State with TickerProviderStateMixin { Widget zoomButton() { final zoomIncrement = .01; final bigDecrementIcon = - (targetedScale > alignedScale || scale > alignedScale) + (targetedScale! > alignedScale || scale > alignedScale) ? Icons.expand : FontAwesomeIcons.compressArrowsAlt; final bigDecrementAction = - (targetedScale > alignedScale || scale > alignedScale) + (targetedScale! > alignedScale || scale > alignedScale) ? () { setState(() { _aligned = true; @@ -1443,7 +1415,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { }); }); } - : (targetedScale > minScale || scale > minScale) + : (targetedScale! > minScale || scale > minScale) ? () { setState(() { minimize(); @@ -1451,11 +1423,11 @@ class _MusicViewState extends State with TickerProviderStateMixin { } : null; final bigIncrementIcon = - (targetedScale >= alignedScale || scale >= alignedScale) + (targetedScale! >= alignedScale || scale >= alignedScale) ? FontAwesomeIcons.expandArrowsAlt : Icons.expand; final bigIncrementAction = - (targetedScale < alignedScale || scale < alignedScale) + (targetedScale! < alignedScale || scale < alignedScale) ? () { setState(() { _aligned = true; @@ -1468,8 +1440,8 @@ class _MusicViewState extends State with TickerProviderStateMixin { }); }); } - : (targetedScale < partAlignedScale || scale < partAlignedScale) && - !targetedScale.roughlyEquals(partAlignedScale) && + : (targetedScale! < partAlignedScale || scale < partAlignedScale) && + !targetedScale!.roughlyEquals(partAlignedScale) && widget.musicViewMode != MusicViewMode.section ? () { setState(() { @@ -1510,7 +1482,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { musicForegroundColor.withOpacity(0.54)))), Transform.translate( offset: Offset(2, 20), - child: Text(scaleText(targetedScale), + child: Text(scaleText(targetedScale!), style: TextStyle( fontWeight: FontWeight.w800, fontSize: 12, @@ -1532,7 +1504,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { incrementIcon: Icons.zoom_in, decrementIcon: Icons.zoom_out, incrementDistance: 1, - onIncrement: (targetedScale < maxScale || scale < maxScale) + onIncrement: (targetedScale! < maxScale || scale < maxScale) ? () { _ignoreNextScale = true; _aligned = false; @@ -1540,12 +1512,12 @@ class _MusicViewState extends State with TickerProviderStateMixin { setState(() { print("zoomIn: scale=$scale, targetedScale=$targetedScale"); targetedScale = - min(maxScale, targetedScale + zoomIncrement); + min(maxScale, targetedScale! + zoomIncrement); // print("zoomIn done; targetedScale=$targetXScale, scale=$targetYScale"); }); } : null, - onDecrement: (targetedScale > minScale || scale > minScale) + onDecrement: (targetedScale! > minScale || scale > minScale) ? () { _ignoreNextScale = true; _aligned = false; @@ -1554,7 +1526,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { print( "zoomOut: scale=$scale, targetedScale=$targetedScale"); targetedScale = - max(minScale, targetedScale - zoomIncrement); + max(minScale, targetedScale! - zoomIncrement); // print("zoomOut done; targetedScale=$targetedScale, scale=$scale"); }); } @@ -1609,7 +1581,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { } } - Widget autoScrollButton({@required bool visible}) { + Widget autoScrollButton({required bool visible}) { return MusicActionButton( child: Stack(children: [ Transform.translate( @@ -1648,7 +1620,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { ); } - Widget nightModeButton({@required bool visible}) { + Widget nightModeButton({required bool visible}) { return MusicActionButton( child: Icon(FontAwesomeIcons.solidMoon, color: musicForegroundColor), color: musicBackgroundColor.withOpacity(0.12), @@ -1660,7 +1632,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { ); } - Widget colorblockButton({@required bool visible}) { + Widget colorblockButton({required bool visible}) { return MusicActionButton( child: Stack(children: [ AnimatedOpacity( @@ -1715,7 +1687,7 @@ class _MusicViewState extends State with TickerProviderStateMixin { } double get sectionsHeight => widget.musicViewMode == MusicViewMode.score || - targetedScale < 2 * MusicScrollContainer.minScale + (targetedScale ?? 1) < 2 * MusicScrollContainer.minScale ? 30 : 0; diff --git a/lib/music_view/part_instrument_picker.dart b/lib/music_view/part_instrument_picker.dart index 02ce5490..84dba0c5 100644 --- a/lib/music_view/part_instrument_picker.dart +++ b/lib/music_view/part_instrument_picker.dart @@ -1,8 +1,6 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:animated_list_plus/animated_list_plus.dart'; import 'package:animated_list_plus/transitions.dart'; import 'package:recase/recase.dart'; @@ -24,11 +22,11 @@ class PartConfiguration extends StatefulWidget { final bool visible; const PartConfiguration( - {Key key, - this.part, - this.superSetState, - this.availableHeight, - this.visible}) + {Key? key, + required this.part, + required this.superSetState, + required this.availableHeight, + required this.visible}) : super(key: key); @override @@ -39,28 +37,26 @@ class _PartConfigurationState extends State { TextEditingController searchController = TextEditingController(); ScrollController scrollController = ScrollController(); - int get midiChannel => widget.part?.instrument?.midiChannel; + int get midiChannel => widget.part.instrument.midiChannel; - int get midiInstrument => widget.part?.instrument?.midiInstrument; + int get midiInstrument => widget.part.instrument.midiInstrument; - int get midiMsb => widget.part?.instrument?.midiGm2Msb; + int get midiMsb => widget.part.instrument.midiGm2Msb; - int get midiLsb => widget.part?.instrument?.midiGm2Lsb; + int get midiLsb => widget.part.instrument.midiGm2Lsb; set midiChannel(int value) { - widget.part?.instrument?.midiChannel = value; + widget.part.instrument.midiChannel = value; } set midiInstrument(int value) { var part = widget.part; - if (part != null) { - part.instrument.midiInstrument = value; - } + part.instrument.midiInstrument = value; } - bool get isHarmonic => widget?.part?.isHarmonic ?? false; + bool get isHarmonic => widget.part.isHarmonic ?? false; - bool get isDrum => widget?.part?.isDrum ?? false; + bool get isDrum => widget.part.isDrum ?? false; String searchText = ""; Widget _buildMidiInstrumentDisplay( @@ -193,7 +189,7 @@ class _PartConfigurationState extends State { onChanged: (value) { widget.superSetState(() { setState(() { - widget.part?.instrument?.volume = value; + widget.part.instrument.volume = value; BeatScratchPlugin.updatePartConfiguration( widget.part); }); diff --git a/lib/music_view/part_melody_browser.dart b/lib/music_view/part_melody_browser.dart index 4bbcf525..13226e86 100644 --- a/lib/music_view/part_melody_browser.dart +++ b/lib/music_view/part_melody_browser.dart @@ -1,6 +1,5 @@ import '../layers_view/melody_menu_browser.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:animated_list_plus/animated_list_plus.dart'; import 'package:animated_list_plus/transitions.dart'; @@ -24,15 +23,15 @@ class PartMelodyBrowser extends StatefulWidget { final Function(Part, Melody, bool) createMelody; const PartMelodyBrowser( - {Key key, - this.sectionColor, - this.score, - this.currentSection, - this.part, - this.browsingMelodies, - this.selectOrDeselectMelody, - this.createMelody, - this.toggleMelodyReference}) + {Key? key, + required this.sectionColor, + required this.score, + required this.currentSection, + required this.part, + required this.browsingMelodies, + required this.selectOrDeselectMelody, + required this.createMelody, + required this.toggleMelodyReference}) : super(key: key); @override @@ -41,7 +40,7 @@ class PartMelodyBrowser extends StatefulWidget { class _PartMelodyBrowserState extends State with TickerProviderStateMixin { - ScrollController _scrollController; + late ScrollController _scrollController; @override void initState() { @@ -164,10 +163,10 @@ class _PartMelodyBrowserState extends State } Widget getList(BuildContext context) { - var items = widget.part?.melodies ?? []; + var items = widget.part.melodies ?? []; items.sort((m1, m2) { - final r1 = widget.currentSection.referenceTo(m1); - final r2 = widget.currentSection.referenceTo(m2); + final r1 = widget.currentSection.referenceTo(m1)!; + final r2 = widget.currentSection.referenceTo(m2)!; if (r1.isEnabled == r2.isEnabled) { return 0; } else if (r1.isEnabled) { @@ -184,7 +183,7 @@ class _PartMelodyBrowserState extends State items: items, areItemsTheSame: (a, b) => a.id == b.id, itemBuilder: (context, animation, melody, index) { - final reference = widget.currentSection.referenceTo(melody); + final reference = widget.currentSection.referenceTo(melody)!; final width = 120.0; final height = widget.browsingMelodies ? 48.0 : 0.0; @@ -211,7 +210,7 @@ class _PartMelodyBrowserState extends State }, onLongPress: () { final reference = - widget.currentSection.referenceTo(melody); + widget.currentSection.referenceTo(melody)!; widget.toggleMelodyReference(reference); }, child: Text(melody.canonicalName, diff --git a/lib/music_view/section_editing_toolbar.dart b/lib/music_view/section_editing_toolbar.dart index 9806c7bf..cc2912ee 100644 --- a/lib/music_view/section_editing_toolbar.dart +++ b/lib/music_view/section_editing_toolbar.dart @@ -15,7 +15,10 @@ class SectionEditingToolbar extends StatefulWidget { final Section currentSection; const SectionEditingToolbar( - {Key key, this.sectionColor, this.score, this.currentSection}) + {Key? key, + required this.sectionColor, + required this.score, + required this.currentSection}) : super(key: key); @override @@ -24,9 +27,9 @@ class SectionEditingToolbar extends StatefulWidget { class _SectionEditingToolbarState extends State with TickerProviderStateMixin { - AnimationController animationController; - Color recordingAnimationColor; - Animation recordingAnimation; + late AnimationController animationController; + late Color recordingAnimationColor; + late Animation recordingAnimation; static final List keys = [ NoteName() @@ -116,7 +119,7 @@ class _SectionEditingToolbarState extends State // } else { // recordingColor = Colors.grey; // } - NoteName key = widget.currentSection.harmony.data[0].rootNote; + NoteName key = widget.currentSection.harmony.data[0]!.rootNote; int keyIndex = keys.indexOf(key); return Row(children: [ SizedBox(width: 1), @@ -290,7 +293,7 @@ class _SectionEditingToolbarState extends State if (newKeyIndex < 0) { newKeyIndex += keys.length; } - widget.currentSection.harmony.data[0].rootNote = keys[newKeyIndex]; + widget.currentSection.harmony.data[0]!.rootNote = keys[newKeyIndex]; MelodyTheory.tonesInMeasureCache.clear(); BeatScratchPlugin.onSynthesizerStatusChange(); BeatScratchPlugin.updateSections(widget.score); @@ -303,7 +306,7 @@ class _SectionEditingToolbarState extends State if (newKeyIndex >= keys.length) { newKeyIndex -= keys.length; } - widget.currentSection.harmony.data[0].rootNote = keys[newKeyIndex]; + widget.currentSection.harmony.data[0]!.rootNote = keys[newKeyIndex]; MelodyTheory.tonesInMeasureCache.clear(); BeatScratchPlugin.onSynthesizerStatusChange(); BeatScratchPlugin.updateSections(widget.score); diff --git a/lib/recording/recording.dart b/lib/recording/recording.dart index 38dcbcc0..d080229a 100644 --- a/lib/recording/recording.dart +++ b/lib/recording/recording.dart @@ -3,7 +3,6 @@ import 'dart:collection'; import '../beatscratch_plugin.dart'; import '../generated/protos/music.pb.dart'; import '../generated/protos/protobeats_plugin.pb.dart'; -import '../util/bs_methods.dart'; import '../util/util.dart'; import '../util/proto_utils.dart'; import '../util/music_theory.dart'; @@ -13,11 +12,11 @@ import '../util/music_utils.dart'; /// are sent to this singleton queue for processing. class RecordedSegmentQueue { /// Gets the recording melody from your UI. Should be set in [State.initState] and [State.dispose]. - static Melody Function() getRecordingMelody; + static Melody? Function()? getRecordingMelody; /// Should be set in [State.initState] and [State.dispose] for - static Function(Melody) updateRecordingMelody; - static Melody get recordingMelody => getRecordingMelody?.call(); + static Function(Melody)? updateRecordingMelody; + static Melody? get recordingMelody => getRecordingMelody?.call(); // static set recordingMelody(Melody melody) => updateRecordingMelody(melody); static final Queue segments = ListQueue(); static final BSValueMethod enabled = BSValueMethod(false) @@ -49,20 +48,20 @@ class RecordedSegmentQueue { static _processSegment(RecordedSegment segment) { if (segment.recordedData.isEmpty) return; final melody = recordingMelody; - if (melody != null) { - RecordedSegment_RecordedBeat firstBeat = - segment.beats.minBy((rb) => rb.timestamp.toInt()); - RecordedSegment_RecordedBeat secondBeat = - segment.beats.maxBy((rb) => rb.timestamp.toInt()); - segment.recordedData.forEach((data) { - _processSegmentData(segment, data, melody, firstBeat, secondBeat); - }); - print("applying PostProcessing: separateNoteOnAndOffs()"); - melody.separateNoteOnAndOffs(); - print("updateRecordingMelody?.call: ${melody.logString}"); - updateRecordingMelody?.call(melody); - BeatScratchPlugin.updateMelody(melody); - } + if (melody == null) return; + + RecordedSegment_RecordedBeat firstBeat = + segment.beats.minBy((rb) => rb.timestamp.toInt()); + RecordedSegment_RecordedBeat secondBeat = + segment.beats.maxBy((rb) => rb.timestamp.toInt())!; + segment.recordedData.forEach((data) { + _processSegmentData(segment, data, melody, firstBeat, secondBeat); + }); + print("applying PostProcessing: separateNoteOnAndOffs()"); + melody.separateNoteOnAndOffs(); + print("updateRecordingMelody?.call: ${melody.logString}"); + updateRecordingMelody?.call(melody); + BeatScratchPlugin.updateMelody(melody); } static _processSegmentData( diff --git a/lib/settings/app_settings.dart b/lib/settings/app_settings.dart index 375e3cf9..3ce354a5 100644 --- a/lib/settings/app_settings.dart +++ b/lib/settings/app_settings.dart @@ -9,7 +9,7 @@ import '../widget/my_platform.dart'; class AppSettings { static bool initializingState = true; static RenderingMode globalRenderingMode = RenderingMode.notation; - SharedPreferences _preferences; + SharedPreferences? _preferences; AppSettings() { _initialize(); } @@ -26,7 +26,7 @@ class AppSettings { melodyColor = darkMode ? Color(0xFF424242) : Color(0xFFDDDDDD); globalRenderingMode = renderingMode; if (!initializingState) { - BeatScratchPlugin.onSynthesizerStatusChange?.call(); + BeatScratchPlugin.onSynthesizerStatusChange.call(); } } @@ -79,7 +79,7 @@ class AppSettings { _preferences?.setBool("autoZoomAlignMusic", value); } - double get musicScale => _preferences?.getDouble('musicScale'); + double get musicScale => _preferences?.getDouble('musicScale') ?? 1.0; set musicScale(double value) => _preferences?.setDouble("musicScale", value); bool get alignMusic => _preferences?.getBool('alignMusic') ?? true; @@ -93,7 +93,9 @@ class AppSettings { } RenderingMode get renderMode => RenderingMode.values.firstWhere( - (m) => m.toString().endsWith(_preferences.getString('renderMode')), + (m) => m + .toString() + .endsWith(_preferences?.getString('renderMode') ?? 'no render mode'), orElse: () => RenderingMode.notation); set renderMode(RenderingMode value) => _preferences?.setString( "musicRenderingType", value.toString().split('.').last); diff --git a/lib/settings/settings_common.dart b/lib/settings/settings_common.dart index 8cf206de..94ba6c11 100644 --- a/lib/settings/settings_common.dart +++ b/lib/settings/settings_common.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_appavailability/flutter_appavailability.dart'; +import 'package:appcheck/appcheck.dart'; import '../beatscratch_plugin.dart'; import '../generated/protos/protos.dart'; @@ -11,7 +10,7 @@ extension Sanitize on String { } extension ControllerNameOrId on MidiController { - String get nameOrId => (name?.isNotEmpty == true) ? name : id; + String get nameOrId => (name.isNotEmpty == true) ? name : id; } const Map supportedAndroidSynthApps = { @@ -22,11 +21,10 @@ const Map supportedAndroidControllerApps = { }; _launchAndroidApp(BuildContext context, String packageName) async { - AppAvailability.launchApp(packageName).then((_) { + new AppCheck().launchApp(packageName).then((_) { print("App $packageName launched!"); }).catchError((err) { ScaffoldMessenger.of(context) - // ignore: deprecated_member_use .showSnackBar(SnackBar(content: Text("App $packageName not found!"))); print(err); }); @@ -45,10 +43,14 @@ Future getApps() async { if (MyPlatform.isAndroid) { // print(await AppAvailability.checkAvailability( // "net.volcanomobile.fluidsynthmidi")); - hasVolcanoFluidSynth = - await AppAvailability.isAppEnabled("net.volcanomobile.fluidsynthmidi"); - hasMobileerMidiBTLEPairing = await AppAvailability.isAppEnabled( - "com.mobileer.example.midibtlepairing"); + hasVolcanoFluidSynth = await new AppCheck() + .checkAvailability("net.volcanomobile.fluidsynthmidi") != + null; + hasMobileerMidiBTLEPairing = await new AppCheck() + .checkAvailability("com.mobileer.example.midibtlepairing") != + null; + // await AppAvailability.isAppEnabled( + // "com.mobileer.example.midibtlepairing"); // Returns: true } diff --git a/lib/settings/settings_panel.dart b/lib/settings/settings_panel.dart index 36368552..a7fb91e0 100644 --- a/lib/settings/settings_panel.dart +++ b/lib/settings/settings_panel.dart @@ -1,14 +1,11 @@ import 'dart:async'; -import 'dart:ui'; -import 'package:dart_midi/dart_midi.dart'; -import 'package:dart_midi/src/byte_writer.dart'; +import 'package:beatscratch_flutter_redux/midi/byte_writer.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_events.dart'; import '../messages/messages_ui.dart'; import 'tile_bluetooth.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter_midi_command/flutter_midi_command.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:animated_list_plus/animated_list_plus.dart'; @@ -16,14 +13,12 @@ import 'package:animated_list_plus/transitions.dart'; import '../beatscratch_plugin.dart'; import '../colors.dart'; -import '../generated/protos/protobeats_plugin.pb.dart'; import '../generated/protos/protos.dart'; import '../music_preview/melody_preview.dart'; import 'app_settings.dart'; import '../storage/universe_manager.dart'; import '../ui_models.dart'; import '../universe_view/universe_icon.dart'; -import '../util/bs_methods.dart'; import '../util/dummydata.dart'; import '../util/midi_theory.dart'; import '../util/util.dart'; @@ -50,20 +45,20 @@ class SettingsPanel extends StatefulWidget { final Part keyboardPart; const SettingsPanel( - {Key key, - @required this.appSettings, - @required this.universeManager, - @required this.sectionColor, - @required this.close, - @required this.enableColorboard, - @required this.setColorboardEnabled, - @required this.toggleKeyboardConfig, - @required this.toggleColorboardConfig, - @required this.bluetoothScan, - @required this.visible, - @required this.messagesUI, - @required this.bluetoothControllerPressedNotes, - @required this.keyboardPart}) + {Key? key, + required this.appSettings, + required this.universeManager, + required this.sectionColor, + required this.close, + required this.enableColorboard, + required this.setColorboardEnabled, + required this.toggleKeyboardConfig, + required this.toggleColorboardConfig, + required this.bluetoothScan, + required this.visible, + required this.messagesUI, + required this.bluetoothControllerPressedNotes, + required this.keyboardPart}) : super(key: key); @override @@ -76,15 +71,15 @@ class _SettingsPanelState extends State { BeatScratchPlugin.midiControllers; Iterable get midiSynthesizers => BeatScratchPlugin.midiSynthesizers; - List observedDevices; - List connectedDeviceIds; - StreamSubscription midiCommandSubscription; + late List observedDevices; + late List connectedDeviceIds; + late StreamSubscription? midiCommandSubscription; _startBluetoothScanLoop() async { MidiCommand().devices.then((results) { - observedDevices = results.where((r) => r.type == "BLE").toList(); + observedDevices = results?.where((r) => r.type == "BLE").toList() ?? []; connectedDeviceIds - .removeWhere((id) => !observedDevices.any((d) => d.id == id)); + .removeWhere((id) => !observedDevices!.any((d) => d.id == id)); Future.delayed( Duration(seconds: widget.visible ? 5 : 15), _startBluetoothScanLoop); }); @@ -108,13 +103,13 @@ class _SettingsPanelState extends State { if (e is NoteOnEvent) { e.channel = widget.keyboardPart.instrument.midiChannel; e.writeEvent(writer); - widget.bluetoothControllerPressedNotes.value[event.device.id] + widget.bluetoothControllerPressedNotes.value[event.device.id]! .add(e.noteNumber - 60); widget.bluetoothControllerPressedNotes.notifyListeners(); } else if (e is NoteOffEvent) { e.channel = widget.keyboardPart.instrument.midiChannel; e.writeEvent(writer); - widget.bluetoothControllerPressedNotes.value[event.device.id] + widget.bluetoothControllerPressedNotes.value[event.device.id]! .remove(e.noteNumber - 60); widget.bluetoothControllerPressedNotes.notifyListeners(); } @@ -172,7 +167,7 @@ class _SettingsPanelState extends State { BeatScratchPlugin.supportsSynthesizerConfig ? this.midiSynthesizers.toList() : [this.midiSynthesizers.toList().first]; - List appSettings = [ + List appSettings = [ SeparatorTile(text: "App Settings", id: "app-settings"), SettingsTile( id: "pasteeIntegration", @@ -474,17 +469,19 @@ class _SettingsPanelState extends State { ), ), ]; - List items = [ + List items = [ if (observedDevices.isNotEmpty) SeparatorTile(text: "Bluetooth Devices", id: "bluetooth-settings"), - ...observedDevices, + ...observedDevices.map((d) => IdentifiableWrapper(d, d.id)), SeparatorTile(text: "MIDI Devices", id: "midi-settings"), - ...midiSynthesizers, - ...midiControllers, + ...midiSynthesizers + .map((d) => IdentifiableWrapper(d, d.id)), + ...midiControllers + .map((d) => IdentifiableWrapper(d, d.id)), ...appSettings, ...features, ]; - return ImplicitlyAnimatedList( + return ImplicitlyAnimatedList( key: ValueKey("MidiSettingsList"), scrollDirection: Axis.horizontal, spawnIsolate: false, @@ -498,40 +495,42 @@ class _SettingsPanelState extends State { return SizedBox(); } final dynamic item = items[index]; - Widget tile; - if (item is MidiController) { - tile = MidiControllerTile( - appSettings: widget.appSettings, - scrollDirection: Axis.horizontal, - midiController: item, - enableColorboard: widget.enableColorboard, - setColorboardEnabled: widget.setColorboardEnabled, - sectionColor: widget.sectionColor, - toggleKeyboardConfig: widget.toggleKeyboardConfig, - toggleColorboardConfig: widget.toggleColorboardConfig, - ); - } else if (item is MidiSynthesizer) { - tile = MidiSynthTile( - scrollDirection: Axis.horizontal, - midiSynthesizer: item, - ); - } else if (item is SettingsTile || item is SeparatorTile) { + Widget? tile; + if (item is SettingsTile || item is SeparatorTile || item is Widget) { tile = item; - } else if (item is MidiDevice) { - tile = BluetoothDeviceTile( - key: ValueKey("Bluetooth-Device-${item.id}"), - device: item, - sectionColor: widget.sectionColor, - connected: connectedDeviceIds.any((id) => id == item.id), - onConnect: () { - connectedDeviceIds.add(item.id.toString()); - }, - onDisconnect: () { - connectedDeviceIds.remove(item.id.toString()); - }, - bluetoothControllerPressedNotes: - widget.bluetoothControllerPressedNotes, - ); + } else if (item is IdentifiableWrapper) { + if (item.item is MidiController) { + tile = MidiControllerTile( + appSettings: widget.appSettings, + scrollDirection: Axis.horizontal, + midiController: item.item, + enableColorboard: widget.enableColorboard, + setColorboardEnabled: widget.setColorboardEnabled, + sectionColor: widget.sectionColor, + toggleKeyboardConfig: widget.toggleKeyboardConfig, + toggleColorboardConfig: widget.toggleColorboardConfig, + ); + } else if (item.item is MidiSynthesizer) { + tile = MidiSynthTile( + scrollDirection: Axis.horizontal, + midiSynthesizer: item.item, + ); + } else if (item is MidiDevice) { + tile = BluetoothDeviceTile( + key: ValueKey("Bluetooth-Device-${item.id}"), + device: item.item, + sectionColor: widget.sectionColor, + connected: connectedDeviceIds.any((id) => id == item.id), + onConnect: () { + connectedDeviceIds.add(item.id.toString()); + }, + onDisconnect: () { + connectedDeviceIds.remove(item.id.toString()); + }, + bluetoothControllerPressedNotes: + widget.bluetoothControllerPressedNotes, + ); + } } tile = Padding(padding: EdgeInsets.all(5), child: tile); return SizeFadeTransition( @@ -708,3 +707,13 @@ showColors(BuildContext context, Color sectionColor) { ), ); } + +abstract class Identifiable { + String get id; +} + +class IdentifiableWrapper extends Identifiable { + final T item; + final String id; + IdentifiableWrapper(this.item, this.id); +} diff --git a/lib/settings/tile.dart b/lib/settings/tile.dart index 82909bc0..333012b0 100644 --- a/lib/settings/tile.dart +++ b/lib/settings/tile.dart @@ -1,21 +1,18 @@ -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; +import 'package:beatscratch_flutter_redux/settings/settings.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import '../ui_models.dart'; import '../widget/my_buttons.dart'; -class SettingsTile extends StatelessWidget { +class SettingsTile extends StatelessWidget implements Identifiable { final String id; - final Widget child; - final Color color; - final VoidCallback onPressed; + final Widget? child; + final Color? color; + final VoidCallback? onPressed; const SettingsTile({ - Key key, - this.id, + Key? key, + required this.id, this.child, this.color, this.onPressed, @@ -39,14 +36,14 @@ class SettingsTile extends StatelessWidget { } } -class SeparatorTile extends StatelessWidget { +class SeparatorTile extends StatelessWidget implements Identifiable { final String text; final String id; const SeparatorTile({ - Key key, - this.text, - this.id, + Key? key, + required this.text, + required this.id, }) : super(key: key); @override Widget build(BuildContext context) { diff --git a/lib/settings/tile_bluetooth.dart b/lib/settings/tile_bluetooth.dart index c3baaaef..7362fc8c 100644 --- a/lib/settings/tile_bluetooth.dart +++ b/lib/settings/tile_bluetooth.dart @@ -1,21 +1,10 @@ -import 'dart:ui'; - -import 'package:beatscratch_flutter_redux/beatscratch_plugin.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import '../ui_models.dart'; import '../widget/my_buttons.dart'; -import 'dart:ui'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter_midi_command/flutter_midi_command.dart'; -import '../ui_models.dart'; -import '../widget/my_buttons.dart'; import '../colors.dart'; class BluetoothDeviceTile extends StatefulWidget { @@ -26,17 +15,16 @@ class BluetoothDeviceTile extends StatefulWidget { final VoidCallback onConnect, onDisconnect; final Color sectionColor; final bool connected; - final ValueNotifier>> - bluetoothControllerPressedNotes; + final ValueNotifier>> bluetoothControllerPressedNotes; const BluetoothDeviceTile( - {Key key, - @required this.connected, - @required this.device, - @required this.sectionColor, - @required this.onConnect, - @required this.onDisconnect, - @required this.bluetoothControllerPressedNotes}) + {Key? key, + required this.connected, + required this.device, + required this.sectionColor, + required this.onConnect, + required this.onDisconnect, + required this.bluetoothControllerPressedNotes}) : super(key: key); @override diff --git a/lib/settings/tile_controller.dart b/lib/settings/tile_controller.dart index 74d4ca74..830fd936 100644 --- a/lib/settings/tile_controller.dart +++ b/lib/settings/tile_controller.dart @@ -1,9 +1,7 @@ import 'dart:math'; -import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import '../beatscratch_plugin.dart'; import '../colors.dart'; @@ -25,15 +23,15 @@ class MidiControllerTile extends StatelessWidget { final VoidCallback toggleColorboardConfig; const MidiControllerTile( - {Key key, - this.appSettings, - this.enableColorboard, - this.setColorboardEnabled, - this.scrollDirection, - this.midiController, - this.sectionColor, - this.toggleKeyboardConfig, - this.toggleColorboardConfig}) + {Key? key, + required this.appSettings, + required this.enableColorboard, + required this.setColorboardEnabled, + required this.scrollDirection, + required this.midiController, + required this.sectionColor, + required this.toggleKeyboardConfig, + required this.toggleColorboardConfig}) : super(key: key); @override Widget build(BuildContext context) { @@ -204,7 +202,7 @@ class MidiControllerTile extends StatelessWidget { value: appSettings.controllersReplacingKeyboard .contains(midiController.nameOrId), onChanged: (v) { - if (v) { + if (v == true) { appSettings.controllersReplacingKeyboard = appSettings.controllersReplacingKeyboard + [midiController.nameOrId]; diff --git a/lib/settings/tile_synth.dart b/lib/settings/tile_synth.dart index 44d7e16f..9d299306 100644 --- a/lib/settings/tile_synth.dart +++ b/lib/settings/tile_synth.dart @@ -1,8 +1,5 @@ -import 'dart:ui'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import '../beatscratch_plugin.dart'; import '../colors.dart'; @@ -17,7 +14,8 @@ class MidiSynthTile extends StatefulWidget { final Axis scrollDirection; final MidiSynthesizer midiSynthesizer; - const MidiSynthTile({Key key, this.scrollDirection, this.midiSynthesizer}) + const MidiSynthTile( + {Key? key, required this.scrollDirection, required this.midiSynthesizer}) : super(key: key); @override diff --git a/lib/storage/migrations.dart b/lib/storage/migrations.dart index 0156f07a..c1cb5bdc 100644 --- a/lib/storage/migrations.dart +++ b/lib/storage/migrations.dart @@ -14,15 +14,15 @@ extension Migrations on Score { } _setBpmsOnSections() { - double firstSeenBpm; + double? firstSeenBpm; for (Section section in sections) { - if (section.tempo != null && section.tempo.bpm != null) { + if (section.tempo.bpm > 0) { firstSeenBpm = section.tempo.bpm; break; } } sections.forEach((section) { - if (section.tempo == null || section.tempo.bpm == null) { + if (section.tempo.bpm > 0) { section.tempo = Tempo()..bpm = firstSeenBpm ?? 123; } }); diff --git a/lib/storage/score_manager.dart b/lib/storage/score_manager.dart index 8fbaea86..646da5d7 100644 --- a/lib/storage/score_manager.dart +++ b/lib/storage/score_manager.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:io'; import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; @@ -27,9 +26,9 @@ class ScoreManager { static const String FROM_WEB = " (from Link)"; static const String UNIVERSE_SCORE = "Universe Score"; static const String FROM_UNIVERSE = " (from Universe)"; - Function(Score) doOpenScore; - Directory scoresDirectory; - SharedPreferences _prefs; + Function(Score)? doOpenScore; + Directory? scoresDirectory; + SharedPreferences? _prefs; String get currentScoreName => _prefs?.getString('currentScoreName') ?? UNIVERSE_SCORE; @@ -38,19 +37,18 @@ class ScoreManager { _prefs?.setString("currentScoreName", value); File get currentScoreFile => File( - "${scoresDirectory.path}/${Uri.encodeComponent(currentScoreName).replaceAll("%20", " ")}.beatscratch"); + "${scoresDirectory?.path}/${Uri.encodeComponent(currentScoreName).replaceAll("%20", " ")}.beatscratch"); List get scoreFiles { - if (scoresDirectory != null) { - List result = scoresDirectory - ?.listSync() - .where((f) => f.path.endsWith(".beatscratch")) - .toList(); - result.sort( - (a, b) => b.statSync().modified.compareTo(a.statSync().modified)); - return result; - } - return []; + List result = scoresDirectory + ?.listSync() + .where((f) => f.path.endsWith(".beatscratch")) + .toList() ?? + []; + result + .sort((a, b) => b.statSync().modified.compareTo(a.statSync().modified)); + return result; + // return []; } ScoreManager() { @@ -65,7 +63,7 @@ class ScoreManager { Directory documentsDirectory = await getApplicationDocumentsDirectory(); final scoresPath = "${documentsDirectory.path}/$scoresDirectoryName"; scoresDirectory = Directory(scoresPath); - scoresDirectory.createSync(); + scoresDirectory!.createSync(); //Migrate files scoreFiles.forEach((file) { @@ -76,11 +74,11 @@ class ScoreManager { } } - createScore(String name, {Score score}) { + createScore(String name, {Score? score}) { score = score ?? defaultScore(); currentScoreName = name; saveCurrentScore(score); - doOpenScore(score); + doOpenScore?.call(score); } saveCurrentScore(Score score) { @@ -115,9 +113,9 @@ class ScoreManager { loadFromScoreUrl(String scoreUrl, {String newScoreDefaultFilename = PASTED_SCORE, String newScoreNameSuffix = FROM_CLIPBOARD, - Score currentScoreToSave, - VoidCallback onFail, - Function(String) onSuccess}) { + required Score currentScoreToSave, + VoidCallback? onFail, + Function(String)? onSuccess}) { print("ScoreURL=$scoreUrl"); scoreUrl = scoreUrl.replaceFirst(new RegExp(r'http.*#score='), ''); scoreUrl = scoreUrl.replaceFirst(new RegExp(r'http.*#/score/'), ''); @@ -126,8 +124,8 @@ class ScoreManager { if (scoreUrl.length < 10) { throw Exception("nope"); } - Score score = scoreFromUrlHashValue(scoreUrl); - if (score == null || score.sections.isEmpty) { + Score score = scoreFromUrlHashValue(scoreUrl)!; + if (score.sections.isEmpty) { throw Exception("nope"); } String scoreName = score.name ?? ""; @@ -137,9 +135,7 @@ class ScoreManager { } else { suggestedScoreName += newScoreNameSuffix; } - if (currentScoreToSave != null) { - saveCurrentScore(currentScoreToSave); - } + saveCurrentScore(currentScoreToSave); openScoreWithFilename( score, newScoreDefaultFilename); // side-effect: updates this.score _lastSuggestedScoreName = suggestedScoreName; @@ -154,8 +150,8 @@ class ScoreManager { } } - static String _lastSuggestedScoreName; - static String get lastSuggestedScoreName { + static String? _lastSuggestedScoreName; + static String? get lastSuggestedScoreName { final value = _lastSuggestedScoreName; _lastSuggestedScoreName = null; return value; @@ -166,7 +162,7 @@ class ScoreManager { } Future loadPastebinScore(String codeOrUrl, - {String titleOverride}) async { + {String? titleOverride}) async { final code = codeOrUrl.replaceFirst(new RegExp(r'http.*#/s/'), ''); http.Response response = await http.get( @@ -180,22 +176,17 @@ class ScoreManager { longUrl = longUrl.replaceFirst(new RegExp(r'http.*#score='), ''); longUrl = longUrl.replaceFirst(new RegExp(r'http.*#/score/'), ''); - Score score = scoreFromUrlHashValue(longUrl); - if (titleOverride != null) { - score.name = titleOverride; - } + Score score = scoreFromUrlHashValue(longUrl)!; + if (titleOverride != null) score.name = titleOverride; return score; } loadPastebinScoreIntoUI(String pastebinCode, {String newScoreDefaultFilename = PASTED_SCORE, String newScoreNameSuffix = FROM_CLIPBOARD, - Score currentScoreToSave, - VoidCallback onFail, - Function(String) onSuccess}) async { - if (pastebinCode == null) { - return; - } + Score? currentScoreToSave, + VoidCallback? onFail, + Function(String)? onSuccess}) async { try { Score score = await loadPastebinScore(pastebinCode); String scoreName = score.name ?? ""; @@ -211,7 +202,7 @@ class ScoreManager { } openScoreWithFilename(score, newScoreDefaultFilename); } else { - doOpenScore(score); + doOpenScore?.call(score); } onSuccess?.call(suggestedScoreName); _lastSuggestedScoreName = suggestedScoreName; @@ -222,7 +213,7 @@ class ScoreManager { openScoreWithFilename(Score score, String filename) async { currentScoreName = filename; - doOpenScore(score); + doOpenScore?.call(score); saveCurrentScore(score); } @@ -234,7 +225,7 @@ class ScoreManager { extension ScoreName on FileSystemEntity { String get scoreName { - String fileName = path.split("/")?.last ?? ".beatscratch"; + String fileName = path.split("/").last ?? ".beatscratch"; fileName = fileName.substring(0, max(0, fileName.length - 12)); String scoreName = Uri.decodeComponent(fileName.replaceAll(" ", "%20")); return scoreName; diff --git a/lib/storage/score_picker.dart b/lib/storage/score_picker.dart index c3c0ca5f..7db8c3b2 100644 --- a/lib/storage/score_picker.dart +++ b/lib/storage/score_picker.dart @@ -1,30 +1,23 @@ import 'dart:io'; import 'dart:math'; -import 'dart:ui'; import 'package:beatscratch_flutter_redux/settings/app_settings.dart'; -import 'package:beatscratch_flutter_redux/storage/url_conversions.dart'; import 'package:beatscratch_flutter_redux/universe_view/universe_icon.dart'; import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; -import 'package:beatscratch_flutter_redux/music_view/music_system_painter.dart'; import 'package:beatscratch_flutter_redux/storage/score_picker_preview.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'dart:convert'; import '../beatscratch_plugin.dart'; import '../colors.dart'; import '../ui_models.dart'; import '../util/dummydata.dart'; import '../util/bs_methods.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:animated_list_plus/animated_list_plus.dart'; import 'package:animated_list_plus/transitions.dart'; import '../generated/protos/music.pb.dart'; import '../generated/protos/protobeats_plugin.pb.dart'; -import '../music_preview/score_preview.dart'; import '../util/music_utils.dart'; import '../widget/my_buttons.dart'; import 'score_manager.dart'; @@ -53,7 +46,7 @@ extension ShowScoreNameEntry on ScorePickerMode { class ScorePicker extends StatefulWidget { final Axis scrollDirection; final Color sectionColor; - final Function(VoidCallback) setState; + final Function(VoidCallback)? setState; final VoidCallback close; final VoidCallback switchToUniverse; final ScorePickerMode mode; @@ -67,22 +60,22 @@ class ScorePicker extends StatefulWidget { final BSMethod refreshUniverseData; const ScorePicker( - {Key key, + {Key? key, this.scrollDirection = Axis.horizontal, - this.sectionColor, + required this.sectionColor, this.setState, - this.close, - this.mode, - this.openedScore, - this.scoreManager, - this.universeManager, - this.appSettings, - this.requestKeyboardFocused, - this.requestMode, - this.width, - this.height, - this.refreshUniverseData, - this.switchToUniverse}) + required this.close, + required this.mode, + required this.openedScore, + required this.scoreManager, + required this.universeManager, + required this.appSettings, + required this.requestKeyboardFocused, + required this.requestMode, + required this.width, + required this.height, + required this.refreshUniverseData, + required this.switchToUniverse}) : super(key: key); @override @@ -98,9 +91,9 @@ class ScorePickerState extends State { FocusNode nameFocus = FocusNode(); ScoreManager get scoreManager => widget.scoreManager; - ScorePickerMode previousMode; - String deletingScoreName; - String overwritingScoreName; + ScorePickerMode? previousMode; + String? deletingScoreName; + String? overwritingScoreName; BSMethod universeAnimation = BSMethod(); bool get wasShowingScoreNameEntry => @@ -187,7 +180,7 @@ class ScorePickerState extends State { text: '', style: detailTextStyle, ); //ing extraDetailText = ""; - IconData icon; + late IconData icon; switch (widget.mode) { case ScorePickerMode.open: operationText = "Open"; @@ -224,13 +217,13 @@ class ScorePickerState extends State { } bool detailsTextInColumn = true; //MediaQuery.of(context).size.width < 500; bool showDetailsText = detailsTextInColumn && - extraDetailText.text.isNotEmpty && + (extraDetailText.text?.isNotEmpty ?? false) && !hideDetailsText; return Column( children: [ AnimatedContainer( - height: showHeader ? 45 : 0, + height: showHeader ? 48 : 0, duration: animationDuration, child: AnimatedOpacity( duration: animationDuration, @@ -263,7 +256,7 @@ class ScorePickerState extends State { animateIcon: universeAnimation, )), )), - if (icon != null && operationText.isNotEmpty) + if (operationText.isNotEmpty) MyFlatButton( padding: EdgeInsets.zero, lightHighlight: true, @@ -394,7 +387,7 @@ class ScorePickerState extends State { ))), AnimatedContainer( height: showDetailsText - ? 0 == extraDetailText.children?.length ?? 0 + ? 0 == (extraDetailText.children?.length ?? 0) ? 32 : 48 : 0, @@ -626,18 +619,15 @@ class ScorePickerState extends State { return widget.universeManager.cachedUniverseData; } else { return scoreManager.scoreFiles.map((scoreFile) { - Future loadScore() async { - if (scoreFile == null) { - return Future.value(defaultScore()); - } - try { - final data = await File(scoreFile?.path).readAsBytes(); + // Future loadScore() async { + // try { + // final data = await File(scoreFile.path).readAsBytes(); - return Score.fromBuffer(data); - } catch (e) { - return Future.value(defaultScore()); - } - } + // return Score.fromBuffer(data); + // } catch (e) { + // return Future.value(defaultScore()); + // } + // } return ScoreFuture(filePath: scoreFile.path); }).toList(); @@ -652,15 +642,18 @@ class ScorePickerState extends State { spawnIsolate: false, controller: _scrollController, items: scores, - areItemsTheSame: (a, b) => a?.identity == b?.identity, + areItemsTheSame: (a, b) => a.identity == b.identity, // Called, as needed, to build list item widgets. // List items are only built when they're scrolled into view. itemBuilder: (context, animation, section, index) { - ScoreFuture scoreFuture; + ScoreFuture? scoreFuture; if (index < scores.length) { scoreFuture = scores[index]; } - File scoreFile = scoreFuture?.file; + final scoreFile = scoreFuture?.file; + if (scoreFuture == null || scoreFile == null) { + return SizedBox(); + } Widget tile = ScorePickerPreview( sectionColor: widget.sectionColor, @@ -673,37 +666,33 @@ class ScorePickerState extends State { : () { switch (widget.mode) { case ScorePickerMode.open: - if (scoreFile != null) { - widget.scoreManager.openScore(scoreFile); - widget.universeManager.currentUniverseScore = ""; - } + widget.scoreManager.openScore(scoreFile); + widget.universeManager.currentUniverseScore = ""; break; case ScorePickerMode.universe: if (BeatScratchPlugin.supportsStorage) { scoreManager.currentScoreName = ScoreManager.UNIVERSE_SCORE; } - scoreFuture.loadScore(scoreManager).then((value) { - widget.scoreManager.doOpenScore(value); + scoreFuture?.loadScore(scoreManager).then((value) { + widget.scoreManager.doOpenScore?.call(value); widget.universeManager.currentUniverseScore = - scoreFuture.identity; + scoreFuture!.identity; widget.scoreManager.saveCurrentScore(value); }); break; default: - String scoreName = scoreFile?.scoreName; - if (scoreName != null) { - nameController.clear(); - setState(() { - nameController.value = - nameController.value.copyWith(text: scoreName); - }); - } + String scoreName = scoreFile.scoreName; + nameController.clear(); + setState(() { + nameController.value = + nameController.value.copyWith(text: scoreName); + }); } }, scoreManager: scoreManager, universeManager: widget.universeManager, - scoreKey: (scoreFile?.lastModifiedSync() ?? DateTime(0)).hashCode, + scoreKey: (scoreFile.lastModifiedSync() ?? DateTime(0)).hashCode, scoreFuture: scoreFuture, deleteScore: widget.mode == ScorePickerMode.universe ? null @@ -744,7 +733,7 @@ class ScorePickerState extends State { bottom: widget.scrollDirection == Axis.vertical ? 10 : 0), child: tile); return SizeFadeTransition( - key: ValueKey("ScorePickerTile-${scoreFuture?.identity}"), + key: ValueKey("ScorePickerTile-${scoreFuture.identity}"), sizeFraction: 0.7, curve: Curves.easeInOut, axis: widget.scrollDirection, diff --git a/lib/storage/score_picker_preview.dart b/lib/storage/score_picker_preview.dart index 9d7d12dc..f33d15e1 100644 --- a/lib/storage/score_picker_preview.dart +++ b/lib/storage/score_picker_preview.dart @@ -1,21 +1,16 @@ import 'dart:io'; import 'dart:math'; -import 'dart:ui'; -import 'package:beatscratch_flutter_redux/music_view/music_system_painter.dart'; import 'package:beatscratch_flutter_redux/settings/app_settings.dart'; import 'package:beatscratch_flutter_redux/util/music_notation_theory.dart'; import 'package:beatscratch_flutter_redux/util/util.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import '../beatscratch_plugin.dart'; import '../colors.dart'; import '../generated/protos/music.pb.dart'; import '../music_preview/score_preview.dart'; import '../ui_models.dart'; -import '../util/bs_methods.dart'; import '../util/dummydata.dart'; import '../widget/my_buttons.dart'; import 'score_manager.dart'; @@ -23,9 +18,9 @@ import 'universe_manager.dart'; import 'url_conversions.dart'; class ScoreFuture { - final String filePath, scoreUrl, title, author, commentUrl, fullName; - int voteCount; - bool likes; + final String? filePath, scoreUrl, title, author, commentUrl, fullName; + int? voteCount; + bool? likes; ScoreFuture({ this.filePath, @@ -60,31 +55,28 @@ class ScoreFuture { }; String get identity => filePath ?? "//universe-score://$scoreUrl"; - FileSystemEntity get file { - if (filePath != null) { - try { - return File(filePath); - } catch (e) { - print("Error loading score from file: $e"); - } + File? get file { + try { + if (filePath == null) return null; + + return File(filePath!); + } catch (e) { + print("Error loading score from file: $e"); } return null; } Future loadScore(ScoreManager scoreManager) async { - if (this.file != null) { - return loadScoreFromFile(); - } else { - return loadScoreFromUniverse(scoreManager); - } + return loadScoreFromFile(); } Future loadScoreFromFile() async { - if (file == null) { - return Future.value(defaultScore()); - } try { - final data = await File(file?.path).readAsBytes(); + final file = this.file; + if (file == null) { + return Future.value(defaultScore()); + } + final data = await File(file.path).readAsBytes(); return Score.fromBuffer(data); } catch (e) { @@ -93,19 +85,19 @@ class ScoreFuture { } Future loadScoreFromUniverse(ScoreManager scoreManager) async { - String scoreUrl = this.scoreUrl; + String scoreUrl = this.scoreUrl!; scoreUrl = scoreUrl.replaceFirst(new RegExp(r'http.*#score='), ''); scoreUrl = scoreUrl.replaceFirst(new RegExp(r'http.*#/score/'), ''); scoreUrl = scoreUrl.replaceFirst(new RegExp(r'http.*#/s/'), ''); try { final score = scoreFromUrlHashValue(scoreUrl); if (score == null) { - throw "failed to load"; + return Future.value(defaultScore()); } - return score..name = title; + return score..name = title!; } catch (e) { try { - return scoreManager.loadPastebinScore(scoreUrl, titleOverride: title); + return scoreManager.loadPastebinScore(scoreUrl, titleOverride: title!); } catch (e) { return Future.value(defaultScore()); } @@ -116,36 +108,36 @@ class ScoreFuture { class ScorePickerPreview extends StatefulWidget { final Color sectionColor; final ScoreFuture scoreFuture; - final VoidCallback deleteScore; - final VoidCallback overwriteScore; + final VoidCallback? deleteScore; + final VoidCallback? overwriteScore; final ScoreManager scoreManager; final UniverseManager universeManager; final AppSettings appSettings; - final VoidCallback onClickScore; + final VoidCallback? onClickScore; final int scoreKey; - final String deletingScoreName; - final String overwritingScoreName; - final VoidCallback cancelDelete; - final VoidCallback cancelOverwrite; + final String? deletingScoreName; + final String? overwritingScoreName; + final VoidCallback? cancelDelete; + final VoidCallback? cancelOverwrite; final double width, height; const ScorePickerPreview( - {Key key, - this.sectionColor, - this.scoreFuture, - this.deleteScore, - this.overwriteScore, - this.scoreManager, - this.appSettings, + {Key? key, + required this.sectionColor, + required this.scoreFuture, + required this.deleteScore, + required this.overwriteScore, + required this.scoreManager, + required this.appSettings, this.deletingScoreName, this.overwritingScoreName, - this.scoreKey, - this.cancelDelete, - this.cancelOverwrite, - this.universeManager, + required this.scoreKey, + required this.cancelDelete, + required this.cancelOverwrite, + required this.universeManager, this.onClickScore, - this.width, - this.height}) + required this.width, + required this.height}) : super(key: key); @override @@ -153,14 +145,14 @@ class ScorePickerPreview extends StatefulWidget { } class _ScorePickerPreviewState extends State { - bool _confirmingDelete; - bool _confirmingOverwrite; - int _lastScoreKey; - bool disposed; + bool _confirmingDelete = false; + bool _confirmingOverwrite = false; + int? _lastScoreKey; + bool disposed = false; - Score _previewScore; - ScrollController scrollController; - BSMethod notifyUpdate; + Score? _previewScore; + late ScrollController scrollController; + late BSMethod notifyUpdate; @override initState() { @@ -180,9 +172,9 @@ class _ScorePickerPreviewState extends State { } String get unloadedScoreName => - widget.scoreFuture?.title ?? widget.scoreFuture?.file?.scoreName ?? ""; + widget.scoreFuture.title ?? widget.scoreFuture.file?.scoreName ?? ""; - bool get isUniverse => widget.scoreFuture?.voteCount != null; + bool get isUniverse => widget.scoreFuture.voteCount != null; @override Widget build(BuildContext context) { final scoreName = unloadedScoreName; @@ -190,7 +182,7 @@ class _ScorePickerPreviewState extends State { ? widget.scoreFuture.identity == widget.universeManager.currentUniverseScore : unloadedScoreName == widget.scoreManager.currentScoreName; - if (widget.scoreKey != _lastScoreKey && widget.scoreFuture != null) { + if (widget.scoreKey != _lastScoreKey) { _confirmingDelete = false; _confirmingOverwrite = false; _previewScore = null; @@ -223,17 +215,17 @@ class _ScorePickerPreviewState extends State { backgroundColor = musicBackgroundColor; } - Score previewScore = _previewScore; + Score previewScore = _previewScore ?? defaultScore(); // if (previewScore == null) { // previewScore = defaultScore(); // } - if (previewScore?.sections?.isEmpty == true) { + if (previewScore.sections.isEmpty == true) { previewScore.sections.add(defaultSection()); } final actualScoreName = isUniverse ? unloadedScoreName : _previewScore != null - ? _previewScore.name + ? _previewScore!.name : unloadedScoreName; double previewScale = @@ -244,8 +236,8 @@ class _ScorePickerPreviewState extends State { return Row(children: [ AnimatedContainer( duration: animationDuration, - width: /*widget.scoreFuture?.loadScore != null ? */ widget - .width /*: 0*/, + width: /*widget.scoreFuture?.loadScore != null ? */ + widget.width /*: 0*/, height: widget.height, color: backgroundColor, padding: EdgeInsets.zero, @@ -368,7 +360,7 @@ class _ScorePickerPreviewState extends State { onPressed: () { if (!disposed) { setState(() { - widget.deleteScore(); + widget.deleteScore?.call(); }); } }, @@ -381,7 +373,7 @@ class _ScorePickerPreviewState extends State { if (!disposed) { setState(() { _confirmingDelete = false; - widget.cancelDelete(); + widget.cancelDelete?.call(); }); } }, @@ -414,7 +406,7 @@ class _ScorePickerPreviewState extends State { onPressed: () { if (!disposed) { setState(() { - widget.overwriteScore(); + widget.overwriteScore?.call(); }); } }, @@ -427,7 +419,7 @@ class _ScorePickerPreviewState extends State { if (!disposed) { setState(() { _confirmingOverwrite = false; - widget.cancelOverwrite(); + widget.cancelOverwrite?.call(); }); } }, @@ -445,7 +437,7 @@ class _ScorePickerPreviewState extends State { alignment: Alignment.bottomLeft, child: AnimatedOpacity( duration: animationDuration, - opacity: widget.scoreFuture?.author != null ? 1 : 0, + opacity: widget.scoreFuture.author != null ? 1 : 0, child: Container( height: 36, padding: EdgeInsets.all(5), @@ -464,7 +456,7 @@ class _ScorePickerPreviewState extends State { fontSize: 8)) ]), SizedBox(width: 5), - Text(widget.scoreFuture?.author ?? "", + Text(widget.scoreFuture.author ?? "", style: TextStyle( color: musicForegroundColor, fontWeight: FontWeight.w700)) @@ -482,70 +474,74 @@ class _ScorePickerPreviewState extends State { child: Column( children: [ MyFlatButton( - color: widget.scoreFuture?.likes == true + color: widget.scoreFuture.likes == true ? chromaticSteps[11] : Colors.transparent, padding: EdgeInsets.symmetric(vertical: 5), onPressed: widget.universeManager.redditUsername.isNotEmpty ? () { - bool oldValue = widget.scoreFuture?.likes; + bool? oldValue = widget.scoreFuture.likes; setState(() { if (oldValue == true) { - widget.scoreFuture?.likes = null; - widget.scoreFuture.voteCount -= 1; + widget.scoreFuture.likes = null; + widget.scoreFuture.voteCount = + widget.scoreFuture.voteCount! - 1; } else { - widget.scoreFuture?.likes = true; - widget.scoreFuture.voteCount += - oldValue == null ? 1 : 2; + widget.scoreFuture.likes = true; + widget.scoreFuture.voteCount = + widget.scoreFuture.voteCount! + + (oldValue == null ? 1 : 2); } widget.universeManager.vote( - widget.scoreFuture?.fullName, - widget.scoreFuture?.likes); + widget.scoreFuture.fullName!, + widget.scoreFuture.likes); }); } : null, child: Icon(Icons.arrow_upward, color: widget.universeManager.isAuthenticated - ? widget.scoreFuture?.likes == true + ? widget.scoreFuture.likes == true ? chromaticSteps[11].textColor() : chromaticSteps[11] : musicForegroundColor.withOpacity(0.5))), Row(children: [ Expanded(child: SizedBox()), - Text(widget.scoreFuture?.voteCount?.toString() ?? '', + Text(widget.scoreFuture.voteCount.toString() ?? '', style: TextStyle( color: musicForegroundColor, fontWeight: FontWeight.w800)), Expanded(child: SizedBox()), ]), MyFlatButton( - color: widget.scoreFuture?.likes == false + color: widget.scoreFuture.likes == false ? chromaticSteps[10] : Colors.transparent, padding: EdgeInsets.symmetric(vertical: 5), onPressed: widget.universeManager.redditUsername.isNotEmpty ? () { - bool oldValue = widget.scoreFuture?.likes; + bool? oldValue = widget.scoreFuture.likes; setState(() { if (oldValue == false) { - widget.scoreFuture?.likes = null; - widget.scoreFuture.voteCount += 1; + widget.scoreFuture.likes = null; + widget.scoreFuture.voteCount = + widget.scoreFuture.voteCount! + 1; } else { - widget.scoreFuture?.likes = false; - widget.scoreFuture.voteCount -= - oldValue == null ? 1 : 2; + widget.scoreFuture.likes = false; + widget.scoreFuture.voteCount = + widget.scoreFuture.voteCount! - + (oldValue == null ? 1 : 2); } widget.universeManager.vote( - widget.scoreFuture?.fullName, - widget.scoreFuture?.likes); + widget.scoreFuture.fullName!, + widget.scoreFuture.likes); }); } : null, child: Icon(Icons.arrow_downward, color: widget.universeManager.isAuthenticated - ? widget.scoreFuture?.likes == false + ? widget.scoreFuture.likes == false ? chromaticSteps[10].textColor() : chromaticSteps[10] : musicForegroundColor.withOpacity(0.5))), @@ -555,11 +551,11 @@ class _ScorePickerPreviewState extends State { onPressed: () { if (widget.appSettings.enableApollo) { launchURL( - widget.scoreFuture.commentUrl + widget.scoreFuture.commentUrl! .replaceAll("https://", "apollo://"), forceSafariVC: false); } else { - launchURL(widget.scoreFuture.commentUrl, + launchURL(widget.scoreFuture.commentUrl!, forceSafariVC: false); } }, diff --git a/lib/storage/universe_manager.dart b/lib/storage/universe_manager.dart index 44019fab..001d32bd 100644 --- a/lib/storage/universe_manager.dart +++ b/lib/storage/universe_manager.dart @@ -2,34 +2,29 @@ import 'package:beatscratch_flutter_redux/messages/messages.dart'; import 'package:beatscratch_flutter_redux/storage/score_manager.dart'; import 'package:beatscratch_flutter_redux/storage/score_picker_preview.dart'; import 'package:beatscratch_flutter_redux/util/util.dart'; +import 'package:collection/collection.dart'; import 'dart:convert'; import 'dart:io'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_icons/flutter_icons.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:http/http.dart' as http; -import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import '../beatscratch_plugin.dart'; import '../colors.dart'; import '../generated/protos/music.pb.dart'; -import '../util/dummydata.dart'; -import '../util/proto_utils.dart'; import '../widget/my_platform.dart'; import 'url_conversions.dart'; class UniverseManager { static final REDDIT_CLIENT_ID = 'rSA9vlCRCznMCw'; static final REDDIT_REDIRECT_URI = 'https://beatscratch.io/app'; - Function(Score) doOpenScore; - Directory scoresDirectory; - SharedPreferences _prefs; - ScoreManager scoreManager; - MessagesUI messagesUI; - BSMethod refreshUniverseData; + late Function(Score) doOpenScore; + late Directory scoresDirectory; + SharedPreferences? _prefs; + late ScoreManager scoreManager; + late MessagesUI messagesUI; + late BSMethod refreshUniverseData; UniverseManager() { _initialize(); @@ -43,10 +38,11 @@ class UniverseManager { set currentUniverseScore(String v) => _prefs?.setString("currentUniverseScore", v); - ScoreFuture get currentUniverseScoreFuture => currentUniverseScore == '' + ScoreFuture? get currentUniverseScoreFuture => currentUniverseScore == '' ? null - : cachedUniverseData.firstWhere((d) => d.identity == currentUniverseScore, - orElse: () => null); + : cachedUniverseData.firstWhereOrNull( + (d) => d.identity == currentUniverseScore, + ); String get redditRefreshToken => _prefs?.getString('redditRefreshToken') ?? ""; @@ -78,8 +74,10 @@ class UniverseManager { List get cachedUniverseData => _cachedUniverseData; set cachedUniverseData(List value) { _cachedUniverseData = value; - Future.microtask(() => _prefs?.setStringList("cachedUniverseData", - value.map((it) => jsonEncode(it.toJson())).toList())); + Future.microtask(() => { + _prefs?.setStringList("cachedUniverseData", + value.map((it) => jsonEncode(it.toJson())).toList()) + }); } bool get isAuthenticated => @@ -110,9 +108,9 @@ class UniverseManager { bool tryAuthentication(String authUrl) { final uri = Uri.parse(authUrl); - String state = uri.queryParameters["state"]; - String code = uri.queryParameters["code"]; - if (state != null && code != null) { + String? state = uri.queryParameters["state"]; + String? code = uri.queryParameters["code"]; + if (code != null) { if (state != _authState) { messagesUI.sendMessage( message: "Auth codes did not match!", @@ -139,7 +137,7 @@ class UniverseManager { final data = jsonDecode(response.body); String accessToken = data['access_token']; String refreshToken = data['refresh_token']; - if (accessToken != null && refreshToken != null) { + if (refreshToken != null) { redditRefreshToken = refreshToken; redditAccessToken = accessToken; loadRedditUsername(); @@ -170,10 +168,11 @@ class UniverseManager { redditAccessToken = ""; redditUsername = ""; redditRefreshToken = ""; - cachedUniverseData = - cachedUniverseData.map((sf) => ScoreFuture.fromJson(sf.toJson() + cachedUniverseData = cachedUniverseData + .map((sf) => ScoreFuture.fromJson(sf.toJson() ..remove("likes") - ..putIfAbsent("likes", () => null))); + ..putIfAbsent("likes", () => null))) + .toList(); refreshAccessToken(); } @@ -188,17 +187,13 @@ class UniverseManager { final username = data['name']; if (username != null) { redditUsername = username; - if (messagesUI != null) { - messagesUI.sendMessage( - message: "Reddit authentication successful!", andSetState: true); - } + messagesUI.sendMessage( + message: "Reddit authentication successful!", andSetState: true); } else { - if (messagesUI != null) { - messagesUI.sendMessage( - message: "Failed to load Reddit user information!", - isError: true, - andSetState: true); - } + messagesUI.sendMessage( + message: "Failed to load Reddit user information!", + isError: true, + andSetState: true); } }); } @@ -231,9 +226,7 @@ class UniverseManager { ).then((response) { final data = jsonDecode(response.body); String accessToken = data['access_token']; - if (accessToken != null) { - redditAccessToken = accessToken; - } + redditAccessToken = accessToken; }); } @@ -254,26 +247,29 @@ class UniverseManager { ).then((response) { final data = jsonDecode(response.body); String accessToken = data['access_token']; - if (accessToken != null) { - redditAccessToken = accessToken; - } + redditAccessToken = accessToken; }); } Future> loadUniverseData() async { - http.Response response = await http - .get(Uri.parse('https://oauth.reddit.com/r/BeatScratch/hot'), - headers: authenticatedRedditRequestHeaders) - .onError((error, stackTrace) { - print(error); + http.Response response = await http.get( + Uri.parse('https://oauth.reddit.com/r/BeatScratch/hot'), + headers: authenticatedRedditRequestHeaders); + // .onError((error, stackTrace) { + // print(error); + // messagesUI.sendMessage( + // message: "Error loading data from Reddit!", + // isError: true, + // andSetState: true); + // // return null; + // return Response(body, statusCode) + // }); + if (response.statusCode != 200 && response.statusCode != 401) { messagesUI.sendMessage( message: "Error loading data from Reddit!", isError: true, andSetState: true); - return null; - }); - if (response == null) { - return []; + return Future.value([]); } if (response.statusCode == 401) { await refreshAccessToken(); @@ -328,7 +324,7 @@ class UniverseManager { } } - vote(String fullName, bool likes, {bool andReauth = true}) async { + vote(String fullName, bool? likes, {bool andReauth = true}) async { http .post(Uri.parse('https://oauth.reddit.com/api/vote'), body: { @@ -386,7 +382,7 @@ class UniverseManager { icon: Icon(FontAwesomeIcons.atom, color: chromaticSteps[0]), message: "Generating short URL via https://paste.ee...", andSetState: true); - String scoreUrl = await score.convertToShortUrl(); + String? scoreUrl = await score.convertToShortUrl(); messagesUI.sendMessage( icon: Icon(FontAwesomeIcons.atom, color: chromaticSteps[0]), message: "Uploading to the Universe...", @@ -418,16 +414,12 @@ class UniverseManager { tryToSelectScore(int retries) { Future.delayed(Duration(seconds: 2), () { refreshUniverseData(); - ScoreFuture scoreFuture = cachedUniverseData.firstWhere( - (it) => it.scoreUrl == scoreUrl, - orElse: () => null); - if (scoreFuture != null) { - messagesUI.setAppState(() { - currentUniverseScore = scoreFuture.identity; - }); - } else if (retries > 0) { - tryToSelectScore(retries - 1); - } + final scoreFuture = cachedUniverseData + .firstWhereOrNull((it) => it.scoreUrl == scoreUrl); + if (scoreFuture == null) return; + messagesUI.setAppState(() { + currentUniverseScore = scoreFuture.identity; + }); }); } diff --git a/lib/storage/url_conversions.dart b/lib/storage/url_conversions.dart index 10284a75..4097429f 100644 --- a/lib/storage/url_conversions.dart +++ b/lib/storage/url_conversions.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:typed_data'; import 'package:archive/archive.dart'; import 'package:base_x/base_x.dart'; @@ -20,7 +21,7 @@ extension URLConversions on Score { return urlString; } - Future convertToShortUrl() async { + Future convertToShortUrl() async { try { http.Response response = await http.post( Uri.parse('https://api.paste.ee/v1/pastes'), @@ -47,14 +48,14 @@ extension URLConversions on Score { final data = writeToBuffer(); final bz2Data = BZip2Encoder().encode(data); final dataToConvert = (data.length <= bz2Data.length) ? data : bz2Data; - final dataString = _base58.encode(dataToConvert); + final dataString = _base58.encode(Uint8List.fromList(dataToConvert)); return dataString; } } -Score scoreFromUrlHashValue(String urlString) { +Score? scoreFromUrlHashValue(String urlString) { final dataBytes = _base58.decode(urlString); - Score score; + Score? score; try { score = Score.fromBuffer(dataBytes); } catch (any) { diff --git a/lib/ui_models.dart b/lib/ui_models.dart index a0b273d3..b51c8f97 100644 --- a/lib/ui_models.dart +++ b/lib/ui_models.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; enum InteractionMode { view, edit, universe } diff --git a/lib/universe_view/universe_icon.dart b/lib/universe_view/universe_icon.dart index 2811945b..5ebddc4a 100644 --- a/lib/universe_view/universe_icon.dart +++ b/lib/universe_view/universe_icon.dart @@ -1,8 +1,9 @@ import 'package:beatscratch_flutter_redux/ui_models.dart'; import 'package:beatscratch_flutter_redux/util/bs_methods.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_icons/flutter_icons.dart'; +// import 'package:flutter_icons/flutter_icons.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'dart:math'; import '../colors.dart'; @@ -10,12 +11,12 @@ import '../colors.dart'; class UniverseIcon extends StatefulWidget { final Color sectionColor; final InteractionMode interactionMode; - final BSMethod animateIcon; + final BSMethod? animateIcon; const UniverseIcon( - {Key key, - this.sectionColor: Colors.white, - this.interactionMode: InteractionMode.view, + {Key? key, + this.sectionColor = Colors.white, + this.interactionMode = InteractionMode.view, this.animateIcon}) : super(key: key); @@ -25,10 +26,10 @@ class UniverseIcon extends StatefulWidget { class _UniverseIconState extends State with TickerProviderStateMixin { - AnimationController orbitRotationController; - Animation orbitRotation; - AnimationController atomRotationController; - Animation atomRotation; + late AnimationController orbitRotationController; + late Animation orbitRotation; + late AnimationController atomRotationController; + late Animation atomRotation; @override initState() { super.initState(); @@ -96,7 +97,7 @@ class _UniverseIconState extends State alignment: Alignment.center, child: Transform.scale( scale: 1.4, - child: Icon(MaterialCommunityIcons.orbit, + child: Icon(MdiIcons.orbit, color: ((widget.interactionMode == InteractionMode.universe) ? widget.sectionColor diff --git a/lib/universe_view/universe_upload_dialog.dart b/lib/universe_view/universe_upload_dialog.dart index 1d9de885..f897b347 100644 --- a/lib/universe_view/universe_upload_dialog.dart +++ b/lib/universe_view/universe_upload_dialog.dart @@ -1,34 +1,13 @@ -import 'dart:async'; -import 'dart:ui'; - import '../music_preview/score_preview.dart'; -import 'package:dart_midi/dart_midi.dart'; -import 'package:dart_midi/src/byte_writer.dart'; -import '../messages/messages_ui.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_midi_command/flutter_midi_command.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:animated_list_plus/animated_list_plus.dart'; -import 'package:animated_list_plus/transitions.dart'; -import '../settings/settings.dart'; -import '../beatscratch_plugin.dart'; import '../colors.dart'; -import '../generated/protos/protobeats_plugin.pb.dart'; import '../generated/protos/protos.dart'; -import '../music_preview/melody_preview.dart'; import '../storage/universe_manager.dart'; import '../ui_models.dart'; -import '../universe_view/universe_icon.dart'; -import '../util/bs_methods.dart'; -import '../util/dummydata.dart'; -import '../util/midi_theory.dart'; import '../util/util.dart'; import '../widget/my_buttons.dart'; -import '../widget/my_platform.dart'; showUniverseUpload(BuildContext context, Score score, Color sectionColor, UniverseManager universeManager, BSMethod onDoDuplicate) { @@ -103,7 +82,10 @@ class UniverseUploadWidget extends StatefulWidget { final BSMethod onDoDuplicate; const UniverseUploadWidget( - {Key key, this.score, this.universeManager, this.onDoDuplicate}) + {Key? key, + required this.score, + required this.universeManager, + required this.onDoDuplicate}) : super(key: key); @override @@ -111,7 +93,7 @@ class UniverseUploadWidget extends StatefulWidget { } class _UniverseUploadWidgetState extends State { - bool didFindDuplicate; + bool? didFindDuplicate; @override void initState() { super.initState(); diff --git a/lib/universe_view/universe_view_ui.dart b/lib/universe_view/universe_view_ui.dart index 218945b9..aa73a6a5 100644 --- a/lib/universe_view/universe_view_ui.dart +++ b/lib/universe_view/universe_view_ui.dart @@ -3,34 +3,26 @@ import 'package:beatscratch_flutter_redux/messages/messages_ui.dart'; import 'package:beatscratch_flutter_redux/storage/universe_manager.dart'; import 'package:beatscratch_flutter_redux/util/bs_methods.dart'; import 'package:beatscratch_flutter_redux/widget/my_buttons.dart'; - -import '../widget/my_popup_menu.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; import '../colors.dart'; import '../ui_models.dart'; import '../util/util.dart'; -import 'universe_icon.dart'; import '../widget/my_platform.dart'; +import '../widget/my_popup_menu.dart'; +import 'universe_icon.dart'; class UniverseViewUI { - BSMethod refreshUniverseData; - VoidCallback switchToLocalScores; - MessagesUI messagesUI; + BSMethod? refreshUniverseData; + VoidCallback? switchToLocalScores; + MessagesUI? messagesUI; bool visible = true; final UniverseManager universeManager; final Function(VoidCallback) setAppState; bool signingIn = false; - final Completer _webViewController = - Completer(); + // final Completer _webViewController = + // Completer(); final TextEditingController _usernameController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); @@ -41,25 +33,25 @@ class UniverseViewUI { } double toolbarHeight(BuildContext context, - {@required double keyboardHeight, @required double settingsHeight}) => + {required double keyboardHeight, required double settingsHeight}) => visible ? 44 : 0; double authFormHeight(BuildContext context, - {@required double keyboardHeight, @required double settingsHeight}) => + {required double keyboardHeight, required double settingsHeight}) => visible && signingIn ? 200 : 0; double height(BuildContext context, - {@required double keyboardHeight, @required double settingsHeight}) => + {required double keyboardHeight, required double settingsHeight}) => toolbarHeight(context, keyboardHeight: keyboardHeight, settingsHeight: settingsHeight) + authFormHeight(context, keyboardHeight: keyboardHeight, settingsHeight: settingsHeight); Widget build( - {@required BuildContext context, - @required Color sectionColor, - @required double keyboardHeight, - @required double settingsHeight, - @required VoidCallback showDownloads, - @required double scorePickerWidth}) { + {required BuildContext context, + required Color sectionColor, + required double keyboardHeight, + required double settingsHeight, + VoidCallback? showDownloads, + required double scorePickerWidth}) { double abbreviateAtWidth = 340; return AnimatedOpacity( duration: animationDuration, @@ -90,7 +82,7 @@ class UniverseViewUI { Transform.translate( offset: Offset(0, 0), child: MyFlatButton( - onPressed: refreshUniverseData, + onPressed: () => refreshUniverseData?.call(), padding: EdgeInsets.all(5), lightHighlight: true, child: Transform.translate( @@ -191,7 +183,7 @@ class UniverseViewUI { case "signOut": setAppState(() { universeManager.signOut(); - messagesUI.sendMessage( + messagesUI?.sendMessage( message: "Signed out of Reddit"); }); break; diff --git a/lib/util/bs_methods.dart b/lib/util/bs_methods.dart index bd4bed4a..bb2a57c4 100644 --- a/lib/util/bs_methods.dart +++ b/lib/util/bs_methods.dart @@ -7,5 +7,5 @@ class BSMethod extends ChangeNotifier { class BSValueMethod extends ValueNotifier { BSValueMethod(value) : super(value); - call(value) => this.value = value; + void call(value) => this.value = value; } diff --git a/lib/util/dummydata.dart b/lib/util/dummydata.dart index 0e48dcc0..1d32aa25 100644 --- a/lib/util/dummydata.dart +++ b/lib/util/dummydata.dart @@ -227,7 +227,7 @@ Melody odeToJoyB() => baseMelody() final defaultSubdivisionsPerBeat = 4; -Melody defaultMelody({int sectionBeats}) => baseMelody() +Melody defaultMelody({int? sectionBeats}) => baseMelody() ..subdivisionsPerBeat = defaultSubdivisionsPerBeat ..length = (sectionBeats ?? defaultSectionBeats) * defaultSubdivisionsPerBeat ..interpretationType = MelodyInterpretationType.fixed diff --git a/lib/util/language_utils.dart b/lib/util/language_utils.dart index 5816e800..7c0eba74 100644 --- a/lib/util/language_utils.dart +++ b/lib/util/language_utils.dart @@ -6,7 +6,7 @@ extension Iterables on Iterable { (Map> map, E element) => map..putIfAbsent(keyFunction(element), () => []).add(element)); - E maxBy(num Function(E) valueFunction) => (isEmpty) + E? maxBy(num Function(E) valueFunction) => (isEmpty) ? null : reduce((value, element) { if (value == null) { diff --git a/lib/util/methodcache_utils.dart b/lib/util/methodcache_utils.dart index 8e60a682..4b7e44ef 100644 --- a/lib/util/methodcache_utils.dart +++ b/lib/util/methodcache_utils.dart @@ -31,14 +31,10 @@ class ArgumentList { @override int get hashCode { - int result; + int result = 0; arguments.forEach((arg) { - if (result == null) { - result = arg.hashCode; - } else { - result = result ^ arg.hashCode; - } + result = result ^ arg.hashCode; }); - return result ?? 0; + return result; } } diff --git a/lib/util/midi_theory.dart b/lib/util/midi_theory.dart index b33a3c12..23a2a3f9 100644 --- a/lib/util/midi_theory.dart +++ b/lib/util/midi_theory.dart @@ -1,20 +1,22 @@ -import 'package:dart_midi/dart_midi.dart'; -// ignore: implementation_imports -import 'package:dart_midi/src/byte_writer.dart'; +import 'package:beatscratch_flutter_redux/midi/byte_writer.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_events.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_parser.dart'; + +import 'package:flutter/foundation.dart'; import 'package:flutter_midi_command/flutter_midi_command.dart'; import '../generated/protos/protos.dart'; import 'util.dart'; extension MidiEventFilters on Iterable { - bool hasNoteOnEvent(int midiNote) => any((it) => - !(it is NoteOnEvent) || (it as NoteOnEvent).noteNumber != midiNote); - bool hasNoteOffEvent(int midiNote) => any((it) => - !(it is NoteOnEvent) || (it as NoteOnEvent).noteNumber != midiNote); - Iterable withoutNoteOnEvents(int midiNote) => where((it) => - !(it is NoteOnEvent) || (it as NoteOnEvent).noteNumber != midiNote); - Iterable withoutNoteOffEvents(int midiNote) => where((it) => - !(it is NoteOffEvent) || (it as NoteOffEvent).noteNumber != midiNote); + bool hasNoteOnEvent(int midiNote) => + any((it) => !(it is NoteOnEvent) || it.noteNumber != midiNote); + bool hasNoteOffEvent(int midiNote) => + any((it) => !(it is NoteOnEvent) || it.noteNumber != midiNote); + Iterable withoutNoteOnEvents(int midiNote) => + where((it) => !(it is NoteOnEvent) || it.noteNumber != midiNote); + Iterable withoutNoteOffEvents(int midiNote) => + where((it) => !(it is NoteOffEvent) || it.noteNumber != midiNote); } extension MidiChangeTheory on MidiChange { @@ -29,7 +31,7 @@ extension MidiChangeTheory on MidiChange { } Iterable get _midiEvents { - if (data == null || data.isEmpty) { + if (data.isEmpty) { return []; } var chunkedData = data.chunked(3); @@ -73,7 +75,7 @@ extension MidiEvents on MidiPacket { } Iterable get _midiEvents { - if (data == null || data.isEmpty) { + if (data.isEmpty) { return []; } var chunkedData = data.chunked(3); @@ -91,7 +93,7 @@ extension MidiEvents on MidiPacket { value.forEach((event) { event.writeEvent(writer); }); - data = writer.buffer; + data = Uint8List.fromList(writer.buffer); // print("done setting midiEvents; data1=${writer.buffer}"); // print("done setting midiEvents; data=$data"); } @@ -113,14 +115,11 @@ extension MidiMelodies on Melody { Map convertedData = Map(); List>> sortedData = simpleData.entries.toList() ..sort((e1, e2) => e1.key.compareTo(e2.key)); - Iterable prevTones; + Iterable prevTones = []; sortedData.forEach((entry) { int key = entry.key; Iterable tones = entry.value; List events = []; - if (prevTones == null) { - prevTones = sortedData.last.value; - } events.addAll(prevTones.map((tone) => NoteOffEvent() ..noteNumber = tone + 60 ..velocity = 127 diff --git a/lib/util/music_notation_theory.dart b/lib/util/music_notation_theory.dart index 1575447f..10772f79 100644 --- a/lib/util/music_notation_theory.dart +++ b/lib/util/music_notation_theory.dart @@ -1,3 +1,5 @@ +import 'package:collection/collection.dart'; + import '../generated/protos/music.pb.dart'; import 'music_theory.dart'; import 'util.dart'; @@ -25,10 +27,12 @@ class NoteSpecification { @override String toString() => "NoteSpecification:$uiString"; - NoteSpecification({this.noteName, this.octave}); + NoteSpecification({required this.noteName, required this.octave}); NoteSpecification.name( - {NoteLetter letter, NoteSign sign = NoteSign.natural, int octave}) + {required NoteLetter letter, + NoteSign sign = NoteSign.natural, + required int octave}) : this( noteName: (NoteName() ..noteLetter = letter @@ -55,9 +59,9 @@ extension HeptatonicConversions on int { .groupBy(((note) => note.tone)); NoteSpecification get naturalOrSharpNote => - _notesFor[this].firstWhere((note) => note.sign == NoteSign.natural, - orElse: () => null) ?? - _notesFor[this].firstWhere((note) => note.sign == NoteSign.sharp); + _notesFor[this]! + .firstWhereOrNull((note) => note.sign == NoteSign.natural) ?? + _notesFor[this]!.firstWhere((note) => note.sign == NoteSign.sharp); // ignore: unused_field static final Map> _noteNameChordCache = @@ -75,73 +79,74 @@ extension HeptatonicConversions on int { int difference = (this - rootNote.tone).mod12; switch (difference) { case 0: - return _notesFor[this].firstWhere((it) => it.letter == rootNote.letter); + return _notesFor[this]! + .firstWhere((it) => it.letter == rootNote.letter); case 1: - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 1); // m2 case 2: - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 1); // M2 case 3: if (chord.hasAug2) { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 1); // A2 } else { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 2); // m3 } break; case 4: - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 2); // M3 case 5: - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 3); // P4 case 6: if (chord.hasAug4) { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 3); // A4 } else { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 4); // d5 } break; case 7: - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 4); // P5 case 8: if (chord.hasAug5) { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 4); // A5 } else { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 5); // m6 } break; case 9: if (chord.hasDim7) { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 5); // d7 } else { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 5); // M6 } break; case 10: if (chord.hasAug6) { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 5); // A6 } else { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 6); // m7 } break; case 11: if (rootNote.sign == NoteSign.double_sharp) { - return _notesFor[this].firstWhere((it) => + return _notesFor[this]!.firstWhere((it) => it.letter == rootNote.letter); // diminished 1 (for readability) } else { - return _notesFor[this] + return _notesFor[this]! .firstWhere((it) => it.letter == rootNote.letter + 6); //M7 } break; @@ -165,7 +170,7 @@ abstract class MusicStaff { MusicStaff(); String get id; - Iterable getParts(Score score, List staffConfiguration); + Iterable getParts(Score score, Iterable staffConfiguration); @override bool operator ==(other) => other is MusicStaff && id == other.id; @@ -179,7 +184,8 @@ class PartStaff extends MusicStaff { @override String get id => "staff-part-${part.id}"; @override - Iterable getParts(Score score, List staffConfiguration) => + Iterable getParts( + Score score, Iterable staffConfiguration) => [part]; } diff --git a/lib/util/music_theory.dart b/lib/util/music_theory.dart index 0b4b9eb0..54c696c3 100644 --- a/lib/util/music_theory.dart +++ b/lib/util/music_theory.dart @@ -1,3 +1,5 @@ +import 'package:collection/collection.dart'; + import '../util/midi_theory.dart'; import '../generated/protos/music.pb.dart'; @@ -113,8 +115,8 @@ extension NoteConversions on int { extension PatternIndexConversions on int { int convertPatternIndex( - {int fromSubdivisionsPerBeat, - int toSubdivisionsPerBeat, + {int fromSubdivisionsPerBeat = 0, + int toSubdivisionsPerBeat = 0, int toLength = 1000000000}) { // In the storageContext of the "from" melody, in, say, sixteenth notes (subdivisionsPerBeat=4), // if this is 5, then currentBeat is 1.25. @@ -155,17 +157,8 @@ extension ChordTheory on Chord { /// Returns the nearest int closestTone(int tone) { - int result; - range(0, 11).forEach((i) { - if (result == null) { - if (containsTone(tone - i)) { - result = tone - i; - } - if (containsTone(tone + i)) { - result = tone + i; - } - } - }); + int? result; + range(0, 11).forEach((i) {}); if (chroma != 2047) { // print("closest to $tone for ${this.toString().replaceAll("\n", "")} is $result"); } @@ -198,7 +191,7 @@ extension HarmonyTheory on Harmony { Chord _changeBefore(int subdivision) { // final int initialSubdivision = subdivision; - Chord result = data[subdivision]; + Chord? result = data[subdivision]; while (result == null) { subdivision = subdivision - 1; if (subdivision < 0) { @@ -212,12 +205,12 @@ extension HarmonyTheory on Harmony { extension MelodyTheory on Melody { String get idName => "Melody ${id.substring(0, 5)}"; - String get canonicalName => name?.isNotEmpty == true ? name : idName; + String get canonicalName => name.isNotEmpty == true ? name : idName; int get beatCount => (length / subdivisionsPerBeat).floor(); double get realBeatCount => length.toDouble() / subdivisionsPerBeat; Iterable get tones => (type == MelodyType.midi) ? midiData.data.values - .expand((it) => it.noteOns.map((e) => e.noteNumber - 60)) + .expand((it) => it.noteOns.map((e) => e!.noteNumber - 60)) : []; static final Map averageToneCache = Map(); double get averageTone => @@ -326,7 +319,7 @@ extension MelodyTheory on Melody { MidiChange midiChangeBefore(int subdivision) { // final int initialSubdivision = subdivision; - MidiChange result = midiData.data[subdivision]; + MidiChange? result = midiData.data[subdivision]; while (result == null) { subdivision = subdivision - 1; if (subdivision < 0) { @@ -344,10 +337,9 @@ extension SectionTheory on Section { double get realBeatCount => harmony.realBeatCount; int get beatCount => harmony.beatCount; - MelodyReference referenceTo(Melody melody) => (melody != null) - ? melodies.firstWhere((element) => element.melodyId == melody.id, - orElse: () => _defaultMelodyReference(melody)) - : null; + MelodyReference referenceTo(Melody melody) => + melodies.firstWhere((element) => element.melodyId == melody.id, + orElse: () => _defaultMelodyReference(melody)); MelodyReference _defaultMelodyReference(Melody melody) { var result = MelodyReference() @@ -381,12 +373,13 @@ extension ScoreTheory on Score { int get beatCount => sections.fold(0, (p, s) => p + s.beatCount); int get maxBeat => beatCount - 1; - Melody melodyReferencedBy(MelodyReference ref) => parts.fold( + Melody? melodyReferencedBy(MelodyReference ref) => parts.fold( null, (previousValue, part) => previousValue ?? - part.melodies.firstWhere((melody) => melody.id == ref.melodyId, - orElse: () => null)); + part.melodies.firstWhereOrNull( + (melody) => melody.id == ref.melodyId, + )); int firstBeatOfSection(Section currentSection) { if (sections.isEmpty) { diff --git a/lib/util/music_utils.dart b/lib/util/music_utils.dart index b89b386e..a2af7dad 100644 --- a/lib/util/music_utils.dart +++ b/lib/util/music_utils.dart @@ -1,13 +1,12 @@ -import '../util/midi_theory.dart'; -import 'package:dart_midi/dart_midi.dart'; -//ignore: implementation_imports -import 'package:dart_midi/src/byte_writer.dart'; +import 'package:beatscratch_flutter_redux/midi/byte_writer.dart'; +import 'package:beatscratch_flutter_redux/midi/midi_events.dart'; +import '../util/midi_theory.dart'; import '../generated/protos/music.pb.dart'; import 'util.dart'; extension ScoreReKey on Score { - reKeyMelodies({bool andParts: true}) { + reKeyMelodies({bool andParts = true}) { parts.forEach((part) { if (andParts) { part.id = uuid.v4(); @@ -37,8 +36,8 @@ extension ScoreReKey on Score { extension DeleteNotes on Melody { deleteMidiNote(int midiNote, int subdivision) { // First delete the NoteOnEvent here - midiData.data[subdivision].midiEvents = midiData - .data[subdivision].midiEvents + midiData.data[subdivision]!.midiEvents = midiData + .data[subdivision]!.midiEvents .withoutNoteOnEvents(midiNote) .toList(); @@ -49,11 +48,9 @@ extension DeleteNotes on Melody { var midiChange = midiData.data[s]; if (midiChange != null) { final midiEvents = midiChange.midiEvents; - if (midiEvents.hasNoteOnEvent(midiNote) != null) { - midiChange.midiEvents = - midiEvents.withoutNoteOffEvents(midiNote).toList(); - foundNoteOff = true; - } + midiChange.midiEvents = + midiEvents.withoutNoteOffEvents(midiNote).toList(); + foundNoteOff = true; } if (foundNoteOff) { break; @@ -80,9 +77,9 @@ extension DeleteNotes on Melody { extension SeparateNoteOnAndOff on Melody { bool separateNoteOnAndOffs() { bool madeChanges = false; - if (type == MelodyType.midi && false) { + if (type == MelodyType.midi) { midiData.data.keys.forEach((index) { - MidiChange midiChange = midiData.data[index]; + MidiChange midiChange = midiData.data[index]!; int nextIndex = (index < midiData.data.length - 1) ? index + 1 : 0; MidiChange nextMidiChange = midiData.data[nextIndex] ?? MidiChange(); @@ -105,7 +102,7 @@ extension SeparateNoteOnAndOff on Melody { } }); - nextWriter.buffer.addAll(nextMidiChange.data ?? []); + nextWriter.buffer.addAll(nextMidiChange.data); midiChange.data = writer.buffer; nextMidiChange.data = nextWriter.buffer; }); diff --git a/lib/util/proto_utils.dart b/lib/util/proto_utils.dart index ffa81d8c..26980524 100644 --- a/lib/util/proto_utils.dart +++ b/lib/util/proto_utils.dart @@ -1,16 +1,16 @@ import 'package:protobuf/protobuf.dart'; -import 'fake_js.dart' -if(dart.library.js) 'dart:js'; +import 'fake_js.dart' if (dart.library.js) 'dart:js'; extension ProtoUtils on T { - dynamic protoJsify() => JsObject.jsify(bsCopy().toProto3Json()); + dynamic protoJsify() => JsObject.jsify(bsCopy().toProto3Json()!); T bsCopy() { return deepCopy(); } + T bsRebuild(Function(T) updates) { - return (deepCopy()..freeze()).rebuild((t) => updates(t)).deepCopy() as T; + return (deepCopy()..freeze()).rebuild((t) => updates(t)).deepCopy(); } - String get logString => "\n ${toString().replaceAll("\n", "\n ")}"; + String get logString => "\n ${toString().replaceAll("\n", "\n ")}"; } diff --git a/lib/util/ui_utils.dart b/lib/util/ui_utils.dart index cb448684..5ca64d11 100644 --- a/lib/util/ui_utils.dart +++ b/lib/util/ui_utils.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; @@ -10,17 +9,17 @@ import 'package:url_launcher/url_launcher.dart'; launchURL( String url, { - bool forceSafariVC, - bool forceWebView, - bool enableJavaScript, - bool enableDomStorage, - bool universalLinksOnly, - Map headers, - Brightness statusBarBrightness, - String webOnlyWindowName, + bool forceSafariVC = false, + bool forceWebView = false, + bool enableJavaScript = false, + bool enableDomStorage = false, + bool universalLinksOnly = false, + Map headers = const {}, + Brightness statusBarBrightness = Brightness.dark, + String? webOnlyWindowName, }) async { - if (await canLaunch(url)) { - await launch( + if (await canLaunchUrl(Uri.parse(url))) { + await launchURL( url, forceSafariVC: forceSafariVC, forceWebView: forceWebView, @@ -73,29 +72,30 @@ Future loadUiImage(String imageAssetPath) async { class CustomSliverToBoxAdapter extends SingleChildRenderObjectWidget { final Function(Rect) setVisibleRect; - const CustomSliverToBoxAdapter({ - this.setVisibleRect, - Key key, - Widget child, + const CustomSliverToBoxAdapter( + this.setVisibleRect, { + Key? key, + Widget? child, }) : super(key: key, child: child); @override _CustomRenderSliverToBoxAdapter createRenderObject(BuildContext context) => - _CustomRenderSliverToBoxAdapter(setVisibleRect: setVisibleRect); + _CustomRenderSliverToBoxAdapter(setVisibleRect); } class _CustomRenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter { final Function(Rect) setVisibleRect; - _CustomRenderSliverToBoxAdapter({ - this.setVisibleRect, - RenderBox child, + _CustomRenderSliverToBoxAdapter( + this.setVisibleRect, { + RenderBox? child, }) : super(child: child); @override void performLayout() { + final child = this.child; if (child == null) { - geometry = SliverGeometry.zero; + this.geometry = SliverGeometry.zero; return; } child.layout(constraints.asBoxConstraints(), parentUsesSize: true); @@ -108,12 +108,11 @@ class _CustomRenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter { childExtent = child.size.height; break; } - assert(childExtent != null); final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent); assert(paintedChildSize.isFinite); assert(paintedChildSize >= 0.0); - geometry = new SliverGeometry( + final geometry = new SliverGeometry( scrollExtent: childExtent, paintExtent: paintedChildSize, maxPaintExtent: childExtent, @@ -121,6 +120,7 @@ class _CustomRenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter { hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0, ); + this.geometry = geometry; setChildParentData(child, constraints, geometry); // Expose geometry diff --git a/lib/widget/beats_badge.dart b/lib/widget/beats_badge.dart index 42efc1a0..237a8f8b 100644 --- a/lib/widget/beats_badge.dart +++ b/lib/widget/beats_badge.dart @@ -1,14 +1,9 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import '../ui_models.dart'; double beatsBadgeWidth(int beats) { double width = 30; - if (beats == null) { - beats = 9999; - } if (beats > 99) width = 40; if (beats > 999) width = 50; if (beats > 9999) width = 60; @@ -23,8 +18,8 @@ class BeatsBadge extends StatelessWidget { final bool isPerBeat; const BeatsBadge( - {Key key, - this.beats, + {Key? key, + required this.beats, this.show = true, this.opacity = 0.5, this.isPerBeat = false}) diff --git a/lib/widget/color_filtered_image_asset.dart b/lib/widget/color_filtered_image_asset.dart index ab687631..909c2ece 100644 --- a/lib/widget/color_filtered_image_asset.dart +++ b/lib/widget/color_filtered_image_asset.dart @@ -4,7 +4,8 @@ class ColorFilteredImageAsset extends StatelessWidget { final String imageSource; final Color imageColor; - const ColorFilteredImageAsset({Key key, this.imageSource, this.imageColor}) + const ColorFilteredImageAsset( + {Key? key, required this.imageSource, required this.imageColor}) : super(key: key); @override diff --git a/lib/widget/colorboard.dart b/lib/widget/colorboard.dart index 90eb798d..290155eb 100644 --- a/lib/widget/colorboard.dart +++ b/lib/widget/colorboard.dart @@ -1,9 +1,9 @@ +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; + import '../drawing/canvas_tone_drawer.dart'; import '../drawing/color_guide.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../beatscratch_plugin.dart'; -import 'package:aeyrium_sensor/aeyrium_sensor.dart'; import 'dart:async'; import 'dart:math'; import '../generated/protos/music.pb.dart'; @@ -30,16 +30,16 @@ class Colorboard extends StatefulWidget { final double leftMargin; Colorboard({ - Key key, - this.height, - this.showConfiguration, - this.hideConfiguration, - this.sectionColor, - this.part, - this.pressedNotesNotifier, - this.distanceFromBottom, - this.width, - this.leftMargin, + Key? key, + required this.height, + required this.showConfiguration, + required this.hideConfiguration, + required this.sectionColor, + required this.part, + required this.pressedNotesNotifier, + required this.distanceFromBottom, + required this.width, + required this.leftMargin, }) : super(key: key); @override @@ -52,8 +52,8 @@ class _ColorboardState extends State with TickerProviderStateMixin { bool useOrientation = true; List> _streamSubscriptions = >[]; - ValueNotifier scrollPositionNotifier; - ValueNotifier chordNotifier; + late ValueNotifier scrollPositionNotifier; + late ValueNotifier chordNotifier; bool reverseScrolling = false; ScrollingMode scrollingMode = ScrollingMode.sideScroll; @@ -73,19 +73,19 @@ class _ColorboardState extends State with TickerProviderStateMixin { _scaleAnimationControllers.clear(); AnimationController scaleAnimationController = animationController(); _scaleAnimationControllers.add(scaleAnimationController); - Animation animation; + late Animation animation; animation = Tween(begin: _halfStepWidthInPx, end: value) .animate(scaleAnimationController) - ..addListener(() { - setState(() { - _halfStepWidthInPx = animation.value; - }); - }); + ..addListener(() { + setState(() { + _halfStepWidthInPx = animation.value; + }); + }); scaleAnimationController.forward(); } - AnimationController orientationAnimationController; - Animation orientationAnimation; + late AnimationController orientationAnimationController; + late Animation orientationAnimation; int highestPitch = CanvasToneDrawer.TOP; int lowestPitch = CanvasToneDrawer.BOTTOM; bool showScrollHint = true; @@ -103,51 +103,51 @@ class _ColorboardState extends State with TickerProviderStateMixin { scrollPositionNotifier = ValueNotifier(0); chordNotifier = ValueNotifier(widget.chord); if (MyPlatform.isMobile) { - try { - _streamSubscriptions.add(AeyriumSensor.sensorEvents.listen((event) { - if (scrollingMode != ScrollingMode.sideScroll) { - print("Sensor event: $event"); - double normalizedPitch; - switch (scrollingMode) { - case ScrollingMode.pitch: - var absoluteScrollPosition = event.pitch; - if (absoluteScrollPosition < 0) { - absoluteScrollPosition = -absoluteScrollPosition; - } - normalizedPitch = max( - 0.0, min(1.0, (1.58 - absoluteScrollPosition * 1.2) / 1.5)); - break; - case ScrollingMode.roll: -// var maxRoll = -1.45; // All the way to the right -// var minRoll = 1.45; // All the way to the left - normalizedPitch = (1.45 - event.roll) / 2.9; - break; - case ScrollingMode.sideScroll: - break; - } +// try { +// _streamSubscriptions.add(AeyriumSensor.sensorEvents.listen((event) { +// if (scrollingMode != ScrollingMode.sideScroll) { +// print("Sensor event: $event"); +// double normalizedPitch; +// switch (scrollingMode) { +// case ScrollingMode.pitch: +// var absoluteScrollPosition = event.pitch; +// if (absoluteScrollPosition < 0) { +// absoluteScrollPosition = -absoluteScrollPosition; +// } +// normalizedPitch = max( +// 0.0, min(1.0, (1.58 - absoluteScrollPosition * 1.2) / 1.5)); +// break; +// case ScrollingMode.roll: +// // var maxRoll = -1.45; // All the way to the right +// // var minRoll = 1.45; // All the way to the left +// normalizedPitch = (1.45 - event.roll) / 2.9; +// break; +// case ScrollingMode.sideScroll: +// break; +// } - double newScrollPositionValue = max(0.0, min(1.0, normalizedPitch)); - if (newScrollPositionValue.isFinite && - !newScrollPositionValue.isNaN) { - Animation animation; - animation = Tween( - begin: scrollPositionNotifier.value, - end: newScrollPositionValue) - .animate(orientationAnimationController) - ..addListener(() { - scrollPositionNotifier.value = animation.value; -// setState(() {}); - }); - orientationAnimationController.forward( - from: scrollPositionNotifier.value); -// scrollPositionNotifier.value = newScrollPositionValue; - } - } - })); - } catch (MissingPluginException) { - // It's fine for this to not work on desktop - scrollingMode = ScrollingMode.sideScroll; - } +// double newScrollPositionValue = max(0.0, min(1.0, normalizedPitch)); +// if (newScrollPositionValue.isFinite && +// !newScrollPositionValue.isNaN) { +// Animation animation; +// animation = Tween( +// begin: scrollPositionNotifier.value, +// end: newScrollPositionValue) +// .animate(orientationAnimationController) +// ..addListener(() { +// scrollPositionNotifier.value = animation.value; +// // setState(() {}); +// }); +// orientationAnimationController.forward( +// from: scrollPositionNotifier.value); +// // scrollPositionNotifier.value = newScrollPositionValue; +// } +// } +// })); +// } catch (MissingPluginException) { +// // It's fine for this to not work on desktop +// scrollingMode = ScrollingMode.sideScroll; +// } } } @@ -184,10 +184,10 @@ class _ColorboardState extends State with TickerProviderStateMixin { scrollDirection: Axis.horizontal, slivers: [ CustomSliverToBoxAdapter( - setVisibleRect: (rect) { + (rect) { _visibleRect = rect; - _visibleRect = Rect.fromLTRB(rect.left, rect.top, rect.right, - rect.bottom - touchScrollAreaHeight); + _visibleRect = RectRendering.fromLTRB(rect.left, rect.top, + rect.right, rect.bottom - touchScrollAreaHeight); double newScrollPositionValue = rect.left / (physicalWidth - rect.width); if (newScrollPositionValue.isFinite && @@ -291,7 +291,7 @@ class _ColorboardState extends State with TickerProviderStateMixin { // print("dy=$dy; maxDy=$maxDy; velocity ratio=$velocityRatio"); int velocity = min(127, max(0, (velocityRatio * 127).toInt())); - int oldTone = _pointerIdsToTones[event.pointer]; + int oldTone = _pointerIdsToTones[event.pointer]!; if (tone != oldTone) { print("moving tone $oldTone to $tone"); try { @@ -306,7 +306,9 @@ class _ColorboardState extends State with TickerProviderStateMixin { } }, onPointerUp: (event) { - int tone = _pointerIdsToTones.remove(event.pointer); + int? tone = _pointerIdsToTones.remove(event.pointer); + if (tone == null) return; + widget.pressedNotesNotifier.value = _pointerIdsToTones.values.toSet(); try { @@ -314,7 +316,9 @@ class _ColorboardState extends State with TickerProviderStateMixin { } catch (t) {} }, onPointerCancel: (event) { - int tone = _pointerIdsToTones.remove(event.pointer); + int? tone = _pointerIdsToTones.remove(event.pointer); + if (tone == null) return; + widget.pressedNotesNotifier.value = _pointerIdsToTones.values.toSet(); try { @@ -488,13 +492,13 @@ class _ColorboardPainter extends CustomPainter { final ValueNotifier chordNotifier; _ColorboardPainter( - {this.chordNotifier, - this.lowestPitch, - this.highestPitch, - this.pressedNotesNotifier, - this.scrollPositionNotifier, - this.halfStepsOnScreen, - this.visibleRect}) + {required this.chordNotifier, + required this.lowestPitch, + required this.highestPitch, + required this.pressedNotesNotifier, + required this.scrollPositionNotifier, + required this.halfStepsOnScreen, + required this.visibleRect}) : super( repaint: Listenable.merge( [scrollPositionNotifier, pressedNotesNotifier, chordNotifier])); diff --git a/lib/widget/incrementable_value.dart b/lib/widget/incrementable_value.dart index 26d1cf5b..dd1f758a 100644 --- a/lib/widget/incrementable_value.dart +++ b/lib/widget/incrementable_value.dart @@ -5,38 +5,36 @@ import 'package:beatscratch_flutter_redux/colors.dart'; import '../music_view/music_action_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/rendering.dart'; import 'dart:async'; import 'my_buttons.dart'; -import 'my_platform.dart'; import '../ui_models.dart'; class IncrementableValue extends StatefulWidget { - final Function onIncrement; - final Function onDecrement; - final Function onBigIncrement; - final Function onBigDecrement; - final String value; - final TextStyle textStyle; + final Function? onIncrement; + final Function? onDecrement; + final Function? onBigIncrement; + final Function? onBigDecrement; + final String? value; + final TextStyle? textStyle; final double valueWidth; - final VoidCallback onValuePressed; - final Widget child; + final VoidCallback? onValuePressed; + final Widget? child; final double incrementDistance; final double incrementTimingDifferenceMs; final bool collapsing; - final IconData incrementIcon; - final IconData decrementIcon; - final IconData bigIncrementIcon; - final IconData bigDecrementIcon; - final VoidCallback onPointerUpCallback; - final VoidCallback onPointerDownCallback; + final IconData? incrementIcon; + final IconData? decrementIcon; + final IconData? bigIncrementIcon; + final IconData? bigDecrementIcon; + final VoidCallback? onPointerUpCallback; + final VoidCallback? onPointerDownCallback; final bool musicActionButtonStyle; - final Color musicActionButtonColor; + final Color? musicActionButtonColor; const IncrementableValue({ - Key key, + Key? key, this.onIncrement, this.onDecrement, this.value, @@ -65,8 +63,8 @@ class IncrementableValue extends StatefulWidget { class _IncrementableValueState extends State { int lastTouchTimeMs = 0; - Offset incrementStartPos; - int incrementStartTimeMs; + Offset? incrementStartPos; + late int incrementStartTimeMs; static const _msDelay = 3000; // static const _delay = Duration(milliseconds: _msDelay); bool _disposed = false; @@ -137,17 +135,17 @@ class _IncrementableValueState extends State { isDown = true; } // print("direction=$direction | isUp=$isUp | isDown=$isDown"); - if (isUp && widget.onIncrement != null) { + if (isUp) { vibrate(); incrementStartPos = event.position; incrementStartTimeMs = eventTime; // print("increment"); - widget.onIncrement(); - } else if (isDown && widget.onDecrement != null) { + widget.onIncrement?.call(); + } else if (isDown) { vibrate(); incrementStartPos = event.position; incrementStartTimeMs = eventTime; - widget.onDecrement(); + widget.onDecrement?.call(); } } @@ -227,7 +225,7 @@ class _IncrementableValueState extends State { ? () { lastTouchTimeMs = DateTime.now().millisecondsSinceEpoch; - widget.onBigDecrement(); + widget.onBigDecrement?.call(); } : null, padding: EdgeInsets.zero, @@ -248,7 +246,7 @@ class _IncrementableValueState extends State { ? () { lastTouchTimeMs = DateTime.now().millisecondsSinceEpoch; - widget.onDecrement(); + widget.onDecrement?.call(); } : null, padding: EdgeInsets.all(0), @@ -281,7 +279,7 @@ class _IncrementableValueState extends State { ? () { lastTouchTimeMs = DateTime.now().millisecondsSinceEpoch; - widget.onIncrement(); + widget.onIncrement?.call(); } : null, padding: EdgeInsets.all(0), @@ -302,7 +300,7 @@ class _IncrementableValueState extends State { ? () { lastTouchTimeMs = DateTime.now().millisecondsSinceEpoch; - widget.onBigIncrement(); + widget.onBigIncrement?.call(); } : null, padding: EdgeInsets.all(0), diff --git a/lib/widget/keyboard.dart b/lib/widget/keyboard.dart index b621c893..31bf76bd 100644 --- a/lib/widget/keyboard.dart +++ b/lib/widget/keyboard.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'dart:math'; import 'dart:ui'; -import 'package:aeyrium_sensor/aeyrium_sensor.dart'; +// import 'package:aeyrium_sensor/aeyrium_sensor.dart'; +import 'package:beatscratch_flutter_redux/drawing/rect_rendering.dart'; import 'package:beatscratch_flutter_redux/settings/app_settings.dart'; import 'package:flutter/services.dart'; import '../drawing/canvas_tone_drawer.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../beatscratch_plugin.dart'; @@ -37,20 +37,20 @@ class Keyboard extends StatefulWidget { final VoidCallback closeKeyboard; const Keyboard( - {Key key, - this.appSettings, - this.height, - this.showConfiguration, - this.hideConfiguration, - this.sectionColor, - this.part, - this.pressedNotesNotifier, - this.bluetoothControllerPressedNotes, - this.width, - this.leftMargin, - this.distanceFromBottom, - this.closed, - this.closeKeyboard}) + {Key? key, + required this.appSettings, + required this.height, + required this.showConfiguration, + required this.hideConfiguration, + required this.sectionColor, + required this.part, + required this.pressedNotesNotifier, + required this.bluetoothControllerPressedNotes, + required this.width, + required this.leftMargin, + required this.distanceFromBottom, + required this.closed, + required this.closeKeyboard}) : super(key: key); @override @@ -76,10 +76,10 @@ class KeyboardState extends State with TickerProviderStateMixin { bool useOrientation = false; List> _streamSubscriptions = >[]; - ValueNotifier scrollPositionNotifier; + late ValueNotifier scrollPositionNotifier; bool reverseScrolling = false; ScrollingMode scrollingMode = ScrollingMode.sideScroll; - ScrollingMode previousScrollingMode; + ScrollingMode? previousScrollingMode; bool showScrollHint = false; List _scaleAnimationControllers = []; @@ -100,7 +100,7 @@ class KeyboardState extends State with TickerProviderStateMixin { _scaleAnimationControllers.clear(); AnimationController scaleAnimationController = animationController(); _scaleAnimationControllers.add(scaleAnimationController); - Animation animation; + late Animation animation; animation = Tween(begin: _halfStepWidthInPx, end: value) .animate(scaleAnimationController) ..addListener(() { @@ -112,10 +112,10 @@ class KeyboardState extends State with TickerProviderStateMixin { } double get diatonicStepWidthInPx => halfStepWidthInPx * 12 / 7; - AnimationController orientationAnimationController; - Animation orientationAnimation; - AnimationController blurAnimationController; - Animation blurAnimation; + late AnimationController orientationAnimationController; + late Animation orientationAnimation; + late AnimationController blurAnimationController; + late Animation blurAnimation; int highestPitch = CanvasToneDrawer.TOP; int lowestPitch = CanvasToneDrawer.BOTTOM; @@ -125,7 +125,7 @@ class KeyboardState extends State with TickerProviderStateMixin { (scrollingMode == ScrollingMode.sideScroll) ? 30 : 0; Map _pointerIdsToTones = Map(); - double _startHalfStepWidthInPx; + late double _startHalfStepWidthInPx; @override void initState() { @@ -142,51 +142,49 @@ class KeyboardState extends State with TickerProviderStateMixin { ).animate(blurAnimationController); scrollPositionNotifier = ValueNotifier(0); if (MyPlatform.isMobile) { - try { - _streamSubscriptions.add(AeyriumSensor.sensorEvents.listen((event) { - if (scrollingMode != ScrollingMode.sideScroll) { - print("Sensor event: $event"); - double normalizedPitch; - switch (scrollingMode) { - case ScrollingMode.pitch: - var absoluteScrollPosition = event.pitch; - if (absoluteScrollPosition < 0) { - absoluteScrollPosition = -absoluteScrollPosition; - } - normalizedPitch = max( - 0.0, min(1.0, (1.58 - absoluteScrollPosition * 1.2) / 1.5)); - break; - case ScrollingMode.roll: -// var maxRoll = -1.45; // All the way to the right -// var minRoll = 1.45; // All the way to the left - normalizedPitch = (1.45 - event.roll) / 2.9; - break; - case ScrollingMode.sideScroll: - break; - } - - double newScrollPositionValue = max(0.0, min(1.0, normalizedPitch)); - if (newScrollPositionValue.isFinite && - !newScrollPositionValue.isNaN) { - Animation animation; - animation = Tween( - begin: scrollPositionNotifier.value, - end: newScrollPositionValue) - .animate(orientationAnimationController) - ..addListener(() { - scrollPositionNotifier.value = animation.value; -// setState(() {}); - }); - orientationAnimationController.forward( - from: scrollPositionNotifier.value); -// scrollPositionNotifier.value = newScrollPositionValue; - } - } - })); - } catch (MissingPluginException) { - // It's fine for this to not work on desktop - scrollingMode = ScrollingMode.sideScroll; - } +// try { +// _streamSubscriptions.add(AeyriumSensor.sensorEvents.listen((event) { +// if (scrollingMode != ScrollingMode.sideScroll) { +// print("Sensor event: $event"); +// double normalizedPitch; +// switch (scrollingMode) { +// case ScrollingMode.pitch: +// var absoluteScrollPosition = event.pitch; +// if (absoluteScrollPosition < 0) { +// absoluteScrollPosition = -absoluteScrollPosition; +// } +// normalizedPitch = max( +// 0.0, min(1.0, (1.58 - absoluteScrollPosition * 1.2) / 1.5)); +// break; +// case ScrollingMode.roll: +// // var maxRoll = -1.45; // All the way to the right +// // var minRoll = 1.45; // All the way to the left +// normalizedPitch = (1.45 - event.roll) / 2.9; +// break; +// case ScrollingMode.sideScroll: +// break; +// } + +// double newScrollPositionValue = max(0.0, min(1.0, normalizedPitch)); +// if (newScrollPositionValue.isFinite && +// !newScrollPositionValue.isNaN) { +// Animation animation; +// animation = Tween( +// begin: scrollPositionNotifier.value, +// end: newScrollPositionValue) +// .animate(orientationAnimationController) +// ..addListener(() { +// scrollPositionNotifier.value = animation.value; +// }); +// orientationAnimationController.forward( +// from: scrollPositionNotifier.value); +// } +// } +// })); +// } catch (MissingPluginException) { +// // It's fine for this to not work on desktop +// scrollingMode = ScrollingMode.sideScroll; +// } } WidgetsBinding.instance.addPostFrameCallback((_) { @@ -243,9 +241,9 @@ class KeyboardState extends State with TickerProviderStateMixin { scrollDirection: Axis.horizontal, slivers: [ CustomSliverToBoxAdapter( - setVisibleRect: (rect) { + (rect) { _visibleRect = rect; - _visibleRect = Rect.fromLTRB(rect.left, rect.top, + _visibleRect = RectRendering.fromLTRB(rect.left, rect.top, rect.right, rect.bottom - touchScrollAreaHeight); double newScrollPositionValue = rect.left / (physicalWidth - rect.width); @@ -390,7 +388,7 @@ class KeyboardState extends State with TickerProviderStateMixin { event.position.dy - widget.distanceFromBottom; double maxDy = widget.height - touchScrollAreaHeight; - int oldTone = _pointerIdsToTones[event.pointer]; + int oldTone = _pointerIdsToTones[event.pointer]!; int tone; if (dy > maxDy / 2) { // Black key area press @@ -413,7 +411,7 @@ class KeyboardState extends State with TickerProviderStateMixin { } }, onPointerUp: (event) { - int tone = _pointerIdsToTones.remove(event.pointer); + int tone = _pointerIdsToTones.remove(event.pointer)!; widget.pressedNotesNotifier.value = _pointerIdsToTones.values.toSet(); try { @@ -421,7 +419,7 @@ class KeyboardState extends State with TickerProviderStateMixin { } catch (t) {} }, onPointerCancel: (event) { - int tone = _pointerIdsToTones.remove(event.pointer); + int tone = _pointerIdsToTones.remove(event.pointer)!; widget.pressedNotesNotifier.value = _pointerIdsToTones.values.toSet(); try { @@ -795,6 +793,9 @@ class KeyboardState extends State with TickerProviderStateMixin { case 6: toneOffset = 11; break; + default: + toneOffset = 0; + break; } int tone = 12 * (octave - 4) + toneOffset; // print("diatonic tone: $diatonicTone octave: $octave toneoffset: $toneOffset tone: $tone"); @@ -810,12 +811,12 @@ class _KeyboardPainter extends CustomPainter { final int highestPitch, lowestPitch; _KeyboardPainter( - {this.highestPitch, - this.lowestPitch, - this.pressedNotesNotifier, - this.scrollPositionNotifier, - this.halfStepsOnScreen, - this.visibleRect}) + {required this.highestPitch, + required this.lowestPitch, + required this.pressedNotesNotifier, + required this.scrollPositionNotifier, + required this.halfStepsOnScreen, + required this.visibleRect}) : super( repaint: Listenable.merge([ scrollPositionNotifier, @@ -877,7 +878,7 @@ class _KeyboardPainter extends CustomPainter { } class KeyboardRenderer extends CanvasToneDrawer { - Iterable pressedNotes; + Iterable pressedNotes = []; bool renderLettersAndNumbers = true; draw(Canvas canvas) { @@ -952,7 +953,10 @@ class KeyboardRenderer extends CanvasToneDrawer { chromaticSteps[(tone - chord.rootNote.tone).mod12]; } canvas.drawRect( - Rect.fromLTRB(toneBounds.left, toneBounds.top, toneBounds.right, + RectRendering.fromLTRB( + toneBounds.left, + toneBounds.top, + toneBounds.right, toneBounds.top + (toneBounds.top + toneBounds.bottom) / 2), alphaDrawerPaint); break; diff --git a/lib/widget/my_buttons.dart b/lib/widget/my_buttons.dart index 82ca9112..a5b3f2e7 100644 --- a/lib/widget/my_buttons.dart +++ b/lib/widget/my_buttons.dart @@ -1,38 +1,33 @@ import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import '../colors.dart'; class MyFlatButton extends TextButton { MyFlatButton({ - Key key, - @required VoidCallback onPressed, - VoidCallback onLongPress, - ValueChanged onHighlightChanged, + Key? key, + required VoidCallback? onPressed, + VoidCallback? onLongPress, + ValueChanged? onHighlightChanged, MouseCursor mouseCursor = SystemMouseCursors.basic, - ButtonTextTheme textTheme, - Color color, - EdgeInsetsGeometry padding, + ButtonTextTheme? textTheme, + Color? color, + EdgeInsetsGeometry? padding, Clip clipBehavior = Clip.none, - FocusNode focusNode, + FocusNode? focusNode, bool autofocus = false, - ButtonStyle style, + ButtonStyle? style, bool lightHighlight = false, - @required Widget child, - }) : assert(clipBehavior != null), - assert(autofocus != null), - super( + required Widget child, + }) : super( key: key, onPressed: onPressed, onLongPress: onLongPress, style: style ?? ButtonStyle( - backgroundColor: MaterialStateProperty.all(color), - overlayColor: MaterialStateProperty.all( + backgroundColor: WidgetStateProperty.all(color), + overlayColor: WidgetStateProperty.all( lightHighlight ? Colors.white10 : null), mouseCursor: - MaterialStateProperty.all(SystemMouseCursors.basic), - padding: MaterialStateProperty.all(padding)), + WidgetStateProperty.all(SystemMouseCursors.basic), + padding: WidgetStateProperty.all(padding)), // ElevatedButton.styleFrom( // primary: color, // // onPrimary: color?.textColor(), @@ -48,48 +43,46 @@ class MyFlatButton extends TextButton { class MyRaisedButton extends ElevatedButton { MyRaisedButton({ - Key key, - @required VoidCallback onPressed, - VoidCallback onLongPress, - ValueChanged onHighlightChanged, + Key? key, + required VoidCallback? onPressed, + VoidCallback? onLongPress, + ValueChanged? onHighlightChanged, MouseCursor mouseCursor = SystemMouseCursors.basic, - ButtonTextTheme textTheme, - Color textColor, - Color disabledTextColor, - Color color, - Color disabledColor, - Color focusColor, - Color hoverColor, - Color highlightColor, - Color splashColor, - Brightness colorBrightness, - double elevation, - double focusElevation, - double hoverElevation, - double highlightElevation, - double disabledElevation, - EdgeInsetsGeometry padding, - VisualDensity visualDensity, - ShapeBorder shape, + ButtonTextTheme? textTheme, + Color? textColor, + Color? disabledTextColor, + Color? color, + Color? disabledColor, + Color? focusColor, + Color? hoverColor, + Color? highlightColor, + Color? splashColor, + Brightness? colorBrightness, + double elevation = 1.0, + double focusElevation = 1.0, + double hoverElevation = 1.0, + double highlightElevation = 1.0, + double disabledElevation = 1.0, + EdgeInsetsGeometry? padding, + VisualDensity? visualDensity, + ShapeBorder? shape, Clip clipBehavior = Clip.none, - FocusNode focusNode, + FocusNode? focusNode, bool autofocus = false, - MaterialTapTargetSize materialTapTargetSize, - Duration animationDuration, - Widget child, - }) : assert(autofocus != null), - assert(elevation == null || elevation >= 0.0), - assert(focusElevation == null || focusElevation >= 0.0), - assert(hoverElevation == null || hoverElevation >= 0.0), - assert(highlightElevation == null || highlightElevation >= 0.0), - assert(disabledElevation == null || disabledElevation >= 0.0), - assert(clipBehavior != null), + MaterialTapTargetSize? materialTapTargetSize, + Duration? animationDuration, + Widget? child, + }) : assert(elevation >= 0.0), + assert(focusElevation >= 0.0), + assert(hoverElevation >= 0.0), + assert(highlightElevation >= 0.0), + assert(disabledElevation >= 0.0), super( key: key, onPressed: onPressed, onLongPress: onLongPress, style: ElevatedButton.styleFrom( - primary: color, + backgroundColor: color, padding: padding, enabledMouseCursor: SystemMouseCursors.basic, disabledMouseCursor: SystemMouseCursors.basic), @@ -104,20 +97,20 @@ class MyRaisedButton extends ElevatedButton { class MySlider extends Slider { const MySlider({ - Key key, - @required double value, - @required ValueChanged onChanged, - ValueChanged onChangeStart, - ValueChanged onChangeEnd, + Key? key, + required double value, + ValueChanged? onChanged, + ValueChanged? onChangeStart, + ValueChanged? onChangeEnd, double min = 0.0, double max = 1.0, - int divisions, - String label, - Color activeColor, - Color inactiveColor, + int? divisions, + String? label, + Color? activeColor, + Color? inactiveColor, MouseCursor mouseCursor = SystemMouseCursors.basic, - SemanticFormatterCallback semanticFormatterCallback, - FocusNode focusNode, + SemanticFormatterCallback? semanticFormatterCallback, + FocusNode? focusNode, bool autofocus = false, }) : super( key: key, diff --git a/lib/widget/my_platform.dart b/lib/widget/my_platform.dart index f8050335..67716a8f 100644 --- a/lib/widget/my_platform.dart +++ b/lib/widget/my_platform.dart @@ -1,6 +1,5 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; -import 'package:package_info_plus/package_info_plus.dart'; class MyPlatform { static final bool isWeb = kIsWeb; diff --git a/lib/widget/my_popup_menu.dart b/lib/widget/my_popup_menu.dart index 4ebe8b27..4713d625 100644 --- a/lib/widget/my_popup_menu.dart +++ b/lib/widget/my_popup_menu.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 - import 'dart:async'; import 'package:flutter/foundation.dart'; @@ -78,7 +76,7 @@ const double _kMenuScreenPadding = 8.0; abstract class PopupMenuEntry extends StatefulWidget { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. - const PopupMenuEntry({Key key}) : super(key: key); + const PopupMenuEntry({Key? key}) : super(key: key); /// The amount of vertical space occupied by this entry. /// @@ -118,7 +116,7 @@ class PopupMenuDivider extends PopupMenuEntry { /// Creates a horizontal divider for a popup menu. /// /// By default, the divider has a height of 16 logical pixels. - const PopupMenuDivider({Key key, this.height = _kMenuDividerHeight}) + const PopupMenuDivider({required Key key, this.height = _kMenuDividerHeight}) : super(key: key); /// The height of the divider entry. @@ -145,11 +143,10 @@ class _PopupMenuDividerState extends State { // item lines up with the center of its PopupMenuButton. class _MenuItem extends SingleChildRenderObjectWidget { const _MenuItem({ - Key key, - @required this.onLayout, - Widget child, - }) : assert(onLayout != null), - super(key: key, child: child); + Key? key, + required this.onLayout, + Widget? child, + }) : super(key: key, child: child); final ValueChanged onLayout; @@ -166,9 +163,7 @@ class _MenuItem extends SingleChildRenderObjectWidget { } class _RenderMenuItem extends RenderShiftedBox { - _RenderMenuItem(this.onLayout, [RenderBox child]) - : assert(onLayout != null), - super(child); + _RenderMenuItem(this.onLayout, [RenderBox? child]) : super(child); ValueChanged onLayout; @@ -177,11 +172,11 @@ class _RenderMenuItem extends RenderShiftedBox { if (child == null) { size = Size.zero; } else { - child.layout(constraints, parentUsesSize: true); - size = constraints.constrain(child.size); + child!.layout(constraints, parentUsesSize: true); + size = constraints.constrain(child!.size); + final BoxParentData childParentData = child!.parentData as BoxParentData; + childParentData.offset = Offset.zero; } - final BoxParentData childParentData = child.parentData as BoxParentData; - childParentData.offset = Offset.zero; onLayout(size); } } @@ -232,15 +227,14 @@ class PopupMenuItem extends PopupMenuEntry { /// /// The `enabled` and `height` arguments must not be null. const PopupMenuItem({ - Key key, - this.value, + Key? key, + required this.value, this.enabled = true, this.height = kMinInteractiveDimension, - this.textStyle, - this.mouseCursor, - @required this.child, - }) : assert(enabled != null), - assert(height != null), + this.textStyle = const TextStyle(), + this.mouseCursor = SystemMouseCursors.basic, + required this.child, + }) : assert(height != null), super(key: key); /// The value that will be returned by [showMenu] if this entry is selected. @@ -268,11 +262,11 @@ class PopupMenuItem extends PopupMenuEntry { /// widget. /// /// If [mouseCursor] is a [MaterialStateProperty], - /// [MaterialStateProperty.resolve] is used for the following [MaterialState]: + /// [WidgetStateProperty.resolve] is used for the following [WidgetState]: /// - /// * [MaterialState.disabled]. + /// * [WidgetState.disabled]. /// - /// If this property is null, [MaterialStateMouseCursor.clickable] will be used. + /// If this property is null, [WidgetStateMouseCursor.clickable] will be used. final MouseCursor mouseCursor; /// The widget below this widget in the tree. @@ -280,7 +274,7 @@ class PopupMenuItem extends PopupMenuEntry { /// Typically a single-line [ListTile] (for menus with icons) or a [Text]. An /// appropriate [DefaultTextStyle] is put in scope for the child. In either /// case, the text should be short enough that it won't wrap. - final Widget child; + final Widget? child; @override bool represents(T value) => value == this.value; @@ -292,13 +286,13 @@ class PopupMenuItem extends PopupMenuEntry { class MyPopupMenuItem extends PopupMenuItem { const MyPopupMenuItem({ - Key key, - T value, + Key? key, + required T value, bool enabled = true, double height = kMinInteractiveDimension, - TextStyle textStyle, + TextStyle textStyle = const TextStyle(), MouseCursor mouseCursor = SystemMouseCursors.basic, - @required Widget child, + Widget? child, }) : super( key: key, value: value, @@ -334,7 +328,7 @@ class PopupMenuItemState> extends State { /// By default, this returns [PopupMenuItem.child]. Override this to put /// something else in the menu entry. @protected - Widget buildChild() => widget.child; + Widget? buildChild() => widget.child; /// The handler for when the user selects the menu item. /// @@ -351,14 +345,14 @@ class PopupMenuItemState> extends State { Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context); - TextStyle style = widget.textStyle ?? + TextStyle? style = widget.textStyle ?? popupMenuTheme.textStyle ?? - theme.textTheme.subtitle1; + theme.textTheme.headlineSmall; - if (!widget.enabled) style = style.copyWith(color: theme.disabledColor); + if (!widget.enabled) style = style?.copyWith(color: theme.disabledColor); Widget item = AnimatedDefaultTextStyle( - style: style, + style: style ?? const TextStyle(), duration: kThemeChangeDuration, child: Container( alignment: AlignmentDirectional.centerStart, @@ -377,10 +371,10 @@ class PopupMenuItemState> extends State { ); } final MouseCursor effectiveMouseCursor = - MaterialStateProperty.resolveAs( - widget.mouseCursor ?? MaterialStateMouseCursor.clickable, - { - if (!widget.enabled) MaterialState.disabled, + WidgetStateProperty.resolveAs( + widget.mouseCursor ?? WidgetStateMouseCursor.clickable, + { + if (!widget.enabled) WidgetState.disabled, }, ); @@ -463,13 +457,12 @@ class CheckedPopupMenuItem extends PopupMenuItem { /// /// The `checked` and `enabled` arguments must not be null. const CheckedPopupMenuItem({ - Key key, - T value, + Key? key, + required T value, this.checked = false, bool enabled = true, - Widget child, - }) : assert(checked != null), - super( + Widget? child, + }) : super( key: key, value: value, enabled: enabled, @@ -494,7 +487,7 @@ class CheckedPopupMenuItem extends PopupMenuItem { /// This widget is placed in the [ListTile.title] slot of a [ListTile] whose /// [ListTile.leading] slot is an [Icons.done] icon. @override - Widget get child => super.child; + Widget? get child => super.child; @override _CheckedPopupMenuItemState createState() => @@ -505,7 +498,7 @@ class _CheckedPopupMenuItemState extends PopupMenuItemState> with SingleTickerProviderStateMixin { static const Duration _fadeDuration = Duration(milliseconds: 150); - AnimationController _controller; + late AnimationController _controller; Animation get _opacity => _controller.view; @override @@ -541,9 +534,9 @@ class _CheckedPopupMenuItemState class _PopupMenu extends StatelessWidget { const _PopupMenu({ - Key key, - this.route, - this.semanticLabel, + Key? key, + required this.route, + required this.semanticLabel, }) : super(key: key); final _PopupMenuRoute route; @@ -559,14 +552,14 @@ class _PopupMenu extends StatelessWidget { for (int i = 0; i < route.items.length; i += 1) { final double start = (i + 1) * unit; - final double end = (start + 1.5 * unit).clamp(0.0, 1.0) as double; + final double end = (start + 1.5 * unit).clamp(0.0, 1.0); final CurvedAnimation opacity = CurvedAnimation( - parent: route.animation, + parent: route.animation!, curve: Interval(start, end), ); Widget item = route.items[i]; if (route.initialValue != null && - route.items[i].represents(route.initialValue)) { + route.items[i].represents(route.initialValue!)) { item = Container( color: Theme.of(context).highlightColor, child: item, @@ -613,10 +606,10 @@ class _PopupMenu extends StatelessWidget { ); return AnimatedBuilder( - animation: route.animation, - builder: (BuildContext context, Widget child) { + animation: route.animation!, + builder: (BuildContext context, Widget? child) { return Opacity( - opacity: opacity.evaluate(route.animation), + opacity: opacity.evaluate(route.animation!), child: Material( shape: route.shape ?? popupMenuTheme.shape, color: route.color ?? popupMenuTheme.color, @@ -624,8 +617,8 @@ class _PopupMenu extends StatelessWidget { elevation: route.elevation ?? popupMenuTheme.elevation ?? 8.0, child: Align( alignment: AlignmentDirectional.topEnd, - widthFactor: width.evaluate(route.animation), - heightFactor: height.evaluate(route.animation), + widthFactor: width.evaluate(route.animation!), + heightFactor: height.evaluate(route.animation!), child: child, ), ), @@ -646,11 +639,11 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { // The sizes of each item are computed when the menu is laid out, and before // the route is laid out. - List itemSizes; + List? itemSizes; // The index of the selected item, or null if PopupMenuButton.initialValue // was not specified. - final int selectedItemIndex; + final int? selectedItemIndex; // Whether to prefer going to the left or to the right. final TextDirection textDirection; @@ -678,11 +671,11 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { // Find the ideal vertical position. double y = position.top; - if (selectedItemIndex != null && itemSizes != null) { + if (itemSizes != null && selectedItemIndex != null) { double selectedItemOffset = _kMenuVerticalPadding; - for (int index = 0; index < selectedItemIndex; index += 1) - selectedItemOffset += itemSizes[index].height; - selectedItemOffset += itemSizes[selectedItemIndex].height / 2; + for (int index = 0; index < selectedItemIndex!; index += 1) + selectedItemOffset += itemSizes![index].height; + selectedItemOffset += itemSizes![selectedItemIndex!].height / 2; y = position.top + (size.height - position.top - position.bottom) / 2.0 - selectedItemOffset; @@ -698,7 +691,6 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { x = position.left; } else { // Menu button is equidistant from both edges, so grow in reading direction. - assert(textDirection != null); switch (textDirection) { case TextDirection.rtl: x = size.width - position.right - childSize.width; @@ -727,7 +719,7 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { // If called when the old and new itemSizes have been initialized then // we expect them to have the same length because there's no practical // way to change length of the items list once the menu has been shown. - assert(itemSizes.length == oldDelegate.itemSizes.length); + assert(itemSizes!.length == oldDelegate.itemSizes!.length); return position != oldDelegate.position || selectedItemIndex != oldDelegate.selectedItemIndex || @@ -738,29 +730,29 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { class _PopupMenuRoute extends PopupRoute { _PopupMenuRoute({ - this.position, - this.items, + required this.position, + required this.items, this.initialValue, this.elevation, - this.theme, - this.popupMenuTheme, - this.barrierLabel, - this.semanticLabel, + required this.theme, + required this.popupMenuTheme, + required this.barrierLabel, + required this.semanticLabel, this.shape, this.color, - this.showMenuContext, - this.captureInheritedThemes, + required this.showMenuContext, + required this.captureInheritedThemes, }) : itemSizes = List.empty() /*(items.length)*/; final RelativeRect position; final List> items; final List itemSizes; - final T initialValue; - final double elevation; + final T? initialValue; + final double? elevation; final ThemeData theme; final String semanticLabel; - final ShapeBorder shape; - final Color color; + final ShapeBorder? shape; + final Color? color; final PopupMenuThemeData popupMenuTheme; final BuildContext showMenuContext; final bool captureInheritedThemes; @@ -781,7 +773,7 @@ class _PopupMenuRoute extends PopupRoute { bool get barrierDismissible => true; @override - Color get barrierColor => null; + Color? get barrierColor => null; @override final String barrierLabel; @@ -789,12 +781,12 @@ class _PopupMenuRoute extends PopupRoute { @override Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { - int selectedItemIndex; + int? selectedItemIndex; if (initialValue != null) { for (int index = 0; selectedItemIndex == null && index < items.length; index += 1) { - if (items[index].represents(initialValue)) selectedItemIndex = index; + if (items[index].represents(initialValue!)) selectedItemIndex = index; } } @@ -805,7 +797,7 @@ class _PopupMenuRoute extends PopupRoute { // For the sake of backwards compatibility. An (unlikely) app that relied // on having menus only inherit from the material Theme could set // captureInheritedThemes to false and get the original behavior. - if (theme != null) menu = Theme(data: theme, child: menu); + menu = Theme(data: theme, child: menu); } return MediaQuery.removePadding( @@ -886,37 +878,32 @@ class _PopupMenuRoute extends PopupRoute { /// calling this method automatically. /// * [SemanticsConfiguration.namesRoute], for a description of edge triggered /// semantics. -Future showMenu({ - @required BuildContext context, - @required RelativeRect position, - @required List> items, - T initialValue, - double elevation, - String semanticLabel, - ShapeBorder shape, - Color color, +Future showMenu({ + required BuildContext context, + required RelativeRect position, + required List> items, + T? initialValue, + double? elevation, + String? semanticLabel, + ShapeBorder? shape, + Color? color, bool captureInheritedThemes = true, bool useRootNavigator = false, }) { - assert(context != null); - assert(position != null); - assert(useRootNavigator != null); - assert(items != null && items.isNotEmpty); - assert(captureInheritedThemes != null); + assert(items.isNotEmpty); assert(debugCheckHasMaterialLocalizations(context)); - String label = semanticLabel; + String label = semanticLabel ?? ''; switch (Theme.of(context).platform) { case TargetPlatform.iOS: case TargetPlatform.macOS: - label = semanticLabel; + label = semanticLabel ?? ''; break; case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: - label = - semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel; + label = semanticLabel ?? MaterialLocalizations.of(context).popupMenuLabel; } return Navigator.of(context, rootNavigator: useRootNavigator) @@ -1014,10 +1001,10 @@ class MyPopupMenuButton extends StatefulWidget { /// /// The [itemBuilder] argument must not be null. const MyPopupMenuButton({ - Key key, - @required this.itemBuilder, + Key? key, + required this.itemBuilder, this.initialValue, - this.onSelected, + required this.onSelected, this.onCanceled, this.tooltip, this.elevation, @@ -1030,19 +1017,16 @@ class MyPopupMenuButton extends StatefulWidget { this.color, this.captureInheritedThemes = true, this.updatedMenu, - }) : assert(itemBuilder != null), - assert(offset != null), - assert(enabled != null), - assert(captureInheritedThemes != null), - assert(!(child != null && icon != null), - 'You can only pass [child] or [icon], not both.'), + }) : assert(offset != null), + // assert( + // !(icon != null), 'You can only pass [child] or [icon], not both.'), super(key: key); /// Called when the button is pressed to create the items to show in the menu. final PopupMenuItemBuilder itemBuilder; /// The value of the menu item, if any, that should be highlighted when the menu opens. - final T initialValue; + final T? initialValue; /// Called when the user selects a value from the popup menu created by this button. /// @@ -1053,19 +1037,19 @@ class MyPopupMenuButton extends StatefulWidget { /// Called when the user dismisses the popup menu without selecting an item. /// /// If the user selects a value, [onSelected] is called instead. - final PopupMenuCanceled onCanceled; + final PopupMenuCanceled? onCanceled; /// Text that describes the action that will occur when the button is pressed. /// /// This text is displayed when the user long-presses on the button and is /// used for accessibility. - final String tooltip; + final String? tooltip; /// The z-coordinate at which to place the menu when open. This controls the /// size of the shadow below the menu. /// /// Defaults to 8, the appropriate elevation for popup menus. - final double elevation; + final double? elevation; /// Matches IconButton's 8 dps padding by default. In some cases, notably where /// this button appears as the trailing element of a list item, it's useful to be able @@ -1074,11 +1058,11 @@ class MyPopupMenuButton extends StatefulWidget { /// If provided, [child] is the widget used for this button /// and the button will utilize an [InkWell] for taps. - final Widget child; + final Widget? child; /// If provided, the [icon] is used for this button /// and the button will behave like an [IconButton]. - final Widget icon; + final Widget? icon; /// The offset applied to the Popup Menu Button. /// @@ -1106,21 +1090,21 @@ class MyPopupMenuButton extends StatefulWidget { /// If [PopupMenuThemeData.shape] is also null, then the default shape for /// [MaterialType.card] is used. This default shape is a rectangle with /// rounded edges of BorderRadius.circular(2.0). - final ShapeBorder shape; + final ShapeBorder? shape; /// If provided, the background color used for the menu. /// /// If this property is null, then [PopupMenuThemeData.color] is used. /// If [PopupMenuThemeData.color] is also null, then /// Theme.of(context).cardColor is used. - final Color color; + final Color? color; /// If true (the default) then the menu will be wrapped with copies /// of the [InheritedThemes], like [Theme] and [PopupMenuTheme], which /// are defined above the [BuildContext] where the menu is shown. final bool captureInheritedThemes; - final ChangeNotifier updatedMenu; + final ChangeNotifier? updatedMenu; @override PopupMenuButtonState createState() => PopupMenuButtonState(); @@ -1134,13 +1118,13 @@ class PopupMenuButtonState extends State> { @override initState() { super.initState(); - widget?.updatedMenu?.addListener(showButtonMenu); + widget.updatedMenu?.addListener(showButtonMenu); } @override dispose() { super.dispose(); - widget?.updatedMenu?.removeListener(showButtonMenu); + widget.updatedMenu?.removeListener(showButtonMenu); } /// A method to show a popup menu with the items supplied to @@ -1176,19 +1160,18 @@ class PopupMenuButtonState extends State> { shape: widget.shape ?? popupMenuTheme.shape, color: widget.color ?? popupMenuTheme.color, captureInheritedThemes: widget.captureInheritedThemes, - ).then((T newValue) { + ).then((T? newValue) { if (!mounted) return null; if (newValue == null) { - if (widget.onCanceled != null) widget.onCanceled(); + widget.onCanceled?.call(); return null; } - if (widget.onSelected != null) widget.onSelected(newValue); + widget.onSelected(newValue); }); } } - Icon _getIcon(TargetPlatform platform) { - assert(platform != null); + Icon? _getIcon(TargetPlatform platform) { switch (platform) { case TargetPlatform.android: case TargetPlatform.fuchsia: @@ -1199,43 +1182,42 @@ class PopupMenuButtonState extends State> { case TargetPlatform.macOS: return const Icon(Icons.more_horiz); } - return null; + // return null; } bool get _canRequestFocus { final NavigationMode mode = - MediaQuery.of(context)?.navigationMode ?? NavigationMode.traditional; + MediaQuery.of(context).navigationMode ?? NavigationMode.traditional; switch (mode) { case NavigationMode.traditional: return widget.enabled; case NavigationMode.directional: return true; } - assert(false, 'Navigation mode $mode not handled'); - return null; + // assert(false, 'Navigation mode $mode not handled'); + // return null; } @override Widget build(BuildContext context) { assert(debugCheckHasMaterialLocalizations(context)); - if (widget.child != null) - return Tooltip( - message: - widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip, - child: InkWell( - onTap: widget.enabled ? showButtonMenu : null, - canRequestFocus: _canRequestFocus, - child: widget.child, - ), - ); - - return IconButton( - icon: widget.icon ?? _getIcon(Theme.of(context).platform), - padding: widget.padding, - mouseCursor: SystemMouseCursors.basic, - tooltip: widget.tooltip ?? null, - onPressed: widget.enabled ? showButtonMenu : null, + return Tooltip( + message: + widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip, + child: InkWell( + onTap: widget.enabled ? showButtonMenu : null, + canRequestFocus: _canRequestFocus, + child: widget.child, + ), ); + + // return IconButton( + // icon: widget.icon ?? _getIcon(Theme.of(context).platform), + // padding: widget.padding, + // mouseCursor: SystemMouseCursors.basic, + // tooltip: widget.tooltip ?? null, + // onPressed: widget.enabled ? showButtonMenu : null, + // ); } } diff --git a/lib/widget/scalable_view.dart b/lib/widget/scalable_view.dart index 1c6274e2..1d75f9aa 100644 --- a/lib/widget/scalable_view.dart +++ b/lib/widget/scalable_view.dart @@ -1,23 +1,20 @@ import '../colors.dart'; import '../music_view/music_action_button.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'incrementable_value.dart'; import '../ui_models.dart'; class ScalableView extends StatefulWidget { - final VoidCallback onMicroScaleDown; - final VoidCallback onMicroScaleUp; - final VoidCallback onScaleDown; - final VoidCallback onScaleUp; - final Widget child; + final VoidCallback? onMicroScaleDown; + final VoidCallback? onMicroScaleUp; + final VoidCallback? onScaleDown; + final VoidCallback? onScaleUp; + final Widget? child; final String zoomLevelDescription; // e.g. 79%, 1x, 2x. Your choice. final bool autoScroll; - final VoidCallback toggleAutoScroll; - final VoidCallback scrollToCurrent; + final VoidCallback? toggleAutoScroll; + final VoidCallback? scrollToCurrent; final bool visible; final Color primaryColor; final bool showViewOptions; @@ -25,12 +22,12 @@ class ScalableView extends StatefulWidget { final bool shiftUpZoomControls; const ScalableView( - {Key key, + {Key? key, this.onScaleDown, this.onScaleUp, this.child, - this.zoomLevelDescription, - this.autoScroll, + required this.zoomLevelDescription, + this.autoScroll = true, this.toggleAutoScroll, this.scrollToCurrent, this.visible = true, @@ -47,128 +44,132 @@ class ScalableView extends StatefulWidget { } class _ScalableViewState extends State { - double lastUpdatedScale; - int directionUpOrDown; + double? lastUpdatedScale; + int? directionUpOrDown; @override Widget build(BuildContext context) { return GestureDetector( child: Stack( - children: [ - widget.child, - Row(children: [ - Expanded(child: SizedBox()), - SizedBox(width: 2), - Column(children: [ - Expanded(child: SizedBox()), - if (widget.autoScroll != null && widget.toggleAutoScroll != null) - MusicActionButton( - visible: widget.visible && widget.showViewOptions, - onPressed: widget.toggleAutoScroll, - child: Stack(children: [ - Transform.translate( - offset: Offset(0, -6), - child: Text("Auto", - maxLines: 1, - overflow: TextOverflow.fade, - style: TextStyle( - fontSize: 10, - color: widget.autoScroll - ? widget.primaryColor - : Colors.grey))), - Transform.translate( - offset: Offset(0, 6), - child: AnimatedOpacity( - duration: animationDuration, - opacity: !widget.autoScroll ? 1 : 0, - child: - Icon(Icons.location_disabled, color: Colors.grey), - ), - ), - Transform.translate( - offset: Offset(0, 6), - child: AnimatedOpacity( - duration: animationDuration, - opacity: widget.autoScroll ? 1 : 0, - child: - Icon(Icons.my_location, color: widget.primaryColor), - ), - ), - ]), - ), - SizedBox(height: 2), - Container( - color: Colors.black12, - padding: EdgeInsets.all(0), - child: IncrementableValue( - child: Container( - width: 48, - height: 48, - child: Align( - alignment: Alignment.center, - child: Transform.translate( - offset: Offset(0, -3), - child: Stack(children: [ - Transform.translate( - offset: Offset(-5, 5), - child: Transform.scale( - scale: 1, - child: Icon(Icons.zoom_out, - color: musicForegroundColor - .withOpacity(0.54)))), - Transform.translate( - offset: Offset(5, -5), - child: Transform.scale( - scale: 1, - child: Icon(Icons.zoom_in, - color: musicForegroundColor - .withOpacity(0.54)))), - Transform.translate( - offset: Offset(2, 20), - child: Text(widget.zoomLevelDescription, - style: TextStyle( - fontWeight: FontWeight.w800, - fontSize: 12, - color: musicForegroundColor - .withOpacity(0.87))), - ), - ])), + children: + ((widget.child != null ? [widget.child!] : []) as List) + + [ + // widget.child, + Row(children: [ + Expanded(child: SizedBox()), + SizedBox(width: 2), + Column(children: [ + Expanded(child: SizedBox()), + if (widget.toggleAutoScroll != null) + MusicActionButton( + visible: widget.visible && widget.showViewOptions, + onPressed: widget.toggleAutoScroll, + child: Stack(children: [ + Transform.translate( + offset: Offset(0, -6), + child: Text("Auto", + maxLines: 1, + overflow: TextOverflow.fade, + style: TextStyle( + fontSize: 10, + color: widget.autoScroll + ? widget.primaryColor + : Colors.grey))), + Transform.translate( + offset: Offset(0, 6), + child: AnimatedOpacity( + duration: animationDuration, + opacity: !widget.autoScroll ? 1 : 0, + child: Icon(Icons.location_disabled, + color: Colors.grey), + ), + ), + Transform.translate( + offset: Offset(0, 6), + child: AnimatedOpacity( + duration: animationDuration, + opacity: widget.autoScroll ? 1 : 0, + child: Icon(Icons.my_location, + color: widget.primaryColor), + ), + ), + ]), ), - ), - valueWidth: 48, - collapsing: true, - musicActionButtonStyle: true, - musicActionButtonColor: - widget.zoomButtonColor.withOpacity(0.26), - incrementIcon: Icons.zoom_in, - decrementIcon: Icons.zoom_out, - onIncrement: widget.onScaleUp, - onDecrement: widget.onScaleDown)), - SizedBox(height: 2), - AnimatedContainer( - duration: animationDuration, - height: widget.shiftUpZoomControls ? 24 : 0) - ]), - SizedBox(width: 2), - ]) - ], + SizedBox(height: 2), + Container( + color: Colors.black12, + padding: EdgeInsets.all(0), + child: IncrementableValue( + child: Container( + width: 48, + height: 48, + child: Align( + alignment: Alignment.center, + child: Transform.translate( + offset: Offset(0, -3), + child: Stack(children: [ + Transform.translate( + offset: Offset(-5, 5), + child: Transform.scale( + scale: 1, + child: Icon(Icons.zoom_out, + color: musicForegroundColor + .withOpacity(0.54)))), + Transform.translate( + offset: Offset(5, -5), + child: Transform.scale( + scale: 1, + child: Icon(Icons.zoom_in, + color: musicForegroundColor + .withOpacity(0.54)))), + Transform.translate( + offset: Offset(2, 20), + child: Text( + widget.zoomLevelDescription, + style: TextStyle( + fontWeight: FontWeight.w800, + fontSize: 12, + color: musicForegroundColor + .withOpacity(0.87))), + ), + ])), + ), + ), + valueWidth: 48, + collapsing: true, + musicActionButtonStyle: true, + musicActionButtonColor: + widget.zoomButtonColor.withOpacity(0.26), + incrementIcon: Icons.zoom_in, + decrementIcon: Icons.zoom_out, + onIncrement: widget.onScaleUp, + onDecrement: widget.onScaleDown)), + SizedBox(height: 2), + AnimatedContainer( + duration: animationDuration, + height: widget.shiftUpZoomControls ? 24 : 0) + ]), + SizedBox(width: 2), + ]) + ], ), onScaleStart: (details) { lastUpdatedScale = 1; }, onScaleUpdate: (details) { + final lastUpdatedScale = this.lastUpdatedScale; + if (lastUpdatedScale == null) { + return; + } final scale = details.scale; // print("scale: $scale"); - if (widget.onMicroScaleUp != null && scale - lastUpdatedScale >= .01) { - widget.onMicroScaleUp(); - } else if (widget.onMicroScaleDown != null && - scale - lastUpdatedScale <= -.01) { - widget.onMicroScaleDown(); - } else if (widget.onScaleUp != null && - scale - lastUpdatedScale >= .09) { - widget.onScaleUp(); - } else if (widget.onScaleDown != null && - scale - lastUpdatedScale <= -.09) { - widget.onScaleDown(); + if (scale - lastUpdatedScale >= .01) { + widget.onMicroScaleUp?.call(); + } else if (scale - lastUpdatedScale <= -.01) { + widget.onMicroScaleDown?.call(); + } else if (scale - lastUpdatedScale >= .09) { + widget.onScaleUp?.call(); + } else if (scale - lastUpdatedScale <= -.09) { + widget.onScaleDown?.call(); } }, onScaleEnd: (details) { diff --git a/lib/widget/section_list.dart b/lib/widget/section_list.dart index c5f55ae8..4584cf74 100644 --- a/lib/widget/section_list.dart +++ b/lib/widget/section_list.dart @@ -12,7 +12,6 @@ import 'package:animated_list_plus/transitions.dart'; import '../ui_models.dart'; import '../util/dummydata.dart'; import '../util/music_theory.dart'; -import '../util/util.dart'; import '../colors.dart'; import 'beats_badge.dart'; import 'my_buttons.dart'; @@ -29,21 +28,21 @@ class SectionList extends StatefulWidget { final VoidCallback toggleShowSectionBeatCounts; final bool allowReordering; final AppSettings appSettings; - final double width, height; + final double? width, height; const SectionList( - {Key key, - this.scrollDirection, - this.score, - this.currentSection, - this.selectSection, - this.insertSection, - this.sectionColor, - this.setState, - this.showSectionBeatCounts, - this.toggleShowSectionBeatCounts, - this.allowReordering, - this.appSettings, + {Key? key, + required this.scrollDirection, + required this.score, + required this.currentSection, + required this.selectSection, + required this.insertSection, + required this.sectionColor, + required this.setState, + required this.showSectionBeatCounts, + required this.toggleShowSectionBeatCounts, + required this.allowReordering, + required this.appSettings, this.width, this.height}) : super(key: key); @@ -175,12 +174,12 @@ class _SectionListState extends State { ]); } - Section _previousSection; + Section? _previousSection; _animateToNewlySelectedSection() { try { if (_previousSection != null && - _previousSection.id != widget.currentSection.id) { + _previousSection!.id != widget.currentSection.id) { _animateToCurrentSection(); } } catch (any) {} @@ -190,14 +189,14 @@ class _SectionListState extends State { _animateToCurrentSection() { int index = widget.score.sections.indexOf(widget.currentSection); if (widget.scrollDirection == Axis.horizontal) { - double margin = max(0.0, widget.width - _Section.width); + double margin = max(0.0, (widget.width ?? 0.0) - _Section.width); double position = _Section.width * (index) - margin * 0.25; position = min(_scrollController.position.maxScrollExtent, position); position = max(0, position); _scrollController.animateTo(position, duration: animationDuration, curve: Curves.easeInOut); } else { - double margin = max(0.0, widget.height - _Section.height); + double margin = max(0.0, (widget.height ?? 0.0) - _Section.height); double position = _Section.height * (index) - margin * 0.25; position = min(_scrollController.position.maxScrollExtent, position); position = max(0, position); @@ -207,7 +206,7 @@ class _SectionListState extends State { } Widget getList(BuildContext context) { - var items = widget.score?.sections ?? []; + var items = widget.score.sections ?? []; if (items.isEmpty) { items = [defaultSection()]; } @@ -293,14 +292,14 @@ class _Section extends StatefulWidget { final bool allowReordering; const _Section( - {Key key, - this.section, - this.selectSection, - this.currentSection, - this.sectionColor, - this.scrollDirection, - this.showBeatCount, - this.allowReordering}) + {Key? key, + required this.section, + required this.selectSection, + required this.currentSection, + required this.sectionColor, + required this.scrollDirection, + required this.showBeatCount, + required this.allowReordering}) : super(key: key); @override diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 45616768..1830cc80 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,15 +6,21 @@ import FlutterMacOS import Foundation import flutter_midi_command -import package_info_plus_macos +import package_info_plus import path_provider_foundation +import share_plus import shared_preferences_foundation +import universal_ble import url_launcher_macos +import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SwiftFlutterMidiCommandPlugin.register(with: registry.registrar(forPlugin: "SwiftFlutterMidiCommandPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UniversalBlePlugin.register(with: registry.registrar(forPlugin: "UniversalBlePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 1080077b..6ec9d3a2 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -2,26 +2,37 @@ PODS: - flutter_midi_command (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - package_info_plus_macos (0.0.1): + - package_info_plus (0.0.1): - FlutterMacOS - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - share_plus (0.0.1): + - FlutterMacOS - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - SwiftProtobuf (1.13.0) + - universal_ble (0.0.1): + - Flutter + - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS + - webview_flutter_wkwebview (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - flutter_midi_command (from `Flutter/ephemeral/.symlinks/plugins/flutter_midi_command/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`) - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`) - - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - SwiftProtobuf + - universal_ble (from `Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: @@ -32,24 +43,33 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/flutter_midi_command/macos FlutterMacOS: :path: Flutter/ephemeral - package_info_plus_macos: - :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + share_plus: + :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos shared_preferences_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + universal_ble: + :path: Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + webview_flutter_wkwebview: + :path: Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin SPEC CHECKSUMS: - flutter_midi_command: c1a74de594e48e8fffd195a2ed5746161d507a48 + flutter_midi_command: c7517347a1acc8e4c7d4e9fa9809b3e87bbfd2b8 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c - path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 - shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 SwiftProtobuf: fd4693388a96c8c2df35d3b063272b0e7c499d00 - url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2 + universal_ble: ff19787898040d721109c6324472e5dd4bc86adc + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 PODFILE CHECKSUM: 2fbdd1e2587d5566bab687bc92adce54b042c807 -COCOAPODS: 1.11.3 +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index fa64242d..edd92b7d 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -296,7 +296,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -394,19 +394,25 @@ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", "${BUILT_PRODUCTS_DIR}/flutter_midi_command/flutter_midi_command.framework", - "${BUILT_PRODUCTS_DIR}/package_info_plus_macos/package_info_plus_macos.framework", + "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", + "${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", + "${BUILT_PRODUCTS_DIR}/universal_ble/universal_ble.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_macos/url_launcher_macos.framework", + "${BUILT_PRODUCTS_DIR}/webview_flutter_wkwebview/webview_flutter_wkwebview.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_midi_command.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus_macos.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/universal_ble.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_macos.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/webview_flutter_wkwebview.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -569,9 +575,9 @@ "-framework", "\"SwiftProtobuf\"", "-framework", - "\"path_provider_macos\"", + "\"path_provider_foundation\"", "-framework", - "\"shared_preferences_macos\"", + "\"shared_preferences_foundation\"", "-framework", "\"url_launcher_macos\"", "-lc++", @@ -728,9 +734,9 @@ "-framework", "\"SwiftProtobuf\"", "-framework", - "\"path_provider_macos\"", + "\"path_provider_foundation\"", "-framework", - "\"shared_preferences_macos\"", + "\"shared_preferences_foundation\"", "-framework", "\"url_launcher_macos\"", "-lc++", @@ -772,9 +778,9 @@ "-framework", "\"SwiftProtobuf\"", "-framework", - "\"path_provider_macos\"", + "\"path_provider_foundation\"", "-framework", - "\"shared_preferences_macos\"", + "\"shared_preferences_foundation\"", "-framework", "\"url_launcher_macos\"", "-lc++", diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 814fcbbf..82757a4f 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 11643d37..21a09241 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -2,7 +2,7 @@ import Cocoa import FlutterMacOS //import Firebase -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override init() { @@ -13,6 +13,10 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } override func application(_ application: NSApplication, continue userActivity: NSUserActivity, diff --git a/macos/Runner/AudioSystem/AKSamplerExtensions.swift b/macos/Runner/AudioSystem/AKSamplerExtensions.swift index 2fab9ca5..19a6aee3 100644 --- a/macos/Runner/AudioSystem/AKSamplerExtensions.swift +++ b/macos/Runner/AudioSystem/AKSamplerExtensions.swift @@ -6,18 +6,18 @@ // Copyright © 2020 The Flutter Authors. All rights reserved. // -import Foundation import AudioKit +import Foundation extension AKSampler { - open func loadSfzWithEmbeddedSpacesInSampleNames(folderPath: String, sfzFileName: String) { + public func loadSfzWithEmbeddedSpacesInSampleNames(folderPath: String, sfzFileName: String) { stopAllVoices() do { try unloadAllSamples() - } catch { + } catch { AKLog("Failed to unload samples") } - + var lastPrefix: String = "" var lokey: Int32 = 0 var hikey: Int32 = 127 @@ -28,7 +28,7 @@ extension AKSampler { var loopmode: String = "" var loopstart: Float32 = 0 var loopend: Float32 = 0 - + func resetVars() { lokey = 0 hikey = 127 @@ -39,7 +39,7 @@ extension AKSampler { loopstart = 0 loopend = 0 } - + let baseURL = URL(fileURLWithPath: folderPath) let sfzURL = baseURL.appendingPathComponent(sfzFileName) do { @@ -52,9 +52,14 @@ extension AKSampler { continue } for token in trimmed.components(separatedBy: .whitespaces) { - if token.hasPrefix("") || token.hasPrefix("") || token.hasPrefix("") { + if token.hasPrefix("") || token.hasPrefix("") + || token.hasPrefix("") + { if lastPrefix == "region" { - try buildSample(baseURL: baseURL, lokey: lokey, hikey: hikey, pitch: pitch, lovel: lovel, hivel: hivel, sample: sample, loopmode: loopmode, loopstart: loopstart, loopend: loopend) + try buildSample( + baseURL: baseURL, lokey: lokey, hikey: hikey, pitch: pitch, lovel: lovel, + hivel: hivel, sample: sample, loopmode: loopmode, loopstart: loopstart, + loopend: loopend) } resetVars() } else if token.hasPrefix("key=") { @@ -78,9 +83,10 @@ extension AKSampler { } else if token.hasPrefix("loop_end") { loopend = Float32(token.components(separatedBy: "=")[1])! } else if token.hasPrefix("sample") { - sample = trimmed.components(separatedBy: "sample=")[1].replacingOccurrences(of: "\\", with: "/") + sample = trimmed.components(separatedBy: "sample=")[1].replacingOccurrences( + of: "\\", with: "/") } - + if token.hasPrefix("") { lastPrefix = "global" } @@ -91,19 +97,21 @@ extension AKSampler { lastPrefix = "region" } } - + // if sample != "" { // buildSample(baseURL: baseURL, lokey: lokey, hikey: hikey, pitch: pitch, lovel: lovel, hivel: hivel, sample: sample, loopmode: loopmode, loopstart: loopstart, loopend: loopend) // sample = "" // } } - if(lastPrefix == "region") { - try buildSample(baseURL: baseURL, lokey: lokey, hikey: hikey, pitch: pitch, lovel: lovel, hivel: hivel, sample: sample, loopmode: loopmode, loopstart: loopstart, loopend: loopend) + if lastPrefix == "region" { + try buildSample( + baseURL: baseURL, lokey: lokey, hikey: hikey, pitch: pitch, lovel: lovel, hivel: hivel, + sample: sample, loopmode: loopmode, loopstart: loopstart, loopend: loopend) } } catch { AKLog(error) } - + buildKeyMap() restartVoices() } @@ -120,20 +128,22 @@ extension AKSampler { loopstart: Float32, loopend: Float32 ) throws { - let noteFreq = Float(AKPolyphonicNode.tuningTable.frequency(forNoteNumber: MIDINoteNumber(pitch))) + let noteFreq = Float( + AKPolyphonicNode.tuningTable.frequency(forNoteNumber: MIDINoteNumber(pitch))) AKLog("load \(pitch) \(noteFreq) Hz range \(lokey)-\(hikey) vel \(lovel)-\(hivel) \(sample)") - - let sd = AKSampleDescriptor(noteNumber: pitch, - noteFrequency: noteFreq, - minimumNoteNumber: lokey, - maximumNoteNumber: hikey, - minimumVelocity: lovel, - maximumVelocity: hivel, - isLooping: loopmode != "" && loopmode != "no_loop", - loopStartPoint: loopstart, - loopEndPoint: loopend, - startPoint: 0.0, - endPoint: 0.0) + + let sd = AKSampleDescriptor( + noteNumber: pitch, + noteFrequency: noteFreq, + minimumNoteNumber: lokey, + maximumNoteNumber: hikey, + minimumVelocity: lovel, + maximumVelocity: hivel, + isLooping: loopmode != "" && loopmode != "no_loop", + loopStartPoint: loopstart, + loopEndPoint: loopend, + startPoint: 0.0, + endPoint: 0.0) let sampleFileURL = baseURL.appendingPathComponent(sample) if sample.hasSuffix(".wv") { let string = (sampleFileURL.path as NSString).utf8String diff --git a/pubspec.lock b/pubspec.lock index d10f4cf3..aa08a158 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,110 +1,126 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - aeyrium_sensor: + animated_list_plus: dependency: "direct main" description: - name: aeyrium_sensor - sha256: d031f52388bcff00a86dfe18701ed5dfbaa9714fcda79f31d08382fef4b77fbd + name: animated_list_plus + sha256: fb3d7f1fbaf5af84907f3c739236bacda8bf32cbe1f118dd51510752883ff50c url: "https://pub.dev" source: hosted - version: "1.0.7" - animated_list_plus: + version: "0.5.2" + appcheck: dependency: "direct main" description: - name: animated_list_plus - sha256: f276e3c42a78a76c295186c4723e206dee7660741bd4e9592f9c5530511b7589 + name: appcheck + sha256: "6971b1ae5833b15b1abc29f7d03d08db79577b2934ceb34fe39ab937b53c2f17" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "1.5.4+1" archive: dependency: "direct main" description: name: archive - sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "3.3.6" + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.13.0" base_x: dependency: "direct main" description: name: base_x - sha256: d18b2b5f8271479f0ca0658c5db25573cfff57370e51a1e8563ee8546abf6795 + sha256: "519abcdafd637d4b6bd7e72fabd8f9264935f804b9b9f6c5d8411c7d52cbf8fd" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "2.0.1" + bluez: + dependency: transitive + description: + name: bluez + sha256: "61a7204381925896a374301498f2f5399e59827c6498ae1e924aaa598751b545" + url: "https://pub.dev" + source: hosted + version: "0.8.3" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: - dependency: transitive + dependency: "direct main" description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.17.0" - convert: + version: "1.19.1" + cross_file: dependency: transitive description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.6" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: a937da4c006989739ceb4d10e3bd6cce64ca85d0fe287fc5b2b9f6ee757dcee6 + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "0.1.3" - dart_midi: - dependency: "direct main" + version: "1.0.8" + dbus: + dependency: transitive description: - name: dart_midi - sha256: "9129dc8a202a00dda0a57e428d4940a2d829cb82407c55ba66e10951fbe01187" + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "0.7.11" dcache: dependency: "direct main" description: @@ -117,99 +133,156 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.4" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.1" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" fluro: dependency: "direct main" description: name: fluro - sha256: "66d82964a1b6967def34155de92f7bb87ee247aba53ea5dbb70e2bf206865dbc" + sha256: "24d07d0b285b213ec2045b83e85d076185fa5c23651e44dae0ac6755784b97d0" url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.5" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" - flutter_appavailability: + flutter_feather_icons: dependency: "direct main" description: - name: flutter_appavailability - sha256: "352b9e84b695ee13b0cfb5a9c8eee61c67e2518930ee633bb54ace923d490cee" + name: flutter_feather_icons + sha256: b33b9c276fc8108254632da6644cf01f71af6c17fbfb26e136a86945f5ff9b67 url: "https://pub.dev" source: hosted - version: "0.0.21" - flutter_icons: + version: "2.0.0+1" + flutter_keyboard_visibility: dependency: "direct main" description: - name: flutter_icons - sha256: ad1c830f729c71216372d1a8d62c08c6dd792227d9492d5130aea5e391210a9f + name: flutter_keyboard_visibility + sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" url: "https://pub.dev" source: hosted - version: "1.1.0" - flutter_keyboard_visibility: - dependency: "direct main" + version: "6.0.0" + flutter_keyboard_visibility_linux: + dependency: transitive description: - name: flutter_keyboard_visibility - sha256: "5aa7937e11b2e0ae203e01b4cd2ac5d3cd601bd16f58b315908fbf29362c29db" + name: flutter_keyboard_visibility_linux + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 url: "https://pub.dev" source: hosted version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_midi_command: dependency: "direct main" description: - path: "../FlutterMidiCommand" - relative: true - source: path - version: "0.3.7" + name: flutter_midi_command + sha256: bce9e83d3fb05aef275af83d65c53a40623735516ef1bdcefc0ba1ca5db1d89f + url: "https://pub.dev" + source: hosted + version: "0.5.3" + flutter_midi_command_linux: + dependency: transitive + description: + name: flutter_midi_command_linux + sha256: c4d6dcf136fd4718aa827c201446f935f2752e37b702ed712373c675cbdbb0d0 + url: "https://pub.dev" + source: hosted + version: "0.3.0" flutter_midi_command_platform_interface: dependency: transitive description: name: flutter_midi_command_platform_interface - sha256: "57607a3cf7169f7a7de2d09b2fbac2beefa34f2b4cec5764c1367d59616f74a3" + sha256: "905ebe90f56af5e9e376688d6baedd94d5378c788d302a141da283dd329dc2ce" + url: "https://pub.dev" + source: hosted + version: "0.4.3" + flutter_midi_command_windows: + dependency: transitive + description: + name: flutter_midi_command_windows + sha256: fd85c892d890f462487601e69228de261954e2cd8740f5396f5e3060d23ff097 url: "https://pub.dev" source: hosted - version: "0.3.4" + version: "0.1.1" flutter_reorderable_list: dependency: "direct main" description: name: flutter_reorderable_list - sha256: "7bb60836a2712fffa23dc71d86ae3c77c72d216ef818b4c7b764424c4f28cafe" + sha256: "0400ef34fa00b7cac69f71efc92d7e49727f425bc1080180ebe70bf47618afe0" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_bluetooth: + dependency: transitive + description: + name: flutter_web_bluetooth + sha256: fcd03e2e5f82edcedcbc940f1b6a0635a50757374183254f447640886c53208e + url: "https://pub.dev" + source: hosted + version: "0.2.4" flutter_web_plugins: dependency: transitive description: flutter @@ -219,82 +292,130 @@ packages: dependency: "direct main" description: name: font_awesome_flutter - sha256: "444af62d91e753b1e221d9f3e2f8df48a995962f33bef77773059cec4dac2c47" + sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a url: "https://pub.dev" source: hosted - version: "8.12.0" + version: "10.8.0" http: dependency: "direct main" description: name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "1.4.0" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" js: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.7.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + material_design_icons_flutter: + dependency: "direct main" + description: + name: material_design_icons_flutter + sha256: "6f986b7a51f3ad4c00e33c5c84e8de1bdd140489bbcdc8b66fc1283dad4dea5a" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "7.0.7296" matrix4_transform: dependency: "direct main" description: name: matrix4_transform - sha256: "6ddeaa2c0e1f5c3f3a197f552377570b3e54fa0b8bf48507728a216fc0fd78a6" + sha256: "1346e53517e3081d3e8362377be97e285e2bd348855c177eae2a18aa965cafa0" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "4.0.1" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + midi: + dependency: transitive + description: + name: midi + sha256: "737c86a4ffbbe06352a8ec90bf5153774202f4b38dfb8e45af255cee18e2b9de" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "0.1.0" mime: dependency: transitive description: name: mime - sha256: "5041f313f4e30fbd70dee7c0403785a4ee270f75830f590e5983960fddf743e4" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "0.9.7" + version: "2.0.0" native_device_orientation: dependency: "direct main" description: name: native_device_orientation - sha256: "4a17190b356d11ef2fce4137578e0fd102990e260f8c03c2ca6bb9a457b17495" + sha256: "0c330c068575e4be72cce5968ca479a3f8d5d1e5dfce7d89d5c13a1e943b338c" url: "https://pub.dev" source: hosted - version: "0.4.3" + version: "2.0.3" nested: dependency: transitive description: @@ -307,391 +428,399 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: f62d7253edc197fe3c88d7c2ddab82d68f555e778d55390ccc3537eca8e8d637 - url: "https://pub.dev" - source: hosted - version: "1.4.3+1" - package_info_plus_linux: - dependency: transitive - description: - name: package_info_plus_linux - sha256: "04b575f44233d30edbb80a94e57cad9107aada334fc02aabb42b6becd13c43fc" - url: "https://pub.dev" - source: hosted - version: "1.0.5" - package_info_plus_macos: - dependency: transitive - description: - name: package_info_plus_macos - sha256: a2ad8b4acf4cd479d4a0afa5a74ea3f5b1c7563b77e52cc32b3ee6956d5482a6 + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: f7a0c8f1e7e981bc65f8b64137a53fd3c195b18d429fba960babc59a5a1c7ae8 - url: "https://pub.dev" - source: hosted - version: "1.0.2" - package_info_plus_web: - dependency: transitive - description: - name: package_info_plus_web - sha256: f0829327eb534789e0a16ccac8936a80beed4e2401c4d3a74f3f39094a822d3b + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "1.0.6" - package_info_plus_windows: - dependency: transitive - description: - name: package_info_plus_windows - sha256: "79524f11c42dd9078b96d797b3cf79c0a2883a50c4920dc43da8562c115089bc" - url: "https://pub.dev" - source: hosted - version: "2.1.0" + version: "3.2.0" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.1" path_drawing: dependency: "direct main" description: name: path_drawing - sha256: f084d82ffbbf8b6f7557987929032fd5b9a9f8e8dc0ab72036591f50389347db + sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "1.0.1" path_parsing: dependency: transitive description: name: path_parsing - sha256: "4e613c811d25f1533c0d05eb2755e21410247b8a13ac7f6cd23c0cecb2ff683d" + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "1.1.0" path_provider: dependency: "direct main" description: name: path_provider - sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.0.12" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.0.22" + version: "2.2.17" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74" + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.4.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.1.3" - platform: + version: "2.3.0" + petitparser: dependency: transitive description: - name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "3.1.0" - plugin_platform_interface: + version: "6.1.0" + platform: dependency: transitive description: - name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "2.1.3" - pointycastle: + version: "3.1.6" + plugin_platform_interface: dependency: transitive description: - name: pointycastle - sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "3.6.2" - process: + version: "2.1.8" + posix: dependency: transitive description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + name: posix + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "6.0.2" protobuf: dependency: "direct main" description: name: protobuf - sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08" + sha256: "579fe5557eae58e3adca2e999e38f02441d8aa908703854a9e0a0f47fa857731" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "4.1.0" provider: dependency: "direct main" description: name: provider - sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" url: "https://pub.dev" source: hosted - version: "6.0.5" + version: "6.1.5" quiver: dependency: "direct main" description: name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" recase: dependency: "direct main" description: name: recase - sha256: d18e5f9cb089cbf535cf4b772c83ca967b77b62dd9570bcd14d762cfb7590586 + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 url: "https://pub.dev" source: hosted - version: "3.0.1" - sensors: + version: "4.1.0" + sensors_plus: dependency: "direct main" description: - name: sensors - sha256: "60442084bbdbfca4c8b8392ebc49acd457cf47e09229f7e1726079b2f37c73f1" + name: sensors_plus + sha256: "905282c917c6bb731c242f928665c2ea15445aa491249dea9d98d7c79dc8fd39" + url: "https://pub.dev" + source: hosted + version: "6.1.1" + sensors_plus_platform_interface: + dependency: transitive + description: + name: sensors_plus_platform_interface + sha256: "58815d2f5e46c0c41c40fb39375d3f127306f7742efe3b891c0b1c87e2b5cd5d" url: "https://pub.dev" source: hosted - version: "0.4.2+6" - share: + version: "2.0.1" + share_plus: dependency: "direct main" description: - name: share - sha256: fc1647d0c3a8eb648dc6d91d2207e7976c1d941e2165a09cba7c805b0c24467e + name: share_plus + sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0 + url: "https://pub.dev" + source: hosted + version: "11.0.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef" url: "https://pub.dev" source: hosted - version: "0.6.5+4" + version: "6.0.0" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "5949029e70abe87f75cfe59d17bf5c397619c4b74a099b10116baeb34786fad9" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "955e9736a12ba776bdd261cf030232b30eadfcd9c79b32a3250dd4a494e8c8f7" + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "2b55c18636a4edc529fa5cd44c03d3f3100c00513f518c5127c951978efcccd0" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: f8ea038aa6da37090093974ebdcf4397010605fd2ff65c37a66f9d28394cb874 + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: da9431745ede5ece47bc26d5d73a9d3c6936ef6945c101a5aca46f62e52c1cf3 + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: a4b5bc37fe1b368bbc81f953197d55e12f49d0296e7e412dfe2d2d77d6929958 + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "5eaf05ae77658d3521d0e993ede1af962d4b326cd2153d312df716dc250f00c9" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.4.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "2.0.2" typed_data: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" + universal_ble: + dependency: transitive + description: + name: universal_ble + sha256: "50011255aa793589ccefb086ceead0f2eaddd30609a6f8b10ee4a0a548c3250e" + url: "https://pub.dev" + source: hosted + version: "0.9.11" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "698fa0b4392effdc73e9e184403b627362eb5fbf904483ac9defbb1c2191d809" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.1.8" + version: "6.3.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "3e2f6dfd2c7d9cd123296cab8ef66cfc2c1a13f5845f42c7a0f365690a8a7dd1" + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" url: "https://pub.dev" source: hosted - version: "6.0.23" + version: "6.3.16" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bb328b24d3bccc20bdf1024a0990ac4f869d57663660de9c936fb8c043edefe3 + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.0.18" + version: "6.3.3" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "318c42cba924e18180c029be69caf0a1a710191b9ec49bb42b5998fdcccee3cc" + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "41988b55570df53b3dd2a7fc90c76756a963de6a8c5f8e113330cb35992e2094" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "44d79408ce9f07052095ef1f9a693c258d6373dc3944249374e30eff7219ccb0" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.4.1" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: b6217370f8eb1fd85c8890c539f5a639a01ab209a36db82c921ebeacefc7a615 + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.1.4" uuid: dependency: "direct main" description: name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "4.5.1" vector_math: dependency: transitive description: @@ -700,54 +829,78 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" webview_flutter: dependency: "direct main" description: name: webview_flutter - sha256: "392c1d83b70fe2495de3ea2c84531268d5b8de2de3f01086a53334d8b6030a88" + sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "4.13.0" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: "8b3b2450e98876c70bfcead876d9390573b34b9418c19e28168b74f6cb252dbd" + sha256: f6e6afef6e234801da77170f7a1847ded8450778caf2fe13979d140484be3678 url: "https://pub.dev" source: hosted - version: "2.10.4" + version: "4.7.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "812165e4e34ca677bdfbfa58c01e33b27fd03ab5fa75b70832d4b7d4ca1fa8cf" + sha256: "7cb32b21825bd65569665c32bb00a34ded5779786d6201f5350979d2d529940d" url: "https://pub.dev" source: hosted - version: "1.9.5" + version: "2.13.0" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: a5364369c758892aa487cbf59ea41d9edd10f9d9baf06a94e80f1bd1b4c7bbc0 + sha256: a3d461fe3467014e05f3ac4962e5fdde2a4bf44c561cb53e9ae5c586600fdbc3 url: "https://pub.dev" source: hosted - version: "2.9.5" + version: "3.22.0" win32: dependency: transitive description: name: win32 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "5.12.0" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "6.5.0" sdks: - dart: ">=2.19.0 <3.0.0" - flutter: ">=3.7.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3df08d64..84c88d23 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,58 +5,60 @@ publish_to: none version: 1.7.5+480 environment: - sdk: ">=2.6.0 <3.0.0" + sdk: ">=2.17.1 <3.0.0" dependencies: flutter: sdk: flutter - http: ^0.13.3 - protobuf: ^2.0.0 - uuid: ^3.0.4 - package_info_plus: ^1.0.1 - recase: ^3.0.0 - share: ^0.6.5+4 + http: ^1.4.0 + protobuf: ^4.1.0 + uuid: ^4.5.1 + package_info_plus: ^8.3.0 + recase: ^4.1.0 + # share: ^0.6.5+4 flutter_reorderable_list: ^1.2.0 url_launcher: ^6.0.3 animated_list_plus: ^0.5.0 - flutter_keyboard_visibility: ^2.0.0 - aeyrium_sensor: ^1.0.7 - sensors: '>=0.4.1+10 <2.0.0' - dart_midi: 1.0.1 + flutter_keyboard_visibility: ^6.0.0 + # aeyrium_sensor: ^1.0.7 + # sensors: ^2.0.3 + # dart_midi: 1.0.1 quiver: ^3.0.1 - path_drawing: 0.4.1 - path_provider: ^2.0.2 + path_drawing: 1.0.1 + path_provider: ^2.1.5 shared_preferences: ^2.0.6 - base_x: ^1.0.1 - archive: ^3.1.2 - font_awesome_flutter: ^8.11.0 - cupertino_icons: ^0.1.2 - fluro: "^2.0.3" - flutter_appavailability: ^0.0.21 - native_device_orientation: ^0.4.3 - webview_flutter: ^3.0.4 - flutter_icons: ^1.1.0 + base_x: ^2.0.0 + archive: ^4.0.7 + font_awesome_flutter: ^10.5.0 + cupertino_icons: ^1.0.5 + fluro: ^2.0.3 + flutter_feather_icons: ^2.0.0 + + # flutter_appavailability: ^0.0.21 + native_device_orientation: ^2.0.3 + webview_flutter: ^4.13.0 + # flutter_icons: ^1.1.0 # flutter_blue: ^0.8.0 - # path: ../flutter_blue # flutter_reactive_ble: #^3.1.0 - # path: ../flutter_reactive_ble - flutter_midi_command: #^0.3.0 - path: ../FlutterMidiCommand + flutter_midi_command: ^0.5.3 dcache: ^0.4.0 provider: ^6.0.2 - matrix4_transform: ^2.0.1 + matrix4_transform: ^4.0.1 + share_plus: ^11.0.0 + collection: ^1.19.1 + appcheck: ^1.5.4+1 + material_design_icons_flutter: ^7.0.7296 + sensors_plus: ^6.1.1 dev_dependencies: flutter_test: sdk: flutter - # 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.