mirror of
https://github.com/Iconica-Development/flutter_google_track_and_trace.git
synced 2025-05-19 05:03:45 +02:00
cleanup code
This commit is contained in:
parent
b630b54519
commit
54fa43ebbf
8 changed files with 486 additions and 180 deletions
|
@ -1,4 +1,208 @@
|
||||||
|
# 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
|
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:
|
||||||
|
always_use_package_imports: true
|
||||||
|
avoid_dynamic_calls: true
|
||||||
|
avoid_empty_else: true
|
||||||
|
avoid_print: true
|
||||||
|
avoid_relative_lib_imports: true
|
||||||
|
avoid_returning_null_for_future: true
|
||||||
|
avoid_slow_async_io: true
|
||||||
|
avoid_type_to_string: true
|
||||||
|
avoid_types_as_parameter_names: true
|
||||||
|
avoid_web_libraries_in_flutter: true
|
||||||
|
cancel_subscriptions: true
|
||||||
|
close_sinks: true
|
||||||
|
comment_references: false
|
||||||
|
control_flow_in_finally: true
|
||||||
|
diagnostic_describe_all_properties: false
|
||||||
|
empty_statements: true
|
||||||
|
hash_and_equals: true
|
||||||
|
invariant_booleans: true
|
||||||
|
iterable_contains_unrelated_type: true
|
||||||
|
list_remove_unrelated_type: true
|
||||||
|
literal_only_boolean_expressions: true
|
||||||
|
no_adjacent_strings_in_list: true
|
||||||
|
no_duplicate_case_values: true
|
||||||
|
no_logic_in_create_state: true
|
||||||
|
prefer_void_to_null: true
|
||||||
|
test_types_in_equals: true
|
||||||
|
throw_in_finally: true
|
||||||
|
unnecessary_statements: true
|
||||||
|
unrelated_type_equality_checks: true
|
||||||
|
use_build_context_synchronously: true
|
||||||
|
use_key_in_widget_constructors: true
|
||||||
|
valid_regexps: true
|
||||||
|
always_declare_return_types: true
|
||||||
|
always_put_control_body_on_new_line: true
|
||||||
|
always_put_required_named_parameters_first: true
|
||||||
|
always_require_non_null_named_parameters: true
|
||||||
|
always_specify_types: false
|
||||||
|
annotate_overrides: true
|
||||||
|
avoid_annotating_with_dynamic: false
|
||||||
|
avoid_bool_literals_in_conditional_expressions: true
|
||||||
|
avoid_catches_without_on_clauses: false
|
||||||
|
avoid_catching_errors: false
|
||||||
|
avoid_classes_with_only_static_members: true
|
||||||
|
avoid_double_and_int_checks: true
|
||||||
|
avoid_equals_and_hash_code_on_mutable_classes: false
|
||||||
|
avoid_escaping_inner_quotes: false
|
||||||
|
avoid_field_initializers_in_const_classes: 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_private_typedef_functions: true
|
||||||
|
avoid_renaming_method_parameters: true
|
||||||
|
avoid_return_types_on_setters: true
|
||||||
|
avoid_returning_null: true
|
||||||
|
avoid_returning_null_for_void: true
|
||||||
|
avoid_returning_this: true
|
||||||
|
avoid_shadowing_type_parameters: true
|
||||||
|
avoid_single_cascade_in_expression_statements: true
|
||||||
|
avoid_types_on_closure_parameters: false
|
||||||
|
avoid_unnecessary_containers: false
|
||||||
|
avoid_unused_constructor_parameters: true
|
||||||
|
avoid_void_async: true
|
||||||
|
await_only_futures: true
|
||||||
|
camel_case_extensions: true
|
||||||
|
camel_case_types: true
|
||||||
|
cascade_invocations: true
|
||||||
|
cast_nullable_to_non_nullable: true
|
||||||
|
constant_identifier_names: true
|
||||||
|
curly_braces_in_flow_control_structures: true
|
||||||
|
deprecated_consistency: true
|
||||||
|
directives_ordering: true
|
||||||
|
do_not_use_environment: true
|
||||||
|
empty_catches: true
|
||||||
|
empty_constructor_bodies: true
|
||||||
|
eol_at_end_of_file: true
|
||||||
|
exhaustive_cases: true
|
||||||
|
file_names: true
|
||||||
|
flutter_style_todos: true
|
||||||
|
implementation_imports: true
|
||||||
|
join_return_with_assignment: true
|
||||||
|
leading_newlines_in_multiline_strings: true
|
||||||
|
library_names: true
|
||||||
|
library_prefixes: true
|
||||||
|
library_private_types_in_public_api: true
|
||||||
|
lines_longer_than_80_chars: true
|
||||||
|
missing_whitespace_between_adjacent_strings: true
|
||||||
|
no_default_cases: true
|
||||||
|
non_constant_identifier_names: true
|
||||||
|
noop_primitive_operations: true
|
||||||
|
null_check_on_nullable_type_parameter: true
|
||||||
|
null_closures: true
|
||||||
|
omit_local_variable_types: true
|
||||||
|
one_member_abstracts: true
|
||||||
|
only_throw_errors: true
|
||||||
|
overridden_fields: true
|
||||||
|
package_api_docs: true
|
||||||
|
package_prefixed_library_names: true
|
||||||
|
parameter_assignments: true
|
||||||
|
prefer_adjacent_string_concatenation: true
|
||||||
|
prefer_asserts_in_initializer_lists: true
|
||||||
|
prefer_asserts_with_message: true
|
||||||
|
prefer_collection_literals: true
|
||||||
|
prefer_conditional_assignment: true
|
||||||
|
prefer_const_constructors: false
|
||||||
|
prefer_const_constructors_in_immutables: true
|
||||||
|
prefer_const_declarations: false
|
||||||
|
prefer_const_literals_to_create_immutables: false
|
||||||
|
prefer_constructors_over_static_methods: true
|
||||||
|
prefer_contains: true
|
||||||
|
prefer_double_quotes: false
|
||||||
|
prefer_equal_for_default_values: true
|
||||||
|
prefer_expression_function_bodies: false
|
||||||
|
prefer_final_fields: true
|
||||||
|
prefer_final_in_for_each: true
|
||||||
|
prefer_final_locals: false
|
||||||
|
prefer_final_parameters: false
|
||||||
|
prefer_for_elements_to_map_fromIterable: true
|
||||||
|
prefer_foreach: 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_int_literals: false
|
||||||
|
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_mixin: true
|
||||||
|
prefer_null_aware_method_calls: true
|
||||||
|
prefer_null_aware_operators: true
|
||||||
|
prefer_single_quotes: true
|
||||||
|
prefer_spread_collections: true
|
||||||
|
prefer_typing_uninitialized_variables: true
|
||||||
|
public_member_api_docs: false
|
||||||
|
recursive_getters: true
|
||||||
|
require_trailing_commas: true
|
||||||
|
sized_box_for_whitespace: true
|
||||||
|
slash_for_doc_comments: true
|
||||||
|
sort_child_properties_last: true
|
||||||
|
sort_constructors_first: true
|
||||||
|
sort_unnamed_constructors_first: true
|
||||||
|
tighten_type_of_initializing_formals: true
|
||||||
|
type_annotate_public_apis: true
|
||||||
|
type_init_formals: true
|
||||||
|
unawaited_futures: true
|
||||||
|
unnecessary_await_in_return: true
|
||||||
|
unnecessary_brace_in_string_interps: true
|
||||||
|
unnecessary_const: false
|
||||||
|
unnecessary_constructor_name: true
|
||||||
|
unnecessary_final: 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_string_escapes: true
|
||||||
|
unnecessary_string_interpolations: true
|
||||||
|
unnecessary_this: 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_late_for_private_fields_and_variables: true
|
||||||
|
use_named_constants: true
|
||||||
|
use_raw_strings: false
|
||||||
|
use_rethrow_when_possible: true
|
||||||
|
use_setters_to_change_properties: true
|
||||||
|
use_string_buffers: true
|
||||||
|
use_test_throws_matchers: true
|
||||||
|
use_to_and_as_if_applicable: true
|
||||||
|
void_checks: true
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
|
@ -22,8 +22,188 @@ linter:
|
||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
# producing the lint.
|
# producing the lint.
|
||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
always_use_package_imports: true
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
avoid_dynamic_calls: true
|
||||||
|
avoid_empty_else: true
|
||||||
|
avoid_print: true
|
||||||
|
avoid_relative_lib_imports: true
|
||||||
|
avoid_returning_null_for_future: true
|
||||||
|
avoid_slow_async_io: true
|
||||||
|
avoid_type_to_string: true
|
||||||
|
avoid_types_as_parameter_names: true
|
||||||
|
avoid_web_libraries_in_flutter: true
|
||||||
|
cancel_subscriptions: true
|
||||||
|
close_sinks: true
|
||||||
|
comment_references: false
|
||||||
|
control_flow_in_finally: true
|
||||||
|
diagnostic_describe_all_properties: false
|
||||||
|
empty_statements: true
|
||||||
|
hash_and_equals: true
|
||||||
|
invariant_booleans: true
|
||||||
|
iterable_contains_unrelated_type: true
|
||||||
|
list_remove_unrelated_type: true
|
||||||
|
literal_only_boolean_expressions: true
|
||||||
|
no_adjacent_strings_in_list: true
|
||||||
|
no_duplicate_case_values: true
|
||||||
|
no_logic_in_create_state: true
|
||||||
|
prefer_void_to_null: true
|
||||||
|
test_types_in_equals: true
|
||||||
|
throw_in_finally: true
|
||||||
|
unnecessary_statements: true
|
||||||
|
unrelated_type_equality_checks: true
|
||||||
|
use_build_context_synchronously: true
|
||||||
|
use_key_in_widget_constructors: true
|
||||||
|
valid_regexps: true
|
||||||
|
always_declare_return_types: true
|
||||||
|
always_put_control_body_on_new_line: true
|
||||||
|
always_put_required_named_parameters_first: true
|
||||||
|
always_require_non_null_named_parameters: true
|
||||||
|
always_specify_types: false
|
||||||
|
annotate_overrides: true
|
||||||
|
avoid_annotating_with_dynamic: false
|
||||||
|
avoid_bool_literals_in_conditional_expressions: true
|
||||||
|
avoid_catches_without_on_clauses: false
|
||||||
|
avoid_catching_errors: false
|
||||||
|
avoid_classes_with_only_static_members: true
|
||||||
|
avoid_double_and_int_checks: true
|
||||||
|
avoid_equals_and_hash_code_on_mutable_classes: false
|
||||||
|
avoid_escaping_inner_quotes: false
|
||||||
|
avoid_field_initializers_in_const_classes: 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_private_typedef_functions: true
|
||||||
|
avoid_renaming_method_parameters: true
|
||||||
|
avoid_return_types_on_setters: true
|
||||||
|
avoid_returning_null: true
|
||||||
|
avoid_returning_null_for_void: true
|
||||||
|
avoid_returning_this: true
|
||||||
|
avoid_shadowing_type_parameters: true
|
||||||
|
avoid_single_cascade_in_expression_statements: true
|
||||||
|
avoid_types_on_closure_parameters: false
|
||||||
|
avoid_unnecessary_containers: false
|
||||||
|
avoid_unused_constructor_parameters: true
|
||||||
|
avoid_void_async: true
|
||||||
|
await_only_futures: true
|
||||||
|
camel_case_extensions: true
|
||||||
|
camel_case_types: true
|
||||||
|
cascade_invocations: true
|
||||||
|
cast_nullable_to_non_nullable: true
|
||||||
|
constant_identifier_names: true
|
||||||
|
curly_braces_in_flow_control_structures: true
|
||||||
|
deprecated_consistency: true
|
||||||
|
directives_ordering: true
|
||||||
|
do_not_use_environment: true
|
||||||
|
empty_catches: true
|
||||||
|
empty_constructor_bodies: true
|
||||||
|
eol_at_end_of_file: true
|
||||||
|
exhaustive_cases: true
|
||||||
|
file_names: true
|
||||||
|
flutter_style_todos: true
|
||||||
|
implementation_imports: true
|
||||||
|
join_return_with_assignment: true
|
||||||
|
leading_newlines_in_multiline_strings: true
|
||||||
|
library_names: true
|
||||||
|
library_prefixes: true
|
||||||
|
library_private_types_in_public_api: true
|
||||||
|
lines_longer_than_80_chars: true
|
||||||
|
missing_whitespace_between_adjacent_strings: true
|
||||||
|
no_default_cases: true
|
||||||
|
non_constant_identifier_names: true
|
||||||
|
noop_primitive_operations: true
|
||||||
|
null_check_on_nullable_type_parameter: true
|
||||||
|
null_closures: true
|
||||||
|
omit_local_variable_types: true
|
||||||
|
one_member_abstracts: true
|
||||||
|
only_throw_errors: true
|
||||||
|
overridden_fields: true
|
||||||
|
package_api_docs: true
|
||||||
|
package_prefixed_library_names: true
|
||||||
|
parameter_assignments: true
|
||||||
|
prefer_adjacent_string_concatenation: true
|
||||||
|
prefer_asserts_in_initializer_lists: true
|
||||||
|
prefer_asserts_with_message: true
|
||||||
|
prefer_collection_literals: true
|
||||||
|
prefer_conditional_assignment: true
|
||||||
|
prefer_const_constructors: false
|
||||||
|
prefer_const_constructors_in_immutables: true
|
||||||
|
prefer_const_declarations: false
|
||||||
|
prefer_const_literals_to_create_immutables: false
|
||||||
|
prefer_constructors_over_static_methods: true
|
||||||
|
prefer_contains: true
|
||||||
|
prefer_double_quotes: false
|
||||||
|
prefer_equal_for_default_values: true
|
||||||
|
prefer_expression_function_bodies: false
|
||||||
|
prefer_final_fields: true
|
||||||
|
prefer_final_in_for_each: true
|
||||||
|
prefer_final_locals: false
|
||||||
|
prefer_final_parameters: false
|
||||||
|
prefer_for_elements_to_map_fromIterable: true
|
||||||
|
prefer_foreach: 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_int_literals: false
|
||||||
|
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_mixin: true
|
||||||
|
prefer_null_aware_method_calls: true
|
||||||
|
prefer_null_aware_operators: true
|
||||||
|
prefer_single_quotes: true
|
||||||
|
prefer_spread_collections: true
|
||||||
|
prefer_typing_uninitialized_variables: true
|
||||||
|
public_member_api_docs: false
|
||||||
|
recursive_getters: true
|
||||||
|
require_trailing_commas: true
|
||||||
|
sized_box_for_whitespace: true
|
||||||
|
slash_for_doc_comments: true
|
||||||
|
sort_child_properties_last: true
|
||||||
|
sort_constructors_first: true
|
||||||
|
sort_unnamed_constructors_first: true
|
||||||
|
tighten_type_of_initializing_formals: true
|
||||||
|
type_annotate_public_apis: true
|
||||||
|
type_init_formals: true
|
||||||
|
unawaited_futures: true
|
||||||
|
unnecessary_await_in_return: true
|
||||||
|
unnecessary_brace_in_string_interps: true
|
||||||
|
unnecessary_const: false
|
||||||
|
unnecessary_constructor_name: true
|
||||||
|
unnecessary_final: 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_string_escapes: true
|
||||||
|
unnecessary_string_interpolations: true
|
||||||
|
unnecessary_this: 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_late_for_private_fields_and_variables: true
|
||||||
|
use_named_constants: true
|
||||||
|
use_raw_strings: false
|
||||||
|
use_rethrow_when_possible: true
|
||||||
|
use_setters_to_change_properties: true
|
||||||
|
use_string_buffers: true
|
||||||
|
use_test_throws_matchers: true
|
||||||
|
use_to_and_as_if_applicable: true
|
||||||
|
void_checks: true
|
||||||
|
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
main() {
|
|
||||||
runApp(MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: ControlledWidget(
|
|
||||||
initialString: 'old value',
|
|
||||||
onCreate: (controller) async {
|
|
||||||
Future.delayed(const Duration(seconds: 5), () {
|
|
||||||
controller.value = 'new value';
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TimePrecision {
|
|
||||||
updateOnly,
|
|
||||||
everySecond,
|
|
||||||
everyMinute
|
|
||||||
}
|
|
||||||
|
|
||||||
class ControlledWidget extends StatefulWidget {
|
|
||||||
final void Function(MyController) onCreate;
|
|
||||||
final String? initialString;
|
|
||||||
final TimePrecision precision;
|
|
||||||
const ControlledWidget({
|
|
||||||
Key? key,
|
|
||||||
required this.onCreate,
|
|
||||||
this.initialString,
|
|
||||||
this.precision = TimePrecision.updateOnly,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_ControlledWidgetState createState() => _ControlledWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ControlledWidgetState extends State<ControlledWidget> {
|
|
||||||
late final MyController _controller;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_controller = MyController(widget.initialString ?? '');
|
|
||||||
_controller.addListener(_onChange);
|
|
||||||
widget.onCreate(_controller);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onChange() {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Text(_controller.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyController extends ChangeNotifier {
|
|
||||||
String _currentString;
|
|
||||||
|
|
||||||
MyController(String initial) : _currentString = initial;
|
|
||||||
|
|
||||||
String get value => _currentString;
|
|
||||||
|
|
||||||
set value(String value) {
|
|
||||||
_currentString = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:google_track_trace/google_track_trace.dart';
|
import 'package:google_track_trace/google_track_trace.dart';
|
||||||
|
|
||||||
class TrackTraceDemo extends StatefulWidget {
|
class TrackTraceDemo extends StatefulWidget {
|
||||||
|
@ -27,22 +26,25 @@ class _TrackTraceDemoState extends State<TrackTraceDemo> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: (controller == null || controller!.route == null)
|
title: (controller == null || controller!.route == null)
|
||||||
? const Text('TrackTrace example')
|
? const Text('TrackTrace example')
|
||||||
: Text(controller!.route!.duration.toString() +
|
: Text(
|
||||||
' seconds, afstand: ' +
|
'${controller!.route!.duration} seconds, afstand: '
|
||||||
(controller!.route!.distance / 1000).toString() +
|
'${controller!.route!.distance / 1000} km',
|
||||||
' km')),
|
),
|
||||||
|
),
|
||||||
body: GoogleTrackTraceMap(
|
body: GoogleTrackTraceMap(
|
||||||
startPosition: const Marker(
|
startPosition: const Marker(
|
||||||
markerId: MarkerId('Start locatie'),
|
markerId: MarkerId('Start locatie'),
|
||||||
position: LatLng(52.356057, 4.897540),
|
position: LatLng(52.356057, 4.897540),
|
||||||
),
|
),
|
||||||
destinationPosition: const Marker(
|
destinationPosition: const Marker(
|
||||||
markerId: MarkerId('Bestemming Locatie'),
|
markerId: MarkerId('Bestemming Locatie'),
|
||||||
position: LatLng(52.364709, 4.877157)),
|
position: LatLng(52.364709, 4.877157),
|
||||||
|
),
|
||||||
googleAPIKey: 'AIzaSyDaxZX8TeQeVf5tW-D6A66WLl20arbWV6c',
|
googleAPIKey: 'AIzaSyDaxZX8TeQeVf5tW-D6A66WLl20arbWV6c',
|
||||||
travelMode: TravelMode.bicycling,
|
travelMode: TravelMode.bicycling,
|
||||||
|
mapType: MapType.satellite,
|
||||||
routeUpdateInterval: 60,
|
routeUpdateInterval: 60,
|
||||||
timerPrecision: TimePrecision.everySecond,
|
timerPrecision: TimePrecision.everySecond,
|
||||||
zoomGesturesEnabled: true,
|
zoomGesturesEnabled: true,
|
||||||
|
@ -66,15 +68,26 @@ class _TrackTraceDemoState extends State<TrackTraceDemo> {
|
||||||
// 51.939909, 6.314950 SE
|
// 51.939909, 6.314950 SE
|
||||||
if (controller != null) {
|
if (controller != null) {
|
||||||
controller!.start = Marker(
|
controller!.start = Marker(
|
||||||
markerId: const MarkerId('Start Locatie'),
|
markerId: const MarkerId('Start Locatie'),
|
||||||
position: LatLng(51.93 + Random().nextDouble() * 0.06,
|
position: LatLng(
|
||||||
6.23 + Random().nextDouble() * 0.08));
|
51.93 + Random().nextDouble() * 0.06,
|
||||||
|
6.23 + Random().nextDouble() * 0.08,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void moveAlongRoute() {
|
void moveAlongRoute() {
|
||||||
if (controller != null && controller!.route != null && controller!.route!.line.length > 1) {
|
if (controller != null &&
|
||||||
controller!.start = Marker(markerId: const MarkerId('Start Locatie'), position: LatLng(controller!.route!.line[1].latitude, controller!.route!.line[1].longitude));
|
controller!.route != null &&
|
||||||
|
controller!.route!.line.length > 1) {
|
||||||
|
controller!.start = Marker(
|
||||||
|
markerId: const MarkerId('Start Locatie'),
|
||||||
|
position: LatLng(
|
||||||
|
controller!.route!.line[1].latitude,
|
||||||
|
controller!.route!.line[1].longitude,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,13 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
|
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
part 'src/google_map.dart';
|
export 'package:google_maps_flutter/google_maps_flutter.dart'
|
||||||
|
show MapType, Marker, MarkerId, Polyline, PolylineId, LatLng;
|
||||||
|
|
||||||
part 'src/controller.dart';
|
part 'src/controller.dart';
|
||||||
part 'src/directions_controller.dart';
|
part 'src/directions_repository.dart';
|
||||||
|
part 'src/google_map.dart';
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
part of google_track_trace;
|
part of google_track_trace;
|
||||||
|
|
||||||
class TrackTraceController extends ChangeNotifier {
|
class TrackTraceController extends ChangeNotifier {
|
||||||
GoogleMapController? _mapController;
|
|
||||||
Marker _startPosition;
|
|
||||||
Marker _destinationPosition;
|
|
||||||
TrackTraceRoute? _route;
|
|
||||||
|
|
||||||
TrackTraceController(Marker start, Marker destination)
|
TrackTraceController(Marker start, Marker destination)
|
||||||
: _startPosition = start,
|
: _startPosition = start,
|
||||||
_destinationPosition = destination;
|
_destinationPosition = destination;
|
||||||
|
|
||||||
|
GoogleMapController? mapController;
|
||||||
|
Marker _startPosition;
|
||||||
|
Marker _destinationPosition;
|
||||||
|
TrackTraceRoute? _route;
|
||||||
|
|
||||||
set start(Marker start) {
|
set start(Marker start) {
|
||||||
_startPosition = start;
|
_startPosition = start;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -31,44 +31,25 @@ class TrackTraceController extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
set mapController(GoogleMapController? controller) {
|
|
||||||
_mapController = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
GoogleMapController? get mapController => _mapController;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_mapController?.dispose();
|
mapController?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrackTraceRoute {
|
class TrackTraceRoute {
|
||||||
|
TrackTraceRoute(
|
||||||
|
int durationValue, int distanceValue, List<PointLatLng> lineValue,)
|
||||||
|
: duration = durationValue,
|
||||||
|
distance = distanceValue,
|
||||||
|
line = lineValue;
|
||||||
/// route duration in seconds
|
/// route duration in seconds
|
||||||
int _duration = 0;
|
int duration = 0;
|
||||||
|
|
||||||
/// route distance in meters
|
/// route distance in meters
|
||||||
int _distance = 0;
|
int distance = 0;
|
||||||
|
|
||||||
/// route edge points
|
/// route edge points
|
||||||
final List<PointLatLng> line;
|
final List<PointLatLng> line;
|
||||||
|
|
||||||
TrackTraceRoute(
|
|
||||||
int durationValue, int distanceValue, List<PointLatLng> lineValue)
|
|
||||||
: _duration = durationValue,
|
|
||||||
_distance = distanceValue,
|
|
||||||
line = lineValue;
|
|
||||||
|
|
||||||
int get distance => _distance;
|
|
||||||
|
|
||||||
int get duration => _duration;
|
|
||||||
|
|
||||||
set distance(int distance) {
|
|
||||||
_distance = distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
set duration(int duration) {
|
|
||||||
_duration = duration;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,10 @@ class DirectionsRepository {
|
||||||
required String key,
|
required String key,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final queryParameters = {
|
var queryParameters = {
|
||||||
'origin': '${origin.latitude},${origin.longitude}',
|
'origin': '${origin.latitude},${origin.longitude}',
|
||||||
'destination': '${destination.latitude},${destination.longitude}',
|
'destination': '${destination.latitude},${destination.longitude}',
|
||||||
'key':
|
'key': key, // get this key from the controller
|
||||||
key, // get this key from the controller
|
|
||||||
'mode': <TravelMode, String>{
|
'mode': <TravelMode, String>{
|
||||||
TravelMode.driving: 'driving',
|
TravelMode.driving: 'driving',
|
||||||
TravelMode.bicycling: 'bicycling',
|
TravelMode.bicycling: 'bicycling',
|
||||||
|
@ -25,26 +24,25 @@ class DirectionsRepository {
|
||||||
TravelMode.walking: 'walking',
|
TravelMode.walking: 'walking',
|
||||||
}[mode],
|
}[mode],
|
||||||
};
|
};
|
||||||
final uri = Uri.https('maps.googleapis.com', _baseUrl, queryParameters);
|
var uri = Uri.https('maps.googleapis.com', _baseUrl, queryParameters);
|
||||||
final response = await http.get(uri, headers: {
|
var response = await http.get(
|
||||||
HttpHeaders.contentTypeHeader: 'application/json',
|
uri,
|
||||||
});
|
headers: {
|
||||||
|
HttpHeaders.contentTypeHeader: 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return Directions.fromMap(jsonDecode(response.body));
|
return Directions.fromMap(jsonDecode(response.body));
|
||||||
}
|
}
|
||||||
} on HttpException catch (e) {
|
} on HttpException catch (e) {
|
||||||
print(e.message);
|
debugPrint(e.message);
|
||||||
}
|
}
|
||||||
throw GoogleMapsException('Unable to retrieve directions from Google Maps API');
|
throw GoogleMapsException(
|
||||||
|
'Unable to retrieve directions from Google Maps API',);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Directions {
|
class Directions {
|
||||||
final LatLngBounds bounds;
|
|
||||||
final List<PointLatLng> polylinePoints;
|
|
||||||
final int totalDistance;
|
|
||||||
final int totalDuration;
|
|
||||||
|
|
||||||
const Directions({
|
const Directions({
|
||||||
required this.bounds,
|
required this.bounds,
|
||||||
required this.polylinePoints,
|
required this.polylinePoints,
|
||||||
|
@ -58,19 +56,19 @@ class Directions {
|
||||||
throw GoogleMapsException('No Routes available');
|
throw GoogleMapsException('No Routes available');
|
||||||
}
|
}
|
||||||
|
|
||||||
final data = Map<String, dynamic>.from(map['routes'][0]);
|
var data = Map<String, dynamic>.from((map['routes'] as List)[0]);
|
||||||
|
|
||||||
final northeast = data['bounds']['northeast'];
|
var northeast = data['bounds']['northeast'];
|
||||||
final southwest = data['bounds']['southwest'];
|
var southwest = data['bounds']['southwest'];
|
||||||
final bounds = LatLngBounds(
|
var bounds = LatLngBounds(
|
||||||
southwest: LatLng(southwest['lat'], southwest['lng']),
|
southwest: LatLng(southwest['lat'], southwest['lng']),
|
||||||
northeast: LatLng(northeast['lat'], northeast['lng']),
|
northeast: LatLng(northeast['lat'], northeast['lng']),
|
||||||
);
|
);
|
||||||
|
|
||||||
int distance = 0;
|
var distance = 0;
|
||||||
int duration = 0;
|
var duration = 0;
|
||||||
if ((data['legs'] as List).isNotEmpty) {
|
if ((data['legs'] as List).isNotEmpty) {
|
||||||
final leg = data['legs'][0];
|
var leg = (data['legs'] as List)[0];
|
||||||
distance = leg['distance']['value'];
|
distance = leg['distance']['value'];
|
||||||
duration = leg['duration']['value'];
|
duration = leg['duration']['value'];
|
||||||
}
|
}
|
||||||
|
@ -83,6 +81,11 @@ class Directions {
|
||||||
totalDuration: duration,
|
totalDuration: duration,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final LatLngBounds bounds;
|
||||||
|
final List<PointLatLng> polylinePoints;
|
||||||
|
final int totalDistance;
|
||||||
|
final int totalDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GoogleMapsException implements Exception {
|
class GoogleMapsException implements Exception {
|
|
@ -4,13 +4,13 @@ part of google_track_trace;
|
||||||
enum TimePrecision { updateOnly, everySecond, everyMinute }
|
enum TimePrecision { updateOnly, everySecond, everyMinute }
|
||||||
|
|
||||||
class GoogleTrackTraceMap extends StatefulWidget {
|
class GoogleTrackTraceMap extends StatefulWidget {
|
||||||
GoogleTrackTraceMap({
|
const GoogleTrackTraceMap({
|
||||||
Key? key,
|
|
||||||
required this.onMapCreated,
|
required this.onMapCreated,
|
||||||
required this.startPosition,
|
required this.startPosition,
|
||||||
required this.destinationPosition,
|
required this.destinationPosition,
|
||||||
required this.googleAPIKey,
|
required this.googleAPIKey,
|
||||||
required this.routeUpdateInterval,
|
required this.routeUpdateInterval,
|
||||||
|
Key? key,
|
||||||
this.timerPrecision = TimePrecision.everyMinute,
|
this.timerPrecision = TimePrecision.everyMinute,
|
||||||
this.travelMode = TravelMode.driving,
|
this.travelMode = TravelMode.driving,
|
||||||
this.compassEnabled = false,
|
this.compassEnabled = false,
|
||||||
|
@ -22,8 +22,7 @@ class GoogleTrackTraceMap extends StatefulWidget {
|
||||||
this.mapMarkations =
|
this.mapMarkations =
|
||||||
'[{"featureType": "poi","stylers": [{"visibility": "off"}]}]',
|
'[{"featureType": "poi","stylers": [{"visibility": "off"}]}]',
|
||||||
this.line,
|
this.line,
|
||||||
}) : assert(true),
|
}) : super(key: key);
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// Callback method for when the map is ready to be used.
|
/// Callback method for when the map is ready to be used.
|
||||||
///
|
///
|
||||||
|
@ -40,7 +39,7 @@ class GoogleTrackTraceMap extends StatefulWidget {
|
||||||
final Marker startPosition;
|
final Marker startPosition;
|
||||||
final Marker destinationPosition;
|
final Marker destinationPosition;
|
||||||
|
|
||||||
Polyline? line;
|
final Polyline? line;
|
||||||
|
|
||||||
final bool compassEnabled;
|
final bool compassEnabled;
|
||||||
final bool zoomControlsEnabled;
|
final bool zoomControlsEnabled;
|
||||||
|
@ -51,11 +50,6 @@ class GoogleTrackTraceMap extends StatefulWidget {
|
||||||
|
|
||||||
final String mapMarkations;
|
final String mapMarkations;
|
||||||
|
|
||||||
CameraPosition initialCameraPosition = const CameraPosition(
|
|
||||||
// doetinchem default initialCamera
|
|
||||||
target: LatLng(51.965578, 6.293439),
|
|
||||||
zoom: 12.0);
|
|
||||||
|
|
||||||
final String googleAPIKey;
|
final String googleAPIKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -88,7 +82,7 @@ class _GoogleTrackTraceMapState extends State<GoogleTrackTraceMap> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GoogleMap(
|
return GoogleMap(
|
||||||
initialCameraPosition: calculateCameraPosition(
|
initialCameraPosition: calculateCameraPosition(
|
||||||
controller.start.position, controller.end.position),
|
controller.start.position, controller.end.position,),
|
||||||
onMapCreated: _onMapCreated,
|
onMapCreated: _onMapCreated,
|
||||||
compassEnabled: widget.compassEnabled,
|
compassEnabled: widget.compassEnabled,
|
||||||
zoomControlsEnabled: widget.zoomControlsEnabled,
|
zoomControlsEnabled: widget.zoomControlsEnabled,
|
||||||
|
@ -96,27 +90,27 @@ class _GoogleTrackTraceMapState extends State<GoogleTrackTraceMap> {
|
||||||
mapToolbarEnabled: widget.mapToolbarEnabled,
|
mapToolbarEnabled: widget.mapToolbarEnabled,
|
||||||
mapType: widget.mapType,
|
mapType: widget.mapType,
|
||||||
buildingsEnabled: widget.buildingsEnabled,
|
buildingsEnabled: widget.buildingsEnabled,
|
||||||
markers: {
|
markers: <Marker>{
|
||||||
controller.start,
|
controller.start,
|
||||||
controller.end,
|
controller.end,
|
||||||
},
|
},
|
||||||
polylines: {
|
polylines: <Polyline>{
|
||||||
if (controller.route != null)
|
if (controller.route != null)
|
||||||
(widget.line != null)
|
(widget.line != null)
|
||||||
? widget.line!.copyWith(
|
? widget.line!.copyWith(
|
||||||
pointsParam: controller.route!.line
|
pointsParam: controller.route!.line
|
||||||
.map((e) => LatLng(e.latitude, e.longitude))
|
.map((PointLatLng e) => LatLng(e.latitude, e.longitude))
|
||||||
.toList())
|
.toList(),)
|
||||||
: Polyline(
|
: Polyline(
|
||||||
// default PolyLine if none is provided
|
// default PolyLine if none is provided
|
||||||
polylineId: const PolylineId('track&trace route'),
|
polylineId: const PolylineId('track&trace route'),
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
width: 4,
|
width: 4,
|
||||||
points: controller.route!.line
|
points: controller.route!.line
|
||||||
.map((e) => LatLng(e.latitude, e.longitude))
|
.map((PointLatLng e) => LatLng(e.latitude, e.longitude))
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
});
|
},);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onChange() {
|
void _onChange() {
|
||||||
|
@ -131,12 +125,17 @@ class _GoogleTrackTraceMapState extends State<GoogleTrackTraceMap> {
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraPosition calculateCameraPosition(LatLng pointA, LatLng pointB) {
|
CameraPosition calculateCameraPosition(LatLng pointA, LatLng pointB) {
|
||||||
LatLng target = LatLng((pointA.latitude + pointB.latitude) / 2,
|
var target = LatLng(
|
||||||
(pointA.longitude + pointB.longitude) / 2);
|
(pointA.latitude + pointB.latitude) / 2,
|
||||||
double calculatedZoom = 13.0; // TODO calculate this zoom
|
(pointA.longitude + pointB.longitude) / 2,
|
||||||
|
);
|
||||||
|
|
||||||
return CameraPosition(
|
return CameraPosition(
|
||||||
target: target, zoom: calculatedZoom, tilt: 0.0, bearing: 0.0);
|
target: target,
|
||||||
|
zoom: 13.0,
|
||||||
|
tilt: 0.0,
|
||||||
|
bearing: 0.0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraUpdate moveCameraToCenter(LatLng pointA, LatLng pointB) {
|
CameraUpdate moveCameraToCenter(LatLng pointA, LatLng pointB) {
|
||||||
|
@ -151,33 +150,34 @@ class _GoogleTrackTraceMapState extends State<GoogleTrackTraceMap> {
|
||||||
max(pointA.longitude, pointB.longitude),
|
max(pointA.longitude, pointB.longitude),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
50);
|
50,);
|
||||||
}
|
}
|
||||||
|
|
||||||
void startRouteUpdateTimer() {
|
void startRouteUpdateTimer() {
|
||||||
calculateRoute(); // run at the start
|
calculateRoute(); // run at the start
|
||||||
Timer.periodic(Duration(seconds: widget.routeUpdateInterval), (timer) {
|
Timer.periodic(Duration(seconds: widget.routeUpdateInterval),
|
||||||
|
(Timer timer) {
|
||||||
calculateRoute();
|
calculateRoute();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void startMarkerUpdateTimer() {
|
void startMarkerUpdateTimer() {
|
||||||
if (widget.timerPrecision != TimePrecision.updateOnly) {
|
if (widget.timerPrecision != TimePrecision.updateOnly) {
|
||||||
int updateInterval =
|
var updateInterval =
|
||||||
(widget.timerPrecision == TimePrecision.everyMinute) ? 60 : 1;
|
(widget.timerPrecision == TimePrecision.everyMinute) ? 60 : 1;
|
||||||
Timer.periodic(Duration(seconds: updateInterval), (timer) {
|
Timer.periodic(Duration(seconds: updateInterval), (timer) {
|
||||||
if (controller.route != null) {
|
if (controller.route != null) {
|
||||||
controller.route = TrackTraceRoute(
|
controller.route = TrackTraceRoute(
|
||||||
controller.route!.duration - updateInterval,
|
controller.route!.duration - updateInterval,
|
||||||
controller.route!.distance,
|
controller.route!.distance,
|
||||||
controller.route!.line);
|
controller.route!.line,);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void calculateRoute() async {
|
void calculateRoute(){
|
||||||
DirectionsRepository() //TODO refactor this away
|
DirectionsRepository() // TODO(freek): refactor this away
|
||||||
.getDirections(
|
.getDirections(
|
||||||
origin: controller.start.position,
|
origin: controller.start.position,
|
||||||
destination: controller.end.position,
|
destination: controller.end.position,
|
||||||
|
@ -186,15 +186,15 @@ class _GoogleTrackTraceMapState extends State<GoogleTrackTraceMap> {
|
||||||
)
|
)
|
||||||
.then((value) => {
|
.then((value) => {
|
||||||
controller.route = TrackTraceRoute(value.totalDuration,
|
controller.route = TrackTraceRoute(value.totalDuration,
|
||||||
value.totalDistance, value.polylinePoints),
|
value.totalDistance, value.polylinePoints,),
|
||||||
if (controller.mapController != null)
|
if (controller.mapController != null)
|
||||||
{
|
{
|
||||||
controller.mapController!.moveCamera(moveCameraToCenter(
|
controller.mapController!.moveCamera(moveCameraToCenter(
|
||||||
controller.start.position, controller.end.position)),
|
controller.start.position, controller.end.position,),),
|
||||||
},
|
},
|
||||||
setState(() {
|
setState(() {
|
||||||
lastRouteUpdate = DateTime.now();
|
lastRouteUpdate = DateTime.now();
|
||||||
})
|
})
|
||||||
});
|
},);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue