Repository: tuntorius/mightier_amp
Branch: main
Commit: e05b6d06fadc
Files: 560
Total size: 3.5 MB
Directory structure:
gitextract_4w2zv_vh/
├── .firebaserc
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .metadata
├── .vscode/
│ ├── launch.json
│ └── settings.json
├── LICENSE.md
├── PRIVACY_POLICY.md
├── README.md
├── analysis_options.yaml
├── android/
│ ├── .gitignore
│ ├── app/
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src/
│ │ ├── debug/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res/
│ │ │ └── values/
│ │ │ └── strings.xml
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin/
│ │ │ │ └── com/
│ │ │ │ └── tuntori/
│ │ │ │ └── mightieramp/
│ │ │ │ └── MainActivity.kt
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ └── launch_background.xml
│ │ │ ├── mipmap-anydpi-v26/
│ │ │ │ └── launcher_icon.xml
│ │ │ └── values/
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── profile/
│ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── settings.gradle
│ └── settings_aar.gradle
├── fastlane/
│ └── metadata/
│ └── android/
│ └── en-US/
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
├── firebase.json
├── ios/
│ ├── .gitignore
│ ├── Flutter/
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Runner/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── LaunchImage.imageset/
│ │ │ ├── Contents.json
│ │ │ └── README.md
│ │ ├── Base.lproj/
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
│ ├── Runner.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── Runner.xcscheme
│ └── Runner.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcshareddata/
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
├── lib/
│ ├── UI/
│ │ ├── mainTabs.dart
│ │ ├── mightierIcons.dart
│ │ ├── pages/
│ │ │ ├── DebugConsolePage.dart
│ │ │ ├── calibration.dart
│ │ │ ├── developerPage.dart
│ │ │ ├── device_specific_settings/
│ │ │ │ ├── LiteMk2Settings.dart
│ │ │ │ ├── PlugAirSettings.dart
│ │ │ │ ├── PlugAirUsbSettings.dart
│ │ │ │ ├── PlugProMicSettings.dart
│ │ │ │ ├── PlugProSettings.dart
│ │ │ │ ├── PlugProUsbSettings.dart
│ │ │ │ └── eq/
│ │ │ │ ├── MightySpaceSpeakerEQ.dart
│ │ │ │ ├── PlugProEQSettings.dart
│ │ │ │ ├── bt_audio_options.dart
│ │ │ │ └── eq_group.dart
│ │ │ ├── drum_editor/
│ │ │ │ ├── DrumStyleBottomSheet.dart
│ │ │ │ ├── drumEditor.dart
│ │ │ │ ├── drum_eq_bottom_sheet.dart
│ │ │ │ ├── drumstyle_scroll_picker.dart
│ │ │ │ ├── looperPage.dart
│ │ │ │ ├── tap_buttons.dart
│ │ │ │ └── tempoTrainerSheet.dart
│ │ │ ├── drumsPage.dart
│ │ │ ├── hotkeysMainPage.dart
│ │ │ ├── hotkeysSetup.dart
│ │ │ ├── jamTracks.dart
│ │ │ ├── midiControllers.dart
│ │ │ ├── mighty_patches_importer.dart
│ │ │ ├── presetEditor.dart
│ │ │ ├── settings.dart
│ │ │ ├── settings_advanced.dart
│ │ │ └── tunerPage.dart
│ │ ├── popups/
│ │ │ ├── alertDialogs.dart
│ │ │ ├── changeCategory.dart
│ │ │ ├── exportQRCode.dart
│ │ │ ├── hotkeyInput.dart
│ │ │ ├── midiControlInfo.dart
│ │ │ ├── savePreset.dart
│ │ │ ├── selectPreset.dart
│ │ │ └── selectTrack.dart
│ │ ├── theme.dart
│ │ ├── toneshare/
│ │ │ ├── cloud_authentication.dart
│ │ │ ├── cloud_login.dart
│ │ │ ├── cloud_signup.dart
│ │ │ ├── share_preset.dart
│ │ │ ├── toneshare_home.dart
│ │ │ └── toneshare_main.dart
│ │ ├── utils.dart
│ │ └── widgets/
│ │ ├── MidiDeviceTile.dart
│ │ ├── ModeControl.dart
│ │ ├── NuxAppBar.dart
│ │ ├── VolumeDrawer.dart
│ │ ├── app_drawer.dart
│ │ ├── bottomBar.dart
│ │ ├── circular_button.dart
│ │ ├── common/
│ │ │ ├── blinkWidget.dart
│ │ │ ├── customPopupMenu.dart
│ │ │ ├── modeControlRegular.dart
│ │ │ ├── nestedWillPopScope.dart
│ │ │ ├── numberPicker.dart
│ │ │ ├── rounded_icon_button.dart
│ │ │ └── searchTextField.dart
│ │ ├── deviceList.dart
│ │ ├── fabMenu.dart
│ │ ├── hold_to_repeat.dart
│ │ ├── presets/
│ │ │ ├── EffectChainBar.dart
│ │ │ ├── EffectChainButton.dart
│ │ │ ├── channelSelector.dart
│ │ │ ├── effectEditor.dart
│ │ │ ├── effectEditors/
│ │ │ │ ├── EqualizerEditor.dart
│ │ │ │ └── SlidersEditor.dart
│ │ │ ├── effectSelector.dart
│ │ │ ├── preset_list/
│ │ │ │ ├── presetEffectPreview.dart
│ │ │ │ ├── presetItem.dart
│ │ │ │ ├── presetList.dart
│ │ │ │ ├── presetListMethods.dart
│ │ │ │ ├── preset_widget.dart
│ │ │ │ └── presets_popup_menus.dart
│ │ │ └── trackEventsBlockInfo.dart
│ │ ├── rounded_icon_button.dart
│ │ ├── scrollParent.dart
│ │ ├── scrollPicker.dart
│ │ ├── search_field.dart
│ │ ├── thickRangeSlider.dart
│ │ ├── thickSlider.dart
│ │ └── verticalThickSlider.dart
│ ├── audio/
│ │ ├── audioEditor.dart
│ │ ├── automationController.dart
│ │ ├── models/
│ │ │ ├── jamTrack.dart
│ │ │ ├── setlist.dart
│ │ │ ├── trackAutomation.dart
│ │ │ └── waveform_data.dart
│ │ ├── online_sources/
│ │ │ ├── YoutubeSource.dart
│ │ │ ├── backingTracksCoSource.dart
│ │ │ ├── guitarBackingTracksSource.dart
│ │ │ ├── onlineSource.dart
│ │ │ ├── onlineTrack.dart
│ │ │ └── sourceResolver.dart
│ │ ├── setlistPage.dart
│ │ ├── setlist_player/
│ │ │ └── setlistPlayerState.dart
│ │ ├── setlistsPage.dart
│ │ ├── trackdata/
│ │ │ └── trackData.dart
│ │ ├── tracksPage.dart
│ │ └── widgets/
│ │ ├── eventEditor.dart
│ │ ├── jamtracksView.dart
│ │ ├── loopPanel.dart
│ │ ├── media_library/
│ │ │ ├── albumTracks.dart
│ │ │ ├── artistAlbums.dart
│ │ │ └── media_browse.dart
│ │ ├── online_source/
│ │ │ ├── online_source.dart
│ │ │ └── search_screen.dart
│ │ ├── painted_waveform.dart
│ │ ├── presetsPanel.dart
│ │ ├── setlistPlayer.dart
│ │ ├── speedPanel.dart
│ │ └── waveform_painter.dart
│ ├── bluetooth/
│ │ ├── NuxDeviceControl.dart
│ │ ├── bleMidiHandler.dart
│ │ ├── ble_controllers/
│ │ │ ├── BLEController.dart
│ │ │ ├── DummyBLEController.dart
│ │ │ ├── FlutterBluePluginController.dart
│ │ │ ├── FlutterBluePlusController.dart
│ │ │ ├── FlutterReactiveBle.dart
│ │ │ ├── MightyBle.dart
│ │ │ ├── QuickBlueController.dart
│ │ │ ├── WebBleController.dart
│ │ │ └── WinBleController.dart
│ │ └── devices/
│ │ ├── NuxConstants.dart
│ │ ├── NuxDevice.dart
│ │ ├── NuxFXID.dart
│ │ ├── NuxMighty2040BT.dart
│ │ ├── NuxMighty8BT.dart
│ │ ├── NuxMighty8BTMk2.dart
│ │ ├── NuxMightyLite.dart
│ │ ├── NuxMightyLiteMk2.dart
│ │ ├── NuxMightyPlugAir.dart
│ │ ├── NuxMightyPlugPro.dart
│ │ ├── NuxMightySpace.dart
│ │ ├── NuxReorderableDevice.dart
│ │ ├── communication/
│ │ │ ├── communication.dart
│ │ │ ├── liteCommunication.dart
│ │ │ ├── liteMk2Communication.dart
│ │ │ ├── plugAirCommunication.dart
│ │ │ └── plugProCommunication.dart
│ │ ├── device_data/
│ │ │ ├── drumstyles.dart
│ │ │ └── processors_list.dart
│ │ ├── effects/
│ │ │ ├── MidiControllerHandles.dart
│ │ │ ├── NoiseGate.dart
│ │ │ ├── Processor.dart
│ │ │ ├── lite/
│ │ │ │ ├── Ambience.dart
│ │ │ │ ├── Amps.dart
│ │ │ │ └── Modulation.dart
│ │ │ ├── liteMk2/
│ │ │ │ ├── delay.dart
│ │ │ │ ├── efx.dart
│ │ │ │ ├── modulation.dart
│ │ │ │ └── reverb.dart
│ │ │ ├── mighty_2040bt/
│ │ │ │ ├── Amps.dart
│ │ │ │ ├── Delay.dart
│ │ │ │ ├── Modulation.dart
│ │ │ │ └── Reverb.dart
│ │ │ ├── mighty_8bt/
│ │ │ │ ├── Amps.dart
│ │ │ │ ├── Delay.dart
│ │ │ │ ├── Modulation.dart
│ │ │ │ └── Reverb.dart
│ │ │ ├── plug_air/
│ │ │ │ ├── Amps.dart
│ │ │ │ ├── Ampsv2.dart
│ │ │ │ ├── Cabinet.dart
│ │ │ │ ├── Delay.dart
│ │ │ │ ├── EFX.dart
│ │ │ │ ├── EFXv2.dart
│ │ │ │ ├── Modulation.dart
│ │ │ │ └── Reverb.dart
│ │ │ └── plug_pro/
│ │ │ ├── Amps.dart
│ │ │ ├── Cabinet.dart
│ │ │ ├── Compressor.dart
│ │ │ ├── Delay.dart
│ │ │ ├── EFX.dart
│ │ │ ├── EQ.dart
│ │ │ ├── EmptyEffects.dart
│ │ │ ├── Modulation.dart
│ │ │ └── Reverb.dart
│ │ ├── features/
│ │ │ ├── drumsTone.dart
│ │ │ ├── looper.dart
│ │ │ ├── proUsbSettings.dart
│ │ │ └── tuner.dart
│ │ ├── presets/
│ │ │ ├── Mighty8BTPreset.dart
│ │ │ ├── MightyLitePreset.dart
│ │ │ ├── MightyMk2Preset.dart
│ │ │ ├── MightyXXBTPreset.dart
│ │ │ ├── PlugAirPreset.dart
│ │ │ ├── PlugProPreset.dart
│ │ │ ├── Preset.dart
│ │ │ └── preset_constants.dart
│ │ └── value_formatters/
│ │ ├── DecibelFormatter.dart
│ │ ├── FrequencyFormatter.dart
│ │ ├── PercentageFormatter.dart
│ │ ├── SwitchFormatters.dart
│ │ ├── TempoFormatter.dart
│ │ └── ValueFormatter.dart
│ ├── main.dart
│ ├── midi/
│ │ ├── BleMidiManager.dart
│ │ ├── ControllerConstants.dart
│ │ ├── MidiControllerManager.dart
│ │ ├── UsbMidiManager.dart
│ │ └── controllers/
│ │ ├── BleMidiController.dart
│ │ ├── ControllerHotkey.dart
│ │ ├── HidController.dart
│ │ ├── MidiController.dart
│ │ └── UsbMidiController.dart
│ ├── modules/
│ │ ├── cloud/
│ │ │ ├── cloudManager.dart
│ │ │ ├── cloudStorageListener.dart
│ │ │ ├── customAuthStore.dart
│ │ │ └── presetEncoder.dart
│ │ └── tempo_trainer.dart
│ ├── platform/
│ │ ├── fileSaver.dart
│ │ ├── platformUtils.dart
│ │ ├── presetStorageListener.dart
│ │ ├── presetsStorage.dart
│ │ └── simpleSharedPrefs.dart
│ └── utilities/
│ ├── DelayTapTimer.dart
│ ├── MathEx.dart
│ ├── list_extenstions.dart
│ └── string_extensions.dart
├── linux/
│ ├── .gitignore
│ ├── CMakeLists.txt
│ ├── flutter/
│ │ ├── CMakeLists.txt
│ │ ├── generated_plugin_registrant.cc
│ │ ├── generated_plugin_registrant.h
│ │ └── generated_plugins.cmake
│ ├── main.cc
│ ├── my_application.cc
│ └── my_application.h
├── mightieramp.code-workspace
├── plugins/
│ ├── audio_picker/
│ │ ├── .gitignore
│ │ ├── .metadata
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── android/
│ │ │ ├── .gitignore
│ │ │ ├── app/
│ │ │ │ └── src/
│ │ │ │ └── main/
│ │ │ │ └── java/
│ │ │ │ └── io/
│ │ │ │ └── flutter/
│ │ │ │ └── plugins/
│ │ │ │ └── GeneratedPluginRegistrant.java
│ │ │ ├── build.gradle
│ │ │ ├── gradle/
│ │ │ │ └── wrapper/
│ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ └── gradle-wrapper.properties
│ │ │ ├── gradle.properties
│ │ │ ├── gradlew
│ │ │ ├── gradlew.bat
│ │ │ ├── settings.gradle
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── com/
│ │ │ │ └── audio_picker/
│ │ │ │ └── audio_picker/
│ │ │ │ └── FileUtils.java
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── audio_picker/
│ │ │ └── audio_picker/
│ │ │ └── AudioPickerPlugin.kt
│ │ ├── ios/
│ │ │ ├── .gitignore
│ │ │ ├── Assets/
│ │ │ │ └── .gitkeep
│ │ │ ├── Classes/
│ │ │ │ ├── AudioPickerPlugin.h
│ │ │ │ ├── AudioPickerPlugin.m
│ │ │ │ └── SwiftAudioPickerPlugin.swift
│ │ │ └── audio_picker.podspec
│ │ ├── lib/
│ │ │ └── audio_picker.dart
│ │ └── pubspec.yaml
│ ├── audio_waveform/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── android/
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle
│ │ │ ├── gradle/
│ │ │ │ └── wrapper/
│ │ │ │ └── gradle-wrapper.properties
│ │ │ ├── gradle.properties
│ │ │ ├── settings.gradle
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── tuntori/
│ │ │ └── audio_waveform/
│ │ │ ├── AudioWaveformPlugin.java
│ │ │ ├── SimpleEncoder.java
│ │ │ └── WaveformExtractor.java
│ │ ├── darwin/
│ │ │ ├── .gitignore
│ │ │ └── Classes/
│ │ │ └── AudioWaveformPlugin.m
│ │ ├── ios/
│ │ │ ├── Assets/
│ │ │ │ └── .gitkeep
│ │ │ ├── Classes/
│ │ │ │ ├── AudioWaveformPlugin.h
│ │ │ │ ├── AudioWaveformPlugin.m
│ │ │ │ ├── SwiftWaveformPlugin.swift
│ │ │ │ └── WaveformExtractor.swift
│ │ │ └── audio_waveform.podspec
│ │ ├── lib/
│ │ │ └── audio_waveform.dart
│ │ ├── macos/
│ │ │ ├── Assets/
│ │ │ │ └── .gitkeep
│ │ │ ├── Classes/
│ │ │ │ └── AudioWaveformPlugin.h
│ │ │ └── audio_waveform.podspec
│ │ ├── pubspec.yaml
│ │ └── test/
│ │ └── audio_waveform_test.dart
│ ├── drag_and_drop_list/
│ │ ├── .gitignore
│ │ ├── .metadata
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── lib/
│ │ │ ├── drag_and_drop_builder_parameters.dart
│ │ │ ├── drag_and_drop_interface.dart
│ │ │ ├── drag_and_drop_item.dart
│ │ │ ├── drag_and_drop_item_target.dart
│ │ │ ├── drag_and_drop_item_wrapper.dart
│ │ │ ├── drag_and_drop_list.dart
│ │ │ ├── drag_and_drop_list_expansion.dart
│ │ │ ├── drag_and_drop_list_interface.dart
│ │ │ ├── drag_and_drop_list_target.dart
│ │ │ ├── drag_and_drop_list_wrapper.dart
│ │ │ ├── drag_and_drop_lists.dart
│ │ │ ├── drag_handle.dart
│ │ │ ├── measure_size.dart
│ │ │ └── programmatic_expansion_tile.dart
│ │ ├── pubspec.yaml
│ │ └── test/
│ │ └── drag_and_drop_lists_test.dart
│ ├── file_picker/
│ │ ├── .gitignore
│ │ ├── .metadata
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── analysis_options.yaml
│ │ ├── android/
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle
│ │ │ ├── settings.gradle
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── file_picker/
│ │ │ └── FilePickerPlugin.kt
│ │ ├── ios/
│ │ │ ├── .gitignore
│ │ │ ├── Assets/
│ │ │ │ └── .gitkeep
│ │ │ ├── Classes/
│ │ │ │ ├── FilePickerPlugin.h
│ │ │ │ ├── FilePickerPlugin.m
│ │ │ │ └── SwiftFilePickerPlugin.swift
│ │ │ └── file_picker.podspec
│ │ ├── lib/
│ │ │ ├── file_picker.dart
│ │ │ ├── file_picker_method_channel.dart
│ │ │ └── file_picker_platform_interface.dart
│ │ └── pubspec.yaml
│ ├── flutter_blue_plus/
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── analysis_options.yaml
│ │ ├── android/
│ │ │ ├── build.gradle
│ │ │ ├── settings.gradle
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── boskokg/
│ │ │ └── flutter_blue_plus/
│ │ │ ├── AdvertisementParser.java
│ │ │ ├── FlutterBluePlusPlugin.java
│ │ │ └── ProtoMaker.java
│ │ ├── ios/
│ │ │ ├── Classes/
│ │ │ │ ├── FlutterBluePlusPlugin.h
│ │ │ │ └── FlutterBluePlusPlugin.m
│ │ │ ├── flutter_blue_plus.podspec
│ │ │ └── gen/
│ │ │ ├── Flutterblueplus.pbobjc.h
│ │ │ └── Flutterblueplus.pbobjc.m
│ │ ├── lib/
│ │ │ ├── flutter_blue_plus.dart
│ │ │ ├── gen/
│ │ │ │ ├── flutterblueplus.pb.dart
│ │ │ │ ├── flutterblueplus.pbenum.dart
│ │ │ │ ├── flutterblueplus.pbjson.dart
│ │ │ │ └── flutterblueplus.pbserver.dart
│ │ │ └── src/
│ │ │ ├── bluetooth_characteristic.dart
│ │ │ ├── bluetooth_descriptor.dart
│ │ │ ├── bluetooth_device.dart
│ │ │ ├── bluetooth_service.dart
│ │ │ ├── flutter_blue_plus.dart
│ │ │ └── guid.dart
│ │ ├── protos/
│ │ │ ├── flutterblueplus.proto
│ │ │ └── regenerate.md
│ │ └── pubspec.yaml
│ ├── flutter_midi_command/
│ │ ├── flutter_midi_command-0.3.7/
│ │ │ ├── .gitignore
│ │ │ ├── .metadata
│ │ │ ├── CHANGELOG.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── android/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── app/
│ │ │ │ │ ├── build.gradle
│ │ │ │ │ └── src/
│ │ │ │ │ ├── debug/
│ │ │ │ │ │ └── AndroidManifest.xml
│ │ │ │ │ ├── main/
│ │ │ │ │ │ ├── AndroidManifest.xml
│ │ │ │ │ │ ├── java/
│ │ │ │ │ │ │ └── io/
│ │ │ │ │ │ │ └── flutter/
│ │ │ │ │ │ │ └── plugins/
│ │ │ │ │ │ │ └── GeneratedPluginRegistrant.java
│ │ │ │ │ │ ├── kotlin/
│ │ │ │ │ │ │ └── com/
│ │ │ │ │ │ │ └── invisiblewrench/
│ │ │ │ │ │ │ └── flutter_midi_command/
│ │ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ │ │ └── res/
│ │ │ │ │ │ ├── drawable/
│ │ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ │ └── values/
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ └── profile/
│ │ │ │ │ └── AndroidManifest.xml
│ │ │ │ ├── build.gradle
│ │ │ │ ├── gradle/
│ │ │ │ │ └── wrapper/
│ │ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ │ └── gradle-wrapper.properties
│ │ │ │ ├── gradle.properties
│ │ │ │ ├── gradlew
│ │ │ │ ├── gradlew.bat
│ │ │ │ ├── settings.gradle
│ │ │ │ └── src/
│ │ │ │ └── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ └── kotlin/
│ │ │ │ └── com/
│ │ │ │ └── invisiblewrench/
│ │ │ │ └── fluttermidicommand/
│ │ │ │ └── FlutterMidiCommandPlugin.kt
│ │ │ ├── ios/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── Assets/
│ │ │ │ │ └── .gitkeep
│ │ │ │ ├── Classes/
│ │ │ │ │ ├── FlutterMidiCommandPlugin.h
│ │ │ │ │ ├── FlutterMidiCommandPlugin.m
│ │ │ │ │ └── SwiftFlutterMidiCommandPlugin.swift
│ │ │ │ ├── Flutter/
│ │ │ │ │ ├── AppFrameworkInfo.plist
│ │ │ │ │ ├── Debug.xcconfig
│ │ │ │ │ └── Release.xcconfig
│ │ │ │ ├── Podfile
│ │ │ │ ├── Runner/
│ │ │ │ │ ├── AppDelegate.swift
│ │ │ │ │ ├── Assets.xcassets/
│ │ │ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── LaunchImage.imageset/
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── README.md
│ │ │ │ │ ├── Base.lproj/
│ │ │ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ │ │ └── Main.storyboard
│ │ │ │ │ ├── Info.plist
│ │ │ │ │ └── Runner-Bridging-Header.h
│ │ │ │ ├── Runner.xcodeproj/
│ │ │ │ │ ├── project.pbxproj
│ │ │ │ │ ├── project.xcworkspace/
│ │ │ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ │ │ └── xcshareddata/
│ │ │ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ │ │ └── xcshareddata/
│ │ │ │ │ └── xcschemes/
│ │ │ │ │ └── Runner.xcscheme
│ │ │ │ ├── Runner.xcworkspace/
│ │ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ │ └── xcshareddata/
│ │ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ │ └── flutter_midi_command.podspec
│ │ │ ├── lib/
│ │ │ │ ├── flutter_midi_command.dart
│ │ │ │ ├── flutter_midi_command_linux_stub.dart
│ │ │ │ └── flutter_midi_command_messages.dart
│ │ │ ├── macos/
│ │ │ │ ├── Classes/
│ │ │ │ │ └── SwiftFlutterMidiCommandPlugin.swift
│ │ │ │ └── flutter_midi_command.podspec
│ │ │ ├── pubspec.yaml
│ │ │ └── test/
│ │ │ └── flutter_midi_command_test.dart
│ │ ├── flutter_midi_command_linux-0.1.3/
│ │ │ ├── .gitignore
│ │ │ ├── .metadata
│ │ │ ├── .vscode/
│ │ │ │ ├── c_cpp_properties.json
│ │ │ │ ├── launch.json
│ │ │ │ └── settings.json
│ │ │ ├── CHANGELOG.md
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── lib/
│ │ │ │ ├── alsa_generated_bindings.dart
│ │ │ │ └── flutter_midi_command_linux.dart
│ │ │ ├── linux/
│ │ │ │ ├── CMakeLists.txt
│ │ │ │ ├── flutter/
│ │ │ │ │ ├── generated_plugin_registrant.cc
│ │ │ │ │ ├── generated_plugin_registrant.h
│ │ │ │ │ └── generated_plugins.cmake
│ │ │ │ ├── flutter_midi_command_linux_plugin.cc
│ │ │ │ └── include/
│ │ │ │ └── flutter_midi_command_linux/
│ │ │ │ └── flutter_midi_command_linux_plugin.h
│ │ │ ├── pubspec.yaml
│ │ │ └── test/
│ │ │ └── flutter_midi_command_linux_test.dart
│ │ └── flutter_midi_command_platform_interface-0.3.3/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── lib/
│ │ │ ├── flutter_midi_command_platform_interface.dart
│ │ │ ├── method_channel_midi_command.dart
│ │ │ ├── midi_device.dart
│ │ │ ├── midi_packet.dart
│ │ │ └── midi_port.dart
│ │ └── pubspec.yaml
│ ├── mighty_ble/
│ │ ├── .gitignore
│ │ ├── .metadata
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── analysis_options.yaml
│ │ ├── android/
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle
│ │ │ ├── settings.gradle
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── tuntori/
│ │ │ └── mighty_ble/
│ │ │ ├── BLEManager.java
│ │ │ └── MightyBlePlugin.java
│ │ ├── ios/
│ │ │ ├── .gitignore
│ │ │ ├── Assets/
│ │ │ │ └── .gitkeep
│ │ │ ├── Classes/
│ │ │ │ ├── MightyBlePlugin.h
│ │ │ │ ├── MightyBlePlugin.m
│ │ │ │ └── SwiftMightyBlePlugin.swift
│ │ │ └── mighty_ble.podspec
│ │ ├── lib/
│ │ │ ├── mighty_ble.dart
│ │ │ ├── mighty_ble_method_channel.dart
│ │ │ └── mighty_ble_platform_interface.dart
│ │ └── pubspec.yaml
│ └── qr_utils-0.1.5/
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── android/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── gradle.properties
│ │ ├── settings.gradle
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── aeologic/
│ │ │ └── adhoc/
│ │ │ └── qr_utils/
│ │ │ ├── QrUtilsPlugin.java
│ │ │ ├── activity/
│ │ │ │ └── QRScannerActivity.java
│ │ │ └── utils/
│ │ │ └── Utility.java
│ │ └── res/
│ │ ├── drawable/
│ │ │ ├── ic_flash_active.xml
│ │ │ ├── ic_flash_inactive.xml
│ │ │ └── ic_shape_circle.xml
│ │ ├── layout/
│ │ │ └── activity_qr_scanner.xml
│ │ ├── values/
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── xml/
│ │ └── provider_paths.xml
│ ├── ios/
│ │ ├── .gitignore
│ │ ├── Assets/
│ │ │ └── .gitkeep
│ │ ├── Classes/
│ │ │ ├── QrUtilsPlugin.h
│ │ │ ├── QrUtilsPlugin.m
│ │ │ ├── SwiftQRScanner.swift
│ │ │ └── SwiftQrUtilsPlugin.swift
│ │ └── qr_utils.podspec
│ ├── lib/
│ │ └── qr_utils.dart
│ └── pubspec.yaml
├── pubspec.yaml
├── web/
│ ├── index.html
│ └── manifest.json
└── windows/
├── .gitignore
├── CMakeLists.txt
├── flutter/
│ ├── CMakeLists.txt
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugin_registrant.h
│ └── generated_plugins.cmake
└── runner/
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
================================================
FILE CONTENTS
================================================
================================================
FILE: .firebaserc
================================================
{
"projects": {
"default": "mightier-amp"
},
"targets": {
"mightieramp": {
"hosting": {
"mightieramp": [
"mightieramp"
]
}
},
"mightier-amp": {
"hosting": {
"mightieramp": [
"mightieramp"
]
}
}
},
"etags": {}
}
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: tuntori
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ["https://www.paypal.com/donate?hosted_button_id=FZWWAM4NUFRPC"]
================================================
FILE: .gitignore
================================================
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
just_audio/
#ignore sign key
/android/key.properties
#ignore api keys and similar
/lib/configKeys.dart
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Web related
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
.firebase/
================================================
FILE: .metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
- platform: linux
create_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
base_revision: e99c9c7cd9f6c0b2f8ae6e3ebfd585239f5568f4
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
================================================
FILE: .vscode/launch.json
================================================
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Mightier Amp",
"request": "launch",
"type": "dart",
"program": "lib/main.dart"
},
{
"name": "Mightier Amp Profile",
"request": "launch",
"type": "dart",
"program": "lib/main.dart",
"flutterMode": "profile"
}
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"cmake.configureOnOpen": false,
}
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2020-2021 Dian Iliev (Tuntorius)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: PRIVACY_POLICY.md
================================================
# Privacy Policy
I (Diyan Iliev) built the Mightier Amp app as an Open Source app. This SERVICE is provided by me at no cost and is intended for use as is.
This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.
If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.
The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Mightier Amp unless otherwise defined in this Privacy Policy.
## Data Collection
Mightier Amp app does not collect any personal data from its users.
## Third-Party Services
Mightier Amp does not use third-party data collection services.
## Contact Us
If you have any questions or concerns about our privacy practices, please contact us at mightieramp@gmail.com.
## Changes to This Policy
I may update this Privacy Policy from time to time. If I make any material changes to how the data is collected, used, or shared, I will reflect the changes on this page.
================================================
FILE: README.md
================================================
# Mightier Amp
Alternative app for controlling NUX Mighty amps series.
## Features
This app aims to cover all the functionality of the original app plus many enhancements:
- Ability to save presets locally on your mobile device and export them to share with others.
- Remote control using a MIDI controller or a HID device. Both USB and Bluetooth MIDI controllers are supported.
- Landscape mode.
- Improved Jam Tracks functionality. Select any audio file in your device, add events like loop points, preset changes and others at any point and play along.
- The app unlocks 8 secret amp models in Mighty Plug Pro and Mighty Space: Twin Rvb, Class A15, Vibro King, Budda, Brit Blues, Match D30, Brit 2000 and Uber HiGain
- Tempo/Beat Trainer
- Various other enhancements.
## Supported Amps
1.Mighty Plug / Mighty Air
2.Mighty Plug Pro / Mighty Space
3.Mighty Lite BT MKII
4.Mighty 8 BT\*\*
5.Mighty 20 BT \*\* / Mighty 40 BT \*\*
6.Mighty BT Lite / AirBorne Go\*\* / GUO AN\*\*
\*\* These amps are not tested and might not work.
## Helpful Resources
Check out the great video tutorial series created by Diego Zuccotti, where he walks through the features and functionality of the app, and the usage of the NUX amps in general.
[Click here to watch!](https://www.youtube.com/@TutoJam)
Facebook groups for presets sharing and amps discussion:
[Mighty Plug/Mighty Air](https://www.facebook.com/groups/nuxmightyplugairgroup/)
[Mighty Plug Pro/Mighty Space](https://www.facebook.com/groups/mightyplugpro/)
## Download
[
](https://play.google.com/store/apps/details?id=com.tuntori.mightieramp)
[
](https://apps.apple.com/us/app/mightier-amp/id6447451371)
[
](https://f-droid.org/packages/com.tuntori.mightieramp/)
## Donation
Mightier Amp is an open-source project that I am developing in my free time. If you find the app useful and would like to support its development, please consider making a donation. Your contribution will help me invest more time into the development of the app. Thank you for your support!
[](https://www.paypal.com/donate?hosted_button_id=FZWWAM4NUFRPC)
================================================
FILE: analysis_options.yaml
================================================
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
================================================
FILE: android/.gitignore
================================================
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
================================================
FILE: android/app/build.gradle
================================================
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
compileSdkVersion 34
namespace 'com.tuntori.mightieramp'
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
applicationId "com.tuntori.mightieramp"
minSdkVersion flutter.minSdkVersion
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
debug {
defaultConfig.minSdkVersion 19
applicationIdSuffix ".debug"
}
profile {
defaultConfig.minSdkVersion 19
applicationIdSuffix ".debug"
}
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
ndk {
abiFilters 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.multidex:multidex:2.0.1'
}
================================================
FILE: android/app/proguard-rules.pro
================================================
-keep class androidx.lifecycle.** { *; }
-keep class com.boskokg.flutter_blue_plus.** { *; }
================================================
FILE: android/app/src/debug/AndroidManifest.xml
================================================
================================================
FILE: android/app/src/debug/res/values/strings.xml
================================================
Mightier Amp Debug
================================================
FILE: android/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: android/app/src/main/kotlin/com/tuntori/mightieramp/MainActivity.kt
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
package com.tuntori.mightieramp
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry
import io.flutter.view.FlutterMain
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import android.content.Intent
import android.app.Activity
import android.net.Uri
import java.io.BufferedWriter
import java.io.OutputStream
import java.io.OutputStreamWriter
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.io.DataOutputStream
import java.nio.ShortBuffer
class MainActivity: FlutterActivity() {
internal var WRITE_REQUEST_CODE = 77777 //unique request code
internal var OPEN_REQUEST_CODE = 22222
internal var OPEN_REQUEST_CODE_BYTEARRAY = 33333
internal var _result: Result? = null
internal var _data: String = ""
internal var _dataBa: ByteArray? = null
internal var saveByteArray:Boolean = false
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
configureFileSaveAPI(flutterEngine);
}
fun configureFileSaveAPI(@NonNull flutterEngine: FlutterEngine)
{
MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "com.msvcode.filesaver/files")
.setMethodCallHandler { call, result ->
// Note: this method is invoked on the main thread.
if (call.method == "saveFile") {
_result = result
saveByteArray = call.argument("byteArray") ?: false;
if (saveByteArray)
_dataBa =call.argument("data")
else
_data = call.argument("data") ?: "";
var mime:String? = call.argument("mime");
var name:String? = call.argument("name");
if (mime!=null && name!=null)
createFile(mime, name)
} else if (call.method == "openFile") {
_result = result
var mime:String? = call.argument("mime");
var byteArray:Boolean? = call.argument("byte_array");
if (mime!=null)
openFile(mime, byteArray)
} else {
result.notImplemented()
}
}
}
private fun createFile(mimeType: String, fileName: String) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
// Filter to only show results that can be "opened", such as
// a file (as opposed to a list of contacts or timezones).
addCategory(Intent.CATEGORY_OPENABLE)
// Create a file with the requested MIME type.
type = mimeType
putExtra(Intent.EXTRA_TITLE, fileName)
}
startActivityForResult(intent, WRITE_REQUEST_CODE)
}
//replace with ACTION_GET_CONTENT for just a temporary access
//the other is ACTION_OPEN_DOCUMENT
private fun openFile(mimeType: String, byteArray: Boolean?) {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
// Filter to only show results that can be "opened", such as
// a file (as opposed to a list of contacts or timezones).
addCategory(Intent.CATEGORY_OPENABLE)
// Create a file with the requested MIME type.
type = mimeType
}
if (byteArray != null && byteArray == true)
startActivityForResult(intent, OPEN_REQUEST_CODE_BYTEARRAY)
else
startActivityForResult(intent, OPEN_REQUEST_CODE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Check which request we're responding to
if (requestCode == WRITE_REQUEST_CODE) {
// Make sure the request was successful
if (resultCode == Activity.RESULT_OK) {
if (data != null && data.getData() != null) {
//now write the data
writeInFile(data.getData() as Uri) //data.getData() is Uri
} else {
_result?.error("NO DATA", "No data", null)
}
} else {
_result?.error("CANCELED", "User cancelled", null)
}
}
else if (requestCode == OPEN_REQUEST_CODE ||
requestCode == OPEN_REQUEST_CODE_BYTEARRAY) {
if (resultCode == Activity.RESULT_OK) {
if (data != null && data.getData() != null) {
//now write the data
if (requestCode == OPEN_REQUEST_CODE)
readFile(data.getData() as Uri, false)
else
readFile(data.getData() as Uri, true)
}else {
_result?.error("NO DATA", "No data", null)
}
} else {
_result?.error("CANCELED", "User cancelled", null)
}
}
}
private fun writeInFile(uri: Uri) {
val outputStream: OutputStream?
try {
outputStream = getContentResolver().openOutputStream(uri);
if (outputStream!=null) {
if (saveByteArray && _dataBa!=null) {
outputStream.write(_dataBa);
outputStream.close();
}
else {
outputStream.write(_data.toByteArray(Charsets.UTF_8));
outputStream.close();
}
_result?.success("SUCCESS");
}
else
_result?.error("ERROR", "writeInFile: Output stream is null", null)
} catch (e:Exception){
_result?.error("ERROR", "Unable to write. Exception: $e", null)
e.printStackTrace()
}
}
private fun readFile(uri: Uri, dataArray: Boolean) {
val inputStream: InputStream?
val inputStreamReader: InputStreamReader
try {
inputStream = getContentResolver().openInputStream(uri)
inputStreamReader = InputStreamReader(inputStream)
if (!dataArray) {
val br = BufferedReader(inputStreamReader)
val fileContent = br.use { inputStreamReader.readText() }
br.close()
_result?.success(fileContent)
}
else {
if (inputStream!=null) {
val array = inputStream.readBytes();
inputStream.close();
_result?.success(array)
}
}
} catch (e:Exception){
_result?.error("ERROR", "Unable to read", null)
}
}
}
================================================
FILE: android/app/src/main/res/drawable/launch_background.xml
================================================
================================================
FILE: android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml
================================================
================================================
FILE: android/app/src/main/res/values/colors.xml
================================================
#ffffff
================================================
FILE: android/app/src/main/res/values/strings.xml
================================================
Mightier Amp
================================================
FILE: android/app/src/main/res/values/styles.xml
================================================
================================================
FILE: android/app/src/profile/AndroidManifest.xml
================================================
================================================
FILE: android/build.gradle
================================================
buildscript {
ext.kotlin_version = '1.8.22'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation"
}
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
================================================
FILE: android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
org.gradle.java.home=C:/AndroidSDK/jdk-17.0.9
================================================
FILE: android/settings.gradle
================================================
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
================================================
FILE: android/settings_aar.gradle
================================================
include ':app'
================================================
FILE: fastlane/metadata/android/en-US/full_description.txt
================================================
An alternative app for controlling NUX Mighty series guitar amplifiers
This app aims to cover all of the functionality the original app offers plus many enhancements:
• All amps and effects are enabled for all channels.
• Ability to save and recall hundreds of presets locally on your mobile device.
• Improved Jam Tracks functionality. Select any audio file in your device, add preset change events at any point and play along.
The app is open-source. Full code available at https://github.com/tuntorius/mightier_amp
"NUX" and the "Mighty" amps series are a trademark of CHERUB TECHNOLOGY COMPANY LIMITED.
================================================
FILE: fastlane/metadata/android/en-US/short_description.txt
================================================
An alternative app for controlling NUX Mighty amps series.
================================================
FILE: fastlane/metadata/android/en-US/title.txt
================================================
Mightier Amp
================================================
FILE: firebase.json
================================================
{
"hosting": {
"public": "/build/web/",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
],
"domain": "mightieramp.web.app",
"site":"mightieramp"
}
}
================================================
FILE: ios/.gitignore
================================================
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
================================================
FILE: ios/Flutter/AppFrameworkInfo.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
App
CFBundleIdentifier
io.flutter.flutter.app
CFBundleInfoDictionaryVersion
6.0
CFBundleName
App
CFBundlePackageType
FMWK
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
1.0
MinimumOSVersion
11.0
================================================
FILE: ios/Flutter/Debug.xcconfig
================================================
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
================================================
FILE: ios/Flutter/Release.xcconfig
================================================
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
================================================
FILE: ios/Podfile
================================================
# Uncomment this line to define a global platform for your project
platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
# Disable the NSMicrophoneUsageDescription requirement
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'AUDIO_SESSION_MICROPHONE=0'
]
end
end
end
================================================
FILE: ios/Runner/AppDelegate.swift
================================================
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
================================================
FILE: ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"filename" : "Icon_20x20@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon_20x20@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "Icon_29x29@1x.png",
"idiom" : "iphone",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "Icon_29x29@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon_29x29@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "Icon_40x40@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon_40x40@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "Icon_60x60@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon_60x60@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "Icon_20x20@1x.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "Icon_20x20@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon_29x29@1x.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "Icon_29x29@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon_40x40@1x.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "Icon_40x40@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon_76x76@1x.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "Icon_76x76@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "Icon_83.5x83.5@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "Icon_1024x1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
================================================
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
================================================
FILE: ios/Runner/Base.lproj/LaunchScreen.storyboard
================================================
================================================
FILE: ios/Runner/Base.lproj/Main.storyboard
================================================
================================================
FILE: ios/Runner/Info.plist
================================================
CADisableMinimumFrameDurationOnPhone
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDocumentTypes
CFBundleTypeName
JSON Document
CFBundleTypeRole
Editor
LSHandlerRank
Owner
LSItemContentTypes
public.json
CFBundleTypeExtensions
nuxpreset
CFBundleTypeName
NUX Preset
CFBundleTypeRole
Editor
LSHandlerRank
Owner
LSItemContentTypes
com.tuntori.nuxpreset
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
Mightier Amp
CFBundlePackageType
APPL
CFBundleShortVersionString
$(FLUTTER_BUILD_NAME)
CFBundleSignature
????
CFBundleVersion
$(FLUTTER_BUILD_NUMBER)
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
LSSupportsOpeningDocumentsInPlace
NSAppleMusicUsageDescription
The app uses music as backing tracks that are streamed to the connected guitar amplifier
NSBluetoothAlwaysUsageDescription
Bluetooth Low Energy is used to connect to a BLE enabled guitar amplifier
NSBluetoothPeripheralUsageDescription
Bluetooth Low Energy is used to connect to a BLE enabled guitar amplifier
NSCameraUsageDescription
This app uses camera to scan QR codes that contain preset data for the connected amplifier
NSPhotoLibraryUsageDescription
This app lets you scan a photo containing QR code with amp preset data.
UIApplicationSupportsIndirectInputEvents
UIBackgroundModes
audio
bluetooth-central
UIFileSharingEnabled
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
Main
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UIViewControllerBasedStatusBarAppearance
UTImportedTypeDeclarations
UTTypeConformsTo
public.json
UTTypeDescription
NUX Preset
UTTypeIconFiles
UTTypeIdentifier
com.tuntori.nuxpreset
UTTypeTagSpecification
public.filename-extension
nuxpreset
================================================
FILE: ios/Runner/Runner-Bridging-Header.h
================================================
#import "GeneratedPluginRegistrant.h"
================================================
FILE: ios/Runner.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
84C82414292992D6001A7DDC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CCC37E5B2EA4022EAC7C519F /* Pods_Runner.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
130F32F4717A7AF5D4F6585E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
97D11407EEF2CD9B9EB7F3BF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
CCC37E5B2EA4022EAC7C519F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F37F8A97064DC522B40DF3A2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84C82414292992D6001A7DDC /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
07C65F7300B0E156A019970F /* Pods */ = {
isa = PBXGroup;
children = (
130F32F4717A7AF5D4F6585E /* Pods-Runner.debug.xcconfig */,
F37F8A97064DC522B40DF3A2 /* Pods-Runner.release.xcconfig */,
97D11407EEF2CD9B9EB7F3BF /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "";
};
65DA474ACEE31D5FA261D7E3 /* Frameworks */ = {
isa = PBXGroup;
children = (
CCC37E5B2EA4022EAC7C519F /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
07C65F7300B0E156A019970F /* Pods */,
65DA474ACEE31D5FA261D7E3 /* Frameworks */,
);
sourceTree = "";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
71EB3D73602EF3E01C2EC6E4 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
C57858137186C0D10D48D7B8 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
71EB3D73602EF3E01C2EC6E4 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
C57858137186C0D10D48D7B8 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 97D11407EEF2CD9B9EB7F3BF /* Pods-Runner.profile.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = BRTQKAVFDX;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Mightier Amp";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
PRODUCT_BUNDLE_IDENTIFIER = com.tuntori.mightieramp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 130F32F4717A7AF5D4F6585E /* Pods-Runner.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = BRTQKAVFDX;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Mightier Amp";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
PRODUCT_BUNDLE_IDENTIFIER = com.tuntori.mightieramp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F37F8A97064DC522B40DF3A2 /* Pods-Runner.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = BRTQKAVFDX;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Mightier Amp";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
PRODUCT_BUNDLE_IDENTIFIER = com.tuntori.mightieramp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
IDEDidComputeMac32BitWarning
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
PreviewsEnabled
================================================
FILE: ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
================================================
================================================
FILE: ios/Runner.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
IDEDidComputeMac32BitWarning
================================================
FILE: ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
PreviewsEnabled
================================================
FILE: lib/UI/mainTabs.dart
================================================
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mighty_plug_manager/UI/pages/drumsPage.dart';
import 'package:mighty_plug_manager/UI/utils.dart';
import 'package:mighty_plug_manager/UI/widgets/NuxAppBar.dart';
import '../bluetooth/NuxDeviceControl.dart';
import '../bluetooth/bleMidiHandler.dart';
import '../bluetooth/ble_controllers/BLEController.dart';
import '../main.dart';
import '../midi/MidiControllerManager.dart';
import '../platform/platformUtils.dart';
import 'pages/jamTracks.dart';
import 'pages/presetEditor.dart';
import 'pages/settings.dart';
import 'popups/alertDialogs.dart';
import 'theme.dart';
import 'widgets/VolumeDrawer.dart';
import 'widgets/app_drawer.dart';
import 'widgets/bottomBar.dart';
import 'widgets/common/nestedWillPopScope.dart';
import 'widgets/presets/preset_list/presetList.dart';
class MainTabs extends StatefulWidget {
final MidiControllerManager midiMan = MidiControllerManager();
MainTabs({Key? key}) : super(key: key);
@override
State createState() => MainTabsState();
}
class MainTabsState extends State with TickerProviderStateMixin {
int _currentIndex = 0;
late BuildContext dialogContext;
late TabController controller;
late TabVisibilityController _visibilityController;
late final List _tabs;
bool isBottomDrawerOpen = false;
bool connectionFailed = false;
late Timer _timeout;
StateSetter? dialogSetState;
@override
void initState() {
if (!AppThemeConfig.allowRotation) {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
} else {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight
]);
}
super.initState();
_visibilityController = TabVisibilityController(5);
//add 5 pages widgets
_tabs = [
const PresetEditor(),
PresetList(
visibilityEventHandler: _visibilityController.getEventHandler(1)),
const DrumsPage(),
const JamTracks(),
const Settings(),
];
controller =
TabController(initialIndex: 0, length: _tabs.length, vsync: this);
// controller.addListener(() {
// setState(() {
// _visibilityController.onTabChanged(_currentIndex, controller.index);
// _currentIndex = controller.index;
// });
// });
NuxDeviceControl.instance().connectStatus.listen(connectionStateListener);
NuxDeviceControl.instance().addListener(onDeviceChanged);
BLEMidiHandler.instance().initBle(bleErrorHandler);
}
void bleErrorHandler(BleError error, dynamic data) {
{
switch (error) {
case BleError.unavailable:
if (!PlatformUtils.isIOS) {
AlertDialogs.showInfoDialog(context,
title: "Warning!",
description: "Your device does not support bluetooth!",
confirmButton: "OK");
}
break;
case BleError.permissionDenied:
AlertDialogs.showLocationPrompt(context, false, null);
break;
case BleError.locationServiceOff:
AlertDialogs.showInfoDialog(context,
title: "Location service is disabled!",
description:
"Please, enable location service. It is required for Bluetooth connection to work.",
confirmButton: "OK");
break;
case BleError.scanPermissionDenied:
AlertDialogs.showInfoDialog(context,
title: "Bluetooth permissions required!",
description:
"Please, grant bluetooth scan and connect permissions. They are required for Mightier Amp to work.",
confirmButton: "OK");
break;
}
}
}
@override
void dispose() {
super.dispose();
NuxDeviceControl.instance().removeListener(onDeviceChanged);
}
void onConnectionTimeout() async {
connectionFailed = true;
if (dialogSetState != null) {
dialogSetState?.call(() {});
await Future.delayed(const Duration(seconds: 3));
Navigator.pop(context);
dialogSetState = null;
BLEMidiHandler.instance().disconnectDevice();
}
}
void connectionStateListener(DeviceConnectionState event) {
switch (event) {
case DeviceConnectionState.connectionBegin:
if (dialogSetState != null) break;
connectionFailed = false;
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
dialogContext = context;
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
dialogSetState = setState;
return NestedWillPopScope(
onWillPop: () => Future.value(false),
child: Dialog(
backgroundColor: Colors.grey[700],
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (!connectionFailed)
const CircularProgressIndicator.adaptive(),
if (connectionFailed)
const Icon(
Icons.error,
color: Colors.red,
),
const SizedBox(
width: 8,
),
Text(connectionFailed
? "Connection Failed!"
: "Connecting"),
],
),
),
),
);
},
);
},
);
//setup a timer in case something fails
_timeout = Timer(const Duration(seconds: 10), onConnectionTimeout);
break;
case DeviceConnectionState.presetsLoaded:
//if the device is connected in this step, then it's
//just a reset, not connect
if (NuxDeviceControl.instance().isConnectionComplete()) {
dialogSetState = null;
if (_timeout.isActive) {
_timeout.cancel();
Navigator.pop(context);
}
}
debugPrint("presets loaded");
break;
case DeviceConnectionState.connectionComplete:
dialogSetState = null;
if (_timeout.isActive) {
_timeout.cancel();
Navigator.pop(context);
}
break;
case DeviceConnectionState.disconnected:
break;
}
}
Future _willPopCallback() async {
Completer confirmation = Completer();
AlertDialogs.showConfirmDialog(context,
title: "Exit Mightier Amp?",
cancelButton: "No",
confirmButton: "Yes",
confirmColor: Colors.red,
description: "Are you sure?", onConfirm: (val) {
if (val) {
//disconnect device if connected
BLEMidiHandler.instance().disconnectDevice();
}
confirmation.complete(val);
});
return confirmation.future;
}
void onDeviceChanged() {
setState(() {});
}
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final screenWidth = mediaQuery.size.width;
final layoutMode = getLayoutMode(mediaQuery);
//WARNING: Workaround for a flutter bug - if the app is started with screen off,
//one of the widgets throws an exception and the app scaffold is empty
if (screenWidth < 10) return const SizedBox();
return PageStorage(
bucket: bucketGlobal,
child: FocusScope(
autofocus: true,
onKey: (node, event) {
if (event.runtimeType.toString() == 'RawKeyDownEvent' &&
event.logicalKey.keyId != 0x100001005) {
MidiControllerManager().onHIDData(event);
}
return KeyEventResult.skipRemainingHandlers;
},
child: NestedWillPopScope(
onWillPop: _willPopCallback,
child: Scaffold(
resizeToAvoidBottomInset: controller.index == 1,
appBar: layoutMode != LayoutMode.navBar ? null : const MAAppBar(),
body: Stack(
alignment: Alignment.bottomCenter,
children: [
Row(
children: [
if (layoutMode == LayoutMode.drawer)
AppDrawer(
onSwitchPageIndex: _onSwitchPageIndex,
currentIndex: _currentIndex,
totalTabs: _tabs.length,
),
Expanded(
child: layoutMode == LayoutMode.navBar
? TabBarView(
physics: const NeverScrollableScrollPhysics(),
controller: controller,
children: _tabs,
)
: _tabs.elementAt(_currentIndex),
),
],
),
if (layoutMode != LayoutMode.drawer && _currentIndex != 3)
BottomDrawer(
isBottomDrawerOpen: isBottomDrawerOpen,
onExpandChange: (val) => setState(() {
isBottomDrawerOpen = val;
}),
child: VolumeSlider(),
),
],
),
bottomNavigationBar: layoutMode == LayoutMode.navBar
? GestureDetector(
onVerticalDragUpdate: _onBottomBarSwipe,
child: BottomBar(
index: _currentIndex,
onTap: _onSwitchPageIndex,
),
)
: null,
),
),
),
);
}
void _onBottomBarSwipe(DragUpdateDetails details) {
if (details.delta.dy < 0) {
//open
setState(() {
isBottomDrawerOpen = true;
});
} else {
//close
setState(() {
isBottomDrawerOpen = false;
});
}
}
void _onSwitchPageIndex(int index) {
setState(() {
_visibilityController.onTabChanged(_currentIndex, index);
_currentIndex = index;
controller.animateTo(_currentIndex);
});
}
}
class TabVisibilityEventHandler {
void Function()? onTabSelected;
void Function()? onTabDeselected;
}
class TabVisibilityController {
late List eventHandlers;
TabVisibilityController(int tabAmount) {
eventHandlers =
List.generate(tabAmount, (index) => TabVisibilityEventHandler());
}
TabVisibilityEventHandler getEventHandler(int tab) {
return eventHandlers[tab];
}
void onTabChanged(int oldTab, int newTab) {
eventHandlers[oldTab].onTabDeselected?.call();
eventHandlers[newTab].onTabSelected?.call();
}
}
================================================
FILE: lib/UI/mightierIcons.dart
================================================
/// Flutter icons MightierIcons
/// Copyright (C) 2021 by original authors @ fluttericon.com, fontello.com
/// This font was generated by FlutterIcon.com, which is derived from Fontello.
///
/// To use this font, place it in your fonts/ directory and include the
/// following in your pubspec.yaml
///
/// flutter:
/// fonts:
/// - family: MightierIcons
/// fonts:
/// - asset: fonts/MightierIcons.ttf
///
///
/// * Font Awesome 5, Copyright (C) 2016 by Dave Gandy
/// Author: Dave Gandy
/// License: SIL (https://github.com/FortAwesome/Font-Awesome/blob/master/LICENSE.txt)
/// Homepage: http://fortawesome.github.com/Font-Awesome/
///
import 'package:flutter/widgets.dart';
class MightierIcons {
MightierIcons._();
static const _kFontFam = 'MightierIcons';
static const String? _kFontPkg = null;
static const IconData tag =
IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData amp =
IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData cabinet =
IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData gate =
IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData pedal =
IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData sliders =
IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData compressor =
IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData sinewave =
IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData repeat =
IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData repeat_a =
IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData repeat_ab =
IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData tuner =
IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
static const IconData drum =
IconData(0xf569, fontFamily: _kFontFam, fontPackage: _kFontPkg);
}
================================================
FILE: lib/UI/pages/DebugConsolePage.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class DebugConsole extends StatelessWidget {
static String output = "";
const DebugConsole({Key? key}) : super(key: key);
static void print(Object? value) {
output += "$value\n";
}
static void printHex(List array) {
output += '[ ';
output += array
.map((x) => '${x < 16 ? '0' : ''}${x.toRadixString(16)}')
.join(" ");
output += ']\n';
}
static void printString(Object? value) {
output += "$value\n";
}
@override
Widget build(BuildContext context) {
TextEditingController c = TextEditingController(text: DebugConsole.output);
return Scaffold(
appBar: AppBar(title: const Text("Debug console")),
body: SafeArea(
child: Column(
children: [
Expanded(
child: TextField(
maxLines: null,
readOnly: true,
controller: c,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: output));
},
child: const Text("Copy to Clipboard")),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
output = "";
c.clear();
},
child: const Text("Clear")),
],
)
],
),
),
);
}
}
================================================
FILE: lib/UI/pages/calibration.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:mighty_plug_manager/UI/widgets/common/nestedWillPopScope.dart';
import 'package:mighty_plug_manager/platform/simpleSharedPrefs.dart';
import '../../bluetooth/NuxDeviceControl.dart';
import '../../bluetooth/devices/presets/preset_constants.dart';
class Calibration extends StatefulWidget {
const Calibration({Key? key}) : super(key: key);
@override
State createState() => _CalibrationState();
}
class _CalibrationState extends State {
int delay = 0;
late AudioPlayer player;
int nuxMode = 0;
bool toggled = false;
Color presetColor = PresetConstants.channelColorsPlug[0];
NuxDeviceControl devControl = NuxDeviceControl.instance();
StreamSubscription? _playerSub;
@override
void initState() {
super.initState();
player = AudioPlayer();
player.setAsset("assets/audio/calibration.wav");
player.setLoopMode(LoopMode.one);
player.play();
_playerSub = player
.createPositionStream(
steps: 99999999,
minPeriod: const Duration(milliseconds: 1),
maxPeriod: const Duration(milliseconds: 100))
.listen(onPositionUpdate);
delay = SharedPrefs().getInt(SettingsKeys.latency, 0);
}
void onPositionUpdate(Duration pos) {
int posMs = pos.inMilliseconds;
if (posMs >= 500 + delay) {
if (!toggled) {
nuxMode++;
if (nuxMode > 2) nuxMode = 0;
devControl.changeDeviceChannel(nuxMode);
setState(() {
presetColor = PresetConstants.channelColorsPlug[nuxMode];
});
toggled = true;
}
} else {
toggled = false;
}
}
@override
Widget build(BuildContext context) {
return NestedWillPopScope(
onWillPop: () async {
await player.stop();
_playerSub?.cancel();
await player.dispose();
//reset to prevent device losing sync
NuxDeviceControl.instance().resetToChannelDefaults();
return true;
},
child: Scaffold(
appBar: AppBar(
title: const Text("Latency Calibration"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
"Make sure the NUX device is connected in both Audio and App mode!",
textAlign: TextAlign.center,
),
const SizedBox(
height: 20,
),
const Text(
"Adjust the slider so that the NUX light change and the audio clicks happen at the same time.",
textAlign: TextAlign.center,
),
// Text(
// "The app uses this value to apply extra latency to the commands sent to the device",
// textAlign: TextAlign.center,
// ),
Slider(
value: delay.toDouble(),
min: 0,
max: 400,
label: "$delay",
divisions: 400,
onChanged: (val) {
setState(() {
delay = val.round();
});
},
onChangeEnd: (val) {
SharedPrefs().setInt(SettingsKeys.latency, val.round());
},
)
],
),
),
),
);
}
}
================================================
FILE: lib/UI/pages/developerPage.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/widgets/common/nestedWillPopScope.dart';
import 'package:mighty_plug_manager/UI/widgets/common/numberPicker.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import 'package:mighty_plug_manager/bluetooth/bleMidiHandler.dart';
import '../../bluetooth/devices/NuxConstants.dart';
enum midiMessage { ccMessage, sysExMessage }
class DeveloperPage extends StatefulWidget {
const DeveloperPage({Key? key}) : super(key: key);
@override
State createState() => _DeveloperPageState();
}
class _DeveloperPageState extends State {
TextEditingController controller = TextEditingController(text: "");
midiMessage msgType = midiMessage.ccMessage;
List data = [0, 0, 0, 0];
bool sliderChanging = false;
@override
void initState() {
super.initState();
NuxDeviceControl.instance().onDataReceiveDebug = _onDataReceive;
NuxDeviceControl.instance().developer = true;
}
void _onDataReceive(List data) {
setState(() {
controller.text += "$data\n";
});
}
Widget _buildCCPage() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
"CC Message",
style: TextStyle(fontSize: 18),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("CC Number"),
NumberPicker(
axis: Axis.horizontal,
minValue: 0,
maxValue: 127,
value: data[0],
//hex: true,
textStyle: TextStyle(color: Colors.grey[600]),
itemWidth: 60,
zeroPad: false,
onChanged: (val) {
data[0] = val;
setState(() {});
},
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("Value (numeric)"),
NumberPicker(
axis: Axis.horizontal,
minValue: 0,
maxValue: 127,
value: data[1],
textStyle: TextStyle(color: Colors.grey[600]),
//hex: true,
itemWidth: 60,
zeroPad: false,
onChanged: (val) {
if (sliderChanging) return;
data[1] = val;
setState(() {});
},
),
],
),
Slider(
min: 0,
max: 127,
label: "${data[1]}",
value: data[1].toDouble(),
onChangeStart: (value) => sliderChanging = true,
onChangeEnd: (value) async {
await Future.delayed(const Duration(milliseconds: 800));
sliderChanging = false;
},
onChanged: (value) {
data[1] = value.round();
setState(() {});
}),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: const Text("Value (switch)"),
value: data[1] != 0,
onChanged: (value) {
data[1] = value ? 127 : 0;
setState(() {});
}),
ElevatedButton(
onPressed: () {
if (!NuxDeviceControl.instance().isConnected) return;
var msg = NuxDeviceControl.instance()
.createCCMessage(data[0], data[1]);
BLEMidiHandler.instance().sendData(msg);
},
child: const Text("Send")),
ElevatedButton(
onPressed: () {
setState(() {
controller.text = "";
});
},
child: const Text("Clear console"))
],
),
);
}
Widget _buildSysExPage() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
"SysEx Request",
style: TextStyle(fontSize: 18),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("SysEx number"),
NumberPicker(
axis: Axis.horizontal,
minValue: 0,
maxValue: 127,
value: data[2],
//hex: true,
textStyle: TextStyle(color: Colors.grey[600]),
itemWidth: 60,
zeroPad: false,
onChanged: (val) {
data[2] = val;
setState(() {});
},
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("Additional (-1 - no val)"),
NumberPicker(
axis: Axis.horizontal,
minValue: -1,
maxValue: 127,
value: data[3],
textStyle: TextStyle(color: Colors.grey[600]),
//hex: true,
itemWidth: 60,
zeroPad: false,
onChanged: (val) {
if (sliderChanging) return;
data[3] = val;
setState(() {});
},
),
],
),
Slider(
min: -1,
max: 127,
label: "${data[1]}",
value: data[1].toDouble(),
onChangeStart: (value) => sliderChanging = true,
onChangeEnd: (value) async {
await Future.delayed(const Duration(milliseconds: 800));
sliderChanging = false;
},
onChanged: (value) {
data[3] = value.round();
setState(() {});
}),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: const Text("Value (switch)"),
value: data[3] != 0,
onChanged: (value) {
data[3] = value ? 127 : 0;
setState(() {});
}),
ElevatedButton(
onPressed: () {
if (!NuxDeviceControl.instance().isConnected) return;
List msg = [];
//create header
msg.addAll([
0x80,
0x80,
MidiMessageValues.sysExStart,
0x43,
0x58,
SysexPrivacy.kSYSEX_PRIVATE,
data[2],
SyxDir.kSYXDIR_REQ,
]);
if (data[3] >= 0) msg.add(data[3]);
msg.addAll([0x80, MidiMessageValues.sysExEnd]);
BLEMidiHandler.instance().sendData(msg);
},
child: const Text("Send")),
ElevatedButton(
onPressed: () {
setState(() {
controller.text = "";
});
},
child: const Text("Clear console"))
],
),
);
}
@override
Widget build(BuildContext context) {
// if (!NuxDeviceControl.instance().isConnected)
// return Center(
// child: Text("No device connected"),
// );
return NestedWillPopScope(
onWillPop: () {
NuxDeviceControl.instance().developer = false;
return Future.value(true);
},
child: Scaffold(
body: Column(mainAxisSize: MainAxisSize.min, children: [
Expanded(
flex: 4,
child: AbsorbPointer(
absorbing: !NuxDeviceControl.instance().developer,
child: Opacity(
opacity: NuxDeviceControl.instance().developer ? 1 : 0.5,
child: SingleChildScrollView(
child: TextField(
enabled: NuxDeviceControl.instance().developer,
controller: controller,
maxLines: null,
readOnly: true,
),
),
),
),
),
SizedBox(
height: 350,
child: PageView(
children: [_buildCCPage(), _buildSysExPage()],
)),
]),
),
);
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/LiteMk2Settings.dart
================================================
import 'package:flutter/material.dart';
import '../../../bluetooth/bleMidiHandler.dart';
import '../../../bluetooth/devices/NuxDevice.dart';
import '../../popups/alertDialogs.dart';
import 'PlugProUsbSettings.dart';
class LiteMk2Settings extends StatefulWidget {
final NuxDevice device;
const LiteMk2Settings({Key? key, required this.device}) : super(key: key);
@override
State createState() => _LiteMk2SettingsState();
}
class _LiteMk2SettingsState extends State {
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
enabled: widget.device.deviceControl.isConnected,
leading: const Icon(Icons.volume_up),
title: const Text("USB Audio Settings"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const PlugProUsbSettings()));
},
),
/*ListTile(
leading: const Icon(Icons.bluetooth_audio),
enabled: widget.device.deviceControl.isConnected,
title: const Text("Bluetooth Audio EQ"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const PlugProEQSettings()));
},
),*/
/*
ListTile(
enabled: widget.device.deviceControl.isConnected,
leading: const Icon(Icons.mic),
title: const Text("Microphone Settings"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const PlugProMicSettings()));
},
),
*/
ListTile(
enabled: widget.device.deviceControl.isConnected,
leading: const Icon(Icons.restart_alt),
title: const Text("Reset Device Presets"),
onTap: () {
if (BLEMidiHandler.instance().connectedDevice != null) {
AlertDialogs.showConfirmDialog(context,
title: "Reset device presets",
cancelButton: "Cancel",
confirmButton: "Reset",
confirmColor: Colors.red,
description: "Are you sure?", onConfirm: (val) {
if (val) widget.device.resetNuxPresets();
});
}
},
),
const Divider()
],
);
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/PlugAirSettings.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/bluetooth/devices/NuxMightyPlugAir.dart';
import '../../../bluetooth/bleMidiHandler.dart';
import '../../../bluetooth/devices/NuxDevice.dart';
import '../../popups/alertDialogs.dart';
import 'PlugAirUsbSettings.dart';
const _eqOptions = [
"Normal",
"Acoustic",
"Blues",
"Clean Bass",
"Guitar Cut",
"Metal",
"Pop",
"Rock",
"Solo Cut"
];
class PlugAirSettings extends StatefulWidget {
final NuxDevice device;
const PlugAirSettings({Key? key, required this.device}) : super(key: key);
@override
State createState() => _PlugAirSettingsState();
NuxMightyPlug get plugAirDevice => device as NuxMightyPlug;
}
class _PlugAirSettingsState extends State {
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
enabled: widget.device.deviceControl.isConnected,
title: const Text("USB Audio Settings"),
leading: const Icon(Icons.volume_up),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const PlugAirUsbSettings()));
},
),
ListTile(
enabled: widget.device.deviceControl.isConnected,
title: const Text("Bluetooth Audio EQ"),
subtitle: Text(_eqOptions[widget.plugAirDevice.btEq]),
leading: const Icon(Icons.bluetooth_audio),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
var oldValue = widget.plugAirDevice.btEq;
var dialog = AlertDialogs.showOptionDialog(context,
confirmButton: "OK",
cancelButton: "Cancel",
title: "Bluetooth Audio EQ",
confirmColor: Theme.of(context).hintColor,
value: widget.plugAirDevice.btEq,
options: _eqOptions, onConfirm: (changed, newValue) {
setState(() {
if (changed) {
widget.plugAirDevice.setBtEq(newValue);
} else {
widget.plugAirDevice.setBtEq(oldValue);
}
});
}, onSelectionChanged: (selected) {
widget.plugAirDevice.setBtEq(selected);
});
showDialog(
context: context,
builder: (BuildContext context) => dialog,
);
},
),
ListTile(
enabled: widget.device.deviceControl.isConnected,
leading: const Icon(Icons.restart_alt),
title: const Text("Reset Device Presets"),
onTap: () {
if (BLEMidiHandler.instance().connectedDevice != null) {
AlertDialogs.showConfirmDialog(context,
title: "Reset device presets",
cancelButton: "Cancel",
confirmButton: "Reset",
confirmColor: Colors.red,
description: "Are you sure?", onConfirm: (val) {
if (val) widget.device.resetNuxPresets();
});
}
},
),
SwitchListTile(
secondary: const Icon(Icons.eco),
title: const Text("Eco Mode"),
value: widget.device.ecoMode,
onChanged: widget.device.deviceControl.isConnected
? (val) {
setState(
() {
widget.device.setEcoMode(val);
},
);
}
: null),
const Divider()
],
);
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/PlugAirUsbSettings.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/widgets/thickSlider.dart';
import 'package:mighty_plug_manager/bluetooth/devices/NuxMightyPlugAir.dart';
import '../../../bluetooth/NuxDeviceControl.dart';
class RouteModel {
final String name;
final int value;
final String schemeAsset;
const RouteModel(
{required this.name, required this.value, required this.schemeAsset});
}
class PlugAirUsbSettings extends StatefulWidget {
static const List routes = [
RouteModel(
name: "Normal",
value: 1,
schemeAsset: "assets/images/route_normal_mp2.png"),
RouteModel(
name: "Reamp", value: 0, schemeAsset: "assets/images/route_reamp.png"),
RouteModel(
name: "Dry Out",
value: 2,
schemeAsset: "assets/images/route_dryout.png"),
];
const PlugAirUsbSettings({Key? key}) : super(key: key);
@override
State createState() => _PlugAirUsbSettingsState();
}
class _PlugAirUsbSettingsState extends State {
final device = NuxDeviceControl.instance().device as NuxMightyPlug;
static const fontSize = TextStyle(fontSize: 20);
@override
void didChangeDependencies() {
// Adjust the provider based on the image type
for (var route in PlugAirUsbSettings.routes) {
precacheImage(AssetImage(route.schemeAsset), context);
}
super.didChangeDependencies();
}
Widget _modeButton(String mode) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Text(mode, style: fontSize),
);
}
void _setUsbInputValue(double value, bool skip) {
if (skip) {
device.config.inputVol = value.round();
} else {
device.setUsbInputVol(value.round());
}
setState(() {});
}
void _setUsbOutputValue(double value, bool skip) {
if (skip) {
device.config.outputVol = value.round();
} else {
device.setUsbOutputVol(value.round());
}
setState(() {});
}
@override
Widget build(BuildContext context) {
const routes = PlugAirUsbSettings.routes;
var routeMode = routes.firstWhere((r) => r.value == device.config.usbMode);
var arrayIndex = routes.indexOf(routeMode);
var selected = List.filled(routes.length, false);
selected[arrayIndex] = true;
return Scaffold(
appBar: AppBar(
title: const Text("USB Audio Settings"),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
"Route Mode",
style: fontSize,
),
),
ToggleButtons(
fillColor: Colors.blue,
selectedBorderColor: Colors.blue,
color: Colors.grey,
isSelected: selected,
onPressed: (index) {
var mode = routes[index];
var value = mode.value;
device.setUsbMode(value);
setState(() {});
},
children: [
for (var i = 0; i < routes.length; i++)
_modeButton(routes[i].name)
],
),
const SizedBox(
height: 8,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Image.asset(routeMode.schemeAsset),
),
ThickSlider(
activeColor: Colors.blue,
label: "Input Level",
value: device.config.inputVol.toDouble(),
min: 0,
max: 100,
labelFormatter: (val) {
return "${val.round()}%";
},
handleVerticalDrag: true,
onChanged: _setUsbInputValue,
onDragEnd: (value) => _setUsbInputValue(value, false),
),
ThickSlider(
activeColor: Colors.blue,
label: "Output Level",
value: device.config.outputVol.toDouble(),
min: 0,
max: 100,
labelFormatter: (val) {
return "${val.round()}%";
},
handleVerticalDrag: true,
onChanged: _setUsbOutputValue,
onDragEnd: (value) => _setUsbOutputValue(value, false),
)
],
),
),
),
);
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/PlugProMicSettings.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/bluetooth/devices/NuxMightyPlugPro.dart';
import '../../../bluetooth/NuxDeviceControl.dart';
import '../../../bluetooth/devices/communication/plugProCommunication.dart';
class PlugProMicSettings extends StatefulWidget {
const PlugProMicSettings({Key? key}) : super(key: key);
@override
State createState() => _PlugProMicSettingsState();
}
class _PlugProMicSettingsState extends State {
final device = NuxDeviceControl.instance().device as NuxMightyPlugPro;
final communication =
NuxDeviceControl.instance().device.communication as PlugProCommunication;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Microphone Settings"),
),
body: ListTileTheme(
minLeadingWidth: 0,
iconColor: Colors.white,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SwitchListTile(
title: const Text("Mute"),
onChanged: (value) {
device.config.micMute = value;
communication.setMicMute(device.config.micMute);
setState(() {});
},
value: device.config.micMute),
ListTile(
title: const Text("Mic Level"),
subtitle: Slider(
min: 0,
max: 100,
label:
"${((device.config.micVolume - 50) / 50 * 12).toStringAsFixed(1)} db",
divisions: 100,
value: device.config.micVolume.toDouble(),
onChanged: (val) {
device.config.micVolume = val.round();
communication.setMicLevel(device.config.micVolume);
setState(() {});
},
),
),
SwitchListTile(
title: const Text("Noise Gate"),
onChanged: (value) {
device.config.micNoiseGate = value;
communication.setMicNoiseGate(value);
setState(() {});
},
value: device.config.micNoiseGate),
ListTile(
enabled: device.config.micNoiseGate,
title: const Text("Gate Sensitivity"),
subtitle: Slider(
min: 0,
max: 100,
label: device.config.micNGSensitivity.toString(),
divisions: 100,
value: device.config.micNGSensitivity.toDouble(),
onChanged: device.config.micNoiseGate == false
? null
: (val) {
device.config.micNGSensitivity = val.round();
communication.setMicNoiseGateSens(
device.config.micNGSensitivity);
setState(() {});
},
),
),
ListTile(
enabled: device.config.micNoiseGate,
title: const Text("Gate Decay"),
subtitle: Slider(
min: 0,
max: 100,
label: device.config.micNGDecay.toString(),
divisions: 100,
value: device.config.micNGDecay.toDouble(),
onChanged: device.config.micNoiseGate == false
? null
: (val) {
device.config.micNGDecay = val.round();
communication
.setMicNoiseGateDecay(device.config.micNGDecay);
setState(() {});
},
),
),
],
),
),
),
);
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/PlugProSettings.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/pages/device_specific_settings/eq/MightySpaceSpeakerEQ.dart';
import 'package:mighty_plug_manager/bluetooth/devices/NuxMightySpace.dart';
import '../../../bluetooth/bleMidiHandler.dart';
import '../../../bluetooth/devices/NuxDevice.dart';
import '../../popups/alertDialogs.dart';
import 'PlugProMicSettings.dart';
import 'PlugProUsbSettings.dart';
import 'eq/PlugProEQSettings.dart';
class PlugProSettings extends StatefulWidget {
final NuxDevice device;
final bool mightySpace;
const PlugProSettings(
{Key? key, required this.device, required this.mightySpace})
: super(key: key);
@override
State createState() => _PlugProSettingsState();
}
class _PlugProSettingsState extends State {
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
enabled: widget.device.deviceControl.isConnected,
leading: const Icon(Icons.volume_up),
title: const Text("USB Audio Settings"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const PlugProUsbSettings()));
},
),
ListTile(
leading: const Icon(Icons.bluetooth_audio),
enabled: widget.device.deviceControl.isConnected,
title: const Text("Bluetooth Audio EQ"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const PlugProEQSettings()));
},
),
if (widget.mightySpace &&
(!widget.device.deviceControl.isConnected ||
(widget.device as NuxMightySpace).speakerAvailable))
ListTile(
leading: const Icon(Icons.speaker),
enabled: widget.device.deviceControl.isConnected,
title: const Text("Speaker EQ"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const SpaceSpeakerEQSettings()));
},
),
if (!widget.mightySpace)
ListTile(
enabled: widget.device.deviceControl.isConnected,
leading: const Icon(Icons.mic),
title: const Text("Microphone Settings"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const PlugProMicSettings()));
},
),
ListTile(
enabled: widget.device.deviceControl.isConnected,
leading: const Icon(Icons.restart_alt),
title: const Text("Reset Device Presets"),
onTap: () {
if (BLEMidiHandler.instance().connectedDevice != null) {
AlertDialogs.showConfirmDialog(context,
title: "Reset device presets",
cancelButton: "Cancel",
confirmButton: "Reset",
confirmColor: Colors.red,
description: "Are you sure?", onConfirm: (val) {
if (val) widget.device.resetNuxPresets();
});
}
},
),
const Divider()
],
);
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/PlugProUsbSettings.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/widgets/thickSlider.dart';
import 'package:mighty_plug_manager/bluetooth/devices/NuxMightyPlugPro.dart';
import '../../../bluetooth/NuxDeviceControl.dart';
import '../../../bluetooth/devices/features/proUsbSettings.dart';
import '../../widgets/common/modeControlRegular.dart';
class RouteModel {
final String name;
final int value;
final String schemeAsset;
final bool loopback;
final bool dryWet;
const RouteModel(
{required this.name,
required this.value,
required this.schemeAsset,
required this.loopback,
required this.dryWet});
}
class PlugProUsbSettings extends StatefulWidget {
static const List routes = [
RouteModel(
name: "Normal",
value: 1,
schemeAsset: "assets/images/route_normal.png",
loopback: true,
dryWet: true),
RouteModel(
name: "Reamp",
value: 2,
schemeAsset: "assets/images/route_reamp.png",
loopback: false,
dryWet: false),
RouteModel(
name: "Dry Out",
value: 0,
schemeAsset: "assets/images/route_dryout.png",
loopback: false,
dryWet: false),
];
const PlugProUsbSettings({Key? key}) : super(key: key);
@override
State createState() => _PlugProUsbSettingsState();
}
class _PlugProUsbSettingsState extends State {
final loopbackMask = 0x10;
final modeMask = 0x07;
final config =
NuxDeviceControl.instance().device.config as NuxPlugProConfiguration;
final usbSettings = NuxDeviceControl.instance().device as ProUsbSettings;
static const fontSize = TextStyle(fontSize: 20);
@override
void didChangeDependencies() {
// Adjust the provider based on the image type
for (var route in PlugProUsbSettings.routes) {
precacheImage(AssetImage(route.schemeAsset), context);
}
super.didChangeDependencies();
}
void _setUsbDryWetValue(double value, bool skip) {
if (skip) {
config.usbDryWet = value.round();
} else {
usbSettings.setUsbDryWetVol(value.round());
}
setState(() {});
}
void _setUsbRecordingValue(double value, bool skip) {
if (skip) {
config.recLevel = value.round();
} else {
usbSettings.setUsbRecordingVol(value.round());
}
setState(() {});
}
void _setUsbPlaybackValue(double value, bool skip) {
if (skip) {
config.playbackLevel = value.round();
} else {
usbSettings.setUsbPlaybackVol(value.round());
}
setState(() {});
}
@override
Widget build(BuildContext context) {
const routes = PlugProUsbSettings.routes;
var routeModeInt = config.routingMode & modeMask;
var routeMode = routes.firstWhere((r) => r.value == routeModeInt);
var modeIndex = routes.indexOf(routeMode);
var loopback = config.routingMode & loopbackMask != 0;
return Scaffold(
appBar: AppBar(
title: const Text("USB Audio Settings"),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
"Route Mode",
style: fontSize,
),
),
ModeControlRegular(
selected: modeIndex,
options: routes.map((e) => e.name).toList(),
onSelected: (index) {
var mode = routes[index];
var value = mode.value;
if (mode.loopback && loopback) value |= loopbackMask;
usbSettings.setUsbMode(value);
setState(() {});
},
textStyle: fontSize,
),
const SizedBox(
height: 8,
),
SwitchListTile(
title: const Text(
"Loopback",
style: fontSize,
),
subtitle: const Text(
"Redirect Bluetooth and microphone audio to USB input"),
value: loopback,
onChanged: !routeMode.loopback
? null
: (value) {
if (value) {
routeModeInt |= loopbackMask;
} else {
routeModeInt &= modeMask;
}
usbSettings.setUsbMode(routeModeInt);
setState(() {});
}),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Image.asset(routeMode.schemeAsset),
),
ThickSlider(
enabled: routeMode.dryWet,
activeColor: Colors.blue,
label: "Dry/Wet",
value: config.usbDryWet.toDouble(),
min: 0,
max: 100,
labelFormatter: (val) {
return "${config.usbDryWet}%";
},
handleVerticalDrag: true,
onChanged: _setUsbDryWetValue,
onDragEnd: (value) => _setUsbDryWetValue(value, false),
),
ThickSlider(
activeColor: Colors.blue,
label: "Recording Level",
value: config.recLevel.toDouble(),
min: 0,
max: 100,
labelFormatter: (val) {
return "${((val - 50) / 50 * 12).toStringAsFixed(2)} db";
},
handleVerticalDrag: true,
onChanged: _setUsbRecordingValue,
onDragEnd: (value) => _setUsbRecordingValue(value, false),
),
ThickSlider(
activeColor: Colors.blue,
label: "Playback Level",
value: config.playbackLevel.toDouble(),
min: 0,
max: 100,
labelFormatter: (val) {
return "${((val - 50) / 50 * 12).toStringAsFixed(2)} db";
},
handleVerticalDrag: true,
onChanged: _setUsbPlaybackValue,
onDragEnd: (value) => _setUsbPlaybackValue(value, false),
)
],
),
),
),
);
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/eq/MightySpaceSpeakerEQ.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/widgets/presets/effectEditors/EqualizerEditor.dart';
import 'package:mighty_plug_manager/bluetooth/devices/NuxMightySpace.dart';
import 'package:mighty_plug_manager/bluetooth/devices/communication/plugProCommunication.dart';
import '../../../../bluetooth/NuxDeviceControl.dart';
import '../../../../bluetooth/devices/effects/Processor.dart';
import '../../../../bluetooth/devices/effects/plug_pro/EQ.dart';
import 'eq_group.dart';
class SpaceSpeakerEQSettings extends StatefulWidget {
const SpaceSpeakerEQSettings({Key? key}) : super(key: key);
@override
State createState() => _SpaceSpeakerEQSettingsState();
}
class _SpaceSpeakerEQSettingsState extends State {
final device = NuxDeviceControl.instance().device as NuxMightySpace;
final communication =
NuxDeviceControl.instance().device.communication as PlugProCommunication;
bool _requestInProgress = false;
static const List defaultSpeakerEQ = [
0x32,
0x49,
0x4b,
0x40,
0x32,
0x43,
0x24,
0x32,
0x32,
0x22,
0x51
];
@override
void initState() {
super.initState();
_requestEQData(device.config.speakerEQGroup);
}
void _requestEQData(int index) {
_requestInProgress = true;
(device.communication as PlugProCommunication).requestSpeakerEQData(index);
}
Widget _buildGroupWidget() {
return EQGroup(
eqGroup: device.config.speakerEQGroup,
onChanged: (int? value) {
if (value != null) {
//request another
device.config.speakerEQGroup = value;
communication.setSpeakerEq(value);
_requestEQData(device.config.speakerEQGroup);
setState(() {});
}
},
);
}
List _buildButtons(EQTenBandSpeaker btEQ) {
return [
ElevatedButton(
child: const Text("Reset"),
onPressed: () {
for (int i = 0; i < btEQ.parameters.length; i++) {
if (device.config.speakerEQGroup == 0) {
btEQ.parameters[i].midiValue = defaultSpeakerEQ[i];
} else {
btEQ.parameters[i].value = 0;
}
NuxDeviceControl.instance()
.sendParameter(btEQ.parameters[i], false);
}
setState(() {});
}),
const SizedBox(width: 6),
ElevatedButton(
child: const Text("Save"),
onPressed: () {
communication.saveSpeakerEQGroup(device.config.speakerEQGroup);
})
];
}
@override
Widget build(BuildContext context) {
var btEQ = device.config.speakerEQ;
bool isPortrait =
MediaQuery.of(context).orientation == Orientation.portrait;
return Scaffold(
appBar: AppBar(
title: const Text("Speaker EQ Settings"),
),
body: StreamBuilder(
stream:
(device.communication as PlugProCommunication).speakerEQStream,
builder: (context, AsyncSnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
_requestInProgress) {
btEQ.setupFromNuxPayload(snapshot.data!);
_requestInProgress = false;
}
return ListTileTheme(
minLeadingWidth: 0,
iconColor: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (isPortrait)
ListTile(
leading: const Icon(Icons.speaker),
title: const Text("Speaker Settings"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: _buildButtons(btEQ)),
),
if (isPortrait) _buildGroupWidget(),
if (!isPortrait)
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildGroupWidget(),
Row(children: _buildButtons(btEQ))
],
),
Expanded(
child: EqualizerEditor(
eqEffect: btEQ,
enabled: true,
onChanged: _changeEQValue,
onChangedFinal: (parameter, value, oldValue) =>
_changeEQValue(parameter, value, false),
),
)
],
));
}),
);
}
void _changeEQValue(Parameter parameter, double value, bool skip) {
parameter.value = value;
setState(() {
if (!skip) NuxDeviceControl.instance().sendParameter(parameter, false);
});
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/eq/PlugProEQSettings.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/pages/device_specific_settings/eq/bt_audio_options.dart';
import 'package:mighty_plug_manager/UI/widgets/presets/effectEditors/EqualizerEditor.dart';
import 'package:mighty_plug_manager/bluetooth/devices/NuxMightyPlugPro.dart';
import 'package:mighty_plug_manager/bluetooth/devices/communication/plugProCommunication.dart';
import '../../../../bluetooth/NuxDeviceControl.dart';
import '../../../../bluetooth/devices/effects/Processor.dart';
import '../../../../bluetooth/devices/effects/plug_pro/EQ.dart';
import 'eq_group.dart';
class PlugProEQSettings extends StatefulWidget {
const PlugProEQSettings({Key? key}) : super(key: key);
@override
State createState() => _PlugProEQSettingsState();
}
class _PlugProEQSettingsState extends State {
final device = NuxDeviceControl.instance().device as NuxMightyPlugPro;
final communication =
NuxDeviceControl.instance().device.communication as PlugProCommunication;
bool _requestInProgress = false;
@override
void initState() {
super.initState();
_requestEQData(device.config.bluetoothGroup);
}
void _requestEQData(int index) {
_requestInProgress = true;
(device.communication as PlugProCommunication).requestBTEQData(index);
}
List _buildGroupWidget() {
return [
EQGroup(
eqGroup: device.config.bluetoothGroup,
onChanged: (int? value) {
if (value != null) {
//request another
device.config.bluetoothGroup = value;
communication.setBTEq(value);
_requestEQData(device.config.bluetoothGroup);
setState(() {});
}
},
),
BTAudioOptions(
btInvertChannel: device.config.bluetoothInvertChannel,
btEQMute: device.config.bluetoothEQMute,
onInvert: (invert) {
device.config.bluetoothInvertChannel = invert;
communication.setBTInvert(device.config.bluetoothInvertChannel);
setState(() {});
},
onMute: (mute) {
device.config.bluetoothEQMute = mute;
communication.setBTMute(device.config.bluetoothEQMute);
setState(() {});
})
];
}
List _buildButtons(EQTenBandBT btEQ) {
return [
ElevatedButton(
child: const Text("Reset"),
onPressed: () {
for (var param in btEQ.parameters) {
param.value = 0;
NuxDeviceControl.instance().sendParameter(param, false);
}
setState(() {});
}),
const SizedBox(width: 6),
ElevatedButton(
child: const Text("Save"),
onPressed: () {
communication.saveBTEQGroup(device.config.bluetoothGroup);
})
];
}
@override
Widget build(BuildContext context) {
var btEQ = device.config.bluetoothEQ;
bool isPortrait =
MediaQuery.of(context).orientation == Orientation.portrait;
return Scaffold(
appBar: AppBar(
title: const Text("Bluetooth EQ Settings"),
),
body: StreamBuilder(
stream:
(device.communication as PlugProCommunication).bluetoothEQStream,
builder: (context, AsyncSnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
_requestInProgress) {
btEQ.setupFromNuxPayload(snapshot.data!);
device.config.bluetoothInvertChannel = snapshot.data![12] > 0;
device.config.bluetoothEQMute = snapshot.data![13] > 0;
_requestInProgress = false;
}
return ListTileTheme(
minLeadingWidth: 0,
iconColor: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (isPortrait)
ListTile(
leading: const Icon(Icons.bluetooth),
title: const Text("Bluetooth Settings"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: _buildButtons(btEQ)),
),
if (isPortrait)
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _buildGroupWidget(),
),
if (!isPortrait)
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
..._buildGroupWidget(),
Row(children: _buildButtons(btEQ))
],
),
Expanded(
child: EqualizerEditor(
eqEffect: btEQ,
enabled: true,
onChanged: _changeEQValue,
onChangedFinal: (parameter, value, oldValue) =>
_changeEQValue(parameter, value, false),
),
)
],
));
}),
);
}
void _changeEQValue(Parameter parameter, double value, bool skip) {
parameter.value = value;
setState(() {
if (!skip) NuxDeviceControl.instance().sendParameter(parameter, false);
});
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/eq/bt_audio_options.dart
================================================
import 'package:flutter/material.dart';
import '../../../mightierIcons.dart';
class BTAudioOptions extends StatelessWidget {
final bool btInvertChannel;
final bool btEQMute;
final void Function(bool) onInvert;
final void Function(bool) onMute;
const BTAudioOptions(
{super.key,
required this.btInvertChannel,
required this.btEQMute,
required this.onInvert,
required this.onMute});
@override
Widget build(BuildContext context) {
return ToggleButtons(
fillColor: Colors.blue,
selectedBorderColor: Colors.blue,
color: Colors.grey,
isSelected: [btInvertChannel, btEQMute],
onPressed: (index) {
switch (index) {
case 0:
onInvert(!btInvertChannel);
break;
case 1:
onMute(!btEQMute);
break;
}
},
children: [
const Tooltip(
message: "Invert the phase of Bluetooth Audio.",
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: Icon(MightierIcons.sinewave, size: 40),
),
),
Tooltip(
message: "Mute Bluetooth audio.",
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: Icon(btEQMute ? Icons.volume_off : Icons.volume_up),
),
),
],
);
}
}
================================================
FILE: lib/UI/pages/device_specific_settings/eq/eq_group.dart
================================================
import 'package:flutter/material.dart';
class EQGroup extends StatelessWidget {
final int eqGroup;
final void Function(int?)? onChanged;
const EQGroup({super.key, required this.eqGroup, this.onChanged});
static const List> eqGroups = [
DropdownMenuItem(
value: 0,
child: Text("Group 1"),
),
DropdownMenuItem(
value: 1,
child: Text("Group 2"),
),
DropdownMenuItem(
value: 2,
child: Text("Group 3"),
),
DropdownMenuItem(
value: 3,
child: Text("Group 4"),
),
];
@override
Widget build(BuildContext context) {
return Row(
children: [
const Text(
"EQ Group",
style: TextStyle(fontSize: 16),
),
const SizedBox(
width: 8,
),
DropdownButton(
items: eqGroups,
onChanged: onChanged,
value: eqGroup,
)
],
);
}
}
================================================
FILE: lib/UI/pages/drum_editor/DrumStyleBottomSheet.dart
================================================
import 'package:flutter/material.dart';
import '../../widgets/scrollPicker.dart';
enum DrumStyleMode { flat, categorized }
class DrumStyleBottomSheet extends StatefulWidget {
final dynamic styleMap;
final DrumStyleMode mode;
final int selected;
final Function(int) onChange;
const DrumStyleBottomSheet(
{Key? key,
required this.styleMap,
required this.selected,
required this.onChange,
required this.mode})
: super(key: key);
@override
State createState() => _DrumStyleBottomSheetState();
}
class _DrumStyleBottomSheetState extends State {
List categoriesList = [];
List stylesList = [];
int categoriesIndex = 0;
int selectedStyle = 0;
@override
void initState() {
super.initState();
selectedStyle = widget.selected;
if (widget.mode == DrumStyleMode.categorized) {
categoriesList = widget.styleMap.keys.toList();
for (var key in widget.styleMap.keys) {
stylesList.addAll(widget.styleMap[key]!.keys as Iterable);
for (var style in widget.styleMap[key]!.keys) {
if (widget.styleMap[key]![style] == widget.selected) {
categoriesIndex = categoriesList.indexOf(key);
}
}
}
} else {
stylesList = widget.styleMap;
}
}
void _onCategoryChanged(int value, bool userGenerated) {
categoriesIndex = value;
if (userGenerated) {
var key = categoriesList[categoriesIndex];
var firstKey = widget.styleMap[key]!.keys.first as String;
selectedStyle = widget.styleMap[key]![firstKey];
setState(() {});
}
}
void _onStyleChanged(int value, bool userGenerated, bool finalChange) {
selectedStyle = value;
if (userGenerated) {
//find category
for (var cat in widget.styleMap.keys) {
for (var style in widget.styleMap[cat]!.keys) {
if (widget.styleMap[cat]![style] == value) {
categoriesIndex = categoriesList.indexOf(cat);
setState(() {});
break;
}
}
}
}
if (finalChange) widget.onChange(selectedStyle);
}
void _onFlatStyleChanged(int value, bool finalChange) {
selectedStyle = value;
widget.onChange(selectedStyle);
if (!finalChange) setState(() {});
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 400,
child: Column(
children: [
const SizedBox(
height: 40,
// ignore: unnecessary_const
child: Center(
child: Text(
"Select Style",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20),
),
),
),
const Divider(
thickness: 1,
),
Expanded(
child: widget.mode == DrumStyleMode.flat
? ScrollPicker(
initialValue: selectedStyle,
items: stylesList,
onChanged: (value) {
_onFlatStyleChanged(value, false);
},
onChangedFinal: (value, user) =>
_onFlatStyleChanged(value, true),
)
: Row(
children: [
Expanded(
child: ScrollPicker(
initialValue: categoriesIndex,
items: categoriesList,
onChanged: (value) {
_onCategoryChanged(value, true);
},
onChangedFinal: _onCategoryChanged,
),
),
Expanded(
child: ScrollPicker(
initialValue: selectedStyle,
items: stylesList,
onChanged: (value) {
_onStyleChanged(value, true, false);
},
onChangedFinal: (value, user) =>
_onStyleChanged(value, user, true),
),
)
],
),
),
],
),
);
}
}
================================================
FILE: lib/UI/pages/drum_editor/drumEditor.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/pages/drum_editor/drum_eq_bottom_sheet.dart';
import 'package:mighty_plug_manager/UI/pages/drum_editor/drumstyle_scroll_picker.dart';
import 'package:mighty_plug_manager/UI/pages/drum_editor/tap_buttons.dart';
import 'package:mighty_plug_manager/UI/widgets/circular_button.dart';
import 'package:mighty_plug_manager/UI/widgets/common/modeControlRegular.dart';
import 'package:mighty_plug_manager/UI/pages/drum_editor/tempoTrainerSheet.dart';
import 'package:mighty_plug_manager/bluetooth/devices/features/drumsTone.dart';
import '../../../bluetooth/devices/NuxDevice.dart';
import '../../../bluetooth/NuxDeviceControl.dart';
import '../../../bluetooth/devices/features/looper.dart';
import '../../../modules/tempo_trainer.dart';
import '../../widgets/thickSlider.dart';
import 'looperPage.dart';
enum DrumEditorLayout { standard, extendedToneControls }
enum DrumEditorMode { regular, trainer, looper }
class DrumEditor extends StatefulWidget {
static const fontStyle = TextStyle(fontSize: 18);
const DrumEditor({Key? key}) : super(key: key);
@override
State createState() => _DrumEditorState();
}
class _DrumEditorState extends State
with AutomaticKeepAliveClientMixin {
late dynamic _drumStyles;
DrumEditorLayout _layout = DrumEditorLayout.standard;
DrumEditorMode _mode = DrumEditorMode.regular;
int _selectedDrumPattern = 0;
late NuxDevice device;
@override
void initState() {
super.initState();
_drumStyles = NuxDeviceControl.instance().device.getDrumStyles();
NuxDeviceControl.instance().addListener(_onStateChanged);
}
@override
void dispose() {
super.dispose();
NuxDeviceControl.instance().removeListener(_onStateChanged);
}
Widget _createScrollPicker(
bool smallControls, bool showPlay, bool playEnabled) {
var picker = DrumStyleScrollPicker(
smallControls: smallControls,
selectedDrumPattern: _selectedDrumPattern,
layout: _layout,
device: device,
drumStyles: _drumStyles,
onChanged: _onScrollPickerChanged,
onChangedFinal: _onScrollPickerChangedFinal,
onComplete: () => setState(() {}));
if (!showPlay) return picker;
return Row(
children: [
Expanded(child: picker),
IconButton(
onPressed: !playEnabled || !NuxDeviceControl().isConnected
? null
: () {
device.setDrumsEnabled(!device.drumsEnabled);
if (device.drumsEnabled == false) {
TempoTrainer.instance().enable = false;
}
NuxDeviceControl.instance().forceNotifyListeners();
},
padding: const EdgeInsets.only(left: 12, right: 4),
iconSize: smallControls ? 44 : 56,
color: device.drumsEnabled ? Colors.orange : Colors.green,
icon: Icon(device.drumsEnabled ? Icons.stop : Icons.play_arrow))
],
);
}
Widget _landscapePlayControl(bool enabled) {
return CircularButton(
onPressed: TempoTrainer.instance().enable || !enabled
? null
: () {
device.setDrumsEnabled(!device.drumsEnabled);
if (device.drumsEnabled == false) {
TempoTrainer.instance().enable = false;
}
NuxDeviceControl.instance().forceNotifyListeners();
},
backgroundColor: device.drumsEnabled ? Colors.orange : Colors.green,
icon: device.drumsEnabled ? Icons.stop : Icons.play_arrow);
}
Widget _createModeControl(
{required bool looper,
required bool landscape,
required bool smallControls}) {
double height = smallControls ? 48 : 56;
if (landscape && !looper) {
return SizedBox(
height: height,
child: const Center(
child: Text(
"Trainer",
style: TextStyle(fontSize: 20),
)),
);
}
if (landscape) {
var controls = ["Trainer", "Looper"];
var mode = _mode;
if (mode == DrumEditorMode.regular) mode = DrumEditorMode.trainer;
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: height),
child: ModeControlRegular(
options: controls,
textStyle: DrumEditor.fontStyle,
selected: mode.index - 1,
onSelected: (index) {
_mode = DrumEditorMode.values[index + 1];
setState(() {});
}),
);
}
var controls = ["Regular", "Trainer"];
if (looper) controls.add("Looper");
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 40),
child: ModeControlRegular(
options: controls,
textStyle: DrumEditor.fontStyle,
selected: _mode.index,
onSelected: (index) {
_mode = DrumEditorMode.values[index];
setState(() {});
}),
),
);
}
Widget _drumLevelSlider(bool small) {
return ThickSlider(
min: 0,
max: 100,
maxHeight: small ? 40 : null,
activeColor: Colors.blue,
label: "Drums Level",
value: device.drumsVolume.toDouble(),
labelFormatter: (val) => "${device.drumsVolume.round()} %",
onChanged: (value, skip) {
setState(() {
device.setDrumsLevel(value, !skip);
});
},
);
}
bool _tempoControlsEnabled() {
return !TempoTrainer.instance().enable &&
(device is! Looper ||
(device is Looper && (device as Looper).loopState == 0));
}
Widget _tempoSlider(bool small, bool landscape) {
if (_mode != DrumEditorMode.trainer || landscape) {
return ThickSlider(
enabled: _tempoControlsEnabled(),
min: device.drumsMinTempo,
max: device.drumsMaxTempo,
maxHeight: small ? 40 : null,
skipEmitting: 5,
activeColor: Colors.blue,
label: "Tempo",
value: device.drumsTempo,
labelFormatter: (val) => "${device.drumsTempo.toStringAsFixed(1)} BPM",
onChanged: (val, skip) {
setState(() {
device.setDrumsTempo(val, !skip);
});
},
);
}
return const SizedBox.shrink();
}
List _toneSliders(bool small) {
if (device is! DrumsTone) return [];
var dev = device as DrumsTone;
return [
ThickSlider(
min: 0,
max: 100,
maxHeight: small ? 40 : null,
skipEmitting: 5,
activeColor: Colors.blue,
label: "Bass",
value: dev.drumsBass,
labelFormatter: (val) => "${dev.drumsBass.round()} %",
onChanged: (val, skip) {
dev.setDrumsTone(val, DrumsToneControl.bass, !skip);
setState(() {});
},
),
ThickSlider(
min: 0,
max: 100,
maxHeight: small ? 40 : null,
skipEmitting: 5,
activeColor: Colors.blue,
label: "Middle",
value: dev.drumsMiddle,
labelFormatter: (val) => "${dev.drumsMiddle.round()} %",
onChanged: (val, skip) {
dev.setDrumsTone(val, DrumsToneControl.middle, !skip);
setState(() {});
},
),
ThickSlider(
min: 0,
max: 100,
maxHeight: small ? 40 : null,
skipEmitting: 5,
activeColor: Colors.blue,
label: "Treble",
value: dev.drumsTreble,
labelFormatter: (val) => "${dev.drumsTreble.round()} %",
onChanged: (val, skip) {
dev.setDrumsTone(val, DrumsToneControl.treble, !skip);
setState(() {});
},
)
];
}
Widget _tapButton(bool smallControls) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TapButtons(
smallControls: smallControls,
device: device,
onTempoModified: _modifyTempo,
onTempoChanged: _onTempoChanged,
enabled: _tempoControlsEnabled()),
);
}
@override
Widget build(BuildContext context) {
super.build(context);
final mediaQuery = MediaQuery.of(context);
final bool portrait = mediaQuery.orientation == Orientation.portrait;
final bool smallControls =
portrait ? mediaQuery.size.height < 690 : mediaQuery.size.height < 400;
device = NuxDeviceControl.instance().device;
final bool hasLooper = device is Looper;
final bool looperEnabled = hasLooper && (device as Looper).loopState != 0;
_layout = _drumStyles is List
? DrumEditorLayout.standard
: DrumEditorLayout.extendedToneControls;
_selectedDrumPattern = device.selectedDrumStyle;
if (portrait) {
return Column(
mainAxisSize: MainAxisSize.max,
//padding: const EdgeInsets.all(16.0),
children: [
Card(
color: Colors.grey[850],
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(children: [
_createScrollPicker(smallControls, true, !looperEnabled),
const SizedBox(height: 6),
_drumLevelSlider(smallControls),
]),
),
),
_createModeControl(
looper: hasLooper,
landscape: false,
smallControls: smallControls),
Card(
color: Colors.grey[850],
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_tempoSlider(smallControls, false),
if (_mode == DrumEditorMode.regular)
_tapButton(smallControls),
if (_mode == DrumEditorMode.regular && device is DrumsTone)
..._toneSliders(smallControls),
if (_mode == DrumEditorMode.trainer)
TempoTrainerSheet(
smallControls: smallControls,
overtakeDrums: true,
enabled: !looperEnabled),
if (_mode == DrumEditorMode.looper)
LooperControl(
onStateChanged: _onStateChanged,
smallControls: smallControls,
),
],
),
),
),
],
);
}
var mode = _mode;
if (mode == DrumEditorMode.regular) mode = DrumEditorMode.trainer;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
flex: 1,
child: Card(
color: Colors.grey[850],
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_createScrollPicker(smallControls, false, false),
const SizedBox(height: 6),
_drumLevelSlider(smallControls),
_tempoSlider(smallControls, true),
_tapButton(smallControls),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (device is DrumsTone)
CircularButton(
icon: Icons.equalizer,
backgroundColor: Colors.blue,
onPressed: () {
showModalBottomSheet(
showDragHandle: true,
context: context,
builder: (context) {
return const DrumEQBottomSheet();
});
}),
_landscapePlayControl(!looperEnabled)
],
)),
]),
),
)),
const SizedBox(
width: 6,
),
Expanded(
flex: 1,
child: Card(
color: Colors.grey[850],
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: _createModeControl(
looper: hasLooper,
landscape: true,
smallControls: smallControls)),
const SizedBox(height: 6),
if (!hasLooper || mode == DrumEditorMode.trainer)
TempoTrainerSheet(
smallControls: smallControls,
overtakeDrums: false,
enabled: device.drumsEnabled ==
TempoTrainer.instance().enable &&
!looperEnabled,
),
if (mode == DrumEditorMode.looper)
LooperControl(
onStateChanged: _onStateChanged,
smallControls: smallControls,
),
],
),
),
))
],
);
}
void _onScrollPickerChanged(value) {
_selectedDrumPattern = value;
setState(() {});
}
void _onScrollPickerChangedFinal(
int value, bool userGenerated, NuxDevice? device) {
if (userGenerated) {
_selectedDrumPattern = value;
device?.setDrumsStyle(value);
//workaround for a bug in Mighty Plug
device?.setDrumsTempo(device.drumsTempo + 1, true);
device?.setDrumsTempo(device.drumsTempo - 1, true);
}
}
void _modifyTempo(double amount) {
setState(() {
double newTempo = device.drumsTempo + amount;
device.setDrumsTempo(newTempo, true);
});
}
void _onTempoChanged(double value) {
setState(() {
device.setDrumsTempo(value, true);
});
}
void _onStateChanged() {
_drumStyles = NuxDeviceControl.instance().device.getDrumStyles();
if (device.drumsEnabled == false &&
TempoTrainer.instance().enable == true) {
TempoTrainer.instance().enable = false;
}
setState(() {});
}
@override
bool get wantKeepAlive => true;
}
================================================
FILE: lib/UI/pages/drum_editor/drum_eq_bottom_sheet.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import '../../../bluetooth/devices/features/drumsTone.dart';
import '../../widgets/thickSlider.dart';
class DrumEQBottomSheet extends StatefulWidget {
const DrumEQBottomSheet({super.key});
@override
State createState() => _DrumEQBottomSheetState();
}
class _DrumEQBottomSheetState extends State {
@override
Widget build(BuildContext context) {
var dev = NuxDeviceControl.instance().device as DrumsTone;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ThickSlider(
min: 0,
max: 100,
maxHeight: 45,
skipEmitting: 5,
activeColor: Colors.blue,
label: "Bass",
value: dev.drumsBass,
handleVerticalDrag: false,
labelFormatter: (val) => "${dev.drumsBass.round()} %",
onChanged: (val, skip) {
dev.setDrumsTone(val, DrumsToneControl.bass, !skip);
setState(() {});
},
),
ThickSlider(
min: 0,
max: 100,
maxHeight: 45,
skipEmitting: 5,
activeColor: Colors.blue,
label: "Middle",
value: dev.drumsMiddle,
handleVerticalDrag: false,
labelFormatter: (val) => "${dev.drumsMiddle.round()} %",
onChanged: (val, skip) {
dev.setDrumsTone(val, DrumsToneControl.middle, !skip);
setState(() {});
},
),
ThickSlider(
min: 0,
max: 100,
maxHeight: 45,
skipEmitting: 5,
activeColor: Colors.blue,
label: "Treble",
value: dev.drumsTreble,
handleVerticalDrag: false,
labelFormatter: (val) => "${dev.drumsTreble.round()} %",
onChanged: (val, skip) {
dev.setDrumsTone(val, DrumsToneControl.treble, !skip);
setState(() {});
},
),
const SizedBox(
height: 15,
)
],
);
}
}
================================================
FILE: lib/UI/pages/drum_editor/drumstyle_scroll_picker.dart
================================================
import 'package:flutter/material.dart';
import '../../../bluetooth/devices/NuxDevice.dart';
import 'DrumStyleBottomSheet.dart';
import 'drumEditor.dart';
class DrumStyleScrollPicker extends StatelessWidget {
static const _fontStyle = TextStyle(fontSize: 20);
final int selectedDrumPattern;
final DrumEditorLayout layout;
final NuxDevice device;
final dynamic drumStyles;
// Events
final ValueChanged onChanged;
final Function(int, bool, NuxDevice) onChangedFinal;
final Function() onComplete;
final bool smallControls;
const DrumStyleScrollPicker(
{super.key,
required this.selectedDrumPattern,
required this.layout,
required this.device,
required this.drumStyles,
required this.onChanged,
required this.onChangedFinal,
required this.onComplete,
required this.smallControls});
String _getComplexListStyle(Map list) {
for (String cat in list.keys) {
for (String style in list[cat]!.keys) {
if (list[cat]![style] == selectedDrumPattern) return "$cat - $style";
}
}
return "";
}
@override
Widget build(BuildContext context) {
return Semantics(
label: "Drum style",
child: ListTile(
dense: smallControls,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
side: const BorderSide(width: 1, color: Colors.white)),
title: Text(
layout == DrumEditorLayout.extendedToneControls
? _getComplexListStyle(drumStyles)
: drumStyles[selectedDrumPattern],
style: _fontStyle,
overflow: TextOverflow.ellipsis,
),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
showModalBottomSheet(
context: context,
builder: (context) {
return DrumStyleBottomSheet(
styleMap: drumStyles,
mode: layout == DrumEditorLayout.extendedToneControls
? DrumStyleMode.categorized
: DrumStyleMode.flat,
selected: selectedDrumPattern,
onChange: (value) {
onChangedFinal(value, true, device);
},
);
}).whenComplete(() {
onComplete();
});
},
),
);
}
}
================================================
FILE: lib/UI/pages/drum_editor/looperPage.dart
================================================
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/widgets/thickSlider.dart';
import '../../../bluetooth/NuxDeviceControl.dart';
import '../../../bluetooth/devices/features/looper.dart';
import '../../../modules/tempo_trainer.dart';
import '../../widgets/circular_button.dart';
import '../../widgets/common/modeControlRegular.dart';
class LooperControl extends StatefulWidget {
final VoidCallback onStateChanged;
final bool smallControls;
const LooperControl(
{super.key, required this.onStateChanged, required this.smallControls});
@override
State createState() => _LooperControlState();
}
class _LooperControlState extends State {
static const fontSize = TextStyle(fontSize: 18);
late Looper _looper;
late LooperData _data = LooperData();
Timer? _blinkTimer;
StreamSubscription? _subscription;
bool _blinkOn = true;
@override
void initState() {
super.initState();
_looper = NuxDeviceControl().device as Looper;
_subscription = _looper.getLooperDataStream().listen(_onData);
_looper.requestLooperSettings();
_blinkTimer = Timer.periodic(const Duration(milliseconds: 400), _onBlink);
}
@override
void dispose() {
_subscription?.cancel();
_blinkTimer?.cancel();
super.dispose();
}
void _onData(LooperData data) {
_data = data;
widget.onStateChanged();
setState(() {});
}
void _onBlink(timer) {
_blinkOn = !_blinkOn;
setState(() {});
}
IconData getRecordButtonIcon() {
switch (_data.loopState) {
case 0:
case 1:
return Icons.fiber_manual_record;
case 2:
case 4:
return Icons.play_arrow;
case 3:
return Icons.fiber_smart_record;
case 9:
return Icons.pause;
default:
return Icons.fiber_manual_record;
}
}
Color _getRecordButtonColor() {
if (!_blinkOn && _data.loopState > 0 && _data.loopState < 4) {
return Colors.grey[700]!;
}
switch (_data.loopState) {
case 0:
case 1:
return Colors.red;
case 2:
case 4:
return Colors.green;
case 3:
return Colors.amber[700]!;
case 9:
return Colors.green;
default:
return Colors.red;
}
}
IconData getUndoButtonIcon() {
if (_data.loopUndoState == 2) return Icons.redo;
return Icons.undo;
}
Color _getUndoButtonColor() {
if (_data.loopUndoState == 2) return Colors.purple[700]!;
return Colors.blue;
}
bool getStopEnabled() {
switch (_data.loopState) {
case 2:
case 3:
return true;
default:
return false;
}
}
bool getClearEnabled() {
if (_data.loopState > 0 && _data.loopState < 5) return true;
return false;
}
bool getUndoEnabled() {
return _data.loopUndoState > 0;
}
@override
Widget build(BuildContext context) {
var looper = (NuxDeviceControl().device as Looper);
var connected = NuxDeviceControl().isConnected;
return Column(children: [
const SizedBox(
height: 8,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: LayoutBuilder(
builder: (content, constraints) {
double width = constraints.maxWidth;
double size = min((width - 28 * 4) / 8, 20);
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CircularButton(
icon: getRecordButtonIcon(),
iconPadding: size,
backgroundColor: _getRecordButtonColor(),
onPressed: connected
? () {
if (TempoTrainer.instance().enable == true) {
TempoTrainer.instance().enable = false;
}
looper.looperRecordPlay();
}
: null),
CircularButton(
icon: Icons.stop,
iconPadding: size,
backgroundColor: Colors.amber,
onPressed: getStopEnabled() ? looper.looperStop : null),
CircularButton(
icon: Icons.clear,
iconPadding: size,
backgroundColor: Colors.amber,
onPressed: getClearEnabled() ? looper.looperClear : null),
CircularButton(
iconPadding: size,
icon: getUndoButtonIcon(),
backgroundColor: _getUndoButtonColor(),
onPressed:
getUndoEnabled() ? looper.looperUndoRedo : null),
]);
},
),
),
const SizedBox(height: 8),
ListTile(
enabled: connected,
title: const Text("Recording", style: fontSize),
trailing: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 40),
child: ModeControlRegular(
options: const ["Normal", "Auto"],
textStyle: fontSize,
selected: looper.loopRecordMode,
onSelected: connected
? (index) {
looper.looperNrAr(index == 1);
setState(() {});
}
: null,
),
),
),
const SizedBox(
height: 8,
),
ThickSlider(
enabled: connected,
min: 0,
max: 100,
maxHeight: widget.smallControls ? 40 : null,
activeColor: Colors.blue,
label: "Looper Level",
value: looper.loopLevel.toDouble(),
labelFormatter: (val) => val.toInt().toString(),
onChanged: (value, skip) {
looper.looperLevel(value.toInt());
setState(() {});
},
onDragEnd: (value) {
looper.looperLevel(value.toInt());
setState(() {});
},
)
]);
}
}
================================================
FILE: lib/UI/pages/drum_editor/tap_buttons.dart
================================================
import 'package:flutter/material.dart';
import '../../../bluetooth/devices/NuxDevice.dart';
import '../../../utilities/DelayTapTimer.dart';
import 'drumEditor.dart';
class TapButtons extends StatelessWidget {
final NuxDevice device;
final Function(double) onTempoModified;
final Function(double) onTempoChanged;
final bool smallControls;
final bool enabled;
const TapButtons(
{super.key,
required this.device,
required this.onTempoModified,
required this.onTempoChanged,
required this.smallControls,
this.enabled = true});
void _onTapTempo() {
DelayTapTimer.addClickTime();
var bpm = DelayTapTimer.calculateBpm();
if (bpm != false) {
onTempoChanged(bpm);
}
}
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: smallControls ? 45 : 60),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
width: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(padding: EdgeInsets.zero),
onPressed: enabled ? () => onTempoModified(-5) : null,
child:
const Text("-5", semanticsLabel: "Tempo -5", softWrap: false),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: SizedBox(
width: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(padding: EdgeInsets.zero),
onPressed: enabled ? () => onTempoModified(-1) : null,
child: const Text("-1",
semanticsLabel: "Tempo -1", softWrap: false),
),
),
),
Expanded(
child: ElevatedButton(
onPressed: enabled ? _onTapTempo : null,
style: ButtonStyle(
overlayColor: MaterialStateProperty.resolveWith(
(states) {
return states.contains(MaterialState.pressed)
? Colors.lightBlue[100]
: null;
},
),
),
child: const Text(
"Tap Tempo",
style: DrumEditor.fontStyle,
textAlign: TextAlign.center,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: SizedBox(
width: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(padding: EdgeInsets.zero),
onPressed: enabled ? () => onTempoModified(1) : null,
child: const Text(
"+1",
softWrap: false,
semanticsLabel: "Tempo +1",
),
),
),
),
SizedBox(
width: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(padding: EdgeInsets.zero),
onPressed: enabled ? () => onTempoModified(5) : null,
child: const Text(
"+5",
softWrap: false,
semanticsLabel: "Tempo +5",
),
),
)
],
),
);
}
}
================================================
FILE: lib/UI/pages/drum_editor/tempoTrainerSheet.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/pages/drum_editor/drumEditor.dart';
import 'package:mighty_plug_manager/UI/widgets/common/modeControlRegular.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import '../../../modules/tempo_trainer.dart';
import '../../widgets/thickRangeSlider.dart';
import '../../widgets/thickSlider.dart';
class TempoTrainerSheet extends StatefulWidget {
final bool smallControls;
final bool overtakeDrums;
final bool enabled;
const TempoTrainerSheet(
{Key? key,
required this.smallControls,
required this.overtakeDrums,
this.enabled = true})
: super(key: key);
@override
State createState() => _TempoTrainerSheetState();
}
class _TempoTrainerSheetState extends State
with SingleTickerProviderStateMixin {
final _tempoTrainer = TempoTrainer.instance();
late AnimationController _animationController;
late Animation _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 16),
vsync: this,
)..repeat();
_animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
NuxDeviceControl.instance().addListener(_updateBpm);
}
@override
void dispose() {
NuxDeviceControl.instance().removeListener(_updateBpm);
_animationController.dispose();
super.dispose();
}
static const List _dropDownValues = ['Beats', 'Seconds'];
void _updateBpm() {
if (widget.overtakeDrums &&
NuxDeviceControl.instance().device.drumsEnabled !=
_tempoTrainer.enable) {
_tempoTrainer.enable = NuxDeviceControl.instance().device.drumsEnabled;
}
setState(() {});
}
Widget _progressPlayPauseButton(bool small) {
double size = small ? 60 : 80;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: size,
height: size,
child: AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget? child) {
double fill = _tempoTrainer.getTimerCountdown();
return CircularProgressIndicator(
value: fill,
strokeWidth: 10,
//backgroundColor: Colors.grey[300],
valueColor: const AlwaysStoppedAnimation(
Colors.green,
),
);
},
),
),
ElevatedButton(
onPressed: !widget.enabled || !NuxDeviceControl().isConnected
? null
: () async {
NuxDeviceControl.instance().device.setDrumsEnabled(false);
await Future.delayed(const Duration(milliseconds: 50));
setState(() {
_tempoTrainer.enable = !_tempoTrainer.enable;
});
},
style: ElevatedButton.styleFrom(
backgroundColor:
_tempoTrainer.enable ? Colors.orange : Colors.green,
shape: const CircleBorder(),
padding: EdgeInsets.all(small ? 14 : 20),
),
child: Icon(_tempoTrainer.enable ? Icons.stop : Icons.play_arrow),
),
],
),
);
}
@override
Widget build(BuildContext context) {
var device = NuxDeviceControl.instance().device;
return Column(children: [
ThickRangeSlider(
maxHeight: widget.smallControls ? 40 : null,
min: device.drumsMinTempo,
max: device.drumsMaxTempo,
enabled: widget.enabled,
activeColor: Colors.blue,
values: _tempoTrainer.tempoRange,
onChanged: (range, skip) {
_tempoTrainer.tempoRange = range;
setState(() {});
},
onDragEnd: (value) {
_tempoTrainer.saveConfig();
},
label: 'Tempo Range',
labelFormatter: (ranges) =>
"${ranges.start.round()} - ${ranges.end.round()}"),
ListTile(
enabled: widget.enabled,
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
title: const FittedBox(
alignment: Alignment.centerLeft,
fit: BoxFit.none,
child: Text(
"Mode",
style: TextStyle(fontSize: 20),
),
),
trailing: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 40),
child: ModeControlRegular(
options: _dropDownValues,
textStyle: DrumEditor.fontStyle,
selected: _tempoTrainer.changeMode.index,
onSelected: !widget.enabled
? null
: (index) {
setState(() {
_tempoTrainer.changeMode =
TempoChangeMode.values[index];
_tempoTrainer.saveConfig();
});
}),
),
),
ThickSlider(
min: 2,
max: 100,
enabled: widget.enabled,
maxHeight: widget.smallControls ? 40 : null,
activeColor: Colors.blue,
label: "Increase every",
labelFormatter: (val) {
return "${val.round()} ${_tempoTrainer.changeMode == TempoChangeMode.beat ? "beats" : "seconds"}";
},
value: _tempoTrainer.changeUnits.toDouble(),
onChanged: (value, skip) {
_tempoTrainer.changeUnits = value.round();
setState(() {});
},
onDragEnd: (value) {
_tempoTrainer.saveConfig();
}),
ThickSlider(
min: 1,
max: 20,
enabled: widget.enabled,
maxHeight: widget.smallControls ? 40 : null,
activeColor: Colors.blue,
label: "Increase by",
labelFormatter: (val) {
return "${val.round()} bpm";
},
value: _tempoTrainer.tempoStep.toDouble(),
onChanged: (value, skip) {
_tempoTrainer.tempoStep = value.round();
setState(() {});
},
onDragEnd: (value) {
_tempoTrainer.saveConfig();
}),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Expanded(child: SizedBox.shrink()),
_progressPlayPauseButton(widget.smallControls),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
_tempoTrainer.enable ? "${device.drumsTempo.round()} bpm" : "",
style: DrumEditor.fontStyle.copyWith(
color: widget.enabled ? Colors.white : Colors.grey[600]),
),
),
)
],
),
]);
}
}
================================================
FILE: lib/UI/pages/drumsPage.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/pages/drum_editor/drumEditor.dart';
class DrumsPage extends StatelessWidget {
const DrumsPage({super.key});
@override
Widget build(BuildContext context) {
return const SafeArea(
child: DrumEditor(),
);
}
}
================================================
FILE: lib/UI/pages/hotkeysMainPage.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/pages/hotkeysSetup.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import 'package:mighty_plug_manager/bluetooth/devices/features/tuner.dart';
import 'package:mighty_plug_manager/midi/ControllerConstants.dart';
import 'package:mighty_plug_manager/midi/controllers/MidiController.dart';
import '../../bluetooth/devices/features/looper.dart';
class HotkeysMainPage extends StatelessWidget {
final MidiController controller;
const HotkeysMainPage({Key? key, required this.controller}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Setup"),
),
body: ListView(
children: [
ListTile(
title: const Text("Channel/Preset Hotkeys"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => HotkeysSetup(
controller: controller,
category: HotkeyCategory.Channels,
))),
),
ListTile(
title: const Text("Effect Hotkeys"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => HotkeysSetup(
controller: controller,
category: HotkeyCategory.EffectSlots,
))),
),
ListTile(
title: const Text("Parameter Hotkeys"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => HotkeysSetup(
controller: controller,
category: HotkeyCategory.EffectParameters,
)))),
ListTile(
title: const Text("Drums Hotkeys"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => HotkeysSetup(
controller: controller,
category: HotkeyCategory.Drums,
)))),
if (NuxDeviceControl.instance().device is Looper)
ListTile(
title: const Text("Looper Hotkeys"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => HotkeysSetup(
controller: controller,
category: HotkeyCategory.Looper,
)))),
ListTile(
title: const Text("JamTracks Hotkeys"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => HotkeysSetup(
controller: controller,
category: HotkeyCategory.JamTracks,
)))),
if (NuxDeviceControl.instance().device is Tuner)
ListTile(
title: const Text("Misc Hotkeys"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => HotkeysSetup(
controller: controller,
category: HotkeyCategory.Misc,
)))),
],
),
);
}
}
================================================
FILE: lib/UI/pages/hotkeysSetup.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/popups/hotkeyInput.dart';
import 'package:mighty_plug_manager/UI/popups/midiControlInfo.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import 'package:mighty_plug_manager/bluetooth/devices/effects/MidiControllerHandles.dart';
import 'package:mighty_plug_manager/bluetooth/devices/effects/Processor.dart';
import 'package:mighty_plug_manager/midi/ControllerConstants.dart';
import 'package:mighty_plug_manager/midi/MidiControllerManager.dart';
import 'package:mighty_plug_manager/midi/controllers/MidiController.dart';
class HotkeysSetup extends StatefulWidget {
final MidiController controller;
final HotkeyCategory category;
const HotkeysSetup(
{Key? key, required this.controller, required this.category})
: super(key: key);
@override
State createState() => _HotkeysSetupState();
}
class _HotkeysSetupState extends State {
Widget buildWidget(String name, IconData? icon, Color? color,
HotkeyControl ctrl, int ctrlIndex, int ctrlSubIndex, bool sliderMode,
{Function()? infoButton}) {
//sliders are not enabled in hid mode
bool enabled =
!(sliderMode && widget.controller.type == ControllerType.Hid);
Widget trailing;
var hk =
widget.controller.getHotkeyByFunction(ctrl, ctrlIndex, ctrlSubIndex);
String key = hk == null ? "None" : hk.hotkeyName;
if (infoButton == null) {
trailing = Text(key);
} else {
trailing = Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: infoButton, icon: const Icon(Icons.info_outline)),
Text(key)
],
);
}
return ListTile(
enabled: enabled,
leading: Icon(
icon,
color: color,
),
minLeadingWidth: 0,
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) => HotkeyInputDialog().buildDialog(
context,
hotkeyName: name,
midiController: widget.controller,
ctrl: ctrl,
ctrlIndex: ctrlIndex,
ctrlSubIndex: ctrlSubIndex,
sliderMode: sliderMode),
).then((value) {
MidiControllerManager().cancelOnDataOverride();
setState(() {});
});
},
title: Text(name),
subtitle: enabled ? null : const Text("Not supported in HID mode"),
trailing: trailing);
}
List _buildChannelWidgets() {
List widgets = [];
widgets.add(buildWidget("Previous Channel", Icons.keyboard_arrow_left, null,
HotkeyControl.PreviousChannel, 0, 0, false));
widgets.add(buildWidget("Next Channel", Icons.keyboard_arrow_right, null,
HotkeyControl.NextChannel, 0, 0, false));
var colors =
NuxDeviceControl.instance().device.presets[0].channelColorsList;
for (int i = 0; i < NuxDeviceControl.instance().device.channelsCount; i++) {
widgets.add(buildWidget("Channel ${i + 1}", Icons.circle, colors[i],
HotkeyControl.ChannelByIndex, i, 0, false));
}
widgets.add(buildWidget("Previous Preset", Icons.keyboard_double_arrow_up,
null, HotkeyControl.PreviousPresetGlobal, 0, 0, false));
widgets.add(buildWidget("Next Preset", Icons.keyboard_double_arrow_down,
null, HotkeyControl.NextPresetGlobal, 0, 0, false));
widgets.add(buildWidget(
"Previous Preset in Category",
Icons.keyboard_arrow_up,
null,
HotkeyControl.PreviousPresetCategory,
0,
0,
false));
widgets.add(buildWidget(
"Next Preset in Category",
Icons.keyboard_arrow_down,
null,
HotkeyControl.NextPresetCategory,
0,
0,
false));
return widgets;
}
List _buildEffectsWidgets() {
List widgets = [];
var dev = NuxDeviceControl.instance().device;
for (int i = 0; i < dev.processorList.length; i++) {
var fxid = dev.processorList[i].nuxFXID;
var slot = dev.getPreset(dev.selectedChannel).getSlotFromFXID(fxid)!;
//var count =
// dev.getPreset(dev.selectedChannel).getEffectsForSlot(prc).length;
//var index = fxid.toInt();
var name = dev.processorList[i].longName;
var icon = dev.processorList[i].icon;
var color = dev.processorList[i].color;
var switchable = dev.getPreset(dev.selectedChannel).slotSwitchable(slot);
var effects = dev.getPreset(dev.selectedChannel).getEffectsForSlot(slot);
var fx = effects[0];
if (switchable) {
widgets.add(buildWidget(
"Switch $name on",
icon,
color,
HotkeyControl.EffectSlotEnable,
fx.midiControlOn!.id.index,
0,
false));
widgets.add(buildWidget(
"Switch $name off",
icon,
color,
HotkeyControl.EffectSlotDisable,
fx.midiControlOff!.id.index,
0,
false));
widgets.add(buildWidget(
"Toggle $name",
icon,
color,
HotkeyControl.EffectSlotToggle,
fx.midiControlToggle!.id.index,
0,
false));
}
if (effects.length > 1) {
widgets.add(buildWidget(
"Previous $name",
icon,
color,
HotkeyControl.EffectDecrement,
fx.midiControlPrev!.id.index,
0,
false));
widgets.add(buildWidget(
"Next $name",
icon,
color,
HotkeyControl.EffectIncrement,
fx.midiControlNext!.id.index,
0,
false));
}
}
return widgets;
}
List _buildParametersWidgets() {
List widgets = [];
var dev = NuxDeviceControl.instance().device;
//add master volume
widgets.add(buildWidget("Volume", Icons.volume_up, Colors.white,
HotkeyControl.MasterVolumeSet, 0, 0, true,
infoButton: null));
List effectHandles = [];
//enumerate all the slots in the signal chain
for (int i = 0; i < dev.processorList.length; i++) {
effectHandles.clear();
var fxid = dev.processorList[i].nuxFXID;
var prc = dev.getPreset(dev.selectedChannel).getSlotFromFXID(fxid)!;
var effects = dev.getPreset(dev.selectedChannel).getEffectsForSlot(prc);
for (int p = 0; p < effects.length; p++) {
for (var param in effects[p].parameters) {
if (param.midiControllerHandle != null &&
!effectHandles.contains(param.midiControllerHandle)) {
effectHandles.add(param.midiControllerHandle!);
}
}
}
var name = dev.processorList[i].longName;
var icon = dev.processorList[i].icon;
var color = dev.processorList[i].color;
for (var handle in effectHandles) {
var title = "$name ${handle.label}";
widgets.add(buildWidget(title, icon, color, HotkeyControl.ParameterSet,
handle.id.index, 0, true,
infoButton: () => _displayParameterInfo(effects, handle.id)));
if (handle.id == ControllerHandleId.delayTime) {
widgets.add(buildWidget("$name Tap Tempo", icon, color,
HotkeyControl.DelayTapTempo, handle.id.index, 0, false,
infoButton: null));
}
}
}
return widgets;
}
List _buildWidgetsRange(HotkeyControl from, HotkeyControl to) {
List widgets = [];
for (int i = from.index; i <= to.index; i++) {
HotkeyControl cat = HotkeyControl.values[i];
widgets.add(
buildWidget(cat.label!, cat.icon, null, cat, 0, 0, cat.sliderMode));
}
return widgets;
}
_displayParameterInfo(List effects, ControllerHandleId handleId) {
showDialog(
context: context,
builder: (BuildContext context) => MidiControlInfoDialog()
.buildDialog(context, effects: effects, handleId: handleId),
);
}
@override
Widget build(BuildContext context) {
List widgetList = [];
String title = "";
switch (widget.category) {
case HotkeyCategory.Channels:
widgetList = _buildChannelWidgets();
title = "Channel Hotkeys";
break;
case HotkeyCategory.EffectSlots:
widgetList = _buildEffectsWidgets();
title = "Effect Hotkeys";
break;
case HotkeyCategory.EffectParameters:
widgetList = _buildParametersWidgets();
title = "Parameter Hotkeys";
break;
case HotkeyCategory.Drums:
title = "Drums Hotkeys";
widgetList = _buildWidgetsRange(
HotkeyControl.DrumsStartStop, HotkeyControl.DrumsNextStyle);
break;
case HotkeyCategory.Looper:
title = "Looper Hotkeys";
widgetList = _buildWidgetsRange(
HotkeyControl.LooperRecord, HotkeyControl.LooperLevel);
break;
case HotkeyCategory.JamTracks:
title = "JamTracks Hotkeys";
widgetList = _buildWidgetsRange(
HotkeyControl.JamTracksPlayPause, HotkeyControl.JamTracksABRepeat);
break;
case HotkeyCategory.Misc:
title = "Misc Hotkeys";
widgetList = _buildWidgetsRange(
HotkeyControl.ToggleTuner, HotkeyControl.ToggleTuner);
}
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: ListView(
children: widgetList,
),
);
}
}
================================================
FILE: lib/UI/pages/jamTracks.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
//import 'package:audio_picker/audio_picker.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/audio/setlistPage.dart';
import 'package:mighty_plug_manager/audio/setlistsPage.dart';
import 'package:mighty_plug_manager/audio/trackdata/trackData.dart';
import 'package:mighty_plug_manager/audio/tracksPage.dart';
import 'package:mighty_plug_manager/platform/presetsStorage.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../audio/models/setlist.dart';
import '../../audio/setlist_player/setlistPlayerState.dart';
import '../../audio/widgets/jamtracksView.dart';
import '../../platform/platformUtils.dart';
import '../widgets/common/nestedWillPopScope.dart';
class JamTracks extends StatefulWidget {
const JamTracks({Key? key}) : super(key: key);
static final GlobalKey jamtracksNavigator = GlobalKey();
@override
State createState() => _JamTracksState();
}
class _JamTracksState extends State
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
late TabController cntrl;
final SetlistPlayerState playerState = SetlistPlayerState.instance();
Permission? _mediaPermission;
@override
void initState() {
super.initState();
DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
if (PlatformUtils.isAndroid) {
deviceInfoPlugin.androidInfo.then((androidInfo) {
int sdk = androidInfo.version.sdkInt;
if (sdk < 33) {
_mediaPermission = Permission.storage;
} else {
_mediaPermission = Permission.audio;
}
setState(() {});
});
} else if (PlatformUtils.isIOS) {
_mediaPermission = Permission.storage;
}
cntrl = TabController(length: 2, vsync: this);
cntrl.addListener(() {
if (cntrl.index == 0) setState(() {});
});
PresetsStorage().waitLoading().then((value) {
TrackData().waitLoading().then((value) {
if (mounted) setState(() {});
});
});
playerState.addListener(onPlayerStateChange);
}
@override
void dispose() {
super.dispose();
cntrl.dispose();
playerState.removeListener(onPlayerStateChange);
}
void onPlayerStateChange() {
setState(() {});
}
Widget showSetlists() {
var innerContext = JamTracks.jamtracksNavigator.currentContext!;
return Setlists(
onAllTracksSelect: () {
Navigator.pushNamed(innerContext, '/setlist',
arguments: SetlistArguments(TrackData().allTracks, true));
},
onSetlistSelect: (setlist) {
Navigator.pushNamed(innerContext, '/setlist',
arguments: SetlistArguments(setlist, false))
.then((value) => onPlayerStateChange());
},
);
}
Widget mainView() {
return Navigator(
key: JamTracks.jamtracksNavigator,
onGenerateRoute: (settings) {
if (settings.name == '/') {
return MaterialPageRoute(
builder: (context) {
return Column(
children: [
TabBar(
tabs: const [Tab(text: "Setlists"), Tab(text: "Tracks")],
controller: cntrl,
),
Expanded(
child: TabBarView(
controller: cntrl,
children: [
showSetlists(),
const TracksPage(),
],
),
),
],
);
},
);
} else if (settings.name == "/setlist") {
return MaterialPageRoute(
builder: (context) {
final SetlistArguments arguments =
settings.arguments as SetlistArguments;
return SetlistPage(
setlist: arguments.setlist,
readOnly: arguments.readOnly,
);
},
);
}
return null;
},
);
}
Widget _permissionInfo() {
return Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
textAlign: TextAlign.center,
"To access your music files and play along to your favorite songs, the app requires permission to access your device's media library."),
const SizedBox(
height: 30,
),
SizedBox(
height: 60,
child: ElevatedButton(
child: const Text("Grant access to Media Library "),
onPressed: () async {
Stopwatch stopwatch = Stopwatch()..start();
var status = await _mediaPermission!.request();
stopwatch.stop();
if (status == PermissionStatus.permanentlyDenied &&
stopwatch.elapsedMilliseconds < 500) {
await openAppSettings();
}
setState(() {});
},
),
),
],
),
),
);
}
Widget _jamtracksWidget() {
return NestedWillPopScope(
onWillPop: () {
if (playerState.expanded) {
playerState.toggleExpanded();
return Future.value(false);
} else if (JamTracks.jamtracksNavigator.currentState?.canPop() ??
false) {
JamTracks.jamtracksNavigator.currentState?.pop();
return Future.value(false);
}
return Future.value(true);
},
child: JamtracksView(child: mainView()));
}
@override
Widget build(BuildContext context) {
super.build(context);
return SafeArea(
child: FutureBuilder(
future: _mediaPermission?.status,
builder:
(BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
switch (snapshot.data) {
case PermissionStatus.denied:
return _permissionInfo();
case PermissionStatus.granted:
return _jamtracksWidget();
default:
return const Text("Permission declined");
}
}
return const SizedBox();
},
),
);
}
@override
bool get wantKeepAlive => true;
}
class SetlistArguments {
final Setlist setlist;
final bool readOnly;
SetlistArguments(this.setlist, this.readOnly);
}
================================================
FILE: lib/UI/pages/midiControllers.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/pages/hotkeysMainPage.dart';
import 'package:mighty_plug_manager/UI/theme.dart';
import 'package:mighty_plug_manager/UI/widgets/MidiDeviceTile.dart';
import 'package:mighty_plug_manager/bluetooth/bleMidiHandler.dart';
import 'package:mighty_plug_manager/midi/MidiControllerManager.dart';
import 'package:mighty_plug_manager/midi/controllers/MidiController.dart';
import '../../bluetooth/ble_controllers/BLEController.dart';
class MidiControllers extends StatefulWidget {
const MidiControllers({Key? key}) : super(key: key);
@override
State createState() => _MidiControllersState();
}
class _MidiControllersState extends State {
final ctrl = MidiControllerManager();
final midiHandler = BLEMidiHandler.instance();
@override
void initState() {
super.initState();
MidiControllerManager().addListener(onControllersUpdate);
}
@override
void dispose() {
super.dispose();
MidiControllerManager().removeListener(onControllersUpdate);
}
onControllersUpdate() {
setState(() {});
}
_onControllerSettings(MidiController controller) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => HotkeysMainPage(
controller: controller,
)));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("MIDI/HID Remote Control"),
),
body: StreamBuilder(
stream: midiHandler.status,
builder: (context, snapshot) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ListTile(
title: Text("Available Devices",
style: AppThemeConfig.ListTileHeaderStyle),
dense: true,
trailing: !ctrl.isScanning
? null
: const SizedBox(
width: 15,
height: 15,
child: CircularProgressIndicator.adaptive()),
),
Flexible(
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 250),
child: ListView.builder(
itemBuilder: (context, index) {
var dev = ctrl.controllers[index];
return MidiControllerTile(
controller: dev,
onTap: () async {
if (!dev.connected) {
await dev.connect();
setState(() {});
} else {
_onControllerSettings(dev);
}
},
onSettings: () => _onControllerSettings(dev),
);
},
itemCount: ctrl.controllers.length,
),
),
),
ElevatedButton(
onPressed: ctrl.isScanning ? ctrl.stopScan : ctrl.startScan,
child: !ctrl.isScanning
? const Text("Start Scanning")
: const Text("Stop Scanning")),
if (midiHandler.bleState == BleState.off)
const Text("Enable Bluetooth to discover BLE MIDI devices"),
const Divider(),
],
);
}),
);
}
}
================================================
FILE: lib/UI/pages/mighty_patches_importer.dart
================================================
// import the necessary packages
/*
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/popups/savePreset.dart';
import 'package:mighty_plug_manager/UI/widgets/common/nestedWillPopScope.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:http/http.dart' as http;
import 'package:qr_utils/qr_utils.dart';
// create a stateful widget for the page
class MightyPatchesPage extends StatefulWidget {
const MightyPatchesPage({super.key});
@override
_MightyPatchesPageState createState() => _MightyPatchesPageState();
}
// create the state class for the widget
class _MightyPatchesPageState extends State {
// create a web view controller
late WebViewController _controller;
bool _fromAppBar = false;
@override
void initState() {
super.initState();
_fromAppBar = false;
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..enableZoom(false)
..setNavigationDelegate(NavigationDelegate(
onPageFinished: (url) {
_registerDOMChanged();
},
onProgress: (progress) {
_addImportButtons();
},
))
..addJavaScriptChannel(
"flutter_inappwebview",
onMessageReceived: (message) {
// Handle the message received from JavaScript
String jsonMessage = message.message;
Map data = json.decode(jsonMessage);
// Extract title and URL from the JSON data
String title = data['title'];
String imageUrl = data['imageUrl'];
// Call your Flutter method with the title and image URL
_importImage(imageUrl, title);
},
)
..addJavaScriptChannel("flutter_domchange", onMessageReceived: (message) {
print("DOM Changed");
_addImportButtons();
});
_home();
}
void _home() {
_controller.loadRequest(Uri.parse('https://www.mightypatches.com/'));
}
void _registerDOMChanged() {
const String script = '''
var observeDOM = (function(){
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
return function( obj, callback ) {
if( !obj || obj.nodeType !== 1 ) return;
if( MutationObserver ){
// define a new observer
var mutationObserver = new MutationObserver(callback)
// have the observer observe for changes in children
mutationObserver.observe( obj, { childList:true, subtree:true })
return mutationObserver
}
// browser support fallback
else if( window.addEventListener ){
obj.addEventListener('DOMNodeInserted', callback, false)
obj.addEventListener('DOMNodeRemoved', callback, false)
}
}
})()
var observed = document.querySelector(".page-content, [data-elementor-type=single-post]");
observeDOM(observed, function(m){
flutter_domchange.postMessage("");
});
''';
_controller.runJavaScript(script);
}
void _addImportButtons() async {
// Evaluate JavaScript to find elements and add import buttons
const String script = '''
function getSmallestSizeImageUrl(srcset) {
// Split the srcset into individual URL-width pairs
const pairs = srcset.split(',').map(pair => pair.trim().split(' '));
// Find the pair with the smallest width
const smallestPair = pairs.reduce((smallest, current) => {
const currentWidth = parseInt(current[1], 10);
const smallestWidth = parseInt(smallest[1], 10);
return currentWidth < smallestWidth ? current : smallest;
}, pairs[0]);
// Return the URL from the smallest pair
return smallestPair[0];
}
var elements = document.querySelectorAll(".page-content [data-elementor-type=jet-listing-items]:not(:has(.import-button)), [data-elementor-type=single-post]:not(:has(.import-button))");
elements.forEach(function(element) {
var imageWidget = element.querySelector(".elementor-widget-image");
var imgElement = imageWidget.querySelector("img");
//var imageUrl = imgElement.src;
const srcset = imgElement.getAttribute('srcset');
const imageUrl = getSmallestSizeImageUrl(srcset);
var titleElement = element.querySelector(".elementor-page-title .elementor-heading-title,.page-content .elementor-heading-title");
var title = titleElement.textContent;
var importButton = document.createElement("a");
importButton.href = "javascript:void(0);"; // Placeholder href, you can change this
importButton.className = "import-button";
importButton.style.position = "absolute";
importButton.style.bottom = "0";
importButton.style.right = "0";
importButton.style.width = "50%";
importButton.style.height = "50px";
importButton.style.backgroundColor = "blue";
importButton.style.color = "white";
importButton.style.display = "flex";
importButton.style.alignItems = "center";
importButton.style.justifyContent = "center";
importButton.innerHTML = "Import";
importButton.onclick = function() {
// Send title and image URL to Flutter using the JavascriptChannel
var data = {
"title": title,
"imageUrl": imageUrl
};
flutter_inappwebview.postMessage(JSON.stringify(data));
};
imageWidget.appendChild(importButton);
});
''';
_controller.runJavaScript(script);
}
// create a method to handle the import button click
void _importImage(String imageUrl, String imageName) async {
// get the image data from the url using http stream
print("opening stream");
http.StreamedResponse response =
await http.Client().send(http.Request('GET', Uri.parse(imageUrl)));
print("Reading stream");
// get the byte data from the response stream
List byteData = await response.stream.toBytes();
print("decoding QR");
// scan the image data using qr utils
String? qrData = await QrUtils.scanImageFromData(byteData);
print("Showing data");
// do something with the qr data, such as showing a dialog
if (qrData != null) {
var device = NuxDeviceControl.instance().device;
var preset = device.setupDetachedPresetFromQRData(qrData);
var saveDialog = SavePresetDialog(
customName: imageName,
customPreset: preset,
device: device,
confirmColor: Theme.of(context).hintColor);
showDialog(
context: context,
builder: (BuildContext context) =>
saveDialog.buildDialog(device, context),
);
}
}
@override
Widget build(BuildContext context) {
return NestedWillPopScope(
onWillPop: () async {
if (_fromAppBar) return true;
if (await _controller.canGoBack()) {
_controller.goBack();
return false;
}
return true;
},
child: SafeArea(
child: Scaffold(
appBar: AppBar(
title: const Text('Mighty Patches'),
leading: IconButton(
icon: Icon(Icons.adaptive.arrow_back),
onPressed: () {
// Set the flag to true before popping the navigation
_fromAppBar = true;
Navigator.of(context).pop();
},
),
actions: [
IconButton(onPressed: _home, icon: const Icon(Icons.home))
],
),
body: WebViewWidget(controller: _controller)),
),
);
}
}
*/
================================================
FILE: lib/UI/pages/presetEditor.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/popups/alertDialogs.dart';
import 'package:mighty_plug_manager/audio/setlist_player/setlistPlayerState.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import '../popups/savePreset.dart';
import '../utils.dart';
import '../widgets/presets/channelSelector.dart';
import '../../bluetooth/devices/NuxDevice.dart';
import '../widgets/presets/trackEventsBlockInfo.dart';
import '../widgets/rounded_icon_button.dart';
class PresetEditor extends StatefulWidget {
const PresetEditor({super.key});
@override
_PresetEditorState createState() => _PresetEditorState();
}
class _PresetEditorState extends State {
late NuxDevice device;
@override
void initState() {
super.initState();
device = NuxDeviceControl.instance().device;
device.addListener(onDeviceDataChanged);
NuxDeviceControl.instance().addListener(onDeviceChanged);
SetlistPlayerState.instance().addListener(onJamTracksStateChange);
}
@override
void dispose() {
super.dispose();
device.removeListener(onDeviceDataChanged);
NuxDeviceControl.instance().removeListener(onDeviceChanged);
SetlistPlayerState.instance().removeListener(onJamTracksStateChange);
}
void onDeviceChanged() {
if (device != NuxDeviceControl.instance().device) {
device.removeListener(onDeviceDataChanged);
device = NuxDeviceControl.instance().device;
device.addListener(onDeviceDataChanged);
}
setState(() {});
}
void onJamTracksStateChange() {
setState(() {});
}
void savePresetToDevice() {
if (device.deviceControl.isConnected) {
AlertDialogs.showConfirmDialog(context,
title: "Save preset to device",
cancelButton: "Cancel",
confirmButton: "Save",
confirmColor: Colors.red,
description: "Are you sure?", onConfirm: (val) {
if (val) device.saveNuxPreset();
});
}
}
void onDeviceDataChanged() {
setState(() {});
}
Widget wrapContainer(bool isExpanded, List children) {
if (isExpanded) {
return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 592),
child: Column(children: children),
);
} else {
return ListView(children: children);
}
}
@override
Widget build(BuildContext context) {
var layout = getEditorLayoutMode(MediaQuery.of(context));
bool uploadPresetEnabled =
device.deviceControl.isConnected && device.presetSaveSupport;
Widget ui = SafeArea(
child: wrapContainer(
layout == EditorLayoutMode.expand,
[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ButtonTheme(
minWidth: 55,
height: 45,
buttonColor: Colors.blue,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
RoundedIconButton(
onPressed: NuxDeviceControl.instance().changes.canUndo
? () {
var changes =
NuxDeviceControl.instance().changes;
if (changes.canUndo) changes.undo();
setState(() {});
}
: null,
tooltip: "Undo",
icon: const Icon(Icons.undo),
),
const SizedBox(
width: 2,
),
RoundedIconButton(
onPressed: NuxDeviceControl.instance().changes.canRedo
? () {
var changes =
NuxDeviceControl.instance().changes;
if (changes.canRedo) changes.redo();
setState(() {});
}
: null,
icon: const Icon(Icons.redo),
tooltip: "Redo",
//padding: EdgeInsets.zero,
),
],
),
Row(
children: [
ToggleButtons(
constraints: const BoxConstraints(
minWidth: 55,
maxWidth: 55,
minHeight: 45,
maxHeight: 45),
isSelected: [
!NuxDeviceControl.instance().changes.canUndo
],
selectedBorderColor: Colors.transparent,
borderColor: Colors.blue,
borderRadius: BorderRadius.circular(3),
color: Colors.white,
fillColor: Colors.blue,
disabledColor: Colors.grey,
onPressed:
NuxDeviceControl.instance().changes.canUndo ||
NuxDeviceControl.instance().changes.canRedo
? (val) {
var changes =
NuxDeviceControl.instance().changes;
if (changes.canUndo) {
//we can go back (that's bad though)
while (changes.canUndo) {
changes.undo();
}
} else {
while (changes.canRedo) {
changes.redo();
}
}
setState(() {});
}
: null,
children: const [
Tooltip(
message: "Switch before/after",
child: Icon(Icons.compare))
],
)
],
),
Row(
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
RoundedIconButton(
tooltip: "Save to device",
onPressed: !uploadPresetEnabled
? null
: savePresetToDevice,
icon: const Icon(Icons.save_alt),
),
const SizedBox(
width: 2,
),
RoundedIconButton(
tooltip: "Save as preset",
icon: const Icon(Icons.playlist_add),
onPressed: () {
var saveDialog = SavePresetDialog(
device: device,
confirmColor: Theme.of(context).hintColor);
showDialog(
context: context,
builder: (BuildContext context) =>
saveDialog.buildDialog(device, context),
);
},
)
],
),
],
)
],
),
),
),
if (layout == EditorLayoutMode.expand)
Flexible(child: ChannelSelector(device: device))
else
ChannelSelector(device: device)
],
),
);
var sps = SetlistPlayerState.instance();
if (sps.state != PlayerState.play ||
(sps.automation?.presetChangeEventsAvailable == false)) {
return ui;
} else {
return TrackEventsBlockInfo(
onBypass: () {
setState(() {});
},
child: ui,
);
}
}
}
================================================
FILE: lib/UI/pages/settings.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/popups/alertDialogs.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../../bluetooth/NuxDeviceControl.dart';
import '../../bluetooth/bleMidiHandler.dart';
import '../../bluetooth/ble_controllers/BLEController.dart';
import '../../bluetooth/devices/NuxDevice.dart';
import '../../bluetooth/devices/features/tuner.dart';
import '../../platform/simpleSharedPrefs.dart';
import '../mightierIcons.dart';
import '../widgets/deviceList.dart';
import 'DebugConsolePage.dart';
import 'developerPage.dart';
import 'midiControllers.dart';
import 'settings_advanced.dart';
import 'tunerPage.dart';
enum TimeUnit { BPM, Seconds }
const _timeUnit = ["BPM", "Seconds"];
class Settings extends StatefulWidget {
static bool devMode = false;
static String output = "";
const Settings({Key? key}) : super(key: key);
static void print(String value) {
if (output.isNotEmpty) output += "\n";
output += value;
}
@override
State createState() => _SettingsState();
}
class _SettingsState extends State {
late final BLEMidiHandler midiHandler;
String _version = "";
int devCounter = 0;
@override
void initState() {
super.initState();
NuxDeviceControl.instance().addListener(_deviceChanged);
midiHandler = BLEMidiHandler.instance();
PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
setState(() {
_version = packageInfo.version;
});
});
}
@override
void dispose() {
NuxDeviceControl.instance().removeListener(_deviceChanged);
super.dispose();
}
void _deviceChanged() {
setState(() {});
}
@override
Widget build(BuildContext context) {
final NuxDevice device = NuxDeviceControl.instance().device;
List items = Settings.output.split('\n');
return SafeArea(
child: ListView(
children: [
if (kDebugMode)
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
return Text(items[index]);
},
itemCount: items.length,
),
),
ListTileTheme(
minLeadingWidth: 0,
iconColor: Colors.white,
child: Column(
children: [
SwitchListTile(
title: const Text("Keep Screen On"),
value: SharedPrefs()
.getValue(SettingsKeys.screenAlwaysOn, false),
onChanged: (val) {
setState(() {
WakelockPlus.toggle(enable: val);
SharedPrefs().setValue(SettingsKeys.screenAlwaysOn, val);
});
},
),
ListTile(
title: const Text("Delay Time Unit"),
subtitle: Text(_timeUnit[SharedPrefs()
.getValue(SettingsKeys.timeUnit, TimeUnit.BPM.index)]),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
var dialog = AlertDialogs.showOptionDialog(context,
confirmButton: "OK",
cancelButton: "Cancel",
title: "Delay Time Unit",
confirmColor: Theme.of(context).hintColor,
value: SharedPrefs().getValue(
SettingsKeys.timeUnit, TimeUnit.BPM.index),
options: _timeUnit, onConfirm: (changed, newValue) {
if (changed) {
setState(() {
SharedPrefs()
.setValue(SettingsKeys.timeUnit, newValue);
});
}
});
showDialog(
context: context,
builder: (BuildContext context) => dialog,
);
},
),
ListTile(
enabled: !device.deviceControl.isConnected,
title: const Text("Device"),
subtitle: Text(device.productName),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
var dialog = AlertDialogs.showOptionDialog(context,
confirmButton: "OK",
cancelButton: "Cancel",
title: "Select Device",
confirmColor: Theme.of(context).hintColor,
value: NuxDeviceControl.instance().deviceIndex,
options: NuxDeviceControl.instance().deviceNameList,
onConfirm: (changed, newValue) {
if (changed) {
NuxDeviceControl.instance().deviceIndex = newValue;
setState(() {});
}
});
showDialog(
context: context,
builder: (BuildContext context) => dialog,
);
},
),
if (device.getAvailableVersions() > 1)
ListTile(
enabled: !device.deviceControl.isConnected,
title: const Text("Firmware Version"),
subtitle: Text(
device.getProductNameVersion(device.productVersion)),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
var dialog = AlertDialogs.showOptionDialog(context,
confirmButton: "OK",
cancelButton: "Cancel",
title: "Select Version",
confirmColor: Theme.of(context).hintColor,
value:
NuxDeviceControl.instance().deviceFirmwareVersion,
options:
NuxDeviceControl.instance().deviceVersionsList,
onConfirm: (changed, newValue) {
if (changed) {
NuxDeviceControl.instance().deviceFirmwareVersion =
newValue;
setState(() {});
}
});
showDialog(
context: context,
builder: (BuildContext context) => dialog,
);
},
),
//Automatically set matching cabinet when changing an amp
CheckboxListTile(
title: const Text("Set matching cabinets automatically"),
value:
SharedPrefs().getInt(SettingsKeys.changeCabs, 1) != 0,
onChanged: (value) {
setState(() {
if (value != null) {
SharedPrefs()
.setInt(SettingsKeys.changeCabs, value ? 1 : 0);
}
});
}),
const Divider(),
if (device.deviceControl.isConnected &&
device is Tuner &&
(device as Tuner).tunerAvailable)
ListTile(
leading: const Icon(MightierIcons.tuner),
title: const Text("Tuner"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => TunerPage(
device: device,
)));
},
),
device.getSettingsWidget(),
ListTile(
title: const Text("Remote Control"),
subtitle:
const Text("Use a MIDI/HID device to control the amp"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const MidiControllers()));
},
),
],
),
),
if (midiHandler.permissionGranted)
StreamBuilder(
stream: midiHandler.status,
builder: (BuildContext context, snapshot) {
return StreamBuilder(
builder: (BuildContext context, snapshot) {
var btOn = midiHandler.bleState == BleState.on;
if (!btOn) {
return const ListTile(
title: Text("Please, turn Bluetooth on!"),
);
}
bool scanning = midiHandler.isScanning;
bool connected =
NuxDeviceControl.instance().isConnected;
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey)),
height: 150,
child: DeviceList(),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: connected || scanning
? null
: () {
midiHandler.startScanning(true);
},
child: const Text("Scan"),
),
ElevatedButton(
onPressed: connected || !scanning
? null
: () {
midiHandler.stopScanning();
},
child: const Text("Stop Scanning"),
),
ElevatedButton(
onPressed: !connected
? null
: () {
midiHandler.disconnectDevice();
setState(() {});
},
child: const Text("Disconnect"),
),
],
)
],
);
},
stream: midiHandler.isScanningStream);
}),
if (!midiHandler.permissionGranted)
ListTile(
title: const Text(
"Please, grant location permission",
style: TextStyle(color: Colors.orange),
),
onTap: () async {
AlertDialogs.showLocationPrompt(context, true, () async {
await Future.delayed(const Duration(milliseconds: 1000));
setState(() {});
});
},
),
const Divider(),
ListTile(
title: const Text("Advanced Settings"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const AdvancedSettings()));
},
),
ListTile(
title: const Text("App Version"),
trailing: Text(_version),
onTap: () {
devCounter++;
if (devCounter == 7) {
Settings.devMode = true;
SharedPrefs().setInt(SettingsKeys.hiddenSources, 1);
setState(() {});
}
},
),
if (Settings.devMode)
ElevatedButton(
onPressed: () => device.communication.fillTestData(),
child: const Text("Fill test data")),
if (Settings.devMode)
ListTile(
title: const Text("Debug Console"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const DebugConsole()));
}),
if (Settings.devMode)
ListTile(
title: const Text("MIDI Commands Utility"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const DeveloperPage()));
}),
// ListTile(
// title: Text("More Info"),
// onTap: () {
// showAboutDialog(
// context: context,
// applicationIcon:
// Icon(MightierIcons.amp, color: Colors.blue, size: 30),
// applicationVersion: _version,
// applicationLegalese: "© 2021 Dian Iliev (Tuntori)",
// );
// },
// ),
],
),
);
}
}
================================================
FILE: lib/UI/pages/settings_advanced.dart
================================================
import 'package:flutter/material.dart';
import '../../bluetooth/NuxDeviceControl.dart';
import '../../platform/platformUtils.dart';
import '../../platform/simpleSharedPrefs.dart';
import 'calibration.dart';
class AdvancedSettings extends StatefulWidget {
const AdvancedSettings({super.key});
@override
State createState() => _AdvancedSettingsState();
}
class _AdvancedSettingsState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Advanced Settings"),
),
body: ListView(
children: [
ListTile(
enabled: NuxDeviceControl().isConnected,
title: const Text("Calibrate BT Audio Latency"),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const Calibration()));
},
),
if (PlatformUtils.isAndroid)
CheckboxListTile(
title: const Text("Use legacy waveform decoder"),
subtitle: const Text(
"Enable this if you experience crashes when editing tracks"),
value:
SharedPrefs().getInt(SettingsKeys.legacyDecoder, 0) != 0,
onChanged: (value) {
setState(() {
if (value != null) {
SharedPrefs()
.setInt(SettingsKeys.legacyDecoder, value ? 1 : 0);
}
});
}),
CheckboxListTile(
title: const Text("Hide non-applicable presets"),
value: SharedPrefs()
.getInt(SettingsKeys.hideNotApplicablePresets, 0) !=
0,
onChanged: (value) {
if (value != null) {
SharedPrefs().setInt(
SettingsKeys.hideNotApplicablePresets, value ? 1 : 0);
}
setState(() {});
NuxDeviceControl().forceNotifyListeners();
}),
],
));
}
}
================================================
FILE: lib/UI/pages/tunerPage.dart
================================================
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:math' as math;
import '../../bluetooth/devices/NuxDevice.dart';
import '../../bluetooth/devices/features/tuner.dart';
import '../../midi/ControllerConstants.dart';
import '../../midi/MidiControllerManager.dart';
class TunerPage extends StatefulWidget {
final NuxDevice device;
const TunerPage({super.key, required this.device}) : assert(device is Tuner);
static const indicatorsAmount = 21;
//how many cents is half of the scale
static const scaleSize = 50;
static const colors = [
Colors.white,
Color.fromARGB(255, 119, 202, 29),
Colors.yellow,
Colors.red
];
static const colorsInactive = [
Color(0x32FFFFFF),
Color.fromARGB(50, 119, 202, 29),
Color(0x32FFEB3B),
Color(0x32F44336)
];
static List notes = [
"A ",
"A♯",
"B ",
"C ",
"C♯",
"D ",
"D♯",
"E ",
"F ",
"F♯",
"G ",
"G♯"
];
@override
State createState() => _TunerPageState();
}
class _TunerPageState extends State {
late Tuner _tuner;
late TunerData data = TunerData();
StreamSubscription? _subscription;
StreamSubscription? _hotkeySub;
bool _tunerEnabled = false;
bool _validDetection = false;
Timer? _timeout;
final List> _modeItems = [];
final List> _referenceItems = [];
@override
void initState() {
super.initState();
_tunerEnabled = false;
_tuner = widget.device as Tuner;
_subscription = _tuner.getTunerDataStream().listen(onData);
_hotkeySub = MidiControllerManager()
.controllerStream
.listen(_onMidiControllerMessage);
//if not requesting 2 times, the device does not answer
_tuner.tunerRequestSettings();
_tuner.tunerRequestSettings();
for (var mode in TunerMode.values) {
_modeItems.add(DropdownMenuItem(
value: mode, child: Text(Tuner.modesString[mode.index])));
}
for (int i = 0; i < 21; i++) {
_referenceItems
.add(DropdownMenuItem(value: i, child: Text("${430 + i} Hz")));
}
}
@override
void dispose() {
_timeout?.cancel();
_subscription?.cancel();
_hotkeySub?.cancel();
_tuner.tunerEnable(false);
super.dispose();
}
void _onMidiControllerMessage(HotkeyControl event) {
if (event == HotkeyControl.ToggleTuner) {
Navigator.maybePop(context);
}
}
void onData(TunerData event) {
if (!_tunerEnabled) {
_tuner.tunerEnable(true);
_tunerEnabled = true;
} else {
_validDetection = true;
}
data = event;
setState(() {});
_timeout?.cancel();
_timeout = Timer(const Duration(seconds: 1), _onTimeout);
}
void _onTimeout() {
if (data.mode == TunerMode.chromatic &&
data.note == 0 &&
data.cents == 50) {
setState(
() {
_validDetection = false;
},
);
}
}
Widget _indicator() {
return LayoutBuilder(
builder: (context, constraints) {
var width = (constraints.maxWidth / TunerPage.indicatorsAmount) * 0.7;
var height = constraints.maxHeight;
int centralIndex = (TunerPage.indicatorsAmount / 2).floor();
int activeIndex = (math.max(
math.min((data.cents - 50), TunerPage.scaleSize),
-TunerPage.scaleSize) *
centralIndex /
TunerPage.scaleSize)
.round() +
centralIndex;
List indicators = [];
for (int i = 0; i < TunerPage.indicatorsAmount; i++) {
int distance = (centralIndex - i).abs();
bool central = i == centralIndex;
int colorIndex = 3;
if (distance == 0) {
colorIndex = 0;
} else if (distance < centralIndex / 10 || distance == 1) {
colorIndex = 1;
} else if (distance < centralIndex / 2) {
colorIndex = 2;
}
bool active = _validDetection &&
(data.note != 0 || data.stringNumber != 0 || data.cents != 0);
Color color = active && i == activeIndex
? TunerPage.colors[colorIndex]
: TunerPage.colorsInactive[colorIndex];
indicators.add(Container(
width: central ? width * 1.5 : width,
height: central ? height : height * 0.7, // - distance * 2,
color: color,
));
}
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: indicators,
);
},
);
}
Widget _noteDisplay() {
String wholeNote = TunerPage.notes[data.note].characters.first;
String sharp = TunerPage.notes[data.note].characters.last;
String stringNumber = "";
if (!_validDetection ||
(data.note == 0 && data.stringNumber == 0 && data.cents == 0)) {
wholeNote = "-";
sharp = " ";
} else if (data.mode == TunerMode.bass && data.stringNumber == 5) {
stringNumber = "L";
} else if (data.stringNumber > 0 && data.mode != TunerMode.chromatic) {
stringNumber = data.stringNumber.toString();
}
return Stack(
alignment: Alignment.topCenter,
children: [
Transform.translate(
offset: const Offset(-47, 42),
child: Text(
stringNumber,
style: const TextStyle(fontSize: 55, fontWeight: FontWeight.bold),
),
),
Text(
wholeNote,
style: const TextStyle(fontSize: 100),
),
Transform.translate(
offset: const Offset(44, -14),
child: Text(
sharp,
style: const TextStyle(fontSize: 50),
),
),
],
);
}
@override
Widget build(BuildContext context) {
bool isPortrait =
MediaQuery.of(context).orientation == Orientation.portrait;
late List tunerWidgets;
if (isPortrait) {
tunerWidgets = [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(height: 100, child: _indicator()),
),
_noteDisplay(),
];
} else {
tunerWidgets = [
Row(
children: [
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(height: 100, child: _indicator()),
),
),
Expanded(flex: 1, child: _noteDisplay()),
IconButton(
onPressed: () {
_tuner.tunerMute(!data.muted);
},
iconSize: 56,
icon: Icon(data.muted ? Icons.volume_off : Icons.volume_up))
],
)
];
}
return Scaffold(
appBar: AppBar(title: const Text("Tuner")),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(children: [
...tunerWidgets,
ListTile(
title: const Text("Mode"),
trailing: DropdownButton(
value: data.mode,
items: _modeItems,
onChanged: (index) {
setState(() {
data.clear();
_tuner.tunerSetMode(index!);
});
}),
),
ListTile(
title: const Text("Reference"),
trailing: DropdownButton(
value: data.referencePitch,
items: _referenceItems,
onChanged: (index) {
setState(() {
_tuner.tunerSetReferencePitch(index!);
});
}),
),
if (isPortrait)
IconButton(
onPressed: () {
_tuner.tunerMute(!data.muted);
},
iconSize: 56,
icon: Icon(data.muted ? Icons.volume_off : Icons.volume_up))
]),
),
);
}
}
================================================
FILE: lib/UI/popups/alertDialogs.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
class AlertDialogs {
static TextEditingController? nameCtrl;
static final _inputFormKey = GlobalKey();
static showInfoDialog(BuildContext context,
{required String title,
required String description,
required String confirmButton,
Function()? onConfirm,
Color? confirmColor}) {
// set up the buttons
Widget continueButton = TextButton(
child: Text(
confirmButton,
style: TextStyle(color: confirmColor),
),
onPressed: () {
Navigator.of(context).pop();
onConfirm?.call();
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text(title),
content: Text(description),
actions: [
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
static showConfirmDialog(BuildContext context,
{required String title,
required String description,
required String confirmButton,
required String cancelButton,
Function(bool)? onConfirm,
Color? confirmColor}) {
// set up the buttons
Widget cancel = TextButton(
child: Text(cancelButton),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
onConfirm?.call(false);
},
);
Widget continueButton = TextButton(
child: Text(
confirmButton,
style: TextStyle(color: confirmColor),
),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
onConfirm?.call(true);
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text(title),
content: Text(description),
actions: [
cancel,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
static showInputDialog(BuildContext context,
{required String title,
required String description,
String confirmButton = "OK",
String cancelButton = "Cancel",
required String value,
bool selectAll = false,
Function(String)? onConfirm,
bool Function(String)? validation,
TextInputType? keyboardType,
String validationErrorMessage = "",
Color? confirmColor}) {
final nameCtrl = TextEditingController(text: value);
if (selectAll) {
nameCtrl.selection =
TextSelection(baseOffset: 0, extentOffset: value.length);
}
// set up the buttons
Widget cancel = TextButton(
child: Text(cancelButton),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
);
Widget continueButton = TextButton(
child: Text(
confirmButton,
style: TextStyle(color: confirmColor),
),
onPressed: () {
if (_inputFormKey.currentState!.validate()) {
Navigator.of(context, rootNavigator: true).pop();
onConfirm?.call(nameCtrl.text);
} else {
//error
}
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text(title),
content: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
key: _inputFormKey,
child: TextFormField(
decoration: InputDecoration(labelText: description),
controller: nameCtrl,
autofocus: true,
keyboardType: keyboardType,
//style: TextStyle(color: Colors.black),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter name';
}
if (validation != null && !validation(value)) {
return validationErrorMessage;
}
return null;
},
),
),
actions: [
cancel,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
static showOptionDialog(BuildContext context,
{required String title,
required String confirmButton,
required String cancelButton,
required List options,
required int value,
required Function(bool, int) onConfirm,
Function(int)? onSelectionChanged,
Color? confirmColor}) {
int selected = value;
// set up the buttons
return StatefulBuilder(builder: (context, setState) {
Widget continueButton = TextButton(
child: Text(
confirmButton,
style: TextStyle(color: confirmColor),
),
onPressed: () {
Navigator.of(context).pop();
onConfirm.call(true, selected);
},
);
Widget closeButton = TextButton(
child: Text(cancelButton),
onPressed: () {
Navigator.of(context).pop();
onConfirm.call(false, 0);
},
);
var widgets = [];
for (int i = 0; i < options.length; i++) {
widgets.add(
RadioListTile(
value: i,
groupValue: selected,
title: Text(options[i]),
onChanged: (currentUser) {
setState(() {
selected = i;
onSelectionChanged?.call(selected);
});
},
selected: selected == i,
activeColor: Theme.of(context).hintColor,
),
);
}
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text(title),
content: SizedBox(
width: double.maxFinite,
child: ListTileTheme(
iconColor: Colors.white,
child: ListView(
shrinkWrap: true,
children: widgets,
),
),
),
actions: [
closeButton,
continueButton,
],
);
return alert;
});
}
static showLocationPrompt(
BuildContext context, bool skipPrompt, Function? onPromptFinished) {
if (skipPrompt) {
_askLocation(onPromptFinished);
return;
}
AlertDialogs.showConfirmDialog(context,
title: "Location is required for Bluetooth",
description:
"Please, consider granting location permission. It is required for Bluetooth connection to work.",
confirmButton: "Grant",
cancelButton: "Keep denying", onConfirm: (val) {
if (val) _askLocation(onPromptFinished);
});
}
static _askLocation(Function? onPromptFinished) async {
var status = await Permission.location.request();
if (status.isPermanentlyDenied) await openAppSettings();
onPromptFinished?.call();
}
}
================================================
FILE: lib/UI/popups/changeCategory.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import '../widgets/scrollParent.dart';
import '../../platform/presetsStorage.dart';
class ChangeCategoryDialog {
static final _formKey = GlobalKey();
final categoryCtrl = TextEditingController();
final parentScroll = ScrollController();
String category;
String name;
final Color? confirmColor;
Function(String) onCategoryChange;
ChangeCategoryDialog(
{required this.category,
required this.name,
required this.onCategoryChange,
this.confirmColor}) {
categoryCtrl.text = category;
}
Widget buildDialog(BuildContext context) {
List categories = PresetsStorage().getCategories();
final height = MediaQuery.of(context).size.height * 0.25;
final node = FocusScope.of(context);
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: const Text('Change Preset Category'),
content: SizedBox(
width: double.maxFinite,
child: SingleChildScrollView(
reverse: true,
controller: parentScroll,
child: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
key: _formKey,
child: Column(
children: [
Text("Categories",
style: TextStyle(color: Theme.of(context).hintColor)),
const SizedBox(
height: 10,
),
Container(
height: height,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
),
child: ScrollParent(
controller: parentScroll,
child: ListTileTheme(
child: ListView.builder(
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
title: Text(categories[index]),
onTap: () {
setState(() {
categoryCtrl.text = categories[index];
});
},
);
},
itemCount: categories.length,
),
),
),
),
TextFormField(
decoration: const InputDecoration(labelText: "Category"),
controller: categoryCtrl,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter preset category';
}
if (PresetsStorage().findPreset(name, value) != null) {
return 'The category already contains a preset with this name!';
}
return null;
},
onEditingComplete: () => node.unfocus(),
),
],
),
),
),
),
actions: [
TextButton(
child: const Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
//call change success
onCategoryChange(categoryCtrl.value.text);
Navigator.of(context).pop();
}
},
child: Text(
'Change',
style: TextStyle(color: confirmColor),
),
),
],
);
},
);
}
}
================================================
FILE: lib/UI/popups/exportQRCode.dart
================================================
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/platform/fileSaver.dart';
import 'package:path_provider/path_provider.dart';
import 'package:screenshot/screenshot.dart';
import 'package:path/path.dart' as path;
import 'package:share_plus/share_plus.dart';
import '../../bluetooth/devices/NuxDevice.dart';
import '../../platform/platformUtils.dart';
class QRExportDialog {
final Image qrImage;
final String presetName;
final NuxDevice device;
QRExportDialog(this.qrImage, this.presetName, this.device);
Widget buildDialog(BuildContext context) {
ScreenshotController screenshotController = ScreenshotController();
return AlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
icon: Icon(
Icons.adaptive.arrow_back,
color: Colors.white,
),
onPressed: () => Navigator.of(context).pop()),
const Text("Share QR Code", style: TextStyle(color: Colors.white)),
],
),
insetPadding: EdgeInsets.zero,
contentPadding:
const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
content: FittedBox(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Screenshot(
controller: screenshotController,
child: ColoredBox(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
device.getProductNameForQR(device.productVersion),
style: const TextStyle(
color: Colors.black, fontWeight: FontWeight.bold),
),
qrImage,
Text(presetName,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold))
],
),
),
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (!PlatformUtils.isIOS)
ElevatedButton.icon(
onPressed: () async {
//var path = '$directory';
//fileSave
var data = await screenshotController.capture();
if (data != null) {
saveFile("image/png", presetName, data);
}
},
icon: const Icon(
Icons.save_alt,
color: Colors.white,
),
label: const Text("Save")),
const SizedBox(
width: 10,
),
ElevatedButton.icon(
onPressed: () async {
Directory? storageDirectory;
if (PlatformUtils.isAndroid) {
storageDirectory =
await getExternalStorageDirectory();
} else if (PlatformUtils.isIOS) {
storageDirectory =
await getApplicationDocumentsDirectory();
}
var tracksPath =
path.join(storageDirectory?.path ?? "", "");
//var path = '$directory';
await screenshotController.captureAndSave(tracksPath,
fileName: "preset.png");
Share.shareXFiles([XFile('$tracksPath/preset.png')],
text: 'QR Code',
sharePositionOrigin: Rect.fromCenter(
center: const Offset(100, 100),
width: 100,
height: 100));
},
icon: Icon(
Icons.adaptive.share,
color: Colors.white,
),
label: const Text("Share"))
],
),
],
),
));
}
}
================================================
FILE: lib/UI/popups/hotkeyInput.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/midi/ControllerConstants.dart';
import 'package:mighty_plug_manager/midi/MidiControllerManager.dart';
import 'package:mighty_plug_manager/midi/controllers/MidiController.dart';
class HotkeyInputDialog {
late BuildContext _context;
late TextEditingController controller;
static const None = "None";
int? _hotkeyCode;
late final MidiController _midiController;
late HotkeyControl _control;
late int _index;
late int _subindex;
bool _sliderMode = false;
//slider mode vars
int _previousCode = -1;
int? previousSliderValue;
bool _invert = false;
_applyHotkey() {
if (_hotkeyCode != null) {
do {
bool ignoreLowByte = _control == HotkeyControl.ParameterSet;
var hk = _midiController.getHotkeyByCode(_hotkeyCode!, ignoreLowByte);
if (hk != null) {
_midiController.removeHotkey(hk);
} else {
break;
}
} while (_control == HotkeyControl.ParameterSet);
_midiController.assignHotkey(
_control, _index, _subindex, _hotkeyCode!, controller.text, _invert);
MidiControllerManager().saveConfig();
} else {
_midiController.removeHotkeyByFunction(_control, _index, _subindex);
MidiControllerManager().saveConfig();
}
Navigator.of(_context).pop();
}
_onControllerData(
MidiController ctrl, int code, int? sliderValue, String name) {
if (_midiController.id != ctrl.id) return;
if (_sliderMode) {
if (sliderValue == null) return;
if (_invert) sliderValue = 127 - sliderValue;
code &= 0xffffff00;
if (code == _previousCode && previousSliderValue != sliderValue) {
//valid adjustment
name = name.substring(0, name.length - 2);
name += sliderValue.toRadixString(16).padLeft(2, '0');
controller.text = name;
_hotkeyCode = code;
}
_previousCode = code;
previousSliderValue = sliderValue;
} else {
controller.text = name;
_hotkeyCode = code;
}
}
Widget buildDialog(BuildContext context,
{required MidiController midiController,
required HotkeyControl ctrl,
required int ctrlIndex,
required int ctrlSubIndex,
required String hotkeyName,
required bool sliderMode}) {
_midiController = midiController;
_context = context;
_control = ctrl;
_index = ctrlIndex;
_subindex = ctrlSubIndex;
_sliderMode = sliderMode;
var hk = _midiController.getHotkeyByFunction(ctrl, ctrlIndex, ctrlSubIndex);
controller = TextEditingController(text: hk == null ? None : hk.hotkeyName);
_invert = hk?.invertSlider ?? false;
MidiControllerManager().overrideOnData(_onControllerData);
return FocusScope(
autofocus: true,
onKey: (node, event) {
if (event.runtimeType.toString() == 'RawKeyDownEvent' &&
event.logicalKey.keyId != 0x100001005) {
MidiControllerManager().onHIDData(event);
}
return KeyEventResult.skipRemainingHandlers;
},
child: StatefulBuilder(builder: (context, setState) {
return AlertDialog(
contentPadding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
icon: Icon(
Icons.adaptive.arrow_back,
color: Colors.white,
),
onPressed: () => Navigator.of(context).pop()),
const Text('Set hotkey'),
],
),
content: Column(mainAxisSize: MainAxisSize.min, children: [
if (sliderMode)
Text(
"Adjust the pedal/knob/slider you wish to assign to $hotkeyName")
else
Text("Press the control you wish to assign to $hotkeyName"),
if (sliderMode)
CheckboxListTile(
title: const Text("Invert"),
value: _invert,
onChanged: (value) {
setState(() {
_invert = value!;
});
}),
AbsorbPointer(
child: TextField(
controller: controller,
readOnly: true,
autofocus: false,
),
)
]),
actions: [
TextButton(
onPressed: () {
controller.text = None;
_hotkeyCode = null;
},
child: const Text("Clear")),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("Cancel")),
TextButton(
onPressed: _applyHotkey,
child: Text(
"OK",
style: TextStyle(color: Theme.of(context).hintColor),
))
],
actionsAlignment: MainAxisAlignment.spaceBetween,
);
}),
);
}
}
================================================
FILE: lib/UI/popups/midiControlInfo.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/bluetooth/devices/effects/Processor.dart';
import '../../bluetooth/devices/effects/MidiControllerHandles.dart';
class MidiControlInfoDialog {
Widget buildDialog(BuildContext context,
{required List effects,
required ControllerHandleId handleId}) {
return AlertDialog(
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
icon: Icon(
Icons.adaptive.arrow_back,
color: Colors.white,
),
onPressed: () => Navigator.of(context).pop()),
const Text('Control Info'),
],
),
content: Container(
height: 400,
width: 300,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
),
child: ListView.builder(
itemBuilder: (context, index) {
String paramName = "N/A";
for (var param in effects[index].parameters) {
if (param.midiControllerHandle != null &&
param.midiControllerHandle!.id == handleId) {
paramName = param.name;
}
}
return ListTile(
title: Text(effects[index].name),
trailing: Text(paramName),
);
},
itemCount: effects.length,
),
),
);
}
}
================================================
FILE: lib/UI/popups/savePreset.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import '../../bluetooth/devices/presets/Preset.dart';
import '../widgets/scrollParent.dart';
import '../../bluetooth/devices/NuxDevice.dart';
import '../../platform/presetsStorage.dart';
import 'alertDialogs.dart';
class SavePresetDialog {
static final _formKey = GlobalKey();
final categoryCtrl = TextEditingController();
final nameCtrl = TextEditingController();
final parentScroll = ScrollController();
final String? customName;
final Preset? customPreset;
final NuxDevice device;
final Color? confirmColor;
late NuxDeviceControl deviceControl;
SavePresetDialog(
{required this.device,
this.confirmColor,
this.customName,
this.customPreset}) {
deviceControl = device.deviceControl;
categoryCtrl.text = deviceControl.presetCategory;
if (customName != null) {
nameCtrl.text = customName!;
} else {
nameCtrl.text = deviceControl.presetName;
}
}
Widget buildDialog(NuxDevice device, BuildContext context) {
List categories = PresetsStorage().getCategories();
var isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
final height = MediaQuery.of(context).size.height * 0.25;
final node = FocusScope.of(context);
return StatefulBuilder(
builder: (context, setState) {
var dialog = AlertDialog(
title: const Text('Save preset'),
content: SizedBox(
width: double.maxFinite,
child: SingleChildScrollView(
reverse: true,
controller: parentScroll,
child: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
key: _formKey,
child: Column(
children: [
Text("Categories",
style: TextStyle(color: Theme.of(context).hintColor)),
const SizedBox(
height: 10,
),
Container(
height: height,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
),
child: ScrollParent(
controller: parentScroll,
child: ListTileTheme(
child: ListView.builder(
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
title: Text(categories[index]),
onTap: () {
setState(() {
categoryCtrl.text = categories[index];
});
},
);
},
itemCount: categories.length,
),
),
),
),
TextFormField(
decoration: const InputDecoration(labelText: "Category"),
controller: categoryCtrl,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter preset category';
}
return null;
},
onEditingComplete: () => node.nextFocus(),
),
TextFormField(
decoration: const InputDecoration(labelText: "Name"),
controller: nameCtrl,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter preset name';
}
return null;
},
onEditingComplete: () => node.unfocus(),
),
],
),
),
),
),
actions: [
TextButton(
child: const Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
//save and pop
if (PresetsStorage().presetExists(
nameCtrl.value.text, categoryCtrl.value.text)) {
//overwriting preset
AlertDialogs.showConfirmDialog(context,
title: "Confirm",
description: "Overwrite existing preset?",
cancelButton: "Cancel",
confirmButton: "Overwrite",
confirmColor: Colors.red, onConfirm: (overwrite) {
if (overwrite) {
savePreset(context);
Navigator.of(context).pop();
}
});
} else {
savePreset(context);
Navigator.of(context).pop();
}
}
},
child: Text(
'Save',
style: TextStyle(color: confirmColor),
),
),
],
);
if (isPortrait) {
return dialog;
} else {
return SingleChildScrollView(
child: dialog,
);
}
},
);
}
savePreset(context) {
var preset = device.presetToJson(customPreset: customPreset);
if (customPreset == null) {
deviceControl.presetName = nameCtrl.value.text;
deviceControl.presetCategory = categoryCtrl.value.text;
}
String uuid = PresetsStorage()
.savePreset(preset, nameCtrl.value.text, categoryCtrl.value.text);
if (customPreset == null) {
deviceControl.presetUUID = uuid;
}
}
}
================================================
FILE: lib/UI/popups/selectPreset.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import '../widgets/presets/preset_list/presetList.dart';
class SelectPresetDialog {
Widget buildDialog(BuildContext context, {required bool noneOption}) {
return AlertDialog(
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
icon: Icon(
Icons.adaptive.arrow_back,
color: Colors.white,
),
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop()),
const Text('Select preset'),
],
),
actions: [],
content: Scaffold(
body: ListView(
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: [
if (noneOption)
ListTile(
title: const Center(child: Text("None")),
onTap: () =>
Navigator.of(context, rootNavigator: true).pop(false),
),
PresetList(
simplified: true,
onTap: (preset) {
Navigator.of(context, rootNavigator: true).pop(preset);
}),
]),
),
);
}
}
================================================
FILE: lib/UI/popups/selectTrack.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/audio/models/jamTrack.dart';
import 'package:mighty_plug_manager/audio/trackdata/trackData.dart';
import 'package:mighty_plug_manager/audio/tracksPage.dart';
class SelectTrackDialog {
bool _multiselect = false;
Map _selected = {};
Widget buildDialog(BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
contentPadding: const EdgeInsets.only(bottom: 20),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
icon: Icon(
Icons.adaptive.arrow_back,
color: Colors.white,
),
onPressed: () => Navigator.of(context).pop()),
Expanded(
child: _multiselect
? Text("${_selected.length} selected")
: const Text('Select Track')),
if (_multiselect)
ElevatedButton(
child: const Text("Add"),
onPressed: () {
List tracks = [];
for (int i = 0; i < _selected.length; i++) {
tracks.add(TrackData().tracks[_selected.keys.elementAt(i)]);
}
Navigator.of(context).pop(tracks);
},
)
],
),
content: TracksPage(
selectorOnly: true,
onSelectedTrack: (track) {
Navigator.of(context).pop(track);
},
multiSelectState: (bool state, Map selected) {
_multiselect = state;
_selected = selected;
setState(() {});
},
),
);
});
}
}
================================================
FILE: lib/UI/theme.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
class AppThemeConfig {
static double dragHandlesWidth = 56;
static Color contextMenuIconColor = Colors.white;
static bool allowRotation = true;
static TextStyle ListTileHeaderStyle =
const TextStyle(color: Colors.lightBlue, fontWeight: FontWeight.bold);
static double toggleButtonHeight(bool hasLongNames) {
if (hasLongNames) return 48;
return 40;
}
}
ThemeData getTheme() {
return ThemeData(
brightness: Brightness.dark,
colorScheme: ColorScheme(
brightness: Brightness.dark,
primary: Colors.blue, //buttons
onPrimary: Colors.white, //text on buttons
secondary: Colors.white,
onSecondary: Colors.grey,
error: Colors.red,
onError: Colors.white,
background: Colors.grey,
onBackground: Colors.grey,
surface: Colors.grey[700]!, //appbar
onSurface: Colors.white, //titlebar text
),
//canvasColor: Colors.white,
scaffoldBackgroundColor: Colors.grey[900],
//primary color is AppBar bg color
primaryColor: Colors.grey[800],
//accentColor: Colors.white,
//unselected labels
hintColor: Colors.blue[300],
disabledColor: Colors.grey[700],
unselectedWidgetColor: Colors.white,
inputDecorationTheme: InputDecorationTheme(
labelStyle: const TextStyle(color: Colors.white),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey[600]!))),
checkboxTheme:
CheckboxThemeData(fillColor: MaterialStateColor.resolveWith((states) {
return Colors.white;
}), checkColor: MaterialStateColor.resolveWith((states) {
return Colors.black;
})),
bottomNavigationBarTheme: BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.grey[800],
selectedItemColor: Colors.white,
unselectedItemColor: Colors.grey[500],
selectedIconTheme: const IconThemeData(
size: 40,
),
unselectedIconTheme: const IconThemeData(
size: 30,
),
),
textButtonTheme: TextButtonThemeData(style:
ButtonStyle(foregroundColor: MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.disabled)) return Colors.grey[700]!;
return Colors.grey[300]!;
}))),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
backgroundColor: MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.disabled)) return Colors.grey[700]!;
return Colors.blue;
}),
foregroundColor: MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.disabled)) return Colors.grey;
return Colors.white;
}),
)),
dialogTheme: DialogTheme(
contentTextStyle: const TextStyle(color: Colors.white),
backgroundColor: Colors.grey[800],
//shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.all(Radius.circular(10))),
),
toggleButtonsTheme: ToggleButtonsThemeData(
color: Colors.grey[600],
selectedColor: Colors.white,
borderColor: Colors.grey[800],
selectedBorderColor: Colors.grey[800],
fillColor: Colors.transparent,
borderWidth: 2,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
popupMenuTheme: PopupMenuThemeData(color: Colors.grey[700]),
dividerTheme:
const DividerThemeData(color: Colors.grey, indent: 15, endIndent: 15),
);
}
================================================
FILE: lib/UI/toneshare/cloud_authentication.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/toneshare/cloud_login.dart';
import 'package:mighty_plug_manager/UI/toneshare/cloud_signup.dart';
enum AuthPage { signIn, signUp }
class CloudAuthentication extends StatefulWidget {
const CloudAuthentication({super.key});
@override
State createState() => _CloudAuthenticationState();
}
class _CloudAuthenticationState extends State {
AuthPage _pageMode = AuthPage.signIn;
void _showSignup() {
setState(() {
_pageMode = AuthPage.signUp;
});
}
void _showSignIn() {
setState(() {
_pageMode = AuthPage.signIn;
});
}
@override
Widget build(BuildContext context) {
if (_pageMode == AuthPage.signIn) {
return SignInForm(onSignUpTap: _showSignup);
} else {
return SignUpForm(onSignInTap: _showSignIn);
}
}
}
================================================
FILE: lib/UI/toneshare/cloud_login.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/modules/cloud/cloudManager.dart';
import 'package:pocketbase/pocketbase.dart';
import 'toneshare_main.dart';
class SignInForm extends StatefulWidget {
final void Function() onSignUpTap;
const SignInForm({super.key, required this.onSignUpTap});
@override
_SignInFormState createState() => _SignInFormState();
}
class _SignInFormState extends State {
final _formKey = GlobalKey();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
String _errorMessage = '';
bool _validateAndSaveForm() {
final form = _formKey.currentState;
if (form?.validate() ?? false) {
form?.save();
return true;
}
return false;
}
void _signInWithEmailAndPassword() async {
if (_validateAndSaveForm()) {
setState(() {
_errorMessage = "";
});
try {
ToneShare.startLoading(context);
var result = await CloudManager.instance.signIn(
email: _emailController.text.trim(),
password: _passwordController.text.trim());
print(result);
} on ClientException catch (e) {
if (e.isAbort) {
_errorMessage = "No internet connection";
} else {
_errorMessage = e.response["message"];
//"Wrong credentials or account not verified";
}
setState(() {});
} finally {
ToneShare.stopLoading(context);
}
}
}
void _signInWithGoogle() async {
// try {
// // Trigger the authentication flow
// final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
// // Obtain the auth details from the request
// final GoogleSignInAuthentication? googleAuth =
// await googleUser?.authentication;
// // Create a new credential
// final AuthCredential credential = GoogleAuthProvider.credential(
// idToken: googleAuth?.idToken,
// accessToken: googleAuth?.accessToken,
// );
// await FirebaseAuth.instance.signInWithCredential(credential);
// } on FirebaseAuthException catch (e) {
// setState(() {
// _errorMessage = e.message ?? "Unknown Error";
// });
// }
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
),
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your email';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your password';
}
return null;
},
),
const SizedBox(height: 16),
Text(
_errorMessage,
style: const TextStyle(color: Colors.red),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _signInWithEmailAndPassword,
child: const Text('Sign In'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _signInWithGoogle,
child: const Text('Sign in with Google'),
),
const SizedBox(height: 16),
GestureDetector(
onTap: widget.onSignUpTap,
child: const Text('Don\'t have an account? Sign up'),
),
],
),
);
}
}
================================================
FILE: lib/UI/toneshare/cloud_signup.dart
================================================
import 'package:flutter/material.dart';
import 'package:pocketbase/pocketbase.dart';
import '../../modules/cloud/cloudManager.dart';
import 'toneshare_main.dart';
class SignUpForm extends StatefulWidget {
final void Function() onSignInTap;
const SignUpForm({super.key, required this.onSignInTap});
@override
_SignUpFormState createState() => _SignUpFormState();
}
class _SignUpFormState extends State {
final _formKey = GlobalKey();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
String _errorMessage = '';
bool _validateAndSaveForm() {
final form = _formKey.currentState;
if (form?.validate() ?? false) {
form?.save();
return true;
}
return false;
}
void _signUpWithEmailAndPassword() async {
if (_validateAndSaveForm()) {
setState(() {
_errorMessage = "";
});
try {
ToneShare.startLoading(context);
var result = await CloudManager.instance.register(
email: _emailController.text.trim(),
password: _passwordController.text.trim());
print(result);
} on ClientException catch (e) {
if (e.isAbort) {
_errorMessage = "No internet connection";
} else {
Map data = e.response["data"];
if (data.containsKey("email") &&
data["email"]["code"] == "validation_invalid_email") {
_errorMessage = data["email"]["message"];
} else {
_errorMessage = e.response["message"];
}
//"Wrong credentials or account not verified";
}
setState(() {});
} finally {
ToneShare.stopLoading(context);
}
}
}
void _signUpWithGoogle() async {
// try {
// // Trigger the authentication flow
// final GoogleSignUpAccount? googleUser = await GoogleSignUp().SignUp();
// // Obtain the auth details from the request
// final GoogleSignUpAuthentication? googleAuth =
// await googleUser?.authentication;
// // Create a new credential
// final AuthCredential credential = GoogleAuthProvider.credential(
// idToken: googleAuth?.idToken,
// accessToken: googleAuth?.accessToken,
// );
// await FirebaseAuth.instance.SignUpWithCredential(credential);
// } on FirebaseAuthException catch (e) {
// setState(() {
// _errorMessage = e.message ?? "Unknown Error";
// });
// }
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
),
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your email';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your password';
}
return null;
},
),
const SizedBox(height: 16),
Text(
_errorMessage,
style: const TextStyle(color: Colors.red),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _signUpWithEmailAndPassword,
child: const Text('Sign Up'),
),
ElevatedButton(
onPressed: _signUpWithGoogle,
child: const Text('Sign in with Google'),
),
ElevatedButton(
onPressed: () => CloudManager.instance
.requestValidation(_emailController.text.trim()),
child: const Text('Validado'),
),
const SizedBox(height: 16),
GestureDetector(
onTap: widget.onSignInTap,
child: const Text('Already have an account? Sign in'),
),
],
),
);
}
}
================================================
FILE: lib/UI/toneshare/share_preset.dart
================================================
import 'package:flutter/material.dart';
class PresetForm extends StatefulWidget {
@override
_PresetFormState createState() => _PresetFormState();
}
class _PresetFormState extends State {
final _formKey = GlobalKey();
final List _instrumentOptions = [
"Electric guitar",
"Bass guitar",
"Acoustic guitar",
];
final List _genreOptions = [
"Rock",
"Blues",
"Pop",
"Other",
];
String _name = "";
String _description = "";
String? _instrument;
String? _genre;
void _submitForm() {
if (_formKey.currentState?.validate() ?? false) {
// TODO: handle form submission
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Create Preset"),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
initialValue: _name,
decoration: const InputDecoration(
labelText: "Name",
hintText: "Enter preset name",
),
validator: (value) {
if (value?.isEmpty ?? true) {
return "Please enter a name";
}
return null;
},
onChanged: (value) => _name = value,
),
const SizedBox(height: 16.0),
TextFormField(
initialValue: _description,
decoration: const InputDecoration(
labelText: "Description",
hintText: "Enter preset description",
),
maxLines: 3,
onChanged: (value) => _description = value,
),
const SizedBox(height: 16.0),
DropdownButtonFormField(
decoration: const InputDecoration(
labelText: "Instrument",
),
value: _instrument,
items: _instrumentOptions
.map((instrument) => DropdownMenuItem(
value: instrument,
child: Text(instrument),
))
.toList(),
onChanged: (value) => setState(() => _instrument = value ?? ""),
),
const SizedBox(height: 16.0),
DropdownButtonFormField(
decoration: const InputDecoration(
labelText: "Genre",
),
value: _genre,
items: _genreOptions
.map((genre) => DropdownMenuItem(
value: genre,
child: Text(genre),
))
.toList(),
onChanged: (value) => setState(() => _genre = value ?? ""),
),
const SizedBox(height: 32.0),
Center(
child: ElevatedButton(
onPressed: _submitForm,
child: const Text("Upload"),
),
),
],
),
),
),
);
}
}
================================================
FILE: lib/UI/toneshare/toneshare_home.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/modules/cloud/cloudManager.dart';
import '../widgets/common/searchTextField.dart';
class ToneShareHome extends StatefulWidget {
ToneShareHome({super.key});
@override
State createState() => _ToneShareHomeState();
}
class _ToneShareHomeState extends State {
final TextEditingController searchCtrl = TextEditingController(text: "");
List? data;
@override
void initState() {
super.initState();
//searchCtrl.addListener(_search);
}
void _search(String? query) async {
if (query == null || query.isEmpty) return;
final response = null;
/*await Supabase.instance.client
.from("presets")
.select("*")
.textSearch("name", query, type: TextSearchType.websearch);*/
if (response != null) {
// Process results
//final results = response.data;
// Do something with the results
data = response;
}
setState(() {});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
//searchCtrl.removeListener(() {});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton(
child: const Text("Sync"),
onPressed: CloudManager.instance.syncPresets),
SearchTextField(controller: searchCtrl, onSearch: _search),
Expanded(
child: ListView.builder(
itemCount: data?.length ?? 0,
itemBuilder: (context, index) {
return ListTile(
title: Text(data![index]["name"]),
);
},
)),
ElevatedButton(
child: const Text("Sign Out"),
onPressed: CloudManager.instance.signOut)
],
);
}
}
================================================
FILE: lib/UI/toneshare/toneshare_main.dart
================================================
import 'package:flutter/material.dart';
import '../../modules/cloud/cloudManager.dart';
import 'cloud_authentication.dart';
import 'toneshare_home.dart';
class ToneShare extends StatefulWidget {
const ToneShare({super.key});
@override
State createState() => _ToneShareState();
static Future startLoading(BuildContext context) async {
return await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const SimpleDialog(
elevation: 0.0,
backgroundColor:
Colors.black45, // can change this to your prefered color
children: [
Center(
child: CircularProgressIndicator.adaptive(),
)
],
);
},
);
}
static Future stopLoading(BuildContext context) async {
Navigator.of(context).pop();
}
}
class _ToneShareState extends State {
@override
void initState() {
super.initState();
CloudManager.instance.addListener(_onAuthChange);
}
@override
void dispose() {
super.dispose();
CloudManager.instance.removeListener(_onAuthChange);
}
void _onAuthChange() {
setState(() {});
}
@override
Widget build(BuildContext context) {
Widget page;
if (CloudManager.instance.signedIn) {
page = ToneShareHome();
} else {
page = const CloudAuthentication();
}
return SafeArea(
child: Scaffold(
body: page,
));
}
}
================================================
FILE: lib/UI/utils.dart
================================================
import 'package:flutter/widgets.dart';
enum LayoutMode { navBar, drawer }
enum EditorLayoutMode { scroll, expand }
LayoutMode getLayoutMode(MediaQueryData mediaQuery) {
final screenWidth = mediaQuery.size.width;
final screenHeight = mediaQuery.size.height;
if (screenHeight > 650) return LayoutMode.navBar;
if (screenWidth >= 500) return LayoutMode.drawer;
return LayoutMode.navBar;
}
EditorLayoutMode getEditorLayoutMode(MediaQueryData mediaQuery) {
final screenHeight = mediaQuery.size.height;
if (screenHeight <= 580) return EditorLayoutMode.scroll;
return EditorLayoutMode.expand;
}
================================================
FILE: lib/UI/widgets/MidiDeviceTile.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/midi/controllers/MidiController.dart';
class MidiControllerTile extends StatelessWidget {
final MidiController controller;
final Function() onTap;
final Function() onSettings;
const MidiControllerTile(
{Key? key,
required this.controller,
required this.onTap,
required this.onSettings})
: super(key: key);
@override
Widget build(BuildContext context) {
IconData icon;
switch (controller.type) {
case ControllerType.Hid:
icon = Icons.keyboard_alt_outlined;
break;
case ControllerType.MidiUsb:
icon = Icons.usb;
break;
case ControllerType.MidiBle:
icon = Icons.bluetooth;
break;
}
return ListTile(
title: Text(controller.name),
onTap: onTap,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (controller.connected)
IconButton(
icon: const Icon(Icons.settings),
onPressed: onSettings,
),
Icon(
icon,
color: controller.connected ? Colors.blue : Colors.grey,
),
],
),
);
}
}
================================================
FILE: lib/UI/widgets/ModeControl.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/bluetooth/devices/effects/Processor.dart';
import 'package:tinycolor2/tinycolor2.dart';
import '../../bluetooth/devices/value_formatters/SwitchFormatters.dart';
class ModeControl extends StatelessWidget {
final bool enabled;
final double value;
final Parameter parameter;
final Color effectColor;
final ValueChanged? onChanged;
const ModeControl(
{Key? key,
required this.parameter,
required this.value,
required this.enabled,
required this.effectColor,
required this.onChanged})
: super(key: key);
String getText() {
return (parameter.formatter as SwitchFormatter).labelTitle;
}
List getElementsCount() {
return (parameter.formatter as SwitchFormatter).labelValues;
}
List getElementValues() {
return (parameter.formatter as SwitchFormatter).midiValues;
}
int getIndexByValue(int midiValue) {
//find element that is closest to the value
var list = (parameter.formatter as SwitchFormatter).midiValues;
int closestValue = 255;
int selected = -1;
for (int i = 0; i < list.length; i++) {
int diff = (list[i] - midiValue).abs();
if (diff < closestValue) {
closestValue = diff;
selected = i;
}
}
return selected;
}
Widget getButtonItem(String text) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Text(
text,
style: const TextStyle(fontSize: 20),
),
);
}
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 45),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
//width = constraints.maxWidth - 1;
var color = enabled
? effectColor
: TinyColor.fromColor(effectColor).desaturate(80).color;
var elements = getElementsCount();
var active = List.filled(elements.length, false);
var index = getIndexByValue(value.round());
active[index] = true;
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 5),
dense: true,
title: Text(getText(),
style: const TextStyle(color: Colors.white, fontSize: 20)),
trailing: ToggleButtons(
isSelected: active,
fillColor: TinyColor.fromColor(color).darken(15).color,
borderColor: color,
selectedBorderColor: color,
color: color,
selectedColor: Colors.white,
onPressed: (int newIndex) {
var val = getElementValues()[newIndex];
onChanged?.call(val.toDouble());
},
children: [
for (var i = 0; i < elements.length; i++)
getButtonItem(elements[i]),
],
),
);
}));
}
}
================================================
FILE: lib/UI/widgets/NuxAppBar.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import 'package:mighty_plug_manager/bluetooth/bleMidiHandler.dart';
import 'package:mighty_plug_manager/midi/MidiControllerManager.dart';
import '../../bluetooth/ble_controllers/BLEController.dart';
import '../../bluetooth/devices/features/tuner.dart';
import '../../midi/ControllerConstants.dart';
import '../mightierIcons.dart';
import '../pages/tunerPage.dart';
import 'common/blinkWidget.dart';
class MAAppBar extends StatefulWidget implements PreferredSizeWidget {
final double? elevation;
final bool showExpandButton;
final bool expanded;
final Function(bool)? onExpandStateChanged;
const MAAppBar({
this.elevation,
this.showExpandButton = false,
this.onExpandStateChanged,
this.expanded = true,
Key? key,
}) : super(key: key);
@override
Size get preferredSize => const Size.fromHeight(46);
@override
State createState() => _NuxAppBarState();
}
class _NuxAppBarState extends State {
static const batteryKey = "batteryValue";
int? batteryValue;
StreamSubscription? _hotkeySub;
@override
void initState() {
super.initState();
batteryValue = PageStorage.of(context)
.readState(context, identifier: batteryKey) as int?;
_hotkeySub = MidiControllerManager()
.controllerStream
.listen(_onMidiControllerMessage);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_hotkeySub?.cancel();
}
void _onMidiControllerMessage(HotkeyControl event) {
if (ModalRoute.of(context)?.isCurrent == false) {
return;
}
if (event == HotkeyControl.ToggleTuner) {
var dev = NuxDeviceControl().device;
if (dev is Tuner) {
var tuner = dev as Tuner;
if (tuner.tunerAvailable) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => TunerPage(
device: dev,
)));
}
}
}
}
@override
Widget build(BuildContext context) {
var devControl = NuxDeviceControl.instance();
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.showExpandButton)
Container(
height: kToolbarHeight,
width: kToolbarHeight,
color: Theme.of(context).primaryColor,
child: IconButton(
icon: Icon(
widget.expanded ? Icons.arrow_left : Icons.arrow_right,
size: 32,
),
onPressed: () {
widget.onExpandStateChanged?.call(!widget.expanded);
},
),
),
if (widget.expanded)
Expanded(
child: AppBar(
elevation: widget.elevation,
title: const AppBarTitle(),
titleSpacing: widget.showExpandButton ? 0 : null,
centerTitle: widget.showExpandButton ? false : null,
actions: [
//battery percentage
StreamBuilder(
stream: devControl.batteryPercentage,
builder: (context, batteryPercentage) {
if (devControl.isConnected &&
(batteryPercentage.data != 0 || batteryValue != null) &&
devControl.device.batterySupport) {
if (batteryPercentage.hasData) {
batteryValue = batteryPercentage.data;
}
if (batteryValue != null) {
PageStorage.of(context).writeState(
context, batteryValue,
identifier: batteryKey);
}
return Stack(
alignment: Alignment.center,
children: [
Transform.rotate(
angle: pi / 2,
child: const Icon(
Icons.battery_full,
size: 40,
)),
Text(
"$batteryValue%",
style: const TextStyle(
color: Colors.black,
fontSize: 12,
fontWeight: FontWeight.bold),
)
],
);
}
return const SizedBox();
},
),
const SizedBox(width: 8),
if (!widget.showExpandButton)
StreamBuilder(
stream: devControl.connectStatus,
builder: (context,
AsyncSnapshot snapshot) {
if (snapshot.data != DeviceConnectionState.disconnected &&
devControl.device is Tuner &&
(devControl.device as Tuner).tunerAvailable) {
return IconButton(
icon: const Icon(MightierIcons.tuner),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => TunerPage(
device: devControl.device,
)));
});
}
return const SizedBox();
},
),
StreamBuilder(
stream: BLEMidiHandler.instance().status,
builder: (context, snapshot) {
IconData icon = Icons.bluetooth_disabled;
Color color = Colors.grey;
var status = BLEMidiHandler.instance().currentStatus;
switch (status) {
case MidiSetupStatus.bluetoothOff:
icon = Icons.bluetooth_disabled;
break;
case MidiSetupStatus.deviceIdle:
case MidiSetupStatus.deviceConnecting:
icon = Icons.bluetooth;
break;
case MidiSetupStatus
.deviceFound: //note device found is issued
//during search only, but here it means nothing
//so keep search status
case MidiSetupStatus.deviceSearching:
icon = Icons.bluetooth_searching;
return const BlinkWidget(
interval: 500,
children: [
Icon(
Icons.bluetooth_searching,
color: Colors.grey,
),
Icon(Icons.bluetooth_searching)
],
);
case MidiSetupStatus.deviceConnected:
icon = Icons.bluetooth_connected;
color = Colors.white;
batteryValue = null;
break;
case MidiSetupStatus.deviceDisconnected:
icon = Icons.bluetooth;
break;
case MidiSetupStatus.unknown:
icon = Icons.bluetooth_disabled;
break;
}
return Icon(icon, color: color);
},
),
const SizedBox(
width: 15,
)
],
),
)
],
);
}
}
class AppBarTitle extends StatelessWidget {
const AppBarTitle({super.key});
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
builder: (BuildContext context, value, Widget? child) {
if (value is String && value.trim() != "") {
return Text("$value - Mightier Amp");
}
return const Text("Mightier Amp");
},
valueListenable: NuxDeviceControl.instance().presetNameNotifier,
);
}
}
================================================
FILE: lib/UI/widgets/VolumeDrawer.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/bluetooth/devices/value_formatters/ValueFormatter.dart';
import '../../bluetooth/NuxDeviceControl.dart';
import '../../platform/simpleSharedPrefs.dart';
import 'thickSlider.dart';
const _kBottomDrawerPickHeight = 50.0;
const _kBottomDrawerHiddenHeight = 60.0;
const _kBottomDrawerHiddenPadding = 8.0;
class BottomDrawer extends StatelessWidget {
final bool isBottomDrawerOpen;
final Function(bool) onExpandChange;
final Widget child;
const BottomDrawer({
Key? key,
required this.isBottomDrawerOpen,
required this.onExpandChange,
required this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
onExpandChange(!isBottomDrawerOpen);
},
onVerticalDragUpdate: (details) {
if (details.delta.dy < -5) {
//open
onExpandChange(true);
} else if (details.delta.dy > 5) {
//close
onExpandChange(false);
}
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: _kBottomDrawerPickHeight,
decoration: BoxDecoration(
color: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(15)),
),
child: Icon(
isBottomDrawerOpen
? Icons.keyboard_arrow_down
: Icons.keyboard_arrow_up,
size: 24,
color: Colors.grey,
),
),
AnimatedContainer(
padding: const EdgeInsets.all(_kBottomDrawerHiddenPadding),
color: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
duration: const Duration(milliseconds: 100),
height: isBottomDrawerOpen ? _kBottomDrawerHiddenHeight : 0,
child: child,
),
],
),
);
}
}
class VolumeSlider extends StatelessWidget {
final String label;
const VolumeSlider({Key? key, this.label = "Volume"}) : super(key: key);
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: NuxDeviceControl.instance().masterVolumeNotifier,
builder: (context, value, child) {
final device = NuxDeviceControl.instance().device;
final volumeFormatter = device.fakeMasterVolume
? ValueFormatters.percentage
: device.decibelFormatter!;
return ThickSlider(
activeColor: Colors.blue,
value: NuxDeviceControl.instance().masterVolume,
skipEmitting: 3,
label: label,
labelFormatter: volumeFormatter.toLabel,
min: volumeFormatter.min.toDouble(),
max: volumeFormatter.max.toDouble(),
handleVerticalDrag: false,
onChanged: _onVolumeChanged,
onDragEnd: _onVolumeDragEnd,
);
},
);
}
void _onVolumeDragEnd(value) {
NuxDeviceControl.instance().masterVolume = value;
if (NuxDeviceControl.instance().device.fakeMasterVolume) {
SharedPrefs().setValue(
SettingsKeys.masterVolume,
NuxDeviceControl.instance().masterVolume,
);
}
}
void _onVolumeChanged(value, bool skip) {
if (!skip) {
NuxDeviceControl.instance().masterVolume = value;
}
}
}
================================================
FILE: lib/UI/widgets/app_drawer.dart
================================================
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/mightierIcons.dart';
import 'package:mighty_plug_manager/UI/widgets/NuxAppBar.dart';
import 'package:mighty_plug_manager/UI/widgets/VolumeDrawer.dart';
final _tiles = [
const TileModel(0, 'Editor', MightierIcons.sliders),
const TileModel(1, 'Presets', Icons.list),
const TileModel(2, 'Drums', MightierIcons.drum),
const TileModel(3, 'Jam Tracks', Icons.queue_music),
const TileModel(4, 'Settings', Icons.settings),
];
class AppDrawer extends StatefulWidget {
final void Function(int) onSwitchPageIndex;
final int currentIndex;
final int totalTabs;
const AppDrawer({
required this.onSwitchPageIndex,
required this.currentIndex,
required this.totalTabs,
Key? key,
}) : super(key: key);
@override
State createState() => _AppDrawerState();
}
class _AppDrawerState extends State {
static const expandedState = "expandedState";
bool isExpanded = false;
bool isBottomDrawerOpen = false;
bool expandChildren = false;
@override
void initState() {
super.initState();
isExpanded = PageStorage.of(context)
.readState(context, identifier: expandedState) as bool? ??
false;
expandChildren = isExpanded;
}
void _onExpandChange(bool expand) {
isExpanded = expand;
PageStorage.of(context)
.writeState(context, isExpanded, identifier: expandedState);
if (isExpanded == false) expandChildren = false;
setState(() {});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragUpdate: (details) {
if (details.delta.dx > 5) {
//open
_onExpandChange(true);
} else if (details.delta.dx < -5) {
//close
_onExpandChange(false);
}
},
child: AnimatedContainer(
color: Colors.grey[850],
onEnd: () {
if (isExpanded) expandChildren = true;
setState(() {});
},
duration: const Duration(milliseconds: 200),
width: isExpanded ? 230 : 56,
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MAAppBar(
elevation: 0,
expanded: expandChildren,
showExpandButton: true,
onExpandStateChanged: _onExpandChange,
),
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_DrawerTile(
tileIndex: 0,
onSwitchPageIndex: widget.onSwitchPageIndex,
currentIndex: widget.currentIndex,
expanded: expandChildren,
),
_DrawerTile(
tileIndex: 1,
onSwitchPageIndex: widget.onSwitchPageIndex,
currentIndex: widget.currentIndex,
expanded: expandChildren,
),
_DrawerTile(
tileIndex: 2,
onSwitchPageIndex: widget.onSwitchPageIndex,
currentIndex: widget.currentIndex,
expanded: expandChildren,
),
_DrawerTile(
tileIndex: 3,
onSwitchPageIndex: widget.onSwitchPageIndex,
currentIndex: widget.currentIndex,
expanded: expandChildren,
),
_DrawerTile(
tileIndex: 4,
onSwitchPageIndex: widget.onSwitchPageIndex,
currentIndex: widget.currentIndex,
expanded: expandChildren,
),
],
),
),
),
if (isExpanded)
BottomDrawer(
isBottomDrawerOpen: isBottomDrawerOpen,
onExpandChange: (val) => setState(() {
isBottomDrawerOpen = val;
}),
child: const VolumeSlider(),
),
],
),
),
),
);
}
}
class _DrawerTile extends StatelessWidget {
final int tileIndex;
final int currentIndex;
final bool expanded;
final void Function(int p1) onSwitchPageIndex;
const _DrawerTile(
{Key? key,
required this.onSwitchPageIndex,
required this.currentIndex,
required this.tileIndex,
required this.expanded})
: super(key: key);
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final color =
tileIndex == currentIndex ? colorScheme.primary : colorScheme.secondary;
if (expanded) {
return ListTile(
selected: tileIndex == currentIndex,
title: Text(
_tiles.elementAt(tileIndex).title,
// textAlign: TextAlign.right,
),
leading: Icon(
_tiles.elementAt(tileIndex).icon,
),
minLeadingWidth: 10,
onTap: () => onSwitchPageIndex(tileIndex),
);
} else {
return Padding(
padding: const EdgeInsets.all(4),
child: IconButton(
onPressed: () => onSwitchPageIndex(tileIndex),
icon: Icon(
_tiles.elementAt(tileIndex).icon,
color: color,
)),
);
}
}
}
@immutable
class TileModel {
final int index;
final String title;
final IconData icon;
const TileModel(this.index, this.title, this.icon);
}
================================================
FILE: lib/UI/widgets/bottomBar.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/mightierIcons.dart';
class BottomBar extends StatefulWidget {
final void Function(int) onTap;
final int index;
const BottomBar({
Key? key,
required this.index,
required this.onTap,
}) : super(key: key);
@override
State createState() => _BottomBarState();
}
class _BottomBarState extends State {
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: widget.index,
onTap: widget.onTap,
items: const [
BottomNavigationBarItem(
icon: Icon(MightierIcons.sliders),
label: "Editor",
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: "Presets",
),
BottomNavigationBarItem(
icon: Icon(MightierIcons.drum),
label: "Drums",
),
BottomNavigationBarItem(
icon: Icon(Icons.queue_music),
label: "JamTracks",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: "Settings",
),
],
);
}
}
================================================
FILE: lib/UI/widgets/circular_button.dart
================================================
import 'package:flutter/material.dart';
class CircularButton extends StatelessWidget {
final IconData icon;
final Color backgroundColor;
final void Function()? onPressed;
final double iconSize;
final double iconPadding;
const CircularButton(
{super.key,
required this.icon,
required this.backgroundColor,
this.onPressed,
this.iconPadding = 20,
this.iconSize = 28});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
padding: EdgeInsets.all(iconPadding),
backgroundColor: backgroundColor,
foregroundColor: Colors.white,
),
child: Icon(
icon,
size: iconSize,
),
);
}
}
================================================
FILE: lib/UI/widgets/common/blinkWidget.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
class BlinkWidget extends StatefulWidget {
final List children;
final int interval;
const BlinkWidget({required this.children, this.interval = 500, Key? key})
: super(key: key);
@override
State createState() => _BlinkWidgetState();
}
class _BlinkWidgetState extends State
with SingleTickerProviderStateMixin {
late AnimationController _controller;
int _currentWidget = 0;
@override
initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: widget.interval), vsync: this);
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
if (++_currentWidget == widget.children.length) {
_currentWidget = 0;
}
});
_controller.forward(from: 0.0);
}
});
_controller.forward();
}
@override
dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
child: widget.children[_currentWidget],
);
}
}
================================================
FILE: lib/UI/widgets/common/customPopupMenu.dart
================================================
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
// Examples can assume:
// enum Commands { heroAndScholar, hurricaneCame }
// dynamic _heroAndScholar;
// dynamic _selection;
// BuildContext context;
// void setState(VoidCallback fn) { }
const Duration _kMenuDuration = Duration(milliseconds: 300);
const double _kBaselineOffsetFromBottom = 20.0;
const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
const double _kMenuHorizontalPadding = 0.0; //16.0;
const double _kMenuItemHeight = 48.0;
const double _kMenuDividerHeight = 16.0;
const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
const double _kMenuVerticalPadding = 0.0; //8.0;
const double _kMenuWidthStep = 56.0;
const double _kMenuScreenPadding = 8.0;
/// A base class for entries in a material design popup menu.
///
/// The popup menu widget uses this interface to interact with the menu items.
/// To show a popup menu, use the [showMenu] function. To create a button that
/// shows a popup menu, consider using [PopupMenuButton].
///
/// The type `T` is the type of the value(s) the entry represents. All the
/// entries in a given menu must represent values with consistent types.
///
/// A [PopupMenuEntry] may represent multiple values, for example a row with
/// several icons, or a single entry, for example a menu item with an icon (see
/// [PopupMenuItem]), or no value at all (for example, [PopupMenuDivider]).
///
/// See also:
///
/// * [PopupMenuItem], a popup menu entry for a single value.
/// * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
/// * [CheckedPopupMenuItem], a popup menu item with a checkmark.
/// * [showMenu], a method to dynamically show a popup menu at a given location.
/// * [PopupMenuButton], an [IconButton] that automatically shows a menu when
/// it is tapped.
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);
/// The amount of vertical space occupied by this entry.
///
/// This value is used at the time the [showMenu] method is called, if the
/// `initialValue` argument is provided, to determine the position of this
/// entry when aligning the selected entry over the given `position`. It is
/// otherwise ignored.
double get height;
/// Whether this entry represents a particular value.
///
/// This method is used by [showMenu], when it is called, to align the entry
/// representing the `initialValue`, if any, to the given `position`, and then
/// later is called on each entry to determine if it should be highlighted (if
/// the method returns true, the entry will have its background color set to
/// the ambient [ThemeData.highlightColor]). If `initialValue` is null, then
/// this method is not called.
///
/// If the [PopupMenuEntry] represents a single value, this should return true
/// if the argument matches that value. If it represents multiple values, it
/// should return true if the argument matches any of them.
bool represents(T value);
}
/// A thin horizontal line, with padding on either side.
///
/// In the material design language, this represents a divider. Dividers can be
/// used in lists, [Drawer]s, and elsewhere to separate content.
///
/// To create a divider between [ListTile] items, consider using
/// [ListTile.divideTiles], which is optimized for this case.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=_liUC641Nmk}
///
/// The box's total height is controlled by [height]. The appropriate
/// padding is automatically computed from the height.
///
/// {@tool dartpad --template=stateless_widget_scaffold}
///
/// This sample shows how to display a Divider between an orange and blue box
/// inside a column. The Divider is 20 logical pixels in height and contains a
/// vertically centered black line that is 5 logical pixels thick. The black
/// line is indented by 20 logical pixels.
///
/// 
///
/// ```dart
/// Widget build(BuildContext context) {
/// return Center(
/// child: Column(
/// children: [
/// Expanded(
/// child: Container(
/// color: Colors.amber,
/// child: const Center(
/// child: Text('Above'),
/// ),
/// ),
/// ),
/// const Divider(
/// color: Colors.black,
/// height: 20,
/// thickness: 5,
/// indent: 20,
/// endIndent: 0,
/// ),
/// Expanded(
/// child: Container(
/// color: Colors.blue,
/// child: const Center(
/// child: Text('Below'),
/// ),
/// ),
/// ),
/// ],
/// ),
/// );
/// }
/// ```
/// {@end-tool}
/// See also:
///
/// * [PopupMenuDivider], which is the equivalent but for popup menus.
/// * [ListTile.divideTiles], another approach to dividing widgets in a list.
/// *
class LabeledDivider extends StatelessWidget {
/// Creates a material design divider.
///
/// The [height], [thickness], [indent], and [endIndent] must be null or
/// non-negative.
const LabeledDivider(
{Key? key,
this.height,
this.thickness,
this.indent,
this.insideIndent,
this.color,
required this.text})
: assert(height == null || height >= 0.0),
assert(thickness == null || thickness >= 0.0),
assert(indent == null || indent >= 0.0),
assert(insideIndent == null || insideIndent >= 0.0),
super(key: key);
/// The divider's height extent.
///
/// The divider itself is always drawn as a horizontal line that is centered
/// within the height specified by this value.
///
/// If this is null, then the [DividerThemeData.space] is used. If that is
/// also null, then this defaults to 16.0.
final double? height;
/// The thickness of the line drawn within the divider.
///
/// A divider with a [thickness] of 0.0 is always drawn as a line with a
/// height of exactly one device pixel.
///
/// If this is null, then the [DividerThemeData.thickness] is used. If
/// that is also null, then this defaults to 0.0.
final double? thickness;
/// The amount of empty space to the leading edge of the divider.
///
/// If this is null, then the [DividerThemeData.indent] is used. If that is
/// also null, then this defaults to 0.0.
final double? indent;
/// The amount of empty space to the trailing edge of the divider.
///
/// If this is null, then the [DividerThemeData.endIndent] is used. If that is
/// also null, then this defaults to 0.0.
final double? insideIndent;
/// The color to use when painting the line.
///
/// If this is null, then the [DividerThemeData.color] is used. If that is
/// also null, then [ThemeData.dividerColor] is used.
///
/// {@tool snippet}
///
/// ```dart
/// Divider(
/// color: Colors.deepOrange,
/// )
/// ```
/// {@end-tool}
final Color? color;
final String text;
@override
Widget build(BuildContext context) {
final DividerThemeData dividerTheme = DividerTheme.of(context);
final double height = this.height ?? dividerTheme.space ?? 16.0;
final double thickness = this.thickness ?? dividerTheme.thickness ?? 1.0;
final double indent = this.indent ?? dividerTheme.indent ?? 6.0;
final double insideIndent =
this.insideIndent ?? dividerTheme.endIndent ?? 4.0;
return Container(
color: Theme.of(context).popupMenuTheme.color,
height: height,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Container(
margin:
EdgeInsetsDirectional.only(start: indent, end: insideIndent),
height: thickness,
color: color,
),
),
Text(text, style: TextStyle(color: color)),
Expanded(
child: Container(
margin:
EdgeInsetsDirectional.only(start: insideIndent, end: indent),
height: thickness,
color: color,
),
),
],
),
);
}
}
/// A horizontal divider in a material design popup menu.
///
/// This widget adapts the [Divider] for use in popup menus.
///
/// See also:
///
/// * [PopupMenuItem], for the kinds of items that this widget divides.
/// * [showMenu], a method to dynamically show a popup menu at a given location.
/// * [PopupMenuButton], an [IconButton] that automatically shows a menu when
/// it is tapped.
// ignore: prefer_void_to_null, https://github.com/dart-lang/sdk/issues/34416
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,
required this.text,
this.color})
: super(key: key);
/// The height of the divider entry.
///
/// Defaults to 16 pixels.
@override
final double height;
final String text;
final Color? color;
@override
bool represents(void value) => false;
@override
State createState() => _PopupMenuDividerState();
}
class _PopupMenuDividerState extends State {
@override
Widget build(BuildContext context) => LabeledDivider(
color: widget.color,
height: widget.height,
text: widget.text,
);
}
/// An item in a material design popup menu.
///
/// To show a popup menu, use the [showMenu] function. To create a button that
/// shows a popup menu, consider using [PopupMenuButton].
///
/// To show a checkmark next to a popup menu item, consider using
/// [CheckedPopupMenuItem].
///
/// Typically the [child] of a [PopupMenuItem] is a [Text] widget. More
/// elaborate menus with icons can use a [ListTile]. By default, a
/// [PopupMenuItem] is 48 pixels high. If you use a widget with a different
/// height, it must be specified in the [height] property.
///
/// {@tool sample}
///
/// Here, a [Text] widget is used with a popup menu item. The `WhyFarther` type
/// is an enum, not shown here.
///
/// ```dart
/// const PopupMenuItem(
/// value: WhyFarther.harder,
/// child: Text('Working a lot harder'),
/// )
/// ```
/// {@end-tool}
///
/// See the example at [PopupMenuButton] for how this example could be used in a
/// complete menu, and see the example at [CheckedPopupMenuItem] for one way to
/// keep the text of [PopupMenuItem]s that use [Text] widgets in their [child]
/// slot aligned with the text of [CheckedPopupMenuItem]s or of [PopupMenuItem]
/// that use a [ListTile] in their [child] slot.
///
/// See also:
///
/// * [PopupMenuDivider], which can be used to divide items from each other.
/// * [CheckedPopupMenuItem], a variant of [PopupMenuItem] with a checkmark.
/// * [showMenu], a method to dynamically show a popup menu at a given location.
/// * [PopupMenuButton], an [IconButton] that automatically shows a menu when
/// it is tapped.
class PopupMenuItem extends PopupMenuEntry {
/// Creates an item for a popup menu.
///
/// By default, the item is [enabled].
///
/// The `height` and `enabled` arguments must not be null.
const PopupMenuItem({
Key? key,
required this.value,
this.enabled = true,
this.height = _kMenuItemHeight,
this.backgroundColor,
required this.child,
}) : super(key: key);
/// The value that will be returned by [showMenu] if this entry is selected.
final T value;
/// Whether the user is permitted to select this entry.
///
/// Defaults to true. If this is false, then the item will not react to
/// touches.
final bool enabled;
/// The height of the entry.
///
/// Defaults to 48 pixels.
@override
final double height;
/// The widget background color
///
/// Defaults to white.
final Color? backgroundColor;
/// The widget below this widget in the tree.
///
/// 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;
@override
bool represents(T value) => value == this.value;
@override
PopupMenuItemState> createState() =>
PopupMenuItemState>();
}
/// The [State] for [PopupMenuItem] subclasses.
///
/// By default this implements the basic styling and layout of Material Design
/// popup menu items.
///
/// The [buildChild] method can be overridden to adjust exactly what gets placed
/// in the menu. By default it returns [PopupMenuItem.child].
///
/// The [handleTap] method can be overridden to adjust exactly what happens when
/// the item is tapped. By default, it uses [Navigator.pop] to return the
/// [PopupMenuItem.value] from the menu route.
///
/// This class takes two type arguments. The second, `W`, is the exact type of
/// the [Widget] that is using this [State]. It must be a subclass of
/// [PopupMenuItem]. The first, `T`, must match the type argument of that widget
/// class, and is the type of values returned from this menu.
class PopupMenuItemState> extends State {
/// The menu item contents.
///
/// Used by the [build] method.
///
/// By default, this returns [PopupMenuItem.child]. Override this to put
/// something else in the menu entry.
@protected
Widget buildChild() => widget.child;
/// The handler for when the user selects the menu item.
///
/// Used by the [InkWell] inserted by the [build] method.
///
/// By default, uses [Navigator.pop] to return the [PopupMenuItem.value] from
/// the menu route.
@protected
void handleTap() {
Navigator.pop(context, widget.value);
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
TextStyle style = theme.textTheme.titleMedium!;
if (!widget.enabled) style = style.copyWith(color: theme.disabledColor);
Widget item = AnimatedDefaultTextStyle(
style: style,
duration: kThemeChangeDuration,
child: Baseline(
baseline: widget.height - _kBaselineOffsetFromBottom,
baselineType: style.textBaseline!,
child: buildChild(),
),
);
if (!widget.enabled) {
final bool isDark = theme.brightness == Brightness.dark;
item = IconTheme.merge(
data: IconThemeData(opacity: isDark ? 0.5 : 0.38),
child: item,
);
}
return InkWell(
onTap: widget.enabled ? handleTap : null,
child: Container(
color: widget.backgroundColor ?? Theme.of(context).popupMenuTheme.color,
height: widget.height,
padding:
const EdgeInsets.symmetric(horizontal: _kMenuHorizontalPadding),
child: item,
),
);
}
}
/// An item with a checkmark in a material design popup menu.
///
/// To show a popup menu, use the [showMenu] function. To create a button that
/// shows a popup menu, consider using [PopupMenuButton].
///
/// A [CheckedPopupMenuItem] is 48 pixels high, which matches the default height
/// of a [PopupMenuItem]. The horizontal layout uses a [ListTile]; the checkmark
/// is an [Icons.done] icon, shown in the [ListTile.leading] position.
///
/// {@tool sample}
///
/// Suppose a `Commands` enum exists that lists the possible commands from a
/// particular popup menu, including `Commands.heroAndScholar` and
/// `Commands.hurricaneCame`, and further suppose that there is a
/// `_heroAndScholar` member field which is a boolean. The example below shows a
/// menu with one menu item with a checkmark that can toggle the boolean, and
/// one menu item without a checkmark for selecting the second option. (It also
/// shows a divider placed between the two menu items.)
///
/// ```dart
/// PopupMenuButton(
/// onSelected: (Commands result) {
/// switch (result) {
/// case Commands.heroAndScholar:
/// setState(() { _heroAndScholar = !_heroAndScholar; });
/// break;
/// case Commands.hurricaneCame:
/// // ...handle hurricane option
/// break;
/// // ...other items handled here
/// }
/// },
/// itemBuilder: (BuildContext context) => >[
/// CheckedPopupMenuItem(
/// checked: _heroAndScholar,
/// value: Commands.heroAndScholar,
/// child: const Text('Hero and scholar'),
/// ),
/// const PopupMenuDivider(),
/// const PopupMenuItem(
/// value: Commands.hurricaneCame,
/// child: ListTile(leading: Icon(null), title: Text('Bring hurricane')),
/// ),
/// // ...other items listed here
/// ],
/// )
/// ```
/// {@end-tool}
///
/// In particular, observe how the second menu item uses a [ListTile] with a
/// blank [Icon] in the [ListTile.leading] position to get the same alignment as
/// the item with the checkmark.
///
/// See also:
///
/// * [PopupMenuItem], a popup menu entry for picking a command (as opposed to
/// toggling a value).
/// * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
/// * [showMenu], a method to dynamically show a popup menu at a given location.
/// * [PopupMenuButton], an [IconButton] that automatically shows a menu when
/// it is tapped.
class CheckedPopupMenuItem extends PopupMenuItem {
/// Creates a popup menu item with a checkmark.
///
/// By default, the menu item is [enabled] but unchecked. To mark the item as
/// checked, set [checked] to true.
///
/// The `checked` and `enabled` arguments must not be null.
const CheckedPopupMenuItem({
Key? key,
required T value,
this.checked = false,
bool enabled = true,
required Widget child,
}) : super(
key: key,
value: value,
enabled: enabled,
child: child,
);
/// Whether to display a checkmark next to the menu item.
///
/// Defaults to false.
///
/// When true, an [Icons.done] checkmark is displayed.
///
/// When this popup menu item is selected, the checkmark will fade in or out
/// as appropriate to represent the implied new state.
final bool checked;
/// The widget below this widget in the tree.
///
/// Typically a [Text]. An appropriate [DefaultTextStyle] is put in scope for
/// the child. The text should be short enough that it won't wrap.
///
/// 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;
@override
_CheckedPopupMenuItemState createState() =>
_CheckedPopupMenuItemState();
}
class _CheckedPopupMenuItemState
extends PopupMenuItemState>
with SingleTickerProviderStateMixin {
static const Duration _fadeDuration = Duration(milliseconds: 150);
late AnimationController _controller;
Animation get _opacity => _controller.view;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: _fadeDuration, vsync: this)
..value = widget.checked ? 1.0 : 0.0
..addListener(() => setState(() {/* animation changed */}));
}
@override
void handleTap() {
// This fades the checkmark in or out when tapped.
if (widget.checked) {
_controller.reverse();
} else {
_controller.forward();
}
super.handleTap();
}
@override
Widget buildChild() {
return ListTile(
enabled: widget.enabled,
leading: FadeTransition(
opacity: _opacity,
child: Icon(_controller.isDismissed ? null : Icons.done),
),
title: widget.child,
);
}
}
class _PopupMenu extends StatelessWidget {
const _PopupMenu({
Key? key,
required this.route,
this.semanticLabel = "",
this.controller,
}) : super(key: key);
final _PopupMenuRoute route;
final String semanticLabel;
final ScrollController? controller;
@override
Widget build(BuildContext context) {
final double unit = 1.0 /
(route.items.length +
1.5); // 1.0 for the width and 0.5 for the last item's fade.
final List children = [];
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);
final CurvedAnimation opacity = CurvedAnimation(
parent: route.animation!,
curve: Interval(start, end),
);
Widget item = route.items[i];
if (route.initialValue != null &&
route.items[i].represents(route.initialValue)) {
item = ColoredBox(
color: Theme.of(context).highlightColor,
child: item,
);
}
children.add(FadeTransition(
opacity: opacity,
child: item,
));
}
final CurveTween opacity =
CurveTween(curve: const Interval(0.0, 1.0 / 3.0));
final CurveTween width = CurveTween(curve: Interval(0.0, unit));
final CurveTween height =
CurveTween(curve: Interval(0.0, unit * route.items.length));
final Widget child = ConstrainedBox(
constraints: const BoxConstraints(
minWidth: _kMenuMinWidth,
maxWidth: _kMenuMaxWidth,
),
child: IntrinsicWidth(
stepWidth: _kMenuWidthStep,
child: Semantics(
scopesRoute: true,
namesRoute: true,
explicitChildNodes: true,
label: semanticLabel,
child: SingleChildScrollView(
controller: controller,
padding:
const EdgeInsets.symmetric(vertical: _kMenuVerticalPadding),
child: ListBody(children: children),
),
),
),
);
return AnimatedBuilder(
animation: route.animation!,
builder: (BuildContext context, Widget? child) {
return Opacity(
opacity: opacity.evaluate(route.animation!),
child: Material(
type: MaterialType.transparency,
elevation: route.elevation,
child: Align(
alignment: AlignmentDirectional.topEnd,
widthFactor: width.evaluate(route.animation!),
heightFactor: height.evaluate(route.animation!),
child: child,
),
),
);
},
child: child,
);
}
}
// Positioning of the menu on the screen.
class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
_PopupMenuRouteLayout(
this.position, this.selectedItemOffset, this.textDirection);
// Rectangle of underlying button, relative to the overlay's dimensions.
final RelativeRect position;
// The distance from the top of the menu to the middle of selected item.
//
// This will be null if there's no item to position in this way.
final double selectedItemOffset;
// Whether to prefer going to the left or to the right.
final TextDirection textDirection;
// We put the child wherever position specifies, so long as it will fit within
// the specified parent size padded (inset) by 8. If necessary, we adjust the
// child's position so that it fits.
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
// The menu can be at most the size of the overlay minus 8.0 pixels in each
// direction.
return BoxConstraints.loose(constraints.biggest -
const Offset(_kMenuScreenPadding * 2.0, _kMenuScreenPadding * 2.0)
as Size);
}
@override
Offset getPositionForChild(Size size, Size childSize) {
// size: The size of the overlay.
// childSize: The size of the menu, when fully open, as determined by
// getConstraintsForChild.
// Find the ideal vertical position.
double y;
y = position.top +
(size.height - position.top - position.bottom) / 2.0 -
selectedItemOffset;
// Find the ideal horizontal position.
double x;
if (position.left > position.right) {
// Menu button is closer to the right edge, so grow to the left, aligned to the right edge.
x = size.width - position.right - childSize.width;
} else if (position.left < position.right) {
// Menu button is closer to the left edge, so grow to the right, aligned to the left edge.
x = position.left;
} else {
// Menu button is equidistant from both edges, so grow in reading direction.
switch (textDirection) {
case TextDirection.rtl:
x = size.width - position.right - childSize.width;
break;
case TextDirection.ltr:
x = position.left;
break;
}
}
// Avoid going outside an area defined as the rectangle 8.0 pixels from the
// edge of the screen in every direction.
if (x < _kMenuScreenPadding) {
x = _kMenuScreenPadding;
} else if (x + childSize.width > size.width - _kMenuScreenPadding) {
x = size.width - childSize.width - _kMenuScreenPadding;
}
if (y < _kMenuScreenPadding) {
y = _kMenuScreenPadding;
} else if (y + childSize.height > size.height - _kMenuScreenPadding) {
y = size.height - childSize.height - _kMenuScreenPadding;
}
return Offset(x, y);
}
@override
bool shouldRelayout(_PopupMenuRouteLayout oldDelegate) {
return position != oldDelegate.position;
}
}
class _PopupMenuRoute extends PopupRoute {
_PopupMenuRoute({
required this.position,
required this.items,
this.initialValue,
this.elevation = 8,
required this.theme,
required this.barrierLabel,
required this.semanticLabel,
});
final RelativeRect position;
final List> items;
final dynamic initialValue;
final double elevation;
final ThemeData theme;
final String semanticLabel;
@override
Animation createAnimation() {
return CurvedAnimation(
parent: super.createAnimation(),
curve: Curves.linear,
reverseCurve: const Interval(0.0, _kMenuCloseIntervalEnd),
);
}
@override
Duration get transitionDuration => _kMenuDuration;
@override
bool get barrierDismissible => true;
@override
Color? get barrierColor => null;
@override
final String barrierLabel;
ScrollController? scrollController;
@override
Widget buildPage(BuildContext context, Animation animation,
Animation secondaryAnimation) {
double selectedItemOffset = 0;
double scrollItemOffset = 0;
if (initialValue != null) {
double y = _kMenuVerticalPadding;
for (PopupMenuEntry entry in items) {
if (entry.represents(initialValue)) {
selectedItemOffset = y + entry.height / 2.0;
scrollItemOffset = y - entry.height / 2.0;
break;
}
y += entry.height;
}
}
scrollController = ScrollController(initialScrollOffset: scrollItemOffset);
Widget menu = _PopupMenu(
route: this,
semanticLabel: semanticLabel,
controller: scrollController);
menu = Theme(data: theme, child: menu);
return SafeArea(
child: Builder(
builder: (BuildContext context) {
return CustomSingleChildLayout(
delegate: _PopupMenuRouteLayout(
position,
selectedItemOffset,
Directionality.of(context),
),
child: menu,
);
},
),
);
}
}
/// Show a popup menu that contains the `items` at `position`.
///
/// `items` should be non-null and not empty.
///
/// If `initialValue` is specified then the first item with a matching value
/// will be highlighted and the value of `position` gives the rectangle whose
/// vertical center will be aligned with the vertical center of the highlighted
/// item (when possible).
///
/// If `initialValue` is not specified then the top of the menu will be aligned
/// with the top of the `position` rectangle.
///
/// In both cases, the menu position will be adjusted if necessary to fit on the
/// screen.
///
/// Horizontally, the menu is positioned so that it grows in the direction that
/// has the most room. For example, if the `position` describes a rectangle on
/// the left edge of the screen, then the left edge of the menu is aligned with
/// the left edge of the `position`, and the menu grows to the right. If both
/// edges of the `position` are equidistant from the opposite edge of the
/// screen, then the ambient [Directionality] is used as a tie-breaker,
/// preferring to grow in the reading direction.
///
/// The positioning of the `initialValue` at the `position` is implemented by
/// iterating over the `items` to find the first whose
/// [PopupMenuEntry.represents] method returns true for `initialValue`, and then
/// summing the values of [PopupMenuEntry.height] for all the preceding widgets
/// in the list.
///
/// The `elevation` argument specifies the z-coordinate at which to place the
/// menu. The elevation defaults to 8, the appropriate elevation for popup
/// menus.
///
/// The `context` argument is used to look up the [Navigator] and [Theme] for
/// the menu. It is only used when the method is called. Its corresponding
/// widget can be safely removed from the tree before the popup menu is closed.
///
/// The `semanticLabel` argument is used by accessibility frameworks to
/// announce screen transitions when the menu is opened and closed. If this
/// label is not provided, it will default to
/// [MaterialLocalizations.popupMenuLabel].
///
/// See also:
///
/// * [PopupMenuItem], a popup menu entry for a single value.
/// * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
/// * [CheckedPopupMenuItem], a popup menu item with a checkmark.
/// * [PopupMenuButton], which provides an [IconButton] that shows a menu by
/// 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 = 8.0,
String semanticLabel = "",
}) {
assert(items.isNotEmpty);
assert(debugCheckHasMaterialLocalizations(context));
String label = semanticLabel;
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
label = semanticLabel;
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
label = semanticLabel.isNotEmpty
? semanticLabel
: MaterialLocalizations.of(context).popupMenuLabel;
break;
default:
label = semanticLabel;
}
return Navigator.push(
context,
_PopupMenuRoute(
position: position,
items: items,
initialValue: initialValue,
elevation: elevation,
semanticLabel: label,
theme: Theme.of(context),
barrierLabel:
MaterialLocalizations.of(context).modalBarrierDismissLabel,
));
}
/// Signature for the callback invoked when a menu item is selected. The
/// argument is the value of the [PopupMenuItem] that caused its menu to be
/// dismissed.
///
/// Used by [PopupMenuButton.onSelected].
typedef PopupMenuItemSelected = void Function(T value);
/// Signature for the callback invoked when a [PopupMenuButton] is dismissed
/// without selecting an item.
///
/// Used by [PopupMenuButton.onCanceled].
typedef PopupMenuCanceled = void Function();
/// Signature used by [PopupMenuButton] to lazily construct the items shown when
/// the button is pressed.
///
/// Used by [PopupMenuButton.itemBuilder].
typedef PopupMenuItemBuilder = List> Function(
BuildContext context);
/// Displays a menu when pressed and calls [onSelected] when the menu is dismissed
/// because an item was selected. The value passed to [onSelected] is the value of
/// the selected menu item.
///
/// One of [child] or [icon] may be provided, but not both. If [icon] is provided,
/// then [PopupMenuButton] behaves like an [IconButton].
///
/// If both are null, then a standard overflow icon is created (depending on the
/// platform).
///
/// {@tool sample}
///
/// This example shows a menu with four items, selecting between an enum's
/// values and setting a `_selection` field based on the selection.
///
/// ```dart
/// // This is the type used by the popup menu below.
/// enum WhyFarther { harder, smarter, selfStarter, tradingCharter }
///
/// // This menu button widget updates a _selection field (of type WhyFarther,
/// // not shown here).
/// PopupMenuButton(
/// onSelected: (WhyFarther result) { setState(() { _selection = result; }); },
/// itemBuilder: (BuildContext context) => >[
/// const PopupMenuItem(
/// value: WhyFarther.harder,
/// child: Text('Working a lot harder'),
/// ),
/// const PopupMenuItem(
/// value: WhyFarther.smarter,
/// child: Text('Being a lot smarter'),
/// ),
/// const PopupMenuItem(
/// value: WhyFarther.selfStarter,
/// child: Text('Being a self-starter'),
/// ),
/// const PopupMenuItem(
/// value: WhyFarther.tradingCharter,
/// child: Text('Placed in charge of trading charter'),
/// ),
/// ],
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [PopupMenuItem], a popup menu entry for a single value.
/// * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
/// * [CheckedPopupMenuItem], a popup menu item with a checkmark.
/// * [showMenu], a method to dynamically show a popup menu at a given location.
class PopupMenuButton extends StatelessWidget {
/// Creates a button that shows a popup menu.
///
/// The [itemBuilder] argument must not be null.
const PopupMenuButton({
Key? key,
required this.itemBuilder,
this.initialValue,
this.onSelected,
this.onCanceled,
this.tooltip,
this.elevation = 8.0,
this.padding = const EdgeInsets.all(8.0),
this.child,
this.icon,
this.offset = Offset.zero,
this.enabled = true,
}) : assert(!(child != null &&
icon != null)), // fails if passed both parameters
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;
/// Called when the user selects a value from the popup menu created by this button.
///
/// If the popup menu is dismissed without selecting a value, [onCanceled] is
/// called instead.
final PopupMenuItemSelected? onSelected;
/// 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;
/// 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;
/// 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;
/// 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
/// to set the padding to zero.
final EdgeInsetsGeometry padding;
/// If provided, the widget used for this button.
final Widget? child;
/// If provided, the icon used for this button.
final Icon? icon;
/// The offset applied to the Popup Menu Button.
///
/// When not set, the Popup Menu Button will be positioned directly next to
/// the button that was used to create it.
final Offset offset;
/// Whether this popup menu button is interactive.
///
/// Must be non-null, defaults to `true`
///
/// If `true` the button will respond to presses by displaying the menu.
///
/// If `false`, the button is styled with the disabled color from the
/// current [Theme] and will not respond to presses or show the popup
/// menu and [onSelected], [onCanceled] and [itemBuilder] will not be called.
///
/// This can be useful in situations where the app needs to show the button,
/// but doesn't currently have anything to show in the menu.
final bool enabled;
// @override
// _PopupMenuButtonState createState() => _PopupMenuButtonState();
void showButtonMenu(BuildContext context) {
final RenderBox button = context.findRenderObject() as RenderBox;
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(offset, ancestor: overlay),
button.localToGlobal(button.size.bottomRight(Offset.zero),
ancestor: overlay),
),
Offset.zero & overlay.size,
);
final List> items = itemBuilder(context);
// Only show the menu if there is something to show
if (items.isNotEmpty) {
showMenu(
context: context,
elevation: elevation,
items: items,
initialValue: initialValue,
position: position,
).then((T? newValue) {
//if (!mounted) return null;
if (newValue == null) {
onCanceled?.call();
return null;
}
onSelected?.call(newValue);
});
}
}
Icon _getIcon(TargetPlatform platform) {
switch (platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return const Icon(Icons.more_vert);
case TargetPlatform.iOS:
return const Icon(Icons.more_horiz);
default:
return const Icon(Icons.more_vert);
}
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
return child != null
? InkWell(
onTap: enabled ? () => showButtonMenu(context) : null,
child: child,
)
: IconButton(
icon: icon ?? _getIcon(Theme.of(context).platform),
padding: padding,
tooltip:
tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
onPressed: enabled ? () => showButtonMenu(context) : null,
);
}
}
// class _PopupMenuButtonState extends State> {
// }
================================================
FILE: lib/UI/widgets/common/modeControlRegular.dart
================================================
import 'package:flutter/material.dart';
class ModeControlRegular extends StatelessWidget {
final List options;
final int selected;
final void Function(int index)? onSelected;
final TextStyle? textStyle;
const ModeControlRegular(
{super.key,
required this.options,
required this.selected,
this.onSelected,
this.textStyle});
Widget _modeButton(String mode) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: Text(mode, style: textStyle),
);
}
@override
Widget build(BuildContext context) {
List active =
List.generate(options.length, (i) => i == selected ? true : false);
return ToggleButtons(
fillColor: Colors.blue,
selectedBorderColor: Colors.blue,
borderColor: Colors.grey[600],
color: Colors.grey,
isSelected: active,
onPressed: onSelected,
children: [
for (var i = 0; i < options.length; i++) _modeButton(options[i])
],
);
}
}
================================================
FILE: lib/UI/widgets/common/nestedWillPopScope.dart
================================================
import 'package:flutter/material.dart';
class NestedWillPopScope extends StatefulWidget {
const NestedWillPopScope({
Key? key,
required this.child,
required this.onWillPop,
}) : super(key: key);
final Widget child;
final WillPopCallback onWillPop;
@override
_NestedWillPopScopeState createState() => _NestedWillPopScopeState();
static _NestedWillPopScopeState? of(BuildContext context) {
return context.findAncestorStateOfType<_NestedWillPopScopeState>();
}
}
class _NestedWillPopScopeState extends State {
ModalRoute? _route;
_NestedWillPopScopeState? _descendant;
set descendant(state) {
_descendant = state;
updateRouteCallback();
}
Future onWillPop() async {
bool? willPop;
if (_descendant != null) {
willPop = await _descendant!.onWillPop();
}
if (willPop == null || willPop) {
willPop = await widget.onWillPop();
}
return willPop;
}
void updateRouteCallback() {
_route?.removeScopedWillPopCallback(onWillPop);
_route = ModalRoute.of(context);
_route?.addScopedWillPopCallback(onWillPop);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
var parentGuard = NestedWillPopScope.of(context);
if (parentGuard != null) {
parentGuard.descendant = this;
}
updateRouteCallback();
}
@override
void dispose() {
_route?.removeScopedWillPopCallback(onWillPop);
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}
================================================
FILE: lib/UI/widgets/common/numberPicker.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
typedef TextMapper = String Function(String numberText);
class NumberPicker extends StatefulWidget {
/// Min value user can pick
final int minValue;
/// Max value user can pick
final int maxValue;
/// Currently selected value
final int value;
/// Called when selected value changes
final ValueChanged? onChanged;
/// Specifies how many items should be shown - defaults to 3
final int itemCount;
/// Step between elements. Only for integer datePicker
/// Examples:
/// if step is 100 the following elements may be 100, 200, 300...
/// if min=0, max=6, step=3, then items will be 0, 3 and 6
/// if min=0, max=5, step=3, then items will be 0 and 3.
final int step;
/// height of single item in pixels
final double itemHeight;
/// width of single item in pixels
final double itemWidth;
/// Direction of scrolling
final Axis axis;
/// Style of non-selected numbers. If null, it uses Theme's bodyText2
final TextStyle? textStyle;
/// Style of non-selected numbers. If null, it uses Theme's headline5 with accentColor
final TextStyle? selectedTextStyle;
/// Whether to trigger haptic pulses or not
final bool haptics;
/// Build the text of each item on the picker
final TextMapper? textMapper;
/// Pads displayed integer values up to the length of maxValue
final bool zeroPad;
/// display the number in hex format
final bool hex;
/// Symbol to display instead of zero
final String? zeroSymbol;
/// Decoration to apply to central box where the selected value is placed
final Decoration? decoration;
const NumberPicker({
Key? key,
required this.minValue,
required this.maxValue,
required this.value,
required this.onChanged,
this.itemCount = 3,
this.step = 1,
this.itemHeight = 50,
this.itemWidth = 100,
this.axis = Axis.vertical,
this.textStyle,
this.selectedTextStyle,
this.haptics = false,
this.decoration,
this.hex = false,
this.zeroSymbol,
this.zeroPad = false,
this.textMapper,
}) : assert(minValue <= value),
assert(value <= maxValue),
super(key: key);
@override
State createState() => _NumberPickerState();
}
class _NumberPickerState extends State {
late ScrollController _scrollController;
@override
void initState() {
super.initState();
final initialOffset =
(widget.value - widget.minValue) ~/ widget.step * itemExtent;
_scrollController = ScrollController(initialScrollOffset: initialOffset)
..addListener(_scrollListener);
}
void _scrollListener() {
final indexOfMiddleElement =
(_scrollController.offset / itemExtent).round().clamp(0, itemCount - 1);
final intValueInTheMiddle = _intValueFromIndex(indexOfMiddleElement + 1);
if (widget.value != intValueInTheMiddle) {
widget.onChanged?.call(intValueInTheMiddle);
if (widget.haptics) {
HapticFeedback.selectionClick();
}
}
Future.delayed(
const Duration(milliseconds: 100),
() => _maybeCenterValue(),
);
}
@override
void didUpdateWidget(NumberPicker oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.value != widget.value) {
_maybeCenterValue();
}
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
bool get isScrolling => _scrollController.position.isScrollingNotifier.value;
double get itemExtent =>
widget.axis == Axis.vertical ? widget.itemHeight : widget.itemWidth;
int get itemCount => (widget.maxValue - widget.minValue) ~/ widget.step + 1;
int get listItemsCount => itemCount + 2;
@override
Widget build(BuildContext context) {
return SizedBox(
width: widget.axis == Axis.vertical
? widget.itemWidth
: widget.itemCount * widget.itemWidth,
height: widget.axis == Axis.vertical
? widget.itemCount * widget.itemHeight
: widget.itemHeight,
child: NotificationListener(
onNotification: (not) {
if (not.dragDetails?.primaryVelocity == 0) {
Future.microtask(() => _maybeCenterValue());
}
return true;
},
child: Stack(
children: [
ListView.builder(
itemCount: listItemsCount,
scrollDirection: widget.axis,
controller: _scrollController,
itemExtent: itemExtent,
itemBuilder: _itemBuilder,
),
_NumberPickerSelectedItemDecoration(
axis: widget.axis,
itemExtent: itemExtent,
decoration: widget.decoration,
),
],
),
),
);
}
Widget _itemBuilder(BuildContext context, int index) {
final themeData = Theme.of(context);
final defaultStyle = widget.textStyle ?? themeData.textTheme.bodyMedium;
final selectedStyle = widget.selectedTextStyle ??
themeData.textTheme.headlineSmall
?.copyWith(color: themeData.colorScheme.surface);
final value = _intValueFromIndex(index);
final isExtra = index == 0 || index == listItemsCount - 1;
final itemStyle = value == widget.value ? selectedStyle : defaultStyle;
final child = isExtra
? const SizedBox.shrink()
: Text(
_getDisplayedValue(value),
style: itemStyle,
);
return Container(
width: widget.itemWidth,
height: widget.itemHeight,
alignment: Alignment.center,
child: child,
);
}
String _getDisplayedValue(int value) {
String text = "";
if (value == 0 && widget.zeroSymbol != null) return widget.zeroSymbol!;
if (widget.hex) {
text = value.toRadixString(16).padLeft(2, '0');
return text;
} else {
String strValue = value.toString();
text = widget.zeroPad
? strValue.padLeft(widget.maxValue.toString().length, '0')
: strValue.toString();
}
if (widget.textMapper != null) {
return widget.textMapper!(text);
} else {
return text;
}
}
int _intValueFromIndex(int index) {
index--;
index %= itemCount;
return widget.minValue + index * widget.step;
}
void _maybeCenterValue() {
if (_scrollController.hasClients && !isScrolling) {
int diff = widget.value - widget.minValue;
int index = diff ~/ widget.step;
_scrollController.animateTo(
index * itemExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
);
}
}
}
class _NumberPickerSelectedItemDecoration extends StatelessWidget {
final Axis axis;
final double itemExtent;
final Decoration? decoration;
const _NumberPickerSelectedItemDecoration({
Key? key,
required this.axis,
required this.itemExtent,
required this.decoration,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: IgnorePointer(
child: Container(
width: isVertical ? double.infinity : itemExtent,
height: isVertical ? itemExtent : double.infinity,
decoration: decoration,
),
),
);
}
bool get isVertical => axis == Axis.vertical;
}
================================================
FILE: lib/UI/widgets/common/rounded_icon_button.dart
================================================
import 'package:flutter/material.dart';
class RoundedIconButton extends StatelessWidget {
final VoidCallback? onPressed;
final Widget icon;
final String? tooltip;
final double borderRadius;
const RoundedIconButton(
{Key? key,
this.onPressed,
required this.icon,
this.tooltip,
this.borderRadius = 6})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: ShapeDecoration(
color: onPressed != null ? Colors.blue : Colors.grey[800],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius)),
),
child: IconButton(
constraints: ButtonTheme.of(context).constraints,
icon: icon,
onPressed: onPressed,
tooltip: tooltip,
),
);
}
}
================================================
FILE: lib/UI/widgets/common/searchTextField.dart
================================================
import 'package:flutter/material.dart';
class SearchTextField extends StatelessWidget {
final TextEditingController controller;
final void Function(String?)? onSearch;
const SearchTextField({Key? key, required this.controller, this.onSearch})
: super(key: key);
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
onSubmitted: onSearch,
decoration: InputDecoration(
hintText: "Search",
hintStyle: const TextStyle(color: Colors.grey),
prefixIcon: const Icon(
Icons.search,
color: Colors.grey,
),
suffixIcon: IconButton(
onPressed: () {
controller.clear();
FocusScope.of(context).unfocus();
},
icon: const Icon(Icons.clear),
color: Colors.grey,
),
),
);
}
}
================================================
FILE: lib/UI/widgets/deviceList.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
import 'package:flutter/material.dart';
import '../../bluetooth/bleMidiHandler.dart';
class DeviceList extends StatelessWidget {
final BLEMidiHandler midiHandler = BLEMidiHandler.instance();
DeviceList({Key? key}) : super(key: key);
bool isConnected(String id) {
//check with nux device first
if (midiHandler.connectedDevice != null &&
id == midiHandler.connectedDevice?.id) return true;
for (var controller in midiHandler.controllerDevices) {
if (controller.id == id) return true;
}
return false;
}
@override
Widget build(BuildContext context) {
return ListView.builder(
// Let the ListView know how many items it needs to build
itemCount: midiHandler.nuxDevices.length,
// Provide a builder function. This is where the magic happens! We'll
// convert each item into a Widget based on the type of item it is.
itemBuilder: (context, index) {
final result = midiHandler.nuxDevices[index];
return ListTile(
title: Text(result.name,
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: isConnected(result.id) ? Colors.blue : Colors.white)),
trailing: const Icon(Icons.bluetooth, color: Colors.white),
onTap: () {
midiHandler.connectToDevice(result.device);
},
);
},
);
}
}
================================================
FILE: lib/UI/widgets/fabMenu.dart
================================================
import 'dart:math';
import 'package:flutter/material.dart';
import 'common/nestedWillPopScope.dart';
class Bubble {
const Bubble(
{required this.title,
required this.titleStyle,
required this.iconColor,
required this.bubbleColor,
required this.icon,
required this.onPress});
final IconData icon;
final Color iconColor;
final Color bubbleColor;
final Function() onPress;
final String title;
final TextStyle titleStyle;
}
class BubbleMenu extends StatelessWidget {
const BubbleMenu(this.item, {Key? key}) : super(key: key);
final Bubble item;
@override
Widget build(BuildContext context) {
return MaterialButton(
shape: const StadiumBorder(),
padding: const EdgeInsets.only(top: 11, bottom: 13, left: 32, right: 32),
color: item.bubbleColor,
splashColor: Colors.grey.withOpacity(0.1),
highlightColor: Colors.grey.withOpacity(0.1),
elevation: 2,
highlightElevation: 2,
disabledColor: item.bubbleColor,
onPressed: item.onPress,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
item.icon,
color: item.iconColor,
),
const SizedBox(
width: 10.0,
),
Text(
item.title,
style: item.titleStyle,
),
],
),
);
}
}
class _DefaultHeroTag {
const _DefaultHeroTag();
@override
String toString() => '';
}
class FloatingActionBubble extends AnimatedWidget {
const FloatingActionBubble({
Key? key,
required this.items,
required this.onPress,
required this.iconColor,
required this.backGroundColor,
required Animation animation,
this.herotag,
this.iconData,
this.animatedIconData,
}) : assert((iconData == null && animatedIconData != null) ||
(iconData != null && animatedIconData == null)),
super(key: key, listenable: animation);
final List items;
final Function() onPress;
final AnimatedIconData? animatedIconData;
final Object? herotag;
final IconData? iconData;
final Color iconColor;
final Color backGroundColor;
get _animation => listenable;
Widget buildItem(BuildContext context, int index) {
final transform = Matrix4.translationValues(
0,
-(_animation.value - 1) * 40 * (items.length - index),
0.0,
);
return Transform(
transform: transform,
child: Opacity(
opacity: _animation.value,
child: BubbleMenu(items[index]),
),
);
}
List buildItems(BuildContext context) {
var widgets = [];
var sb = const SizedBox(height: 12.0);
for (int i = 0; i < items.length; i++) {
widgets.add(buildItem(context, i));
if (i < items.length - 1) widgets.add(sb);
}
return widgets;
}
Future _preventPopIfOpen() async {
if (_animation.value > 0.8) {
onPress();
return false;
}
return true;
}
buildBackgroundWidget() {
double val = _animation.value;
return IgnorePointer(
ignoring: val == 0,
child: InkWell(
onTap: onPress,
child: Opacity(
opacity: val,
child: const ColoredBox(
color: Colors.black54,
),
),
),
);
}
@override
Widget build(BuildContext context) {
return NestedWillPopScope(
onWillPop: _preventPopIfOpen,
child: Stack(
alignment: Alignment.bottomRight,
children: [
buildBackgroundWidget(),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
child: IgnorePointer(
ignoring: _animation.value == 0,
child: SingleChildScrollView(
// physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(vertical: 12),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: buildItems(context),
)),
),
),
Transform.rotate(
angle: _animation.value * pi * 3 / 4,
child: FloatingActionButton(
heroTag: herotag ?? const _DefaultHeroTag(),
backgroundColor: backGroundColor,
onPressed: onPress,
// iconData is mutually exclusive with animatedIconData
// only 1 can be null at the time
child: iconData == null
? AnimatedIcon(
icon: animatedIconData!,
progress: _animation,
color: iconColor,
)
: Icon(
iconData,
color: iconColor,
),
),
),
],
),
],
),
);
}
}
================================================
FILE: lib/UI/widgets/hold_to_repeat.dart
================================================
import 'package:flutter/material.dart';
class HoldToRepeat extends StatefulWidget {
final Widget child;
final VoidCallback onPressed;
final Duration initialDelay;
final Duration repeatInterval;
const HoldToRepeat({
Key? key,
required this.child,
required this.onPressed,
this.initialDelay = const Duration(milliseconds: 800),
this.repeatInterval = const Duration(milliseconds: 200),
}) : super(key: key);
@override
_HoldToRepeatState createState() => _HoldToRepeatState();
}
class _HoldToRepeatState extends State {
bool _isPressed = false;
void _doRepeatingAction() {
if (_isPressed) {
widget.onPressed();
Future.delayed(widget.repeatInterval, _doRepeatingAction);
}
}
void _onTapDown() {
widget.onPressed();
setState(() {
_isPressed = true;
Future.delayed(widget.initialDelay, () {
if (_isPressed) {
_doRepeatingAction();
}
});
});
Feedback.forTap(context);
}
void _onTapUp() {
setState(() {
_isPressed = false;
});
}
@override
Widget build(BuildContext context) {
return InkWell(
onTapDown: (_) => _onTapDown(),
onTapUp: (_) => _onTapUp(),
onTapCancel: _onTapUp,
child: widget.child,
);
}
}
================================================
FILE: lib/UI/widgets/presets/EffectChainBar.dart
================================================
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/bluetooth/devices/NuxDevice.dart';
import '../../../bluetooth/devices/presets/Preset.dart';
import 'EffectChainButton.dart';
class EffectChainBar extends StatelessWidget {
static const double effectsChainPadding = 6;
final double maxHeight;
final NuxDevice device;
final Preset preset;
final bool reorderable;
final void Function(int) onTap;
final void Function(int) onDoubleTap;
final ReorderCallback onReorder;
const EffectChainBar(
{Key? key,
required this.maxHeight,
required this.device,
required this.preset,
required this.onTap,
required this.onDoubleTap,
required this.onReorder,
required this.reorderable})
: super(key: key);
EffectChainButton buildItem(context, index) {
var proc = preset.getFXIDFromSlot(index);
var effect = device.getProcessorInfoByFXID(proc);
bool selected = index == device.selectedSlot;
return EffectChainButton(
index: index,
effectInfo: effect!,
color: preset.effectColor(index),
enabled: preset.slotEnabled(index),
selected: selected,
reorderable: reorderable,
key: Key(index.toString()),
onTap: () => onTap(index),
onDoubleTap: () => onDoubleTap(index));
}
@override
Widget build(BuildContext context) {
double constrainHeight = max(
min(
(MediaQuery.of(context).size.width - effectsChainPadding * 2) /
device.effectsChainLength /
0.8,
maxHeight),
10);
Widget list;
if (reorderable) {
list = ReorderableList(
padding: const EdgeInsets.symmetric(horizontal: effectsChainPadding),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
itemCount: device.effectsChainLength,
proxyDecorator: (widget, index, animation) {
return AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget? child) {
final double animValue =
Curves.easeInOut.transform(animation.value);
final double offset = lerpDouble(0, -15, animValue)!;
return Material(
color: Colors.transparent,
child: Transform.translate(
offset: Offset(0, offset),
child: widget,
),
);
},
);
},
itemBuilder: buildItem,
onReorder: (a, b) {
if (b > a) b--;
onReorder(a, b);
},
);
} else {
list = ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: effectsChainPadding),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
itemCount: device.effectsChainLength,
itemBuilder: buildItem,
);
}
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: constrainHeight),
child: list,
);
}
}
================================================
FILE: lib/UI/widgets/presets/EffectChainButton.dart
================================================
import 'package:flutter/material.dart';
import '../../../bluetooth/devices/effects/Processor.dart';
class EffectChainButton extends StatelessWidget {
final ProcessorInfo effectInfo;
final bool enabled;
final bool selected;
final bool reorderable;
final Color color;
final GestureTapCallback? onTap;
final GestureTapCallback? onDoubleTap;
final int index;
const EffectChainButton(
{Key? key,
required this.effectInfo,
required this.enabled,
required this.selected,
required this.color,
this.onTap,
this.onDoubleTap,
required this.index,
required this.reorderable})
: super(key: key);
@override
Widget build(BuildContext context) {
Color _color = enabled ? color : Theme.of(context).disabledColor;
return ReorderableDragStartListener(
index: index,
child: AspectRatio(
aspectRatio: 0.8,
child: FittedBox(
fit: BoxFit.fitHeight,
child: Semantics(
label: effectInfo.longName,
selected: selected,
child: GestureDetector(
onTap: onTap,
onHorizontalDragStart: reorderable ? null : (details) {},
onVerticalDragStart: (details) {
onDoubleTap?.call();
},
child: Transform.translate(
offset: Offset(0, selected ? -5 : 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: const EdgeInsets.symmetric(horizontal: 1),
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
color: selected
? _color
: Theme.of(context).scaffoldBackgroundColor,
border: Border.all(
color: _color,
),
borderRadius:
const BorderRadius.all(Radius.circular(3))),
child: Icon(
effectInfo.icon,
//size: 30,
color: selected ? Colors.black : _color,
),
),
ExcludeSemantics(
child: Text(
effectInfo.shortName,
style: TextStyle(
fontSize: 10,
color: enabled
? null
: Theme.of(context).textTheme.bodySmall!.color),
),
),
],
),
),
),
),
),
),
);
}
}
================================================
FILE: lib/UI/widgets/presets/channelSelector.dart
================================================
// (c) 2020-2021 Dian Iliev (Tuntorius)
// This code is licensed under MIT license (see LICENSE.md for details)
//
import 'package:flutter/material.dart';
import 'package:mighty_plug_manager/UI/popups/alertDialogs.dart';
import 'package:mighty_plug_manager/UI/popups/exportQRCode.dart';
import 'package:mighty_plug_manager/bluetooth/NuxDeviceControl.dart';
import 'package:mighty_plug_manager/platform/fileSaver.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:qr_utils/qr_utils.dart';
import '../../../bluetooth/devices/presets/Preset.dart';
import '../../../bluetooth/devices/NuxDevice.dart';
import '../../../platform/platformUtils.dart';
import '../../theme.dart';
import '../../utils.dart';
import 'effectSelector.dart';
class ChannelSelector extends StatefulWidget {
final NuxDevice device;
const ChannelSelector({Key? key, required this.device}) : super(key: key);
@override
State createState() => _ChannelSelectorState();
}
class _ChannelSelectorState extends State {
late List