diff --git a/CHANGELOG.md b/CHANGELOG.md
index 137d1394..1756849c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,65 +1,6 @@
-## 0.1.0
+## 0.5.0
-* Initial release.
-
-## 0.1.1
-
-* Fix third-party repos in Android build. Now they are downloaded by the Gradle build if they don't exist.
-
-## 0.1.2
-
-* SampleDescriptor uses default values for most properties.
-* README is updated to explain how to build a sampler without an SFZ.
-* "pitch" has been replaced with "noteNumber" in all the public APIs.
-* Minor refactoring in sfz_parser.dart
-
-## 0.1.3
-
-* Merged PR to expose presetIndex: https://github.com/mikeperri/flutter_sequencer/pull/2
-
-## 0.1.4
-
-* Merge PR to fix presetIndex assignment: https://github.com/mikeperri/flutter_sequencer/pull/7
-* Fix iOS release mode issue: https://github.com/mikeperri/flutter_sequencer/issues/8
-
-## 0.2.0
-
-* Migrate to null safety
-* Upgrade lint rules
-
-## 0.2.1
-
-* Change AudioKit branch name
-
-## 0.3.0
-
-* Send MIDI note off for all notes when a track is stopped
-* Prevent rounding errors in getIsOver() by using frames instead of beats
-* Add Track.addNoteOn and .addNoteOff methods
-
-## 0.3.1
-* Clone third party repos with JGit library instead of deprecated Gradle plugin in Android build
-* Update Kotlin version to get rid of warnings in Android build
-
-## 0.3.2
-* Set Xcode STRIP_STYLE to "non-global" in podspec
-
-## 0.4.0
-* Replaced AudioKit Sampler with [sfizz](https://sfz.tools/sfizz/) because it supports many more SFZ opcodes, including filters and effects, and can stream samples from disk instead of loading them all into RAM.
-* Exposed APIs for scheduling MIDI CC and pitch bend events
-* BREAKING CHANGE - Replaced SamplerInstrument with RuntimeSfzInstrument. Instead of creating SampleDescriptors, now you have to build an Sfz object.
-* BREAKING CHANGE - Assets are copied to context.filesDir on Android for SFZ instruments. See the README for more info.
-* BREAKING CHANGE - SFZ player no longer handles URL-decoding asset paths
-
-## 0.4.1
-* Update README
-
-## 0.4.2
-* Remove hard-coded FLUTTER_ROOT in example iOS app
-* Run `flutter format`
-
-## 0.4.3
-* Fix for sfizz.hpp file not found on ios release build
-
-## 0.4.4
-* Add ENABLE_TESTABILITY back to pod target xcconfig to fix iOS release mode
+* Tested on Flutter 3.41.9
+* Analysis rules enforced
+* Refactoring
+* C++ static flag
\ No newline at end of file
diff --git a/all_lint_rules.yaml b/all_lint_rules.yaml
new file mode 100644
index 00000000..64d37e60
--- /dev/null
+++ b/all_lint_rules.yaml
@@ -0,0 +1,237 @@
+linter:
+ rules:
+ - always_declare_return_types
+ - always_put_control_body_on_new_line
+ - always_put_required_named_parameters_first
+ - always_specify_types
+ - always_use_package_imports
+ - annotate_overrides
+ - annotate_redeclares
+ - avoid_annotating_with_dynamic
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_catches_without_on_clauses
+ - avoid_catching_errors
+ - avoid_classes_with_only_static_members
+ - avoid_double_and_int_checks
+ - avoid_dynamic_calls
+ - avoid_empty_else
+ - avoid_equals_and_hash_code_on_mutable_classes
+ - avoid_escaping_inner_quotes
+ - avoid_field_initializers_in_const_classes
+ - avoid_final_parameters
+ - avoid_function_literals_in_foreach_calls
+ - avoid_futureor_void
+ - avoid_implementing_value_types
+ - avoid_init_to_null
+ - avoid_js_rounded_ints
+ - avoid_multiple_declarations_per_line
+ - avoid_null_checks_in_equality_operators
+ - avoid_positional_boolean_parameters
+ - avoid_print
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_relative_lib_imports
+ - avoid_renaming_method_parameters
+ - avoid_return_types_on_setters
+ - avoid_returning_null_for_void
+ - avoid_returning_this
+ - avoid_setters_without_getters
+ - avoid_shadowing_type_parameters
+ - avoid_single_cascade_in_expression_statements
+ - avoid_slow_async_io
+ - avoid_type_to_string
+ - avoid_types_as_parameter_names
+ - avoid_types_on_closure_parameters
+ - avoid_unnecessary_containers
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - avoid_web_libraries_in_flutter
+ - await_only_futures
+ - camel_case_extensions
+ - camel_case_types
+ - cancel_subscriptions
+ - cascade_invocations
+ - cast_nullable_to_non_nullable
+ - close_sinks
+ - collection_methods_unrelated_type
+ - combinators_ordering
+ - comment_references
+ - conditional_uri_does_not_exist
+ - constant_identifier_names
+ - control_flow_in_finally
+ - curly_braces_in_flow_control_structures
+ - dangling_library_doc_comments
+ - depend_on_referenced_packages
+ - deprecated_consistency
+ - deprecated_member_use_from_same_package
+ - diagnostic_describe_all_properties
+ - directives_ordering
+ - discarded_futures
+ - do_not_use_environment
+ - document_ignores
+ - empty_catches
+ - empty_constructor_bodies
+ - empty_statements
+ - eol_at_end_of_file
+ - exhaustive_cases
+ - file_names
+ - flutter_style_todos
+ - hash_and_equals
+ - implementation_imports
+ - implicit_call_tearoffs
+ - implicit_reopen
+ - invalid_case_patterns
+ - invalid_runtime_check_with_js_interop_types
+ - join_return_with_assignment
+ - leading_newlines_in_multiline_strings
+ - library_annotations
+ - library_names
+ - library_prefixes
+ - library_private_types_in_public_api
+ - lines_longer_than_80_chars
+ - literal_only_boolean_expressions
+ - matching_super_parameters
+ - missing_code_block_language_in_doc_comment
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_default_cases
+ - no_duplicate_case_values
+ - no_leading_underscores_for_library_prefixes
+ - no_leading_underscores_for_local_identifiers
+ - no_literal_bool_comparisons
+ - no_logic_in_create_state
+ - no_runtimeType_toString
+ - no_self_assignments
+ - no_wildcard_variable_uses
+ - non_constant_identifier_names
+ - noop_primitive_operations
+ - null_check_on_nullable_type_parameter
+ - null_closures
+ - omit_local_variable_types
+ - omit_obvious_local_variable_types
+ - omit_obvious_property_types
+ - one_member_abstracts
+ - only_throw_errors
+ - overridden_fields
+ - package_names
+ - package_prefixed_library_names
+ - parameter_assignments
+ - prefer_adjacent_string_concatenation
+ - prefer_asserts_in_initializer_lists
+ - prefer_asserts_with_message
+ - prefer_collection_literals
+ - prefer_conditional_assignment
+ - prefer_const_constructors
+ - prefer_const_constructors_in_immutables
+ - prefer_const_declarations
+ - prefer_const_literals_to_create_immutables
+ - prefer_constructors_over_static_methods
+ - prefer_contains
+ - prefer_double_quotes
+ - prefer_expression_function_bodies
+ - prefer_final_fields
+ - prefer_final_in_for_each
+ - prefer_final_locals
+ - prefer_final_parameters
+ - prefer_for_elements_to_map_fromIterable
+ - prefer_foreach
+ - prefer_function_declarations_over_variables
+ - prefer_generic_function_type_aliases
+ - prefer_if_elements_to_conditional_expressions
+ - prefer_if_null_operators
+ - prefer_initializing_formals
+ - prefer_inlined_adds
+ - prefer_int_literals
+ - prefer_interpolation_to_compose_strings
+ - prefer_is_empty
+ - prefer_is_not_empty
+ - prefer_is_not_operator
+ - prefer_iterable_whereType
+ - prefer_mixin
+ - prefer_null_aware_method_calls
+ - prefer_null_aware_operators
+ - prefer_relative_imports
+ - prefer_single_quotes
+ - prefer_spread_collections
+ - prefer_typing_uninitialized_variables
+ - prefer_void_to_null
+ - provide_deprecation_message
+ # - public_member_api_docs
+ - recursive_getters
+ - remove_deprecations_in_breaking_versions
+ - require_trailing_commas
+ - secure_pubspec_urls
+ - sized_box_for_whitespace
+ - sized_box_shrink_expand
+ - slash_for_doc_comments
+ - sort_child_properties_last
+ - sort_constructors_first
+ - sort_pub_dependencies
+ - sort_unnamed_constructors_first
+ - specify_nonobvious_local_variable_types
+ - specify_nonobvious_property_types
+ - strict_top_level_inference
+ - switch_on_type
+ - test_types_in_equals
+ - throw_in_finally
+ - tighten_type_of_initializing_formals
+ # - type_annotate_public_apis
+ - type_init_formals
+ - type_literal_in_constant_pattern
+ - unawaited_futures
+ - unintended_html_in_doc_comment
+ - unnecessary_async
+ - unnecessary_await_in_return
+ - unnecessary_brace_in_string_interps
+ - unnecessary_breaks
+ - unnecessary_const
+ - unnecessary_constructor_name
+ - unnecessary_final
+ - unnecessary_getters_setters
+ - unnecessary_ignore
+ - unnecessary_lambdas
+ - unnecessary_late
+ - unnecessary_library_directive
+ - unnecessary_library_name
+ - unnecessary_new
+ - unnecessary_null_aware_assignments
+ - unnecessary_null_aware_operator_on_extension_on_nullable
+ - unnecessary_null_checks
+ - unnecessary_null_in_if_null_operators
+ - unnecessary_nullable_for_final_variable_declarations
+ - unnecessary_overrides
+ - unnecessary_parenthesis
+ - unnecessary_raw_strings
+ - unnecessary_statements
+ - unnecessary_string_escapes
+ - unnecessary_string_interpolations
+ - unnecessary_this
+ - unnecessary_to_list_in_spreads
+ - unnecessary_unawaited
+ - unnecessary_underscores
+ - unreachable_from_main
+ - unrelated_type_equality_checks
+ - unsafe_variance
+ - use_build_context_synchronously
+ - use_colored_box
+ - use_decorated_box
+ - use_enums
+ - use_full_hex_values_for_flutter_colors
+ - use_function_type_syntax_for_parameters
+ - use_if_null_to_convert_nulls_to_bools
+ - use_is_even_rather_than_modulo
+ - use_key_in_widget_constructors
+ - use_late_for_private_fields_and_variables
+ - use_named_constants
+ - use_null_aware_elements
+ - use_raw_strings
+ - use_rethrow_when_possible
+ - use_setters_to_change_properties
+ - use_string_buffers
+ - use_string_in_part_of_directives
+ - use_super_parameters
+ - use_test_throws_matchers
+ - use_to_and_as_if_applicable
+ - use_truncating_division
+ - valid_regexps
+ - void_checks
\ No newline at end of file
diff --git a/analysis_app.yaml b/analysis_app.yaml
new file mode 100644
index 00000000..ecc578cc
--- /dev/null
+++ b/analysis_app.yaml
@@ -0,0 +1,203 @@
+analysis_options.yamlinclude:
+ - package:flutter_lints/flutter.yaml
+ - package:lints/recommended.yaml
+ - ./analysis_options.yaml
+
+linter:
+ rules:
+ unnecessary_ignore: false
+ diagnostic_describe_all_properties: false
+ always_declare_return_types: true
+ always_put_required_named_parameters_first: true
+ always_use_package_imports: true
+ annotate_overrides: true
+ avoid_bool_literals_in_conditional_expressions: true
+ avoid_catching_errors: true
+ avoid_double_and_int_checks: true
+ avoid_dynamic_calls: true
+ avoid_empty_else: true
+ avoid_escaping_inner_quotes: true
+ avoid_field_initializers_in_const_classes: true
+ avoid_final_parameters: true
+ avoid_function_literals_in_foreach_calls: true
+ avoid_implementing_value_types: true
+ avoid_init_to_null: true
+ avoid_multiple_declarations_per_line: true
+ avoid_null_checks_in_equality_operators: true
+ avoid_positional_boolean_parameters: true
+ avoid_print: true
+ avoid_private_typedef_functions: true
+ avoid_redundant_argument_values: true
+ avoid_relative_lib_imports: true
+ avoid_renaming_method_parameters: true
+ avoid_return_types_on_setters: true
+ avoid_returning_null_for_void: true
+ avoid_setters_without_getters: true
+ avoid_shadowing_type_parameters: true
+ avoid_single_cascade_in_expression_statements: true
+ avoid_type_to_string: true
+ avoid_types_as_parameter_names: true
+ avoid_unnecessary_containers: true
+ avoid_unused_constructor_parameters: true
+ avoid_void_async: true
+ await_only_futures: true
+ camel_case_extensions: true
+ camel_case_types: true
+ cancel_subscriptions: true
+ cast_nullable_to_non_nullable: true
+ close_sinks: true
+ comment_references: true
+ conditional_uri_does_not_exist: true
+ constant_identifier_names: true
+ control_flow_in_finally: true
+ curly_braces_in_flow_control_structures: true
+ depend_on_referenced_packages: true
+ deprecated_consistency: true
+ directives_ordering: true
+ empty_catches: true
+ empty_constructor_bodies: true
+ empty_statements: true
+ eol_at_end_of_file: true
+ exhaustive_cases: true
+ file_names: true
+ hash_and_equals: true
+ implementation_imports: true
+ collection_methods_unrelated_type: true
+ join_return_with_assignment: true
+ leading_newlines_in_multiline_strings: true
+ library_names: true
+ library_prefixes: true
+ literal_only_boolean_expressions: true
+ no_adjacent_strings_in_list: true
+ no_duplicate_case_values: true
+ no_leading_underscores_for_library_prefixes: true
+ no_leading_underscores_for_local_identifiers: true
+ no_logic_in_create_state: true
+ no_runtimeType_toString: true
+ non_constant_identifier_names: true
+ noop_primitive_operations: true
+ null_check_on_nullable_type_parameter: true
+ overridden_fields: true
+ package_names: true
+ package_prefixed_library_names: true
+ parameter_assignments: true
+ prefer_adjacent_string_concatenation: true
+ prefer_asserts_in_initializer_lists: true
+ prefer_collection_literals: true
+ prefer_conditional_assignment: true
+ prefer_const_constructors: true
+ prefer_const_constructors_in_immutables: true
+ prefer_const_declarations: true
+ prefer_const_literals_to_create_immutables: true
+ prefer_constructors_over_static_methods: true
+ prefer_contains: true
+ prefer_final_fields: true
+ prefer_final_in_for_each: true
+ prefer_final_locals: true
+ prefer_for_elements_to_map_fromIterable: true
+ prefer_function_declarations_over_variables: true
+ prefer_generic_function_type_aliases: true
+ prefer_if_elements_to_conditional_expressions: true
+ prefer_if_null_operators: true
+ prefer_initializing_formals: true
+ prefer_inlined_adds: true
+ prefer_interpolation_to_compose_strings: true
+ prefer_is_empty: true
+ prefer_is_not_empty: true
+ prefer_is_not_operator: true
+ prefer_iterable_whereType: true
+ prefer_null_aware_method_calls: true
+ prefer_null_aware_operators: true
+ prefer_spread_collections: true
+ prefer_typing_uninitialized_variables: true
+ prefer_void_to_null: true
+ provide_deprecation_message: true
+ recursive_getters: true
+ require_trailing_commas: true
+ secure_pubspec_urls: true
+ sized_box_for_whitespace: true
+ sized_box_shrink_expand: true
+ slash_for_doc_comments: true
+ sort_child_properties_last: true
+ sort_pub_dependencies: true
+ sort_unnamed_constructors_first: true
+ test_types_in_equals: true
+ throw_in_finally: true
+ tighten_type_of_initializing_formals: true
+ type_annotate_public_apis: false
+ type_init_formals: true
+ unawaited_futures: true
+ unnecessary_await_in_return: true
+ unnecessary_brace_in_string_interps: true
+ unnecessary_breaks: true
+ unnecessary_const: true
+ unnecessary_getters_setters: true
+ unnecessary_lambdas: true
+ unnecessary_new: true
+ unnecessary_null_aware_assignments: true
+ unnecessary_null_checks: true
+ unnecessary_null_in_if_null_operators: true
+ unnecessary_nullable_for_final_variable_declarations: true
+ unnecessary_overrides: true
+ unnecessary_parenthesis: true
+ unnecessary_raw_strings: true
+ unnecessary_statements: true
+ unnecessary_string_escapes: true
+ unnecessary_string_interpolations: true
+ unnecessary_this: true
+ unnecessary_to_list_in_spreads: true
+ unrelated_type_equality_checks: true
+ use_build_context_synchronously: true
+ use_colored_box: true
+ use_decorated_box: true
+ use_full_hex_values_for_flutter_colors: true
+ use_function_type_syntax_for_parameters: true
+ use_if_null_to_convert_nulls_to_bools: true
+ use_is_even_rather_than_modulo: true
+ use_named_constants: true
+ use_raw_strings: true
+ use_rethrow_when_possible: true
+ use_setters_to_change_properties: true
+ use_string_buffers: true
+ use_super_parameters: true
+ use_test_throws_matchers: true
+ valid_regexps: true
+ void_checks: true
+ prefer_single_quotes: true
+ public_member_api_docs: false
+
+analyzer:
+ strong-mode:
+ implicit-casts: false
+ implicit-dynamic: false
+ errors:
+ inference_failure_on_instance_creation: ignore
+ inference_failure_on_function_invocation: ignore
+ inference_failure_on_untyped_parameter: ignore
+ strict_raw_type: ignore
+ prefer_relative_imports: ignore
+ prefer_foreach: ignore
+
+ exclude:
+ - '**/*.g.dart'
+ - '**/*.gr.dart'
+
+ # Flutter
+ - 'lib/generated_plugin_registrant.dart'
+
+ # mockito
+ - '*.mocks.dart'
+ - '**/*.mocks.dart'
+
+ # freezed
+ - '**/*.freezed.dart'
+
+ # protobuf
+ - '**/*.pb.dart'
+ - '**/*.pbenum.dart'
+ - '**/*.pbjson.dart'
+
+ # test_coverage
+ - test/.test_coverage.dart
+
+plugins:
diff --git a/analysis_options.yaml b/analysis_options.yaml
index f49fa824..3716bd46 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1 +1,4 @@
-include: package:pedantic/analysis_options.1.11.0.yaml
\ No newline at end of file
+include: analysis_options_without_plugins.yaml
+
+plugins:
+# riverpod_lint: ^3.1.3
\ No newline at end of file
diff --git a/analysis_options_without_plugins.yaml b/analysis_options_without_plugins.yaml
new file mode 100644
index 00000000..c2d06858
--- /dev/null
+++ b/analysis_options_without_plugins.yaml
@@ -0,0 +1,114 @@
+include: all_lint_rules.yaml
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+ errors:
+ # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts.
+ # We explicitly enabled even conflicting rules and are fixing the conflict
+ # in this file
+ included_file_warning: ignore
+ # false positive when using Freezed
+ invalid_annotation_target: ignore
+ # I prefer specifying a parameter on a widget even if they are unused (such as Key)
+ # for the sake of consistency.
+ unused_element_parameter: false
+ unrecognized_error_code: ignore
+ # Remove once we require analyzer 8.0.0
+ # We currently support both 7.0 and 8.0, so we can't migrate yet.
+ deprecated_member_use: ignore
+ # Analyzer API is experimental, but kind of necessary for analyzer plugins
+ experimental_member_use: ignore
+
+linter:
+ rules:
+ # No good way to "fix" the diagnostic. Cf https://github.com/dart-lang/sdk/issues/61976
+ unsafe_variance: false
+
+ # Conflicts with having to specify types on public properties
+ omit_obvious_property_types: false
+
+ # Using it on purpose for signaling that an API can be both async and sync
+ avoid_futureor_void: false
+
+ # I hate it
+ specify_nonobvious_property_types: false
+ specify_nonobvious_local_variable_types: false
+
+ # Clashes with newer dartfmt
+ require_trailing_commas: false
+
+ # Maybe later. Too many to fix right now.
+ document_ignores: false
+
+ # Not an issue if using dartfmt
+ no_adjacent_strings_in_list: false
+
+ # False positive for custom enum-like classes (such as Flutter's "Colors")
+ avoid_classes_with_only_static_members: false
+
+ # False positive when the future is returned by the function
+ discarded_futures: false
+
+ # Low value and lacks a quick fix
+ combinators_ordering: false
+
+ # Conflicts with unused variables
+ no_leading_underscores_for_local_identifiers: false
+
+ # false positive
+ one_member_abstracts: false
+
+ # too verbose
+ prefer_final_parameters: false
+
+ # Too verbose with little value, and this is taken care of by the Flutter devtool anyway.
+ diagnostic_describe_all_properties: false
+
+ # Personal preference. I prefer "if (bool) return;" over having it in multiple lines
+ always_put_control_body_on_new_line: false
+
+ # Personal preference. I don't find it more readable
+ cascade_invocations: false
+
+ # Conflicts with `prefer_single_quotes`
+ # Single quotes are easier to type and don't compromise on readability.
+ prefer_double_quotes: false
+
+ # Conflicts with `omit_local_variable_types` and other rules.
+ # As per Dart guidelines, we want to avoid unnecessary types to make the code
+ # more readable.
+ # See https://dart.dev/guides/language/effective-dart/design#avoid-type-annotating-initialized-local-variables
+ always_specify_types: false
+
+ # Incompatible with `prefer_final_locals`
+ # Having immutable local variables makes larger functions more predictable
+ # so we will use `prefer_final_locals` instead.
+ unnecessary_final: false
+
+ # Not quite suitable for Flutter, which may have a `build` method with a single
+ # return, but that return is still complex enough that a "body" is worth it.
+ prefer_expression_function_bodies: false
+
+ # Conflicts with the convention used by flutter, which puts `Key key`
+ # and `@required Widget child` last.
+ always_put_required_named_parameters_first: false
+
+ # This project doesn't use Flutter-style todos
+ flutter_style_todos: false
+
+ # There are situations where we voluntarily want to catch everything,
+ # especially as a library.
+ avoid_catches_without_on_clauses: false
+
+ # Boring as it sometimes force a line of 81 characters to be split in two.
+ # As long as we try to respect that 80 characters limit, going slightly
+ # above is fine.
+ lines_longer_than_80_chars: false
+
+ # Conflicts with disabling `implicit-dynamic`
+ avoid_annotating_with_dynamic: false
+
+ # conflicts with `prefer_relative_imports`
+ always_use_package_imports: false
diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt
index ad655bb9..197cd0e3 100644
--- a/android/CMakeLists.txt
+++ b/android/CMakeLists.txt
@@ -1,6 +1,9 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 3.18.1)
+
+set(ANDROID_STL c++_static)
+
PROJECT(Sequencer)
-CMAKE_MINIMUM_REQUIRED(VERSION 3.18.1)
SET(CMAKE_CXX_STANDARD 17)
SET(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -26,7 +29,7 @@ TARGET_INCLUDE_DIRECTORIES(flutter_sequencer PRIVATE
../ios/Classes/CallbackManager
../ios/Classes/Scheduler
../ios/Classes/IInstrument
- )
+)
TARGET_SOURCES(flutter_sequencer PRIVATE
../ios/Classes/CallbackManager/CallbackManager.h
../ios/Classes/CallbackManager/CallbackManager.cpp
@@ -45,8 +48,14 @@ TARGET_SOURCES(flutter_sequencer PRIVATE
./src/main/cpp/Utils/Logging.h
./src/main/cpp/Utils/OptionArray.h
./src/main/cpp/Plugin.cpp
- )
+)
FIND_LIBRARY(android-lib android)
-TARGET_LINK_LIBRARIES(flutter_sequencer ${android-lib} oboe sfizz_static)
+# Fixed target link libraries with C++ standard library
+TARGET_LINK_LIBRARIES(flutter_sequencer
+ ${android-lib}
+ oboe
+ sfizz_static
+ c++_static
+)
\ No newline at end of file
diff --git a/android/build.gradle b/android/build.gradle
index 9941b80a..1c79efa6 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -4,22 +4,23 @@ group 'com.michaeljperri.flutter_sequencer'
version '1.0-SNAPSHOT'
buildscript {
- ext.kotlin_version = '1.5.21'
+ ext.kotlin_version = '2.2.20'
repositories {
google()
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.2'
+ classpath 'com.android.tools.build:gradle:8.11.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- classpath "org.eclipse.jgit:org.eclipse.jgit:5.12.0+"
+ classpath "org.eclipse.jgit:org.eclipse.jgit:7.0.0.202409031743-r"
}
}
rootProject.allprojects {
repositories {
google()
+ mavenCentral()
}
}
@@ -27,30 +28,48 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
- compileSdkVersion 29
+ namespace "com.michaeljperri.flutter_sequencer"
+
+ compileSdk = 36
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+// ndkVersion = "27.0.12077973"
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
+
defaultConfig {
- minSdkVersion 16
+ minSdk 24
externalNativeBuild {
cmake {
- cppFlags '-std=c++17', '-frelaxed-template-template-args'
+ arguments "-DCMAKE_CXX_STANDARD=17", "-DCMAKE_CXX_STANDARD_REQUIRED=ON"
+ cppFlags '-frelaxed-template-template-args'
}
}
+
+ ndk {
+ abiFilters "arm64-v8a"
+ }
}
lintOptions {
disable 'InvalidPackage'
}
- ndkVersion "22.1.7171670"
+
externalNativeBuild {
cmake {
- version "3.18.1"
path "CMakeLists.txt"
}
}
+
}
void cloneThirdPartyRepo(String name, String uri, String commit) {
@@ -60,12 +79,12 @@ void cloneThirdPartyRepo(String name, String uri, String commit) {
if (!dir.exists()) {
dir.mkdirs()
def repo =
- Git.cloneRepository()
- .setDirectory(dir)
- .setURI(uri)
- .setRemote('origin')
- .setCloneSubmodules(true)
- .call()
+ Git.cloneRepository()
+ .setDirectory(dir)
+ .setURI(uri)
+ .setRemote('origin')
+ .setCloneSubmodules(true)
+ .call()
repo.checkout().setName(commit).call()
repo.submoduleUpdate()
@@ -74,13 +93,9 @@ void cloneThirdPartyRepo(String name, String uri, String commit) {
}
task cloneThirdPartyRepos {
- cloneThirdPartyRepo('oboe', 'https://github.com/google/oboe', '06ec23e4f6bc00ba7eea9b84e299f9200a598838')
- cloneThirdPartyRepo('TinySoundFont', 'https://github.com/schellingb/TinySoundFont.git', 'bf574519e601202c3a9d27a74f345921277eed39')
- cloneThirdPartyRepo('sfizz', 'https://github.com/sfztools/sfizz.git', 'fc1f0451cebd8996992cbc4f983fcf76b03295c5')
+ cloneThirdPartyRepo('oboe', 'https://github.com/google/oboe', 'e40043061cc94df965fda802938b0989f201e86c')
+ cloneThirdPartyRepo('TinySoundFont', 'https://github.com/schellingb/TinySoundFont.git', '790a219810cb0fca5defa8cdbd88e2487e5efc7a')
+ cloneThirdPartyRepo('sfizz', 'https://github.com/PROGrand/sfizz.git', '040be90a34cfa14493a3b51887161d5393616638')
}
preBuild.dependsOn cloneThirdPartyRepos
-
-dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-}
diff --git a/android/gradle.properties b/android/gradle.properties
deleted file mode 100644
index abd7239b..00000000
--- a/android/gradle.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-org.gradle.jvmargs=-Xmx1536M
-android.useAndroidX=true
-android.enableJetifier=true
-android.enablePrefab=true
diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372aef..00000000
Binary files a/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 3c9d0852..00000000
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
diff --git a/android/gradlew b/android/gradlew
deleted file mode 100755
index 9d82f789..00000000
--- a/android/gradlew
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=$((i+1))
- done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/android/gradlew.bat b/android/gradlew.bat
deleted file mode 100644
index aec99730..00000000
--- a/android/gradlew.bat
+++ /dev/null
@@ -1,90 +0,0 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 91156f8a..a0a94fb7 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,4 +1,5 @@
+ package="com.michaeljperri.flutter_sequencer"
+>
diff --git a/android/src/main/kotlin/com/michaeljperri/flutter_sequencer/FlutterSequencerPlugin.kt b/android/src/main/kotlin/com/michaeljperri/flutter_sequencer/FlutterSequencerPlugin.kt
index 8661e275..74b84d77 100644
--- a/android/src/main/kotlin/com/michaeljperri/flutter_sequencer/FlutterSequencerPlugin.kt
+++ b/android/src/main/kotlin/com/michaeljperri/flutter_sequencer/FlutterSequencerPlugin.kt
@@ -1,130 +1,111 @@
package com.michaeljperri.flutter_sequencer
import android.content.Context
-import android.content.res.AssetManager;
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat
-
+import android.content.res.AssetManager
import io.flutter.embedding.engine.plugins.FlutterPlugin
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 io.flutter.plugin.common.PluginRegistry.Registrar
import java.io.File
-import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.URLDecoder
const val flutterAssetRoot = "flutter_assets"
/** FlutterSequencerPlugin */
-public class FlutterSequencerPlugin: FlutterPlugin, MethodCallHandler {
- /// The MethodChannel that will the communication between Flutter and native Android
- ///
- /// This local reference serves to register the plugin with the Flutter Engine and unregister it
- /// when the Flutter Engine is detached from the Activity
- private lateinit var channel : MethodChannel
-
- override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
- channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "flutter_sequencer")
- channel.setMethodCallHandler(this);
- context = flutterPluginBinding.applicationContext
- }
-
- // This static function is optional and equivalent to onAttachedToEngine. It supports the old
- // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
- // plugin registration via this function while apps migrate to use the new Android APIs
- // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
- //
- // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
- // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
- // depending on the user's project. onAttachedToEngine or registerWith must both be defined
- // in the same class.
- companion object {
- private lateinit var context : Context
-
- @JvmStatic
- fun registerWith(registrar: Registrar) {
- val channel = MethodChannel(registrar.messenger(), "flutter_sequencer")
- channel.setMethodCallHandler(FlutterSequencerPlugin())
- context = registrar.context()
+class FlutterSequencerPlugin : FlutterPlugin, MethodCallHandler {
+ /// The MethodChannel that will the communication between Flutter and native Android
+ ///
+ /// This local reference serves to register the plugin with the Flutter Engine and unregister it
+ /// when the Flutter Engine is detached from the Activity
+ private lateinit var channel: MethodChannel
+
+ private lateinit var context: Context
+
+ override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_sequencer")
+ channel.setMethodCallHandler(this)
+ context = flutterPluginBinding.applicationContext
}
- init {
- System.loadLibrary("flutter_sequencer")
+ companion object {
+ init {
+ System.loadLibrary("flutter_sequencer")
+ }
}
- }
-
- override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
- if (call.method == "getPlatformVersion") {
- result.success("Android ${android.os.Build.VERSION.RELEASE}")
- } else if (call.method == "setupAssetManager") {
- setupAssetManager(context.assets)
- result.success(null)
- } else if (call.method == "normalizeAssetDir") {
- val assetDir = call.argument("assetDir")!!
- val filesDir = context.filesDir
- val isSuccess = copyAssetDirOrFile(assetDir, filesDir)
-
- if (isSuccess) {
- val copiedDir = filesDir.resolve(assetDir).absolutePath
- result.success(copiedDir)
- } else {
- result.success(null)
- }
- } else if (call.method == "listAudioUnits") {
- result.success(emptyList())
- } else {
- result.notImplemented()
+
+ private var isLibraryLoaded = false
+
+ override fun onMethodCall(call: MethodCall, result: Result) {
+ if (call.method == "getPlatformVersion") {
+ result.success("Android ${android.os.Build.VERSION.RELEASE}")
+ } else if (call.method == "setupAssetManager") {
+ setupAssetManager(context.assets)
+ result.success(null)
+ } else if (call.method == "normalizeAssetDir") {
+ val assetDir = call.argument("assetDir")!!
+ val filesDir = context.filesDir
+ val isSuccess = copyAssetDirOrFile(assetDir, filesDir)
+
+ if (isSuccess) {
+ val copiedDir = filesDir.resolve(assetDir).absolutePath
+ result.success(copiedDir)
+ } else {
+ result.success(null)
+ }
+ } else if (call.method == "listAudioUnits") {
+ result.success(emptyList())
+ } else {
+ result.notImplemented()
+ }
}
- }
-
- override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
- channel.setMethodCallHandler(null)
- }
-
- private fun copyAssetFile(assetFilePath: String, outputDir: File): Boolean {
- val inputStream = context.assets.open("$flutterAssetRoot/$assetFilePath")
- val decodedAssetFilePath = URLDecoder.decode(assetFilePath, "UTF-8")
- val outputFile = outputDir.resolve(decodedAssetFilePath)
- var outputStream: FileOutputStream? = null
-
- try {
- outputFile.parentFile.mkdirs()
- outputFile.createNewFile()
-
- outputStream = FileOutputStream(outputFile)
- inputStream.copyTo(outputStream, 1024)
- } catch (e: SecurityException) {
- return false;
- } catch (e: java.io.IOException) {
- return false;
- } finally {
- inputStream.close()
- outputStream?.flush()
- outputStream?.close()
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
}
- return true;
- }
-
- private fun copyAssetDirOrFile(assetPath: String, outputDir: File): Boolean {
- val paths = context.assets.list("$flutterAssetRoot/$assetPath")!!
- var isSuccess = true;
-
- if (paths.isEmpty()) {
- // It's a file.
- isSuccess = isSuccess && copyAssetFile(assetPath, outputDir)
- } else {
- // It's a directory.
- paths.forEach {
- isSuccess = isSuccess && copyAssetDirOrFile("$assetPath/$it", outputDir)
- }
+ private fun copyAssetFile(assetFilePath: String, outputDir: File): Boolean {
+ val inputStream = context.assets.open("$flutterAssetRoot/$assetFilePath")
+ val decodedAssetFilePath = URLDecoder.decode(assetFilePath, "UTF-8")
+ val outputFile = outputDir.resolve(decodedAssetFilePath)
+ var outputStream: FileOutputStream? = null
+
+ try {
+ outputFile.parentFile!!.mkdirs()
+ outputFile.createNewFile()
+
+ outputStream = FileOutputStream(outputFile)
+ inputStream.copyTo(outputStream, 1024)
+ } catch (e: SecurityException) {
+ return false
+ } catch (e: java.io.IOException) {
+ return false
+ } finally {
+ inputStream.close()
+ outputStream?.flush()
+ outputStream?.close()
+ }
+
+ return true
}
- return isSuccess
- }
+ private fun copyAssetDirOrFile(assetPath: String, outputDir: File): Boolean {
+ val paths = context.assets.list("$flutterAssetRoot/$assetPath")!!
+ var isSuccess = true
+
+ if (paths.isEmpty()) {
+ // It's a file.
+ isSuccess = isSuccess && copyAssetFile(assetPath, outputDir)
+ } else {
+ // It's a directory.
+ paths.forEach {
+ isSuccess = isSuccess && copyAssetDirOrFile("$assetPath/$it", outputDir)
+ }
+ }
+
+ return isSuccess
+ }
- private external fun setupAssetManager(assetManager: AssetManager)
+ private external fun setupAssetManager(assetManager: AssetManager)
}
diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml
index e69de29b..9e3db072 100644
--- a/example/analysis_options.yaml
+++ b/example/analysis_options.yaml
@@ -0,0 +1,2 @@
+include:
+ - ../analysis_app.yaml
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
deleted file mode 100644
index c98e3289..00000000
--- a/example/android/app/build.gradle
+++ /dev/null
@@ -1,63 +0,0 @@
-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"
-
-android {
- compileSdkVersion 29
-
- sourceSets {
- main.java.srcDirs += 'src/main/kotlin'
- }
-
- lintOptions {
- disable 'InvalidPackage'
- }
-
- defaultConfig {
- // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId "com.michaeljperri.flutter_sequencer_example"
- minSdkVersion 16
- targetSdkVersion 29
- versionCode flutterVersionCode.toInteger()
- versionName flutterVersionName
- }
-
- buildTypes {
- release {
- // TODO: Add your own signing config for the release build.
- // Signing with the debug keys for now, so `flutter run --release` works.
- signingConfig signingConfigs.debug
- }
- }
-}
-
-flutter {
- source '../..'
-}
-
-dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-}
diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts
new file mode 100644
index 00000000..0bf491bb
--- /dev/null
+++ b/example/android/app/build.gradle.kts
@@ -0,0 +1,41 @@
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ id("dev.flutter.flutter-gradle-plugin")
+}
+android {
+ namespace = "com.michaeljperri.flutter_sequencer_example"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+
+ defaultConfig {
+ applicationId = "com.michaeljperri.flutter_sequencer_example"
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+
+ ndk {
+ abiFilters.add("arm64-v8a")
+ }
+ }
+
+ buildTypes {
+ release {
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index ed4504e8..b8d0e479 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-
+
-
-
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme"
+ />
@@ -44,7 +35,13 @@
+ android:name="flutterEmbedding"
+ android:value="2"/>
+
+
+
+
+
+
diff --git a/example/android/build.gradle b/example/android/build.gradle
deleted file mode 100644
index f797cfb7..00000000
--- a/example/android/build.gradle
+++ /dev/null
@@ -1,31 +0,0 @@
-buildscript {
- ext.kotlin_version = '1.5.21'
- repositories {
- google()
- jcenter()
- }
-
- dependencies {
- classpath 'com.android.tools.build:gradle:7.0.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
- }
-}
-
-rootProject.buildDir = '../build'
-subprojects {
- project.buildDir = "${rootProject.buildDir}/${project.name}"
-}
-subprojects {
- project.evaluationDependsOn(':app')
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts
new file mode 100644
index 00000000..89176ef4
--- /dev/null
+++ b/example/android/build.gradle.kts
@@ -0,0 +1,21 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
index 94adc3a3..fbee1d8c 100644
--- a/example/android/gradle.properties
+++ b/example/android/gradle.properties
@@ -1,3 +1,2 @@
-org.gradle.jvmargs=-Xmx1536M
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
-android.enableJetifier=true
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index 596278c4..9470727f 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Fri Jul 24 13:54:59 EDT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
\ No newline at end of file
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
deleted file mode 100644
index d3b6a401..00000000
--- a/example/android/settings.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2014 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-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"
diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts
new file mode 100644
index 00000000..da20e6da
--- /dev/null
+++ b/example/android/settings.gradle.kts
@@ -0,0 +1,25 @@
+pluginManagement {
+ val flutterSdkPath = run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "8.11.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.2.20" apply false
+}
+
+include(":app")
diff --git a/example/android/settings_aar.gradle b/example/android/settings_aar.gradle
deleted file mode 100644
index e7b4def4..00000000
--- a/example/android/settings_aar.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':app'
diff --git a/example/lib/components/drum_machine/drum_machine.dart b/example/lib/components/drum_machine/drum_machine.dart
index dacc72b6..0f9aa184 100644
--- a/example/lib/components/drum_machine/drum_machine.dart
+++ b/example/lib/components/drum_machine/drum_machine.dart
@@ -1,15 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_sequencer/track.dart';
-
+import 'package:flutter_sequencer_example/components/drum_machine/grid/grid.dart';
+import 'package:flutter_sequencer_example/components/drum_machine/volume_slider.dart';
import 'package:flutter_sequencer_example/models/step_sequencer_state.dart';
-import 'volume_slider.dart';
-import 'grid/grid.dart';
-
class DrumMachineWidget extends StatefulWidget {
const DrumMachineWidget({
- Key? key,
required this.track,
required this.stepCount,
required this.currentStep,
@@ -19,7 +16,8 @@ class DrumMachineWidget extends StatefulWidget {
required this.stepSequencerState,
required this.handleVolumeChange,
required this.handleVelocitiesChange,
- }) : super(key: key);
+ super.key,
+ });
final Track track;
final int stepCount;
@@ -32,26 +30,18 @@ class DrumMachineWidget extends StatefulWidget {
final Function(int, int, int, double) handleVelocitiesChange;
@override
- _DrumMachineWidgetState createState() => _DrumMachineWidgetState();
+ State createState() => _DrumMachineWidgetState();
}
-class _DrumMachineWidgetState extends State
- with SingleTickerProviderStateMixin {
+class _DrumMachineWidgetState extends State with SingleTickerProviderStateMixin {
Ticker? ticker;
- @override
- void dispose() {
- super.dispose();
- }
-
double? getVelocity(int step, int col) {
- return widget.stepSequencerState!
- .getVelocity(step, widget.columnPitches[col]);
+ return widget.stepSequencerState!.getVelocity(step, widget.columnPitches[col]);
}
void handleVelocityChange(int col, int step, double velocity) {
- widget.handleVelocitiesChange(
- widget.track.id, step, widget.columnPitches[col], velocity);
+ widget.handleVelocitiesChange(widget.track.id, step, widget.columnPitches[col], velocity);
}
void handleVolumeChange(double nextVolume) {
@@ -59,8 +49,7 @@ class _DrumMachineWidgetState extends State
}
void handleNoteOn(int col) {
- widget.track
- .startNoteNow(noteNumber: widget.columnPitches[col], velocity: .75);
+ widget.track.startNoteNow(noteNumber: widget.columnPitches[col], velocity: .75);
}
void handleNoteOff(int col) {
@@ -70,22 +59,26 @@ class _DrumMachineWidgetState extends State
@override
Widget build(BuildContext context) {
return Expanded(
- child: Container(
- padding: EdgeInsets.fromLTRB(32, 16, 32, 0),
- decoration: BoxDecoration(
- color: Colors.black54,
+ child: Container(
+ padding: const EdgeInsets.fromLTRB(32, 16, 32, 0),
+ decoration: const BoxDecoration(color: Colors.black54),
+ child: Column(
+ children: [
+ VolumeSlider(value: widget.volume, onChange: handleVolumeChange),
+ Expanded(
+ child: Grid(
+ columnLabels: widget.rowLabels,
+ getVelocity: getVelocity,
+ stepCount: widget.stepCount,
+ currentStep: widget.currentStep,
+ onChange: handleVelocityChange,
+ onNoteOn: handleNoteOn,
+ onNoteOff: handleNoteOff,
+ ),
),
- child: Column(children: [
- VolumeSlider(value: widget.volume, onChange: handleVolumeChange),
- Expanded(
- child: Grid(
- columnLabels: widget.rowLabels,
- getVelocity: getVelocity,
- stepCount: widget.stepCount,
- currentStep: widget.currentStep,
- onChange: handleVelocityChange,
- onNoteOn: handleNoteOn,
- onNoteOff: handleNoteOff)),
- ])));
+ ],
+ ),
+ ),
+ );
}
}
diff --git a/example/lib/components/drum_machine/grid/cell.dart b/example/lib/components/drum_machine/grid/cell.dart
index 94acfcf5..8abbe553 100644
--- a/example/lib/components/drum_machine/grid/cell.dart
+++ b/example/lib/components/drum_machine/grid/cell.dart
@@ -1,15 +1,14 @@
import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
import 'package:flutter_sequencer_example/constants.dart';
class Cell extends StatelessWidget {
- Cell({
- Key? key,
+ const Cell({
required this.size,
required this.velocity,
required this.isCurrentStep,
required this.onChange,
- }) : super(key: key);
+ super.key,
+ });
final double size;
final double velocity;
@@ -22,27 +21,32 @@ class Cell extends StatelessWidget {
width: size,
height: size,
decoration: BoxDecoration(
- color: Color.lerp(isCurrentStep ? Colors.white30 : Colors.black,
- isCurrentStep ? Colors.blue : Colors.pink, velocity),
+ color: Color.lerp(
+ isCurrentStep ? Colors.white30 : Colors.black,
+ isCurrentStep ? Colors.blue : Colors.pink,
+ velocity,
+ ),
border: Border.all(color: Colors.white70),
),
child: Transform(
- transform:
- Matrix4.translationValues(0, (-1 * size * velocity) + 2, 0),
- child: Container(
- width: size,
- decoration: const BoxDecoration(
- border: Border(bottom: BorderSide(color: Colors.white))))),
+ transform: Matrix4.translationValues(0, (-1 * size * velocity) + 2, 0),
+ child: Container(
+ width: size,
+ decoration: const BoxDecoration(
+ border: Border(bottom: BorderSide(color: Colors.white)),
+ ),
+ ),
+ ),
);
return GestureDetector(
onTap: () {
- final nextVelocity = velocity == 0.0 ? DEFAULT_VELOCITY : 0.0;
+ final nextVelocity = velocity == 0.0 ? kDefaultVelocity : 0.0;
onChange(nextVelocity);
},
onVerticalDragUpdate: (details) {
- final renderBox = context.findRenderObject() as RenderBox;
+ final renderBox = context.findRenderObject()! as RenderBox;
final yPos = renderBox.globalToLocal(details.globalPosition).dy;
final nextVelocity = 1.0 - (yPos / size).clamp(0.0, 1.0);
diff --git a/example/lib/components/drum_machine/grid/grid.dart b/example/lib/components/drum_machine/grid/grid.dart
index 51bd4409..9f3d0522 100644
--- a/example/lib/components/drum_machine/grid/grid.dart
+++ b/example/lib/components/drum_machine/grid/grid.dart
@@ -2,12 +2,10 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
-
-import 'cell.dart';
+import 'package:flutter_sequencer_example/components/drum_machine/grid/cell.dart';
class Grid extends StatelessWidget {
- Grid({
- Key? key,
+ const Grid({
required this.getVelocity,
required this.columnLabels,
required this.stepCount,
@@ -15,7 +13,8 @@ class Grid extends StatelessWidget {
required this.onChange,
required this.onNoteOn,
required this.onNoteOff,
- }) : super(key: key);
+ super.key,
+ });
final Function(int step, int col) getVelocity;
final List columnLabels;
@@ -33,26 +32,28 @@ class Grid extends StatelessWidget {
final cellSize = min(constraints.maxWidth / columnLabels.length, 50.0);
return ListView.builder(
- padding: EdgeInsets.fromLTRB(0, 0, 0, 16),
- shrinkWrap: true,
- itemCount: stepCount,
- itemBuilder: (BuildContext context, int step) {
- final List cellWidgets = [];
-
- for (var col = 0; col < columnsCount; col++) {
- final velocity = getVelocity(step, col);
-
- final cellWidget = Cell(
- size: cellSize,
- velocity: velocity,
- isCurrentStep: step == currentStep,
- onChange: (velocity) => onChange(col, step, velocity),
- );
-
- cellWidgets.add(cellWidget);
- }
- return Row(children: cellWidgets);
- });
+ // ignore: prefer_const_constructors
+ padding: EdgeInsets.fromLTRB(0, 0, 0, 16),
+ shrinkWrap: true,
+ itemCount: stepCount,
+ itemBuilder: (BuildContext context, int step) {
+ final List cellWidgets = [];
+
+ for (var col = 0; col < columnsCount; col++) {
+ final velocity = getVelocity(step, col);
+
+ final cellWidget = Cell(
+ size: cellSize,
+ velocity: velocity,
+ isCurrentStep: step == currentStep,
+ onChange: (velocity) => onChange(col, step, velocity),
+ );
+
+ cellWidgets.add(cellWidget);
+ }
+ return Row(children: cellWidgets);
+ },
+ );
},
);
}
diff --git a/example/lib/components/drum_machine/grid/label_row.dart b/example/lib/components/drum_machine/grid/label_row.dart
index 8740b254..cb0e2070 100644
--- a/example/lib/components/drum_machine/grid/label_row.dart
+++ b/example/lib/components/drum_machine/grid/label_row.dart
@@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
class LabelRow extends StatelessWidget {
- LabelRow({
- Key? key,
+ const LabelRow({
required this.columnLabels,
required this.cellSize,
required this.onNoteOn,
required this.onNoteOff,
- }) : super(key: key);
+ super.key,
+ });
final List columnLabels;
final Function(int) onNoteOn;
diff --git a/example/lib/components/drum_machine/volume_slider.dart b/example/lib/components/drum_machine/volume_slider.dart
index ecaccd60..35dfd1dd 100644
--- a/example/lib/components/drum_machine/volume_slider.dart
+++ b/example/lib/components/drum_machine/volume_slider.dart
@@ -1,26 +1,19 @@
import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
class VolumeSlider extends StatelessWidget {
- VolumeSlider({
- Key? key,
- required this.value,
- required this.onChange,
- });
+ const VolumeSlider({required this.value, required this.onChange, super.key});
final double value;
final Function(double) onChange;
@override
Widget build(BuildContext context) {
- return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
- Text('Volume:'),
- Slider(
- min: 0,
- max: 1,
- value: value,
- onChanged: onChange,
- ),
- ]);
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Text('Volume:'),
+ Slider(value: value, onChanged: onChange),
+ ],
+ );
}
}
diff --git a/example/lib/components/position_view.dart b/example/lib/components/position_view.dart
index 24bbc1d9..bb5d4ad3 100644
--- a/example/lib/components/position_view.dart
+++ b/example/lib/components/position_view.dart
@@ -1,13 +1,14 @@
import 'package:flutter/widgets.dart';
class PositionView extends StatelessWidget {
- PositionView({required this.position});
+ const PositionView({required this.position, super.key});
final double position;
@override
Widget build(BuildContext context) {
return Container(
+ // ignore: prefer_const_constructors
margin: EdgeInsets.fromLTRB(0, 0, 0, 8.0),
child: Text('Position: ${position.toStringAsFixed(3)}'),
);
diff --git a/example/lib/components/step_count_selector.dart b/example/lib/components/step_count_selector.dart
index 28327fbb..3b0f41c2 100644
--- a/example/lib/components/step_count_selector.dart
+++ b/example/lib/components/step_count_selector.dart
@@ -1,21 +1,16 @@
import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
class StepCountSelector extends StatelessWidget {
- const StepCountSelector({
- Key? key,
- required this.stepCount,
- required this.onChange,
- }) : super(key: key);
+ const StepCountSelector({required this.stepCount, required this.onChange, super.key});
final int stepCount;
final Function(int) onChange;
- handleLess() {
+ void handleLess() {
onChange(stepCount - 1);
}
- handleMore() {
+ void handleMore() {
onChange(stepCount + 1);
}
@@ -24,17 +19,10 @@ class StepCountSelector extends StatelessWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- Text('Steps'),
- IconButton(
- icon: Icon(Icons.arrow_back),
- onPressed: handleLess,
- ),
- Text(stepCount.toString(),
- style: TextStyle(fontWeight: FontWeight.bold)),
- IconButton(
- icon: Icon(Icons.arrow_forward),
- onPressed: handleMore,
- ),
+ const Text('Steps'),
+ IconButton(icon: const Icon(Icons.arrow_back), onPressed: handleLess),
+ Text(stepCount.toString(), style: const TextStyle(fontWeight: FontWeight.bold)),
+ IconButton(icon: const Icon(Icons.arrow_forward), onPressed: handleMore),
],
);
}
diff --git a/example/lib/components/tempo_selector.dart b/example/lib/components/tempo_selector.dart
index d3b6b81f..0b8dd2ab 100644
--- a/example/lib/components/tempo_selector.dart
+++ b/example/lib/components/tempo_selector.dart
@@ -1,18 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'package:flutter/widgets.dart';
class TempoSelector extends StatefulWidget {
- TempoSelector({
- required this.selectedTempo,
- required this.handleChange,
- });
+ TempoSelector({required this.selectedTempo, required this.handleChange, super.key}) {
+ // TODO: implement TempoSelector
+ // throw UnimplementedError();
+ }
final double selectedTempo;
final Function(double nextTempo) handleChange;
@override
- _TempoSelectorState createState() => _TempoSelectorState();
+ State createState() => _TempoSelectorState();
}
class _TempoSelectorState extends State {
@@ -30,11 +29,10 @@ class _TempoSelectorState extends State {
@override
void initState() {
super.initState();
- controller =
- TextEditingController(text: widget.selectedTempo.toInt().toString());
+ controller = TextEditingController(text: widget.selectedTempo.toInt().toString());
}
- handleTextChange(String input) {
+ void handleTextChange(String input) {
final parsedValue = double.tryParse(input);
if (parsedValue != null && parsedValue > 0) {
@@ -50,23 +48,22 @@ class _TempoSelectorState extends State {
@override
Widget build(BuildContext context) {
- return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
- Container(
- child: Text('Tempo:'),
- margin: EdgeInsets.only(right: 16.0),
- ),
- Container(
- width: 50,
- height: 50,
- child: TextField(
- controller: controller,
- maxLines: 1,
- keyboardType: TextInputType.number,
- onSubmitted: handleTextChange,
- inputFormatters: [FilteringTextInputFormatter.digitsOnly],
- decoration: InputDecoration(hintText: "..."),
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Container(margin: const EdgeInsets.only(right: 16.0), child: const Text('Tempo:')),
+ SizedBox(
+ width: 50,
+ height: 50,
+ child: TextField(
+ controller: controller,
+ keyboardType: TextInputType.number,
+ onSubmitted: handleTextChange,
+ inputFormatters: [FilteringTextInputFormatter.digitsOnly],
+ decoration: const InputDecoration(hintText: '...'),
+ ),
),
- ),
- ]);
+ ],
+ );
}
}
diff --git a/example/lib/components/track_selector.dart b/example/lib/components/track_selector.dart
index acb37685..b53f5a3e 100644
--- a/example/lib/components/track_selector.dart
+++ b/example/lib/components/track_selector.dart
@@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
import 'package:flutter_sequencer/track.dart';
class TrackSelector extends StatelessWidget {
- TrackSelector({
+ const TrackSelector({
required this.selectedTrack,
required this.tracks,
required this.handleChange,
+ super.key,
});
final Track? selectedTrack;
@@ -16,11 +16,11 @@ class TrackSelector extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DropdownButton