chore: Update the version, linting and formating

This commit is contained in:
Inex Code 2025-01-02 17:52:07 +03:00
parent e567c39d20
commit 04793a588f
No known key found for this signature in database
7 changed files with 299 additions and 159 deletions

View file

@ -1,3 +1,7 @@
## 0.3.0
* Ignore swipe when it starts in system gesture insets by [EpicKiwi](https://github.com/EpicKiwi)
## 0.2.0
* Null-safety migration by [devcat37](https://github.com/devcat37)

70
analysis_options.yaml Normal file
View file

@ -0,0 +1,70 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
avoid_print: false # Uncomment to disable the `avoid_print` rule
always_declare_return_types: true
always_put_control_body_on_new_line: true
always_put_required_named_parameters_first: true
always_use_package_imports: true
avoid_escaping_inner_quotes: true
avoid_setters_without_getters: true
collection_methods_unrelated_type: true
combinators_ordering: true
directives_ordering: true
eol_at_end_of_file: true
no_adjacent_strings_in_list: true
prefer_constructors_over_static_methods: true
prefer_expression_function_bodies: true
prefer_final_in_for_each: true
prefer_final_locals: true
prefer_final_parameters: true
prefer_foreach: true
prefer_if_elements_to_conditional_expressions: true
prefer_mixin: true
prefer_null_aware_method_calls: true
prefer_single_quotes: true
require_trailing_commas: true
sized_box_shrink_expand: true
sort_constructors_first: true
unawaited_futures: true
unnecessary_await_in_return: true
unnecessary_null_aware_operator_on_extension_on_nullable: true
unnecessary_null_checks: true
unnecessary_parenthesis: true
unnecessary_statements: true
unnecessary_to_list_in_spreads: true
unreachable_from_main: true
use_enums: 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_setters_to_change_properties: true
use_string_buffers: true
use_string_in_part_of_directives: true
use_super_parameters: true
use_to_and_as_if_applicable: true
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -2,76 +2,74 @@ import 'package:flutter/material.dart';
import 'package:swipe_to_action/swipe_to_action.dart';
void main() {
runApp(MyApp());
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({final Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _text = "Swipe some tiles!";
String _text = 'Swipe some tiles!';
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: ListView(
children: [
Swipeable(
key: ValueKey(1),
onSwipe: (direction) {
if (direction == SwipeDirection.startToEnd) {
Widget build(final BuildContext context) => MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: ListView(
children: [
Swipeable(
key: const ValueKey(1),
onSwipe: (final direction) {
if (direction == SwipeDirection.startToEnd) {
setState(() {
_text = 'Swiped to right!';
});
} else {
setState(() {
_text = 'Swiped to left!';
});
}
},
child: const ListTile(
title: Text('Tile one'),
),
),
Swipeable(
key: const ValueKey(2),
onSwipe: (final direction) {
setState(() {
_text = "Swiped to right!";
_text = 'This one can only be swiped to right!';
});
} else {
},
background: Container(color: Colors.orange),
direction: SwipeDirection.startToEnd,
child: const ListTile(
title: Text('Tile one'),
),
),
Swipeable(
key: const ValueKey(3),
onSwipe: (final direction) {
setState(() {
_text = "Swiped to left!";
_text = 'This one was confirmed with a function!';
});
}
},
child: ListTile(
title: Text("Tile one"),
},
background: Container(color: Colors.green),
secondaryBackground: Container(color: Colors.teal),
confirmSwipe: (final direction) async => true,
child: const ListTile(
title: Text('Tile three'),
),
),
),
Swipeable(
key: ValueKey(2),
onSwipe: (direction) {
setState(() {
_text = "This one can only be swiped to right!";
});
},
background: Container(color: Colors.orange),
direction: SwipeDirection.startToEnd,
child: ListTile(
title: Text("Tile one"),
),
),
Swipeable(
key: ValueKey(3),
onSwipe: (direction) {
setState(() {
_text = "This one was confirmed with a function!";
});
},
background: Container(color: Colors.green),
secondaryBackground: Container(color: Colors.teal),
confirmSwipe: (direction) async {
return true;
},
child: ListTile(
title: Text("Tile three"),
),
),
Center(child: Text(_text)),
],
Center(child: Text(_text)),
],
),
),
),
);
}
);
}

View file

@ -171,7 +171,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.2.0"
version: "0.3.0"
term_glyph:
dependency: transitive
description:

View file

@ -46,7 +46,7 @@ class Swipeable extends StatefulWidget {
/// state of the dismissed item. Using keys causes the widgets to sync
/// according to their keys and avoids this pitfall.
const Swipeable({
required Key key,
required final Key key,
required this.child,
required this.onSwipe,
this.background,
@ -61,7 +61,7 @@ class Swipeable extends StatefulWidget {
this.allowedPointerKinds = const {
PointerDeviceKind.invertedStylus,
PointerDeviceKind.stylus,
PointerDeviceKind.touch
PointerDeviceKind.touch,
},
}) : assert(secondaryBackground == null || background != null),
super(key: key);
@ -155,7 +155,7 @@ class Swipeable extends StatefulWidget {
final DragStartBehavior dragStartBehavior;
@override
_SwipeableState createState() => _SwipeableState();
State<Swipeable> createState() => _SwipeableState();
}
class _SwipeableClipper extends CustomClipper<Rect> {
@ -166,7 +166,7 @@ class _SwipeableClipper extends CustomClipper<Rect> {
final Animation<Offset> moveAnimation;
@override
Rect getClip(Size size) {
Rect getClip(final Size size) {
final offset = moveAnimation.value.dx * size.width;
if (offset < 0) {
return Rect.fromLTRB(size.width + offset, 0.0, size.width, size.height);
@ -175,21 +175,22 @@ class _SwipeableClipper extends CustomClipper<Rect> {
}
@override
Rect getApproximateClipRect(Size size) => getClip(size);
Rect getApproximateClipRect(final Size size) => getClip(size);
@override
bool shouldReclip(_SwipeableClipper oldClipper) {
return oldClipper.moveAnimation.value != moveAnimation.value;
}
bool shouldReclip(final _SwipeableClipper oldClipper) =>
oldClipper.moveAnimation.value != moveAnimation.value;
}
enum _FlingGestureKind { none, forward, reverse }
class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
class _SwipeableState extends State<Swipeable>
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
@override
void initState() {
_moveController = AnimationController(duration: widget.movementDuration, vsync: this)
..addStatusListener(_handleDismissStatusChanged);
_moveController =
AnimationController(duration: widget.movementDuration, vsync: this)
..addStatusListener(_handleDismissStatusChanged);
_updateMoveAnimation();
super.initState();
@ -221,41 +222,43 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
super.dispose();
}
SwipeDirection _extentToDirection(double extent) {
SwipeDirection _extentToDirection(final double extent) {
if (extent == 0.0) {
return SwipeDirection.none;
}
switch (Directionality.of(context)) {
case TextDirection.rtl:
return extent < 0 ? SwipeDirection.startToEnd : SwipeDirection.endToStart;
return extent < 0
? SwipeDirection.startToEnd
: SwipeDirection.endToStart;
case TextDirection.ltr:
return extent > 0 ? SwipeDirection.startToEnd : SwipeDirection.endToStart;
return extent > 0
? SwipeDirection.startToEnd
: SwipeDirection.endToStart;
}
}
SwipeDirection get _swipeDirection => _extentToDirection(_dragExtent);
bool get _isActive {
return _dragUnderway || _moveController.isAnimating;
}
bool get _isActive => _dragUnderway || _moveController.isAnimating;
double get _overallDragAxisExtent {
final size = context.size;
return size?.width ?? 0.0;
}
void _handlePointerDown(PointerDownEvent event) {
void _handlePointerDown(final PointerDownEvent event) {
final xPos = event.position.dx;
var validTouch = widget.allowedPointerKinds.contains(event.kind);
// Check if touch was performed after minX and before maxX to avoid system
// gesture insets
if(validTouch && _minX != null){
if (validTouch && _minX != null) {
validTouch = xPos > _minX!;
}
if(validTouch && _maxX != null){
if (validTouch && _maxX != null) {
validTouch = xPos < _maxX!;
}
@ -264,10 +267,11 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
});
}
void _handleDragStart(DragStartDetails details) {
void _handleDragStart(final DragStartDetails details) {
_dragUnderway = true;
if (_moveController.isAnimating) {
_dragExtent = _moveController.value * _overallDragAxisExtent * _dragExtent.sign;
_dragExtent =
_moveController.value * _overallDragAxisExtent * _dragExtent.sign;
_moveController.stop();
} else {
_dragExtent = 0.0;
@ -278,7 +282,7 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
});
}
void _handleDragUpdate(DragUpdateDetails details) {
void _handleDragUpdate(final DragUpdateDetails details) {
if (!_isActive || _moveController.isAnimating) {
return;
}
@ -343,7 +347,7 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
);
}
_FlingGestureKind _describeFlingGesture(Velocity velocity) {
_FlingGestureKind _describeFlingGesture(final Velocity velocity) {
if (_dragExtent == 0.0) {
// If it was a fling, then it was a fling that was let loose at the exact
// middle of the range (i.e. when there's no displacement). In that case,
@ -356,7 +360,8 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
final vy = velocity.pixelsPerSecond.dy;
SwipeDirection flingDirection;
// Verify that the fling is in the generally right direction and fast enough.
if (vx.abs() - vy.abs() < _kMinFlingVelocityDelta || vx.abs() < _kMinFlingVelocity) {
if (vx.abs() - vy.abs() < _kMinFlingVelocityDelta ||
vx.abs() < _kMinFlingVelocity) {
return _FlingGestureKind.none;
}
assert(vx != 0.0);
@ -368,12 +373,13 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
return _FlingGestureKind.reverse;
}
Future<void> _handleDragEnd(DragEndDetails details) async {
Future<void> _handleDragEnd(final DragEndDetails details) async {
if (!_isActive || _moveController.isAnimating) {
return;
}
_dragUnderway = false;
if (_moveController.isCompleted && await _confirmStartSwipeAnimation() == true) {
if (_moveController.isCompleted &&
await _confirmStartSwipeAnimation() == true) {
_startSwipeAnimation();
return;
}
@ -382,23 +388,30 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
case _FlingGestureKind.forward:
assert(_dragExtent != 0.0);
assert(!_moveController.isDismissed);
if ((widget.dismissThresholds[_swipeDirection] ?? _kDismissThreshold) >= 1.0) {
if ((widget.dismissThresholds[_swipeDirection] ?? _kDismissThreshold) >=
1.0) {
await _moveController.reverse();
break;
}
_dragExtent = flingVelocity.sign;
await _moveController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
await _moveController.fling(
velocity: flingVelocity.abs() * _kFlingVelocityScale,
);
break;
case _FlingGestureKind.reverse:
assert(_dragExtent != 0.0);
assert(!_moveController.isDismissed);
_dragExtent = flingVelocity.sign;
await _moveController.fling(velocity: -flingVelocity.abs() * _kFlingVelocityScale);
await _moveController.fling(
velocity: -flingVelocity.abs() * _kFlingVelocityScale,
);
break;
case _FlingGestureKind.none:
if (!_moveController.isDismissed) {
// we already know it's not completed, we check that above
if (_moveController.value > (widget.dismissThresholds[_swipeDirection] ?? _kDismissThreshold)) {
if (_moveController.value >
(widget.dismissThresholds[_swipeDirection] ??
_kDismissThreshold)) {
await _moveController.forward();
} else {
await _moveController.reverse();
@ -408,7 +421,7 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
}
}
Future<void> _handleDismissStatusChanged(AnimationStatus status) async {
Future<void> _handleDismissStatusChanged(final AnimationStatus status) async {
if (status == AnimationStatus.completed && !_dragUnderway) {
if (await _confirmStartSwipeAnimation() == true) {
_startSwipeAnimation();
@ -438,7 +451,7 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
}
@override
Widget build(BuildContext context) {
Widget build(final BuildContext context) {
super.build(context); // See AutomaticKeepAliveClientMixin.
assert(debugCheckHasDirectionality(context));
@ -453,14 +466,15 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
// Get system screen size and system gesture insets
// to avoid starting a swipe in this areas
MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
if (mediaQuery != null) {
if(_widthReference == null || _widthReference != mediaQuery.size.width){
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_widthReference == null || _widthReference != mediaQuery.size.width) {
WidgetsBinding.instance.addPostFrameCallback((final _) {
setState(() {
_widthReference = mediaQuery.size.width;
_minX = mediaQuery.systemGestureInsets.left;
_maxX = mediaQuery.size.width - mediaQuery.systemGestureInsets.right;
_maxX =
mediaQuery.size.width - mediaQuery.systemGestureInsets.right;
});
});
}
@ -472,18 +486,20 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
);
if (background != null) {
content = Stack(children: <Widget>[
if (!_moveAnimation.isDismissed)
Positioned.fill(
child: ClipRect(
clipper: _SwipeableClipper(
moveAnimation: _moveAnimation,
content = Stack(
children: <Widget>[
if (!_moveAnimation.isDismissed)
Positioned.fill(
child: ClipRect(
clipper: _SwipeableClipper(
moveAnimation: _moveAnimation,
),
child: background,
),
child: background,
),
),
content,
]);
content,
],
);
}
// We are not swiping but we may be being dragging in widget.direction.
return Listener(
@ -493,8 +509,8 @@ class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin, Au
onHorizontalDragUpdate: _isTouch ? _handleDragUpdate : null,
onHorizontalDragEnd: _isTouch ? _handleDragEnd : null,
behavior: HitTestBehavior.opaque,
child: content,
dragStartBehavior: widget.dragStartBehavior,
child: content,
),
);
}

View file

@ -5,56 +5,63 @@ packages:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.15.0"
version: "1.18.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
@ -65,34 +72,70 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
js:
leak_tracker:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "0.6.3"
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.10"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.15.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
version: "1.9.0"
sky_engine:
dependency: transitive
description: flutter
@ -102,58 +145,66 @@ packages:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev"
source: hosted
version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "0.7.2"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
version: "14.2.4"
sdks:
dart: ">=2.12.0 <3.0.0"
flutter: ">=2.0.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View file

@ -1,6 +1,6 @@
name: swipe_to_action
description: A widget which can be used to call functions when the wrapped child is dragged or flinged.
version: 0.2.0
version: 0.3.0
homepage: https://inex.dev/inex/swipe_to_action
environment:
@ -16,6 +16,7 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec