diff --git a/analysis_options.yaml b/analysis_options.yaml index f9b3034..69dea8d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,187 @@ include: package:flutter_lints/flutter.yaml + +analyzer: + errors: + annotate_overrides: error + avoid_shadowing_type_parameters: warning + exhaustive_cases: warning + literal_only_boolean_expressions: warning + missing_required_param: warning + missing_return: warning + no_duplicate_case_values: error + only_throw_errors: error + override_on_non_overriding_member: error + unnecessary_overrides: error + +linter: + rules: + - always_declare_return_types + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_dynamic_calls + - avoid_empty_else + - avoid_escaping_inner_quotes + - avoid_field_initializers_in_const_classes + - avoid_final_parameters + - avoid_function_literals_in_foreach_calls + - avoid_implementing_value_types + - avoid_init_to_null + - avoid_js_rounded_ints + - avoid_null_checks_in_equality_operators + - 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_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 + - comment_references + - conditional_uri_does_not_exist + - constant_identifier_names + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - depend_on_referenced_packages + - deprecated_consistency + - directives_ordering + - discarded_futures + - empty_catches + - empty_constructor_bodies + - empty_statements + - enable_null_safety + - eol_at_end_of_file + - exhaustive_cases + - file_names + - hash_and_equals + - implementation_imports + - iterable_contains_unrelated_type + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + - library_private_types_in_public_api + - list_remove_unrelated_type + - literal_only_boolean_expressions + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_leading_underscores_for_library_prefixes + - no_leading_underscores_for_local_identifiers + - no_logic_in_create_state + - no_runtimeType_toString + - non_constant_identifier_names + - noop_primitive_operations + - null_check_on_nullable_type_parameter + - null_closures + - omit_local_variable_types + - only_throw_errors + - overridden_fields + - package_api_docs + - 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_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - 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_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + - prefer_null_aware_method_calls + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + - recursive_getters + - require_trailing_commas + - sized_box_for_whitespace + - sized_box_shrink_expand + - slash_for_doc_comments + - sort_child_properties_last + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - tighten_type_of_initializing_formals + - type_annotate_public_apis + - type_init_formals + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_constructor_name + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_late + - unnecessary_new + - unnecessary_null_aware_assignments + - 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 + - unrelated_type_equality_checks + - unsafe_html + - 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_is_even_rather_than_modulo + - use_key_in_widget_constructors + - use_late_for_private_fields_and_variables + - use_named_constants + - use_raw_strings + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_string_buffers + - use_super_parameters + - use_to_and_as_if_applicable + - valid_regexps + - void_checks diff --git a/example/lib/example_candidate_model.dart b/example/lib/example_candidate_model.dart index 006f8db..0f53a26 100644 --- a/example/lib/example_candidate_model.dart +++ b/example/lib/example_candidate_model.dart @@ -14,29 +14,29 @@ class ExampleCandidateModel { }); } -List candidates = [ +final List candidates = [ ExampleCandidateModel( name: 'One, 1', job: 'Developer', city: 'Areado', - color: [Color(0xFFFF3868), Color(0xFFFFB49A)], + color: const [Color(0xFFFF3868), Color(0xFFFFB49A)], ), ExampleCandidateModel( name: 'Two, 2', job: 'Manager', city: 'New York', - color: [Color(0xFF736EFE), Color(0xFF62E4EC)], + color: const [Color(0xFF736EFE), Color(0xFF62E4EC)], ), ExampleCandidateModel( name: 'Three, 3', job: 'Engineer', city: 'London', - color: [Color(0xFF2F80ED), Color(0xFF56CCF2)], + color: const [Color(0xFF2F80ED), Color(0xFF56CCF2)], ), ExampleCandidateModel( name: 'Four, 4', job: 'Designer', city: 'Tokyo', - color: [Color(0xFF0BA4E0), Color(0xFFA9E4BD)], + color: const [Color(0xFF0BA4E0), Color(0xFFA9E4BD)], ), ]; diff --git a/example/lib/example_card.dart b/example/lib/example_card.dart index 3faf1ca..dc5c7fa 100644 --- a/example/lib/example_card.dart +++ b/example/lib/example_card.dart @@ -15,7 +15,7 @@ class ExampleCard extends StatelessWidget { return Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: const BorderRadius.all(Radius.circular(10)), color: Colors.white, boxShadow: [ BoxShadow( diff --git a/example/lib/main.dart b/example/lib/main.dart index 14540c9..d2a6c2e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,10 +4,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_card_swiper/flutter_card_swiper.dart'; void main() { - runApp(const MaterialApp( - debugShowCheckedModeBanner: false, - home: Example(), - )); + runApp( + const MaterialApp( + debugShowCheckedModeBanner: false, + home: Example(), + ), + ); } class Example extends StatefulWidget { @@ -39,29 +41,29 @@ class _ExamplePageState extends State { ), ), Padding( - padding: EdgeInsets.symmetric(vertical: 16.0), + padding: const EdgeInsets.symmetric(vertical: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ FloatingActionButton( onPressed: controller.swipe, - child: Icon(Icons.rotate_right), + child: const Icon(Icons.rotate_right), ), FloatingActionButton( onPressed: controller.swipeLeft, - child: Icon(Icons.keyboard_arrow_left), + child: const Icon(Icons.keyboard_arrow_left), ), FloatingActionButton( onPressed: controller.swipeRight, - child: Icon(Icons.keyboard_arrow_right), + child: const Icon(Icons.keyboard_arrow_right), ), FloatingActionButton( onPressed: controller.swipeTop, - child: Icon(Icons.keyboard_arrow_up), + child: const Icon(Icons.keyboard_arrow_up), ), FloatingActionButton( onPressed: controller.swipeBottom, - child: Icon(Icons.keyboard_arrow_down), + child: const Icon(Icons.keyboard_arrow_down), ), ], ), @@ -73,6 +75,6 @@ class _ExamplePageState extends State { } void _swipe(int index, CardSwiperDirection direction) { - debugPrint("the card was swiped to the: " + direction.name); + debugPrint('the card $index was swiped to the: ${direction.name}'); } } diff --git a/lib/src/card_swiper.dart b/lib/src/card_swiper.dart index d444d88..d15525a 100644 --- a/lib/src/card_swiper.dart +++ b/lib/src/card_swiper.dart @@ -52,9 +52,18 @@ class CardSwiper extends StatefulWidget { this.onSwipe, this.onEnd, this.direction = CardSwiperDirection.right, - }) : assert(maxAngle >= 0 && maxAngle <= 360), - assert(threshold >= 1 && threshold <= 100), - assert(direction != CardSwiperDirection.none), + }) : assert( + maxAngle >= 0 && maxAngle <= 360, + 'maxAngle must be between 0 and 360', + ), + assert( + threshold >= 1 && threshold <= 100, + 'threshold must be between 1 and 100', + ), + assert( + direction != CardSwiperDirection.none, + 'direction must not be none', + ), super(key: key); @override @@ -67,13 +76,12 @@ class _CardSwiperState extends State double _top = 0; double _total = 0; double _angle = 0; - double _maxAngle = 0; double _scale = 0.9; double _difference = 40; int _currentIndex = 0; - SwipeType _swipeTyp = SwipeType.none; + SwipeType _swipeType = SwipeType.none; bool _tapOnTop = false; //position of starting drag point on card late AnimationController _animationController; @@ -84,6 +92,8 @@ class _CardSwiperState extends State CardSwiperDirection detectedDirection = CardSwiperDirection.none; + double get _maxAngle => widget.maxAngle * (pi / 180); + bool get _isLastCard => _currentIndex == widget.cards.length - 1; int get _nextCardIndex => _isLastCard ? 0 : _currentIndex + 1; @@ -91,80 +101,21 @@ class _CardSwiperState extends State void initState() { super.initState(); - if (widget.controller != null) { - //swipe widget from the outside - widget.controller!.addListener(() { - switch (widget.controller!.state) { - case CardSwiperState.swipe: - _swipe(context, widget.direction); - break; - case CardSwiperState.swipeLeft: - _swipe(context, CardSwiperDirection.left); - break; - case CardSwiperState.swipeRight: - _swipe(context, CardSwiperDirection.right); - break; - case CardSwiperState.swipeTop: - _swipe(context, CardSwiperDirection.top); - break; - case CardSwiperState.swipeBottom: - _swipe(context, CardSwiperDirection.bottom); - break; - default: - break; - } - }); - } + widget.controller?.addListener(_controllerListener); - if (widget.maxAngle > 0) { - _maxAngle = widget.maxAngle * (pi / 180); - } - - _animationController = - AnimationController(duration: widget.duration, vsync: this); - _animationController.addListener(() { - //when value of controller changes - if (_animationController.status == AnimationStatus.forward) { - setState(() { - _left = _leftAnimation.value; - _top = _topAnimation.value; - _scale = _scaleAnimation.value; - _difference = _differenceAnimation.value; - }); - } - }); - - _animationController.addStatusListener((status) { - //when status of controller changes - if (status == AnimationStatus.completed) { - setState(() { - if (_swipeTyp == SwipeType.swipe) { - widget.onSwipe?.call(_currentIndex, detectedDirection); - - if (_isLastCard) { - widget.onEnd?.call(); - _currentIndex = 0; - } else { - _currentIndex++; - } - } - _animationController.reset(); - _left = 0; - _top = 0; - _total = 0; - _angle = 0; - _scale = 0.9; - _difference = 40; - _swipeTyp = SwipeType.none; - }); - } - }); + _animationController = AnimationController( + duration: widget.duration, + vsync: this, + ) + ..addListener(_animationListener) + ..addStatusListener(_animationStatusListener); } @override void dispose() { super.dispose(); _animationController.dispose(); + widget.controller?.dispose(); } @override @@ -176,12 +127,13 @@ class _CardSwiperState extends State child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Stack( - clipBehavior: Clip.none, - fit: StackFit.expand, - children: [ - _backItem(constraints, _nextCardIndex), - _frontItem(constraints, _currentIndex) - ]); + clipBehavior: Clip.none, + fit: StackFit.expand, + children: [ + _backItem(constraints), + _frontItem(constraints), + ], + ); }, ), ); @@ -189,7 +141,7 @@ class _CardSwiperState extends State ); } - Widget _frontItem(BoxConstraints constraints, int index) { + Widget _frontItem(BoxConstraints constraints) { return Positioned( left: _left, top: _top, @@ -198,7 +150,7 @@ class _CardSwiperState extends State angle: _angle, child: ConstrainedBox( constraints: constraints, - child: widget.cards[index], + child: widget.cards[_currentIndex], ), ), onTap: () { @@ -208,8 +160,8 @@ class _CardSwiperState extends State }, onPanStart: (tapInfo) { if (!widget.isDisabled) { - RenderBox renderBox = context.findRenderObject() as RenderBox; - Offset position = renderBox.globalToLocal(tapInfo.globalPosition); + final renderBox = context.findRenderObject()! as RenderBox; + final position = renderBox.globalToLocal(tapInfo.globalPosition); if (position.dy < renderBox.size.height / 2) _tapOnTop = true; } @@ -237,7 +189,7 @@ class _CardSwiperState extends State ); } - Widget _backItem(BoxConstraints constraints, int index) { + Widget _backItem(BoxConstraints constraints) { return Positioned( top: _difference, left: 0, @@ -245,15 +197,76 @@ class _CardSwiperState extends State scale: _scale, child: ConstrainedBox( constraints: constraints, - child: widget.cards[index], + child: widget.cards[_nextCardIndex], ), ), ); } + //swipe widget from the outside + void _controllerListener() { + switch (widget.controller!.state) { + case CardSwiperState.swipe: + _swipe(context, widget.direction); + break; + case CardSwiperState.swipeLeft: + _swipe(context, CardSwiperDirection.left); + break; + case CardSwiperState.swipeRight: + _swipe(context, CardSwiperDirection.right); + break; + case CardSwiperState.swipeTop: + _swipe(context, CardSwiperDirection.top); + break; + case CardSwiperState.swipeBottom: + _swipe(context, CardSwiperDirection.bottom); + break; + default: + break; + } + } + + //when value of controller changes + void _animationListener() { + if (_animationController.status == AnimationStatus.forward) { + setState(() { + _left = _leftAnimation.value; + _top = _topAnimation.value; + _scale = _scaleAnimation.value; + _difference = _differenceAnimation.value; + }); + } + } + + //when the status of animation changes + void _animationStatusListener(AnimationStatus status) { + if (status == AnimationStatus.completed) { + setState(() { + if (_swipeType == SwipeType.swipe) { + widget.onSwipe?.call(_currentIndex, detectedDirection); + + if (_isLastCard) { + widget.onEnd?.call(); + _currentIndex = 0; + } else { + _currentIndex++; + } + } + _animationController.reset(); + _left = 0; + _top = 0; + _total = 0; + _angle = 0; + _scale = 0.9; + _difference = 40; + _swipeType = SwipeType.none; + }); + } + } + void _calculateAngle() { if (_angle <= _maxAngle && _angle >= -_maxAngle) { - (_tapOnTop) + _tapOnTop ? _angle = (_maxAngle / 100) * (_left / 10) : _angle = (_maxAngle / 100) * (_left / 10) * -1; } @@ -285,7 +298,7 @@ class _CardSwiperState extends State //moves the card away to the left or right void _swipeHorizontal(BuildContext context) { setState(() { - _swipeTyp = SwipeType.swipe; + _swipeType = SwipeType.swipe; _leftAnimation = Tween( begin: _left, end: (_left == 0) @@ -346,7 +359,7 @@ class _CardSwiperState extends State //moves the card away to the top or bottom void _swipeVertical(BuildContext context) { setState(() { - _swipeTyp = SwipeType.swipe; + _swipeType = SwipeType.swipe; _leftAnimation = Tween( begin: _left, end: _left + _left, @@ -381,7 +394,7 @@ class _CardSwiperState extends State //moves the card back to starting position void _goBack(BuildContext context) { setState(() { - _swipeTyp = SwipeType.back; + _swipeType = SwipeType.back; _leftAnimation = Tween( begin: _left, end: 0, diff --git a/lib/src/typedefs.dart b/lib/src/typedefs.dart index d022e77..a83bf04 100644 --- a/lib/src/typedefs.dart +++ b/lib/src/typedefs.dart @@ -1,6 +1,10 @@ import 'package:flutter_card_swiper/src/enums.dart'; typedef CardSwiperOnSwipe = void Function( - int index, CardSwiperDirection direction); + int index, + CardSwiperDirection direction, +); + typedef CardSwiperOnEnd = void Function(); + typedef CardSwiperOnTapDisabled = void Function();