feat: add ci and linter

This commit is contained in:
mike doornenbal 2024-02-02 11:48:45 +01:00
parent 8eb1d80a9f
commit fee3d681cb
32 changed files with 1008 additions and 961 deletions

View file

@ -60,3 +60,7 @@
## 2.7.0
* Addition of 'decoration' parameter to 'FlutterFormInputPassword'
## 2.7.1
* Added Iconica CI and Iconica Linter

View file

@ -1,4 +1,9 @@
include: package:flutter_lints/flutter.yaml
include: package:flutter_iconica_analysis/analysis_options.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
# Possible to overwrite the rules from the package
analyzer:
exclude:
linter:
rules:

View file

@ -1,11 +1,11 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
# This file should be version controlled and should not be manually edited.
version:
revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
channel: stable
revision: "67457e669f79e9f8d13d7a68fe09775fefbb79f4"
channel: "stable"
project_type: app
@ -13,11 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
- platform: web
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: ios
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
# User provided section

View file

@ -37,10 +37,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.2"
version: "1.18.0"
cupertino_icons:
dependency: "direct main"
description:
@ -68,7 +68,7 @@ packages:
path: ".."
relative: true
source: path
version: "2.4.0"
version: "2.7.0"
flutter_lints:
dependency: "direct dev"
description:
@ -118,10 +118,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
path:
dependency: transitive
description:
@ -147,18 +147,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@ -179,10 +179,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "0.6.1"
vector_math:
dependency: transitive
description:
@ -195,10 +195,10 @@ packages:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
version: "0.3.0"
sdks:
dart: ">=3.1.0-185.0.dev <4.0.0"
dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=1.17.0"

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
/// A library for creating input fields in Flutter.
library flutter_input_library;
export 'src/inputs/inputs.dart';

View file

@ -4,20 +4,18 @@
import 'package:flutter/material.dart';
import 'carousel_form.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_form.dart';
class FlutterFormInputCarousel extends StatelessWidget {
const FlutterFormInputCarousel({
Key? key,
required this.items,
super.key,
this.height = 425,
this.onSaved,
this.onChanged,
this.initialValue,
this.validator,
}) : super(
key: key,
);
});
final List<Widget> items;
final double height;
@ -27,8 +25,7 @@ class FlutterFormInputCarousel extends StatelessWidget {
final int? initialValue;
@override
Widget build(BuildContext context) {
return CarouselFormField(
Widget build(BuildContext context) => CarouselFormField(
onSaved: (value) => onSaved?.call(value),
validator: (value) => validator?.call(value),
onChanged: (value) => onChanged?.call(value),
@ -37,4 +34,3 @@ class FlutterFormInputCarousel extends StatelessWidget {
height: height,
);
}
}

View file

@ -5,12 +5,12 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'carousel_utils.dart';
import 'carousel_options.dart';
import 'carousel_state.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_options.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_state.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_utils.dart';
abstract class CarouselController {
factory CarouselController() => CarouselControllerImpl();
bool get ready;
Future<void> get onReady;
@ -26,8 +26,6 @@ abstract class CarouselController {
void startAutoPlay();
void stopAutoPlay();
factory CarouselController() => CarouselControllerImpl();
}
class CarouselControllerImpl implements CarouselController {
@ -56,10 +54,11 @@ class CarouselControllerImpl implements CarouselController {
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
@override
Future<void> nextPage(
{Duration? duration = const Duration(milliseconds: 300),
Curve? curve = Curves.linear}) async {
final bool isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate;
Future<void> nextPage({
Duration? duration = const Duration(milliseconds: 300),
Curve? curve = Curves.linear,
}) async {
var isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate;
if (isNeedResetTimer) {
_state!.onResetTimer();
}
@ -75,10 +74,11 @@ class CarouselControllerImpl implements CarouselController {
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
@override
Future<void> previousPage(
{Duration? duration = const Duration(milliseconds: 300),
Curve? curve = Curves.linear}) async {
final bool isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate;
Future<void> previousPage({
Duration? duration = const Duration(milliseconds: 300),
Curve? curve = Curves.linear,
}) async {
var isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate;
if (isNeedResetTimer) {
_state!.onResetTimer();
}
@ -96,11 +96,14 @@ class CarouselControllerImpl implements CarouselController {
/// without animation, and without checking if the new value is in range.
@override
void jumpToPage(int page) {
final index = getRealIndex(_state!.pageController!.page!.toInt(),
_state!.realPage - _state!.initialPage, _state!.itemCount);
var index = getRealIndex(
_state!.pageController!.page!.toInt(),
_state!.realPage - _state!.initialPage,
_state!.itemCount,
);
_setModeController();
final int pageToJump = _state!.pageController!.page!.toInt() + page - index;
var pageToJump = _state!.pageController!.page!.toInt() + page - index;
return _state!.pageController!.jumpToPage(pageToJump);
}
@ -110,20 +113,26 @@ class CarouselControllerImpl implements CarouselController {
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
@override
Future<void> animateToPage(int page,
{Duration? duration = const Duration(milliseconds: 300),
Curve? curve = Curves.linear}) async {
final bool isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate;
Future<void> animateToPage(
int page, {
Duration? duration = const Duration(milliseconds: 300),
Curve? curve = Curves.linear,
}) async {
var isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate;
if (isNeedResetTimer) {
_state!.onResetTimer();
}
final index = getRealIndex(_state!.pageController!.page!.toInt(),
_state!.realPage - _state!.initialPage, _state!.itemCount);
var index = getRealIndex(
_state!.pageController!.page!.toInt(),
_state!.realPage - _state!.initialPage,
_state!.itemCount,
);
_setModeController();
await _state!.pageController!.animateToPage(
_state!.pageController!.page!.toInt() + page - index,
duration: duration!,
curve: curve!);
curve: curve!,
);
if (isNeedResetTimer) {
_state!.onResumeTimer();
}

View file

@ -3,26 +3,19 @@
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'carousel_slider.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_slider.dart';
class CarouselFormField extends FormField<int> {
CarouselFormField({
Key? key,
required FormFieldSetter<int> onSaved,
required FormFieldValidator<int> validator,
void Function(int value)? onChanged,
void Function(int value)? onSubmit,
int initialValue = 0,
bool autovalidate = false,
required FormFieldSetter<int> super.onSaved,
required FormFieldValidator<int> super.validator,
required List<Widget> items,
super.key,
void Function(int value)? onChanged,
int super.initialValue = 0,
double height = 425,
}) : super(
key: key,
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<int> state) {
return CarouselSlider(
builder: (FormFieldState<int> state) => CarouselSlider(
options: CarouselOptions(
initialPage: initialValue,
onPageChanged: (index, reason) {
@ -35,9 +28,7 @@ class CarouselFormField extends FormField<int> {
enlargeCenterPage: true,
enableInfiniteScroll: false,
),
items: items.map((Widget item) {
return item;
}).toList(),
items: items.map((Widget item) => item).toList(),
),
);
});
}

View file

@ -9,6 +9,33 @@ enum CarouselPageChangedReason { timed, manual, controller }
enum CenterPageEnlargeStrategy { scale, height }
class CarouselOptions {
CarouselOptions({
this.height,
this.aspectRatio = 16 / 9,
this.viewportFraction = 0.8,
this.initialPage = 0,
this.enableInfiniteScroll = true,
this.reverse = false,
this.autoPlay = false,
this.autoPlayInterval = const Duration(seconds: 4),
this.autoPlayAnimationDuration = const Duration(milliseconds: 800),
this.autoPlayCurve = Curves.fastOutSlowIn,
this.enlargeCenterPage = false,
this.onPageChanged,
this.onScrolled,
this.scrollPhysics,
this.pageSnapping = true,
this.scrollDirection = Axis.horizontal,
this.pauseAutoPlayOnTouch = true,
this.pauseAutoPlayOnManualNavigate = true,
this.pauseAutoPlayInFiniteScroll = false,
this.pageViewKey,
this.enlargeStrategy = CenterPageEnlargeStrategy.scale,
this.disableCenter = false,
this.padEnds = true,
this.clipBehavior = Clip.hardEdge,
});
/// Set carousel height and overrides any existing [aspectRatio].
final double? height;
@ -105,18 +132,19 @@ class CarouselOptions {
/// Default to `true`.
final bool pauseAutoPlayOnManualNavigate;
/// If [enableInfiniteScroll] is `false`, and [autoPlay] is `true`, this option
/// decide the carousel should go to the first item when it reach the last item or not.
/// If set to `true`, the auto play will be paused when it reach the last item.
/// If set to `false`, the auto play function will animate to the first item
/// when it was in the last item.
/// If [enableInfiniteScroll] is `false`, and [autoPlay] is `true`,
/// this option decide the carousel should go to the first item when it
/// reach the last item or not. If set to `true`, the auto play will be
/// paused when it reach the last item. If set to `false`, the auto play
/// function will animate to the first item when it was in the last item.
final bool pauseAutoPlayInFiniteScroll;
/// Pass a [PageStorageKey] if you want to keep the pageview's position when
/// it was recreated.
final PageStorageKey? pageViewKey;
/// Use [enlargeStrategy] to determine which method to enlarge the center page.
/// Use [enlargeStrategy] to determine which method to enlarge the
/// center page.
final CenterPageEnlargeStrategy enlargeStrategy;
/// Whether or not to disable the [Center] widget for each slide.
@ -134,37 +162,10 @@ class CarouselOptions {
/// Exposed [clipBehavior] of [PageView]
final Clip clipBehavior;
CarouselOptions({
this.height,
this.aspectRatio = 16 / 9,
this.viewportFraction = 0.8,
this.initialPage = 0,
this.enableInfiniteScroll = true,
this.reverse = false,
this.autoPlay = false,
this.autoPlayInterval = const Duration(seconds: 4),
this.autoPlayAnimationDuration = const Duration(milliseconds: 800),
this.autoPlayCurve = Curves.fastOutSlowIn,
this.enlargeCenterPage = false,
this.onPageChanged,
this.onScrolled,
this.scrollPhysics,
this.pageSnapping = true,
this.scrollDirection = Axis.horizontal,
this.pauseAutoPlayOnTouch = true,
this.pauseAutoPlayOnManualNavigate = true,
this.pauseAutoPlayInFiniteScroll = false,
this.pageViewKey,
this.enlargeStrategy = CenterPageEnlargeStrategy.scale,
this.disableCenter = false,
this.padEnds = true,
this.clipBehavior = Clip.hardEdge,
});
///Generate new [CarouselOptions] based on old ones.
CarouselOptions copyWith(
{double? height,
CarouselOptions copyWith({
double? height,
double? aspectRatio,
double? viewportFraction,
int? initialPage,
@ -187,7 +188,8 @@ class CarouselOptions {
CenterPageEnlargeStrategy? enlargeStrategy,
bool? disableCenter,
Clip? clipBehavior,
bool? padEnds}) =>
bool? padEnds,
}) =>
CarouselOptions(
height: height ?? this.height,
aspectRatio: aspectRatio ?? this.aspectRatio,

View file

@ -1,26 +1,51 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
///
library carousel_slider;
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'carousel_state.dart';
import 'carousel_utils.dart';
import 'carousel_controller.dart';
import 'carousel_options.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_controller.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_options.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_state.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_utils.dart';
export 'carousel_controller.dart';
export 'carousel_options.dart';
typedef ExtendedIndexedWidgetBuilder = Widget Function(
BuildContext context, int index, int realIndex);
BuildContext context,
int index,
int realIndex,
);
class CarouselSlider extends StatefulWidget {
CarouselSlider({
required this.items,
required this.options,
CarouselController? carouselController,
super.key,
}) : itemBuilder = null,
itemCount = items != null ? items.length : 0,
_carouselController = carouselController != null
? carouselController as CarouselControllerImpl
: CarouselController() as CarouselControllerImpl;
/// The on demand item builder constructor/
CarouselSlider.builder({
required this.itemCount,
required this.itemBuilder,
required this.options,
CarouselController? carouselController,
super.key,
}) : items = null,
_carouselController = carouselController != null
? carouselController as CarouselControllerImpl
: CarouselController() as CarouselControllerImpl;
/// [CarouselOptions] to create a [CarouselState] with.
final CarouselOptions options;
@ -28,8 +53,8 @@ class CarouselSlider extends StatefulWidget {
final List<Widget>? items;
/// The widget item builder that will be used to build item on demand
/// The third argument is the [PageView]'s real index, can be used to cooperate
/// with Hero.
/// The third argument is the [PageView]'s real index, can be used
/// to cooperate with Hero.
final ExtendedIndexedWidgetBuilder? itemBuilder;
/// A [MapController], used to control the map.
@ -37,37 +62,13 @@ class CarouselSlider extends StatefulWidget {
final int? itemCount;
CarouselSlider(
{required this.items,
required this.options,
CarouselController? carouselController,
Key? key})
: itemBuilder = null,
itemCount = items != null ? items.length : 0,
_carouselController = carouselController != null
? carouselController as CarouselControllerImpl
: CarouselController() as CarouselControllerImpl,
super(key: key);
/// The on demand item builder constructor/
CarouselSlider.builder(
{required this.itemCount,
required this.itemBuilder,
required this.options,
CarouselController? carouselController,
Key? key})
: items = null,
_carouselController = carouselController != null
? carouselController as CarouselControllerImpl
: CarouselController() as CarouselControllerImpl,
super(key: key);
@override
CarouselSliderState createState() => CarouselSliderState();
}
class CarouselSliderState extends State<CarouselSlider>
with TickerProviderStateMixin {
CarouselSliderState();
late CarouselControllerImpl carouselController;
Timer? timer;
@ -80,8 +81,6 @@ class CarouselSliderState extends State<CarouselSlider>
/// [mode] is related to why the page is being changed.
CarouselPageChangedReason mode = CarouselPageChangedReason.controller;
CarouselSliderState();
void changeMode(CarouselPageChangedReason mode) {
this.mode = mode;
}
@ -127,21 +126,19 @@ class CarouselSliderState extends State<CarouselSlider>
carouselState!.pageController = pageController;
}
Timer? getTimer() {
return widget.options.autoPlay
? Timer.periodic(widget.options.autoPlayInterval, (_) {
final route = ModalRoute.of(context);
Timer? getTimer() => widget.options.autoPlay
? Timer.periodic(widget.options.autoPlayInterval, (_) async {
var route = ModalRoute.of(context);
if (route?.isCurrent == false) {
return;
}
CarouselPageChangedReason previousReason = mode;
var previousReason = mode;
changeMode(CarouselPageChangedReason.timed);
int nextPage = carouselState!.pageController!.page!.round() + 1;
int itemCount = widget.itemCount ?? widget.items!.length;
var nextPage = carouselState!.pageController!.page!.round() + 1;
var itemCount = widget.itemCount ?? widget.items!.length;
if (nextPage >= itemCount &&
widget.options.enableInfiniteScroll == false) {
if (nextPage >= itemCount && !widget.options.enableInfiniteScroll) {
if (widget.options.pauseAutoPlayInFiniteScroll) {
clearTimer();
return;
@ -149,14 +146,15 @@ class CarouselSliderState extends State<CarouselSlider>
nextPage = 0;
}
carouselState!.pageController!
.animateToPage(nextPage,
await carouselState!.pageController!
.animateToPage(
nextPage,
duration: widget.options.autoPlayAnimationDuration,
curve: widget.options.autoPlayCurve)
curve: widget.options.autoPlayCurve,
)
.then((_) => changeMode(previousReason));
})
: null;
}
void clearTimer() {
if (timer != null) {
@ -170,7 +168,7 @@ class CarouselSliderState extends State<CarouselSlider>
}
void handleAutoPlay() {
bool autoPlayEnabled = widget.options.autoPlay;
var autoPlayEnabled = widget.options.autoPlay;
if (autoPlayEnabled && timer != null) return;
@ -193,7 +191,7 @@ class CarouselSliderState extends State<CarouselSlider>
gestures: {
_MultipleGestureRecognizer:
GestureRecognizerFactoryWithHandlers<_MultipleGestureRecognizer>(
() => _MultipleGestureRecognizer(),
_MultipleGestureRecognizer.new,
(_MultipleGestureRecognizer instance) {
instance.onStart = (_) {
onStart();
@ -204,9 +202,7 @@ class CarouselSliderState extends State<CarouselSlider>
instance.onEnd = (_) {
onPanUp();
};
instance.onCancel = () {
onPanUp();
};
instance.onCancel = onPanUp;
}),
},
child: NotificationListener(
@ -231,14 +227,19 @@ class CarouselSliderState extends State<CarouselSlider>
return Center(child: child);
}
Widget getEnlargeWrapper(Widget? child,
{double? width, double? height, double? scale}) {
Widget getEnlargeWrapper(
Widget? child, {
double? width,
double? height,
double? scale,
}) {
if (widget.options.enlargeStrategy == CenterPageEnlargeStrategy.height) {
return SizedBox(width: width, height: height, child: child);
}
return Transform.scale(
scale: scale!,
child: SizedBox(width: width, height: height, child: child));
scale: scale,
child: SizedBox(width: width, height: height, child: child),
);
}
void onStart() {
@ -266,8 +267,8 @@ class CarouselSliderState extends State<CarouselSlider>
}
@override
Widget build(BuildContext context) {
return getGestureWrapper(PageView.builder(
Widget build(BuildContext context) => getGestureWrapper(
PageView.builder(
padEnds: widget.options.padEnds,
scrollBehavior: ScrollConfiguration.of(context).copyWith(
scrollbars: false,
@ -280,34 +281,42 @@ class CarouselSliderState extends State<CarouselSlider>
pageSnapping: widget.options.pageSnapping,
controller: carouselState!.pageController,
reverse: widget.options.reverse,
itemCount: widget.options.enableInfiniteScroll ? null : widget.itemCount,
itemCount:
widget.options.enableInfiniteScroll ? null : widget.itemCount,
key: widget.options.pageViewKey,
onPageChanged: (int index) {
int currentPage = getRealIndex(index + carouselState!.initialPage,
carouselState!.realPage, widget.itemCount);
if (widget.options.onPageChanged != null) {
widget.options.onPageChanged!(currentPage, mode);
}
var currentPage = getRealIndex(
index + carouselState!.initialPage,
carouselState!.realPage,
widget.itemCount,
);
widget.options.onPageChanged?.call(currentPage, mode);
},
itemBuilder: (BuildContext context, int idx) {
final int index = getRealIndex(idx + carouselState!.initialPage,
carouselState!.realPage, widget.itemCount);
var index = getRealIndex(
idx + carouselState!.initialPage,
carouselState!.realPage,
widget.itemCount,
);
return AnimatedBuilder(
animation: carouselState!.pageController!,
child: (widget.items != null)
? (widget.items!.isNotEmpty ? widget.items![index] : Container())
? (widget.items!.isNotEmpty
? widget.items![index]
: Container())
: widget.itemBuilder!(context, index, idx),
builder: (BuildContext context, child) {
double distortionValue = 1.0;
// if [enlargeCenterPage] is true, we must calculate the carousel item's height
var distortionValue = 1.0;
// if [enlargeCenterPage] is true, we must calculate the
// carousel item's height
// to display the visual effect
if (widget.options.enlargeCenterPage != null &&
widget.options.enlargeCenterPage == true) {
// [pageController.page] can only be accessed after the first build,
// so in the first build we calculate the [itemOffset] manually
double itemOffset = 0;
if (widget.options.enlargeCenterPage ?? false) {
// [pageController.page] can only be accessed after the
// first build, so in the first build we calculate the
// [itemOffset] manually
var itemOffset = 0.0;
var position = carouselState?.pageController?.position;
if (position != null &&
position.hasPixels &&
@ -317,11 +326,10 @@ class CarouselSliderState extends State<CarouselSlider>
itemOffset = page - idx;
}
} else {
BuildContext storageContext = carouselState!
var storageContext = carouselState!
.pageController!.position.context.storageContext;
final double? previousSavedPosition =
PageStorage.of(storageContext).readState(storageContext)
as double?;
var previousSavedPosition = PageStorage.of(storageContext)
.readState(storageContext) as double?;
if (previousSavedPosition != null) {
itemOffset = previousSavedPosition - idx.toDouble();
} else {
@ -330,29 +338,39 @@ class CarouselSliderState extends State<CarouselSlider>
}
}
final num distortionRatio =
num distortionRatio =
(1 - (itemOffset.abs() * 0.3)).clamp(0.0, 1.0);
distortionValue =
Curves.easeOut.transform(distortionRatio as double);
}
final double height = widget.options.height ??
var height = widget.options.height ??
MediaQuery.of(context).size.width *
(1 / widget.options.aspectRatio);
if (widget.options.scrollDirection == Axis.horizontal) {
return getCenterWrapper(getEnlargeWrapper(child,
height: distortionValue * height, scale: distortionValue));
return getCenterWrapper(
getEnlargeWrapper(
child,
height: distortionValue * height,
scale: distortionValue,
),
);
} else {
return getCenterWrapper(getEnlargeWrapper(child,
width: distortionValue * MediaQuery.of(context).size.width,
scale: distortionValue));
return getCenterWrapper(
getEnlargeWrapper(
child,
width:
distortionValue * MediaQuery.of(context).size.width,
scale: distortionValue,
),
);
}
},
);
},
));
}
),
);
}
class _MultipleGestureRecognizer extends PanGestureRecognizer {}

View file

@ -3,9 +3,16 @@
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'carousel_slider.dart';
import 'package:flutter_input_library/src/inputs/carousel/carousel_slider.dart';
class CarouselState {
CarouselState(
this.options,
this.onResetTimer,
this.onResumeTimer,
this.changeMode,
);
/// The [CarouselOptions] to create this state
CarouselOptions options;
@ -30,16 +37,13 @@ class CarouselState {
/// Will be called when using [pageController] to go to next page or
/// previous page. It will clear the autoPlay timer.
/// Internal use only
Function onResetTimer;
Function() onResetTimer;
/// Will be called when using pageController to go to next page or
/// previous page. It will restart the autoPlay timer.
/// Internal use only
Function onResumeTimer;
Function() onResumeTimer;
/// The callback to set the Reason Carousel changed
Function(CarouselPageChangedReason) changeMode;
CarouselState(
this.options, this.onResetTimer, this.onResumeTimer, this.changeMode);
}

View file

@ -2,26 +2,27 @@
//
// SPDX-License-Identifier: BSD-3-Clause
/// Converts an index of a set size to the corresponding index of a collection of another size
/// as if they were circular.
/// Converts an index of a set size to the corresponding index of a collection
/// of another size as if they were circular.
///
/// Takes a [position] from collection Foo, a [base] from where Foo's index originated
/// and the [length] of a second collection Baa, for which the correlating index is sought.
/// Takes a [position] from collection Foo, a [base] from where Foo's index
/// originated and the [length] of a second collection Baa, for which the
/// correlating index is sought.
///
/// For example; We have a Carousel of 10000(simulating infinity) but only 6 images.
/// We need to repeat the images to give the illusion of a never ending stream.
/// By calling [getRealIndex] with position and base we get an offset.
/// This offset modulo our length, 6, will return a number between 0 and 5, which represent the image
/// to be placed in the given position.
/// For example; We have a Carousel of 10000(simulating infinity) but only 6
/// images. We need to repeat the images to give the illusion of a never
/// ending stream. By calling [getRealIndex] with position and base we get
/// an offset. This offset modulo our length, 6, will return a number between
/// 0 and 5, which represent the image to be placed in the given position.
int getRealIndex(int position, int base, int? length) {
final int offset = position - base;
var offset = position - base;
return remainder(offset, length);
}
/// Returns the remainder of the modulo operation [input] % [source], and adjust it for
/// negative values.
/// Returns the remainder of the modulo operation [input] % [source], and adjust
/// it for negative values.
int remainder(int input, int? source) {
if (source == 0) return 0;
final int result = input % source!;
var result = input % source!;
return result < 0 ? source + result : result;
}

View file

@ -15,13 +15,13 @@ enum FlutterFormDateTimeType {
class FlutterFormInputDateTime extends StatelessWidget {
const FlutterFormInputDateTime({
this.decoration,
this.style,
Key? key,
this.label,
this.showIcon = true,
required this.inputType,
required this.dateFormat,
this.decoration,
this.style,
super.key,
this.label,
this.showIcon = true,
this.firstDate,
this.lastDate,
this.initialDate,
@ -36,9 +36,7 @@ class FlutterFormInputDateTime extends StatelessWidget {
this.timePickerEntryMode = TimePickerEntryMode.dial,
this.enabled = true,
this.onTapEnabled = true,
}) : super(
key: key,
);
});
final TextStyle? style;
final InputDecoration? decoration;
final Widget? label;
@ -61,8 +59,7 @@ class FlutterFormInputDateTime extends StatelessWidget {
final bool onTapEnabled;
@override
Widget build(BuildContext context) {
return DateTimeInputField(
Widget build(BuildContext context) => DateTimeInputField(
style: style,
decoration: decoration,
autovalidateMode: autovalidateMode,
@ -85,4 +82,3 @@ class FlutterFormInputDateTime extends StatelessWidget {
onTapEnabled: onTapEnabled,
);
}
}

View file

@ -10,16 +10,18 @@ import 'package:intl/intl.dart';
class DateTimeInputField extends StatefulWidget {
const DateTimeInputField({
this.decoration,
Key? key,
required this.inputType,
required this.autovalidateMode,
this.label,
this.showIcon = true,
this.icon,
required this.dateFormat,
required this.firstDate,
required this.lastDate,
required this.timePickerEntryMode,
required this.style,
this.decoration,
super.key,
this.label,
this.showIcon = true,
this.icon,
this.initialDate,
this.initialTime,
this.initialDateTimeRange,
@ -27,13 +29,9 @@ class DateTimeInputField extends StatefulWidget {
this.onChanged,
this.onSaved,
this.validator,
required this.timePickerEntryMode,
required this.style,
this.enabled = true,
this.onTapEnabled = true,
}) : super(
key: key,
);
});
final TextStyle? style;
final InputDecoration? decoration;
final AutovalidateMode autovalidateMode;
@ -93,12 +91,14 @@ class _DateInputFieldState extends State<DateTimeInputField> {
@override
Widget build(BuildContext context) {
Future<String> getInputFromUser(FlutterFormDateTimeType inputType,
[DateFormat? dateFormat]) async {
String userInput = '';
Future<String> getInputFromUser(
FlutterFormDateTimeType inputType, [
DateFormat? dateFormat,
]) async {
var userInput = '';
switch (inputType) {
case FlutterFormDateTimeType.date:
DateTime? unformatted = await showDatePicker(
var unformatted = await showDatePicker(
initialDate: initialDate,
context: context,
firstDate: firstDate,
@ -112,7 +112,7 @@ class _DateInputFieldState extends State<DateTimeInputField> {
await getInputFromUser(FlutterFormDateTimeType.date)
.then((value) async {
if (value != '') {
String secondInput =
var secondInput =
await getInputFromUser(FlutterFormDateTimeType.time);
if (secondInput != '') {
var date = widget.dateFormat.parse(value);
@ -120,49 +120,51 @@ class _DateInputFieldState extends State<DateTimeInputField> {
? dateFormat.parse('01 01 1970 $secondInput')
: DateFormat('dd MM yyyy HH:mm')
.parse('01 01 1970 $secondInput');
userInput = widget.dateFormat.format(DateTime(
userInput = widget.dateFormat.format(
DateTime(
date.year,
date.month,
date.day,
time.hour,
time.minute,
));
),
);
}
}
});
break;
case FlutterFormDateTimeType.range:
if (context.mounted) {
userInput = (await showDateRangePicker(
userInput = await showDateRangePicker(
context: context,
firstDate: firstDate,
lastDate: lastDate,
initialDateRange: initialDateRange)
.then((value) {
return value != null
? '${widget.dateFormat.format(value.start)} - ${widget.dateFormat.format(value.end)}'
: '';
}))
.toString();
initialDateRange: initialDateRange,
).then(
(value) => value != null
? '${widget.dateFormat.format(value.start)} -'
'${widget.dateFormat.format(value.end)}'
: '',
);
}
break;
case FlutterFormDateTimeType.time:
if (context.mounted) {
userInput = await showTimePicker(
initialEntryMode: widget.timePickerEntryMode,
builder: (BuildContext context, Widget? child) {
return MediaQuery(
builder: (BuildContext context, Widget? child) => MediaQuery(
data: MediaQuery.of(context)
.copyWith(alwaysUse24HourFormat: true),
child: child!,
);
},
),
context: context,
initialTime: initialTimeOfDay,
).then((value) => value == null
).then(
(value) => value == null
? ''
: MaterialLocalizations.of(context)
.formatTimeOfDay(value, alwaysUse24HourFormat: true));
.formatTimeOfDay(value, alwaysUse24HourFormat: true),
);
}
}
return userInput;
@ -178,7 +180,7 @@ class _DateInputFieldState extends State<DateTimeInputField> {
onSaved: (value) => widget.onSaved?.call(value),
onTap: widget.onTapEnabled
? () async {
String userInput = await getInputFromUser(
var userInput = await getInputFromUser(
widget.inputType,
DateFormat('dd MM yyyy HH:mm'),
);
@ -194,7 +196,7 @@ class _DateInputFieldState extends State<DateTimeInputField> {
InputDecoration(
suffixIcon: widget.showIcon ? Icon(widget.icon) : null,
focusColor: Theme.of(context).primaryColor,
label: widget.label ?? const Text("Date"),
label: widget.label ?? const Text('Date'),
),
enabled: widget.enabled,
);

View file

@ -1,8 +1,8 @@
export 'carousel/carousel.dart';
export 'date_picker/date_picker.dart';
export 'number_picker/number_picker.dart';
export 'text/password.dart';
export 'text/plain_text.dart';
export 'scroll_picker/scroll_picker.dart';
export 'slider/slider.dart';
export 'switch/switch.dart';
export 'date_picker/date_picker.dart';
export 'scroll_picker/scroll_picker.dart';
export 'text/password.dart';
export 'text/plain_text.dart';

View file

@ -6,9 +6,30 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'number_picker_field.dart';
import 'package:flutter_input_library/src/inputs/number_picker/number_picker_field.dart';
class DecimalNumberPicker extends StatelessWidget {
const DecimalNumberPicker({
required this.minValue,
required this.maxValue,
required this.value,
required this.onChanged,
super.key,
this.itemCount = 3,
this.itemHeight = 50,
this.itemWidth = 100,
this.axis = Axis.vertical,
this.textStyle,
this.selectedTextStyle,
this.haptics = false,
this.decimalPlaces = 1,
this.integerTextMapper,
this.decimalTextMapper,
this.integerZeroPad = false,
this.integerDecoration,
this.decimalDecoration,
}) : assert(minValue <= value, 'value must be greater than minValue'),
assert(value <= maxValue, 'value must be less than maxValue');
final int minValue;
final int maxValue;
final double value;
@ -24,47 +45,26 @@ class DecimalNumberPicker extends StatelessWidget {
final TextMapper? decimalTextMapper;
final bool integerZeroPad;
/// Decoration to apply to central box where the selected integer value is placed
/// Decoration to apply to central box where the selected integer value
/// is placed
final Decoration? integerDecoration;
/// Decoration to apply to central box where the selected decimal value is placed
/// Decoration to apply to central box where the selected decimal value
/// is placed
final Decoration? decimalDecoration;
/// Inidcates how many decimal places to show
/// e.g. 0=>[1,2,3...], 1=>[1.0, 1.1, 1.2...] 2=>[1.00, 1.01, 1.02...]
final int decimalPlaces;
const DecimalNumberPicker({
Key? key,
required this.minValue,
required this.maxValue,
required this.value,
required this.onChanged,
this.itemCount = 3,
this.itemHeight = 50,
this.itemWidth = 100,
this.axis = Axis.vertical,
this.textStyle,
this.selectedTextStyle,
this.haptics = false,
this.decimalPlaces = 1,
this.integerTextMapper,
this.decimalTextMapper,
this.integerZeroPad = false,
this.integerDecoration,
this.decimalDecoration,
}) : assert(minValue <= value),
assert(value <= maxValue),
super(key: key);
@override
Widget build(BuildContext context) {
final isMax = value.floor() == maxValue;
final decimalValue = isMax
var isMax = value.floor() == maxValue;
var decimalValue = isMax
? 0
: ((value - value.floorToDouble()) * math.pow(10, decimalPlaces))
.round();
final doubleMaxValue = isMax ? 0 : math.pow(10, decimalPlaces).toInt() - 1;
var doubleMaxValue = isMax ? 0 : math.pow(10, decimalPlaces).toInt() - 1;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -102,15 +102,15 @@ class DecimalNumberPicker extends StatelessWidget {
}
void _onIntChanged(int intValue) {
final newValue =
(value - value.floor() + intValue).clamp(minValue, maxValue);
var newValue = (value - value.floor() + intValue).clamp(minValue, maxValue);
onChanged(newValue.toDouble());
}
void _onDoubleChanged(int doubleValue) {
final decimalPart = double.parse(
var decimalPart = double.parse(
(doubleValue * math.pow(10, -decimalPlaces))
.toStringAsFixed(decimalPlaces));
.toStringAsFixed(decimalPlaces),
);
onChanged(value.floor() + decimalPart);
}
}

View file

@ -2,8 +2,6 @@
//
// SPDX-License-Identifier: BSD-3-Clause
library infinite_listview;
import 'dart:math' as math;
import 'package:flutter/gestures.dart' show DragStartBehavior;
@ -18,14 +16,14 @@ import 'package:flutter/widgets.dart';
class InfiniteListView extends StatefulWidget {
/// See [ListView.builder]
const InfiniteListView.builder({
Key? key,
required this.itemBuilder,
super.key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.physics,
this.padding,
this.itemExtent,
required this.itemBuilder,
this.itemCount,
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
@ -36,19 +34,18 @@ class InfiniteListView extends StatefulWidget {
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
}) : separatorBuilder = null,
super(key: key);
}) : separatorBuilder = null;
/// See [ListView.separated]
const InfiniteListView.separated({
Key? key,
required this.itemBuilder,
required this.separatorBuilder,
super.key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.physics,
this.padding,
required this.itemBuilder,
required this.separatorBuilder,
this.itemCount,
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
@ -59,8 +56,7 @@ class InfiniteListView extends StatefulWidget {
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
}) : itemExtent = null,
super(key: key);
}) : itemExtent = null;
/// See: [ScrollView.scrollDirection]
final Axis scrollDirection;
@ -153,20 +149,20 @@ class InfiniteListViewState extends State<InfiniteListView> {
@override
Widget build(BuildContext context) {
final List<Widget> slivers = _buildSlivers(context, negative: false);
final List<Widget> negativeSlivers = _buildSlivers(context, negative: true);
final AxisDirection axisDirection = _getDirection(context);
final scrollPhysics =
widget.physics ?? const AlwaysScrollableScrollPhysics();
var slivers = _buildSlivers(context, negative: false);
var negativeSlivers = _buildSlivers(context, negative: true);
var axisDirection = _getDirection(context);
var scrollPhysics = widget.physics ?? const AlwaysScrollableScrollPhysics();
return Scrollable(
axisDirection: axisDirection,
controller: _effectiveController,
physics: scrollPhysics,
viewportBuilder: (BuildContext context, ViewportOffset offset) {
return Builder(builder: (BuildContext context) {
/// Build negative [ScrollPosition] for the negative scrolling [Viewport].
final state = Scrollable.of(context);
final negativeOffset = _InfiniteScrollPosition(
viewportBuilder: (BuildContext context, ViewportOffset offset) => Builder(
builder: (BuildContext context) {
/// Build negative [ScrollPosition] for the negative scrolling
/// [Viewport].
var state = Scrollable.of(context);
var negativeOffset = _InfiniteScrollPosition(
physics: scrollPhysics,
context: state,
initialPixels: -offset.pixels,
@ -174,12 +170,14 @@ class InfiniteListViewState extends State<InfiniteListView> {
negativeScroll: true,
);
/// Keep the negative scrolling [Viewport] positioned to the [ScrollPosition].
/// Keep the negative scrolling [Viewport] positioned to the
/// [ScrollPosition].
offset.addListener(() {
negativeOffset._forceNegativePixels(offset.pixels);
});
/// Stack the two [Viewport]s on top of each other so they move in sync.
/// Stack the two [Viewport]s on top of each other so they move in
/// sync.
return Stack(
children: <Widget>[
Viewport(
@ -198,19 +196,21 @@ class InfiniteListViewState extends State<InfiniteListView> {
),
],
);
});
},
),
);
}
AxisDirection _getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality(
context, widget.scrollDirection, widget.reverse);
}
AxisDirection _getDirection(BuildContext context) =>
getAxisDirectionFromAxisReverseAndDirectionality(
context,
widget.scrollDirection,
widget.reverse,
);
List<Widget> _buildSlivers(BuildContext context, {bool negative = false}) {
final itemExtent = widget.itemExtent;
final padding = widget.padding ?? EdgeInsets.zero;
var itemExtent = widget.itemExtent;
var padding = widget.padding ?? EdgeInsets.zero;
return <Widget>[
SliverPadding(
padding: negative
@ -228,16 +228,16 @@ class InfiniteListViewState extends State<InfiniteListView> {
? negativeChildrenDelegate
: positiveChildrenDelegate,
),
)
),
];
}
SliverChildDelegate get negativeChildrenDelegate {
return SliverChildBuilderDelegate(
SliverChildDelegate get negativeChildrenDelegate =>
SliverChildBuilderDelegate(
(BuildContext context, int index) {
final separatorBuilder = widget.separatorBuilder;
var separatorBuilder = widget.separatorBuilder;
if (separatorBuilder != null) {
final itemIndex = (-1 - index) ~/ 2;
var itemIndex = (-1 - index) ~/ 2;
return index.isOdd
? widget.itemBuilder(context, itemIndex)
: separatorBuilder(context, itemIndex);
@ -249,15 +249,14 @@ class InfiniteListViewState extends State<InfiniteListView> {
addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
addRepaintBoundaries: widget.addRepaintBoundaries,
);
}
SliverChildDelegate get positiveChildrenDelegate {
final separatorBuilder = widget.separatorBuilder;
final itemCount = widget.itemCount;
var separatorBuilder = widget.separatorBuilder;
var itemCount = widget.itemCount;
return SliverChildBuilderDelegate(
(separatorBuilder != null)
? (BuildContext context, int index) {
final itemIndex = index ~/ 2;
var itemIndex = index ~/ 2;
return index.isEven
? widget.itemBuilder(context, itemIndex)
: separatorBuilder(context, itemIndex);
@ -276,40 +275,63 @@ class InfiniteListViewState extends State<InfiniteListView> {
super.debugFillProperties(properties);
properties
.add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection));
properties.add(FlagProperty('reverse',
value: widget.reverse, ifTrue: 'reversed', showName: true));
properties.add(DiagnosticsProperty<ScrollController>(
'controller', widget.controller,
showName: false, defaultValue: null));
properties.add(DiagnosticsProperty<ScrollPhysics>('physics', widget.physics,
showName: false, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>(
'padding', widget.padding,
defaultValue: null));
properties.add(
DoubleProperty('itemExtent', widget.itemExtent, defaultValue: null));
FlagProperty(
'reverse',
value: widget.reverse,
ifTrue: 'reversed',
showName: true,
),
);
properties.add(
DoubleProperty('cacheExtent', widget.cacheExtent, defaultValue: null));
DiagnosticsProperty<ScrollController>(
'controller',
widget.controller,
showName: false,
defaultValue: null,
),
);
properties.add(
DiagnosticsProperty<ScrollPhysics>(
'physics',
widget.physics,
showName: false,
defaultValue: null,
),
);
properties.add(
DiagnosticsProperty<EdgeInsetsGeometry>(
'padding',
widget.padding,
defaultValue: null,
),
);
properties.add(
DoubleProperty('itemExtent', widget.itemExtent, defaultValue: null),
);
properties.add(
DoubleProperty('cacheExtent', widget.cacheExtent, defaultValue: null),
);
}
}
/// Same as a [ScrollController] except it provides [ScrollPosition] objects with infinite bounds.
/// Same as a [ScrollController] except it provides [ScrollPosition] objects
/// with infinite bounds.
class InfiniteScrollController extends ScrollController {
/// Creates a new [InfiniteScrollController]
InfiniteScrollController({
double initialScrollOffset = 0.0,
bool keepScrollOffset = true,
String? debugLabel,
}) : super(
initialScrollOffset: initialScrollOffset,
keepScrollOffset: keepScrollOffset,
debugLabel: debugLabel,
);
super.initialScrollOffset,
super.keepScrollOffset,
super.debugLabel,
});
@override
ScrollPosition createScrollPosition(ScrollPhysics physics,
ScrollContext context, ScrollPosition? oldPosition) {
return _InfiniteScrollPosition(
ScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition? oldPosition,
) =>
_InfiniteScrollPosition(
physics: physics,
context: context,
initialPixels: initialScrollOffset,
@ -318,25 +340,17 @@ class InfiniteScrollController extends ScrollController {
debugLabel: debugLabel,
);
}
}
class _InfiniteScrollPosition extends ScrollPositionWithSingleContext {
_InfiniteScrollPosition({
required ScrollPhysics physics,
required ScrollContext context,
double? initialPixels = 0.0,
bool keepScrollOffset = true,
ScrollPosition? oldPosition,
String? debugLabel,
required super.physics,
required super.context,
super.initialPixels,
super.keepScrollOffset,
super.oldPosition,
super.debugLabel,
this.negativeScroll = false,
}) : super(
physics: physics,
context: context,
initialPixels: initialPixels,
keepScrollOffset: keepScrollOffset,
oldPosition: oldPosition,
debugLabel: debugLabel,
);
});
final bool negativeScroll;

View file

@ -4,22 +4,18 @@
import 'package:flutter/material.dart';
import 'number_picker_field.dart';
import 'package:flutter_input_library/src/inputs/number_picker/number_picker_field.dart';
class FlutterFormInputNumberPicker extends StatelessWidget {
const FlutterFormInputNumberPicker({
Key? key,
Widget? label,
super.key,
this.minValue = 0,
this.maxValue = 100,
this.onSaved,
this.onChanged,
this.initialValue,
this.validator,
}) : assert(minValue < maxValue),
super(
key: key,
);
}) : assert(minValue < maxValue, 'minValue must be less than maxValue');
final int minValue;
final int maxValue;
@ -29,8 +25,7 @@ class FlutterFormInputNumberPicker extends StatelessWidget {
final Function(int?)? onChanged;
@override
Widget build(BuildContext context) {
return NumberPickerFormField(
Widget build(BuildContext context) => NumberPickerFormField(
minValue: minValue,
maxValue: maxValue,
onSaved: (value) => onSaved?.call(value),
@ -39,25 +34,18 @@ class FlutterFormInputNumberPicker extends StatelessWidget {
initialValue: initialValue ?? 0,
);
}
}
class NumberPickerFormField extends FormField<int> {
NumberPickerFormField({
Key? key,
required FormFieldSetter<int> onSaved,
required FormFieldValidator<int> validator,
required FormFieldSetter<int> super.onSaved,
required FormFieldValidator<int> super.validator,
super.key,
void Function(int value)? onChanged,
int initialValue = 0,
bool autovalidate = false,
int super.initialValue = 0,
int minValue = 0,
int maxValue = 100,
}) : super(
key: key,
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<int> state) {
return NumberPicker(
builder: (FormFieldState<int> state) => NumberPicker(
minValue: minValue,
maxValue: maxValue,
value: initialValue,
@ -68,6 +56,6 @@ class NumberPickerFormField extends FormField<int> {
},
itemHeight: 35,
itemCount: 5,
),
);
});
}

View file

@ -2,13 +2,36 @@
//
// SPDX-License-Identifier: BSD-3-Clause
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'infinite_listview.dart';
import 'package:flutter_input_library/src/inputs/number_picker/infinite_listview.dart';
typedef TextMapper = String Function(String numberText);
class NumberPicker extends StatefulWidget {
const NumberPicker({
required this.minValue,
required this.maxValue,
required this.value,
required this.onChanged,
super.key,
this.itemCount = 3,
this.step = 1,
this.itemHeight = 50,
this.itemWidth = 100,
this.axis = Axis.vertical,
this.textStyle,
this.selectedTextStyle,
this.haptics = false,
this.decoration,
this.zeroPad = false,
this.textMapper,
this.infiniteLoop = false,
}) : assert(minValue <= value, 'value must be greater than minValue'),
assert(value <= maxValue, 'value must be less than maxValue');
/// Min value user can pick
final int minValue;
@ -43,7 +66,8 @@ class NumberPicker extends StatefulWidget {
/// Style of non-selected numbers. If null, it uses Theme's bodyText2
final TextStyle? textStyle;
/// Style of non-selected numbers. If null, it uses Theme's headline5 with accentColor
/// Style of non-selected numbers. If null, it uses Theme's headline5 with
/// accentColor
final TextStyle? selectedTextStyle;
/// Whether to trigger haptic pulses or not
@ -60,28 +84,6 @@ class NumberPicker extends StatefulWidget {
final bool infiniteLoop;
const NumberPicker({
Key? key,
required this.minValue,
required this.maxValue,
required this.value,
required this.onChanged,
this.itemCount = 3,
this.step = 1,
this.itemHeight = 50,
this.itemWidth = 100,
this.axis = Axis.vertical,
this.textStyle,
this.selectedTextStyle,
this.haptics = false,
this.decoration,
this.zeroPad = false,
this.textMapper,
this.infiniteLoop = false,
}) : assert(minValue <= value),
assert(value <= maxValue),
super(key: key);
@override
NumberPickerState createState() => NumberPickerState();
}
@ -97,7 +99,7 @@ class NumberPickerState extends State<NumberPicker> {
value = widget.value;
final initialOffset = (value - widget.minValue) ~/ widget.step * itemExtent;
var initialOffset = (value - widget.minValue) ~/ widget.step * itemExtent;
if (widget.infiniteLoop) {
_scrollController =
InfiniteScrollController(initialScrollOffset: initialOffset);
@ -107,14 +109,14 @@ class NumberPickerState extends State<NumberPicker> {
_scrollController.addListener(_scrollListener);
}
void _scrollListener() {
Future<void> _scrollListener() async {
var indexOfMiddleElement = (_scrollController.offset / itemExtent).round();
if (widget.infiniteLoop) {
indexOfMiddleElement %= itemCount;
} else {
indexOfMiddleElement = indexOfMiddleElement.clamp(0, itemCount - 1);
}
final intValueInTheMiddle =
var intValueInTheMiddle =
_intValueFromIndex(indexOfMiddleElement + additionalItemsOnEachSide);
if (value != intValueInTheMiddle) {
@ -124,12 +126,12 @@ class NumberPickerState extends State<NumberPicker> {
widget.onChanged(intValueInTheMiddle);
if (widget.haptics) {
HapticFeedback.selectionClick();
await HapticFeedback.selectionClick();
}
}
Future.delayed(
const Duration(milliseconds: 100),
() => _maybeCenterValue(),
_maybeCenterValue,
);
}
@ -137,7 +139,7 @@ class NumberPickerState extends State<NumberPicker> {
void didUpdateWidget(NumberPicker oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.value != value) {
_maybeCenterValue();
unawaited(_maybeCenterValue());
}
}
@ -159,8 +161,7 @@ class NumberPickerState extends State<NumberPicker> {
int get additionalItemsOnEachSide => (widget.itemCount - 1) ~/ 2;
@override
Widget build(BuildContext context) {
return SizedBox(
Widget build(BuildContext context) => SizedBox(
width: widget.axis == Axis.vertical
? widget.itemWidth
: widget.itemCount * widget.itemWidth,
@ -170,12 +171,13 @@ class NumberPickerState extends State<NumberPicker> {
child: NotificationListener<ScrollEndNotification>(
onNotification: (not) {
if (not.dragDetails?.primaryVelocity == 0) {
Future.microtask(() => _maybeCenterValue());
Future.microtask(_maybeCenterValue);
}
return true;
},
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
behavior:
ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: Stack(
children: [
Center(
@ -215,22 +217,21 @@ class NumberPickerState extends State<NumberPicker> {
),
),
);
}
Widget _itemBuilder(BuildContext context, int index) {
final themeData = Theme.of(context);
final defaultStyle = widget.textStyle ?? themeData.textTheme.bodyMedium;
final selectedStyle = widget.selectedTextStyle ??
var themeData = Theme.of(context);
var defaultStyle = widget.textStyle ?? themeData.textTheme.bodyMedium;
var selectedStyle = widget.selectedTextStyle ??
themeData.textTheme.headlineSmall
?.copyWith(color: themeData.highlightColor);
final valueFromIndex = _intValueFromIndex(index % itemCount);
final isExtra = !widget.infiniteLoop &&
var valueFromIndex = _intValueFromIndex(index % itemCount);
var isExtra = !widget.infiniteLoop &&
(index < additionalItemsOnEachSide ||
index >= listItemsCount - additionalItemsOnEachSide);
final itemStyle = valueFromIndex == value ? selectedStyle : defaultStyle;
var itemStyle = valueFromIndex == value ? selectedStyle : defaultStyle;
final child = isExtra
var child = isExtra
? const SizedBox.shrink()
: Text(
_getDisplayedValue(valueFromIndex),
@ -246,7 +247,7 @@ class NumberPickerState extends State<NumberPicker> {
}
String _getDisplayedValue(int value) {
final text = widget.zeroPad
var text = widget.zeroPad
? value.toString().padLeft(widget.maxValue.toString().length, '0')
: value.toString();
if (widget.textMapper != null) {
@ -257,21 +258,22 @@ class NumberPickerState extends State<NumberPicker> {
}
int _intValueFromIndex(int index) {
index -= additionalItemsOnEachSide;
index %= itemCount;
return widget.minValue + index * widget.step;
var newIndex = index;
newIndex -= additionalItemsOnEachSide;
newIndex %= itemCount;
return widget.minValue + newIndex * widget.step;
}
void _maybeCenterValue() {
Future<void> _maybeCenterValue() async {
if (_scrollController.hasClients && !isScrolling) {
int diff = value - widget.minValue;
int index = diff ~/ widget.step;
var diff = value - widget.minValue;
var index = diff ~/ widget.step;
if (widget.infiniteLoop) {
final offset = _scrollController.offset + 0.5 * itemExtent;
final cycles = (offset / (itemCount * itemExtent)).floor();
var offset = _scrollController.offset + 0.5 * itemExtent;
var cycles = (offset / (itemCount * itemExtent)).floor();
index += cycles * itemCount;
}
_scrollController.animateTo(
await _scrollController.animateTo(
index * itemExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
@ -281,20 +283,17 @@ class NumberPickerState extends State<NumberPicker> {
}
class _NumberPickerSelectedItemDecoration extends StatelessWidget {
const _NumberPickerSelectedItemDecoration({
required this.axis,
required this.itemExtent,
required this.decoration,
});
final Axis axis;
final double itemExtent;
final Decoration? decoration;
const _NumberPickerSelectedItemDecoration({
Key? key,
required this.axis,
required this.itemExtent,
required this.decoration,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
Widget build(BuildContext context) => Center(
child: IgnorePointer(
child: Container(
width: isVertical ? double.infinity : itemExtent,
@ -303,7 +302,6 @@ class _NumberPickerSelectedItemDecoration extends StatelessWidget {
),
),
);
}
bool get isVertical => axis == Axis.vertical;
}

View file

@ -1,3 +1,3 @@
export 'scroll_picker_widget.dart';
export 'scroll_picker_decoration.dart';
export 'scroll_picker_type_extensions.dart';
export 'scroll_picker_widget.dart';

View file

@ -22,57 +22,87 @@ class ScrollPickerDecoration {
});
/// Ability to provide your own builder for the scroll items
// ignore: avoid_annotating_with_dynamic
final Widget Function(BuildContext context, int index, dynamic value)?
scrollItemBuilder;
/// Override the standard highlight widget. (Grey container which is placed behind the selected item).
/// Override the standard highlight widget. (Grey container which is placed
/// behind the selected item).
final Widget? highlightWidget;
/// Textstyle of the scroll items. Will be overridden if the [scrollItemBuilder] is set.
/// Textstyle of the scroll items. Will be overridden if the
/// [scrollItemBuilder] is set.
final TextStyle? scrollItemTextStyle;
/// Amount of visible items in the scroll wheel. Changing this changes the height of the widget.
/// Amount of visible items in the scroll wheel. Changing this changes the
/// height of the widget.
final int numberOfVisibleItems;
/// Height of each item in the scrollwheel. Chaging this changes the height of the widget.
/// Height of each item in the scrollwheel. Chaging this changes the height
/// of the widget.
final double itemHeight;
/// A ratio between the diameter of the cylinder and the viewport's size in the main axis.
/// A ratio between the diameter of the cylinder and the viewport's size in
/// the main axis.
///
/// A value of 1 means the cylinder has the same diameter as the viewport's size.
/// A value of 1 means the cylinder has the same diameter as the viewport's
/// size.
///
/// A value smaller than 1 means items at the edges of the cylinder are entirely contained inside the viewport.
/// A value smaller than 1 means items at the edges of the cylinder are
/// entirely contained inside the viewport.
///
/// A value larger than 1 means angles less than ±[pi] / 2 from the center of the cylinder are visible.
/// A value larger than 1 means angles less than ±[pi] / 2 from the center
/// of the cylinder are visible.
///
/// The same number of children will be visible in the viewport regardless of the [diameterRatio]. The number of children visible is based on the viewport's length along the main axis divided by the children's [itemExtent]. Then the children are evenly distributed along the visible angles up to ±[pi] / 2.
/// The same number of children will be visible in the viewport regardless of
/// the [diameterRatio]. The number of children visible is based on the
/// viewport's length along the main axis divided by the children's
/// [itemExtent]. Then the children are evenly distributed along the visible angles up to ±[pi] / 2.
///
/// Just as it's impossible to stretch a paper to cover the an entire half of a cylinder's surface where the cylinder has the same diameter as the paper's length, choosing a [diameterRatio] smaller than [pi] will leave same gaps between the children.
/// Just as it's impossible to stretch a paper to cover the an entire half of
/// a cylinder's surface where the cylinder has the same diameter as the
/// paper's length, choosing a [diameterRatio] smaller than [pi] will leave
/// same gaps between the children.
///
/// Defaults to an arbitrary but aesthetically reasonable number of 2.0.
///
/// Must not be null and must be positive.
final double diameterRatio;
/// Size of each child in the main axis. Must not be null and must be positive.
/// Size of each child in the main axis. Must not be null and
/// must be positive.
final double perspective;
/// The opacity value that will be applied to the wheel that appears below and above the magnifier.
/// The opacity value that will be applied to the wheel that appears below
/// and above the magnifier.
///
/// The default value is 1.0, which will not change anything.
///
/// Must be greater than or equal to 0, and less than or equal to 1.
final double overAndUnderCenterOpacity;
/// How much the wheel is horizontally off-center, as a fraction of its width. This property creates the visual effect of looking at a vertical wheel from its side where its vanishing points at the edge curves to one side instead of looking at the wheel head-on.
/// How much the wheel is horizontally off-center, as a fraction of its width.
/// This property creates the visual effect of looking at a vertical wheel
/// from its side where its vanishing points at the edge curves to one side
/// instead of looking at the wheel head-on.
///
/// The value is horizontal distance between the wheel's center and the vertical vanishing line at the edges of the wheel, represented as a fraction of the wheel's width.
/// The value is horizontal distance between the wheel's center and the
/// vertical vanishing line at the edges of the wheel, represented as a
/// fraction of the wheel's width.
///
/// The value 0.0 means the wheel is looked at head-on and its vanishing line runs through the center of the wheel. Negative values means moving the wheel to the left of the observer, thus the edges curve to the right. Positive values means moving the wheel to the right of the observer, thus the edges curve to the left.
/// The value 0.0 means the wheel is looked at head-on and its vanishing line
/// runs through the center of the wheel. Negative values means moving the
/// wheel to the left of the observer, thus the edges curve to the right.
/// Positive values means moving the wheel to the right of the observer,
/// thus the edges curve to the left.
///
/// The visual effect causes the wheel's edges to curve rather than moving the center. So a value of 0.5 means the edges' vanishing line will touch the wheel's size's left edge.
/// The visual effect causes the wheel's edges to curve rather than moving
/// the center. So a value of 0.5 means the edges' vanishing line will touch
/// the wheel's size's left edge.
///
/// Defaults to 0.0, which means looking at the wheel head-on. The visual effect can be unaesthetic if this value is too far from the range [-0.5, 0.5].
/// Defaults to 0.0, which means looking at the wheel head-on. The visual
/// effect can be unaesthetic if this value is too far from the range
/// [-0.5, 0.5].
final double offAxisFraction;
/// Whether to use the magnifier for the center item of the wheel.
@ -80,18 +110,28 @@ class ScrollPickerDecoration {
/// The zoomed-in rate of the magnifier, if it is used.
///
/// The default value is 1.0, which will not change anything. If the value is > 1.0, the center item will be zoomed in by that rate, and it will also be rendered as flat, not cylindrical like the rest of the list. The item will be zoomed out if magnification < 1.0.
/// The default value is 1.0, which will not change anything. If the value
/// is > 1.0, the center item will be zoomed in by that rate, and it will
/// also be rendered as flat, not cylindrical like the rest of the list.
/// The item will be zoomed out if magnification < 1.0.
///
/// Must be positive.
final double magnification;
/// The angular compactness of the children on the wheel.
///
/// This denotes a ratio of the number of children on the wheel vs the number of children that would fit on a flat list of equivalent size, assuming [diameterRatio] of 1.
/// This denotes a ratio of the number of children on the wheel vs the number
/// of children that would fit on a flat list of equivalent size, assuming
/// [diameterRatio] of 1.
///
/// For instance, if this RenderListWheelViewport has a height of 100px and [itemExtent] is 20px, 5 items would fit on an equivalent flat list. With a [squeeze] of 1, 5 items would also be shown in the RenderListWheelViewport. With a [squeeze] of 2, 10 items would be shown in the RenderListWheelViewport.
/// For instance, if this RenderListWheelViewport has a height of 100px and
/// [itemExtent] is 20px, 5 items would fit on an equivalent flat list. With
/// a [squeeze] of 1, 5 items would also be shown in the
/// RenderListWheelViewport. With a [squeeze] of 2, 10 items would be shown
/// in the RenderListWheelViewport.
///
/// Changing this value will change the number of children built and shown inside the wheel.
/// Changing this value will change the number of children built and shown
/// inside the wheel.
///
/// Must not be null and must be positive.
///
@ -100,8 +140,11 @@ class ScrollPickerDecoration {
/// Whether to paint children inside the viewport only.
///
/// If false, every child will be painted. However the [Scrollable] is still the size of the viewport and detects gestures inside only.
/// If false, every child will be painted. However the [Scrollable] is still
/// the size of the viewport and detects gestures inside only.
///
/// Defaults to false. Must not be null. Cannot be true if [clipBehavior] is not [Clip.none] since children outside the viewport will be clipped, and therefore cannot render children outside the viewport.
/// Defaults to false. Must not be null. Cannot be true if [clipBehavior] is
/// not [Clip.none] since children outside the viewport will be clipped, and
/// therefore cannot render children outside the viewport.
final bool renderChildrenOutsideViewport;
}

View file

@ -58,7 +58,8 @@ class _ScrollPickerState extends State<ScrollPicker> {
if (newIndex != selectedIndex) {
widget.onChanged.call(
(scrollController.offset / widget.decoration.itemHeight).round());
(scrollController.offset / widget.decoration.itemHeight).round(),
);
selectedIndex = newIndex;
}
@ -73,8 +74,7 @@ class _ScrollPickerState extends State<ScrollPicker> {
}
@override
Widget build(BuildContext context) {
return Stack(
Widget build(BuildContext context) => Stack(
children: [
Positioned.fill(
child: Center(
@ -123,4 +123,3 @@ class _ScrollPickerState extends State<ScrollPicker> {
],
);
}
}

View file

@ -28,7 +28,8 @@ enum Month {
}
class TypeUtils {
/// Creates list of Datetime with days. These fall on the respective week days from start to end.
/// Creates list of Datetime with days. These fall on the respective week
/// days from start to end.
List<DateTime> createWeekDays(
WeekDay start,
WeekDay end,
@ -37,8 +38,8 @@ class TypeUtils {
throw ArgumentError('Start month must be before or equal to end month.');
}
List<DateTime> result = [];
for (int i = start.index; i <= end.index; i++) {
var result = <DateTime>[];
for (var i = start.index; i <= end.index; i++) {
result.add(DateTime(2024, 1, WeekDay.values[i].index + 1));
}
@ -51,10 +52,11 @@ class TypeUtils {
throw ArgumentError('Start month must be before or equal to end month.');
}
List<DateTime> result = [];
for (int i = start.index; i <= end.index; i++) {
var result = <DateTime>[];
for (var i = start.index; i <= end.index; i++) {
result.add(
DateTime(year ?? DateTime.now().year, Month.values[i].index + 1, 1));
DateTime(year ?? DateTime.now().year, Month.values[i].index + 1, 1),
);
}
return result;
@ -66,8 +68,8 @@ class TypeUtils {
throw ArgumentError('Start year must be before or equal to year month.');
}
List<DateTime> result = [];
for (int i = 0; i <= end - start; i++) {
var result = <DateTime>[];
for (var i = 0; i <= end - start; i++) {
result.add(DateTime(start + i, 1, 1));
}

View file

@ -14,8 +14,8 @@ class FlutterFormInputScrollPicker<T> extends StatelessWidget {
this.onSaved,
this.onChanged,
this.initialIndex = 0,
Key? key,
}) : super(key: key);
super.key,
});
/// Values that will be shown in the scroll picker.
final List<T> values;
@ -36,8 +36,7 @@ class FlutterFormInputScrollPicker<T> extends StatelessWidget {
final ScrollPickerDecoration decoration;
@override
Widget build(BuildContext context) {
return ScrollPickerFormField<T>(
Widget build(BuildContext context) => ScrollPickerFormField<T>(
values: values,
initialIndex: initialIndex,
decoration: decoration,
@ -46,23 +45,19 @@ class FlutterFormInputScrollPicker<T> extends StatelessWidget {
onChanged: (value) => onChanged?.call(value),
);
}
}
class ScrollPickerFormField<T> extends FormField<T> {
ScrollPickerFormField({
required List<T> values,
int? initialIndex,
required ScrollPickerDecoration decoration,
required FormFieldSetter<T> onSaved,
void Function(T value)? onChanged,
required FormFieldSetter<T> super.onSaved,
required String Function(T) childToString,
Key? key,
int? initialIndex,
void Function(T value)? onChanged,
super.key,
}) : super(
key: key,
onSaved: onSaved,
initialValue: values[initialIndex ?? (values.length / 2).floor()],
builder: (FormFieldState<T> state) {
return ScrollPicker(
builder: (FormFieldState<T> state) => ScrollPicker(
list: values.map((e) => childToString(e)).toList(),
decoration: decoration,
initialIndex: initialIndex,
@ -71,7 +66,6 @@ class ScrollPickerFormField<T> extends FormField<T> {
state.didChange(values[index]);
},
);
},
),
);
}

View file

@ -7,7 +7,7 @@ import 'package:flutter_input_library/src/inputs/slider/slider_field.dart';
class FlutterFormInputSlider extends StatelessWidget {
const FlutterFormInputSlider({
Key? key,
super.key,
this.minValue = 0,
this.maxValue = 100,
this.onSaved,
@ -15,10 +15,7 @@ class FlutterFormInputSlider extends StatelessWidget {
this.initialValue,
this.validator,
this.focusNode,
}) : assert(minValue < maxValue),
super(
key: key,
);
}) : assert(minValue < maxValue, 'minValue must be less than maxValue');
final int minValue;
final int maxValue;
@ -29,8 +26,7 @@ class FlutterFormInputSlider extends StatelessWidget {
final FocusNode? focusNode;
@override
Widget build(BuildContext context) {
return SliderFormField(
Widget build(BuildContext context) => SliderFormField(
onSaved: (value) => onSaved?.call(value),
validator: (value) => validator?.call(value),
onChanged: (value) => onChanged?.call(value),
@ -38,4 +34,3 @@ class FlutterFormInputSlider extends StatelessWidget {
focusNode: focusNode,
);
}
}

View file

@ -7,19 +7,14 @@ import 'package:flutter/material.dart';
/// Creates a slider with the given input parameters
class SliderFormField extends FormField<double> {
SliderFormField({
Key? key,
required FormFieldSetter<double> onSaved,
required FormFieldValidator<double> validator,
required FormFieldSetter<double> super.onSaved,
required FormFieldValidator<double> super.validator,
super.key,
void Function(double value)? onChanged,
FocusNode? focusNode,
double initialValue = 0.5,
double super.initialValue = 0.5,
}) : super(
key: key,
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<double> state) {
return Slider(
builder: (FormFieldState<double> state) => Slider(
value: state.value ?? initialValue,
focusNode: focusNode,
onChanged: (double value) {
@ -27,6 +22,6 @@ class SliderFormField extends FormField<double> {
state.didChange(value);
},
),
);
});
}

View file

@ -2,11 +2,22 @@
//
// SPDX-License-Identifier: BSD-3-Clause
// ignore_for_file: avoid_positional_boolean_parameters
import 'package:flutter/material.dart';
import 'package:flutter_input_library/src/inputs/switch/switch_field.dart';
class FlutterFormInputSwitch extends StatelessWidget {
const FlutterFormInputSwitch({
super.key,
this.label,
this.onSaved,
this.validator,
this.onChanged,
this.focusNode,
this.initialValue = false,
});
final Widget? label;
final Function(bool?)? onSaved;
final String? Function(bool?)? validator;
@ -14,21 +25,8 @@ class FlutterFormInputSwitch extends StatelessWidget {
final bool? initialValue;
final FocusNode? focusNode;
const FlutterFormInputSwitch({
Key? key,
this.label,
this.onSaved,
this.validator,
this.onChanged,
this.focusNode,
this.initialValue = false,
}) : super(
key: key,
);
@override
Widget build(BuildContext context) {
return SwitchFormField(
Widget build(BuildContext context) => SwitchFormField(
onSaved: (value) => onSaved?.call(value),
onChanged: (value) => onChanged?.call(value),
validator: (value) => validator?.call(value),
@ -36,4 +34,3 @@ class FlutterFormInputSwitch extends StatelessWidget {
focusNode: focusNode,
);
}
}

View file

@ -6,32 +6,27 @@ import 'package:flutter/material.dart';
class SwitchFormField extends FormField<bool> {
SwitchFormField({
Key? key,
required FormFieldSetter<bool> onSaved,
required FormFieldValidator<bool> validator,
required FormFieldSetter<bool> super.onSaved,
required FormFieldValidator<bool> super.validator,
super.key,
FocusNode? focusNode,
bool initialValue = false,
bool autovalidate = false,
bool super.initialValue = false,
// ignore: avoid_positional_boolean_parameters
void Function(bool? value)? onChanged,
}) : super(
key: key,
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<bool> state) {
return SwitchWidget(
builder: (FormFieldState<bool> state) => SwitchWidget(
initialValue: initialValue,
state: state,
focusNode: focusNode,
onChanged: onChanged,
),
);
});
}
class SwitchWidget extends StatefulWidget {
const SwitchWidget({
this.initialValue = false,
required this.state,
this.initialValue = false,
this.onChanged,
this.focusNode,
super.key,
@ -40,6 +35,7 @@ class SwitchWidget extends StatefulWidget {
final bool initialValue;
final FormFieldState<bool> state;
final FocusNode? focusNode;
// ignore: avoid_positional_boolean_parameters
final void Function(bool? value)? onChanged;
@override
@ -50,8 +46,7 @@ class _SwitchWidgetState extends State<SwitchWidget> {
late bool value = widget.initialValue;
@override
Widget build(BuildContext context) {
return Switch(
Widget build(BuildContext context) => Switch(
value: value,
focusNode: widget.focusNode,
onChanged: (bool value) {
@ -65,4 +60,3 @@ class _SwitchWidgetState extends State<SwitchWidget> {
},
);
}
}

View file

@ -5,23 +5,12 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// Generates a [TextFormField] for passwords. It requires a [FlutterFormInputController]
/// as the [controller] parameter and an optional [Widget] as [label]
/// Generates a [TextFormField] for passwords. It requires a
/// [FlutterFormInputController] as the [controller] parameter and an
/// optional [Widget] as [label]
class FlutterFormInputPassword extends StatefulWidget {
final Widget? label;
final FocusNode? focusNode;
final TextStyle? style;
final String? initialValue;
final List<TextInputFormatter>? inputFormatters;
final Function(String?)? onSaved;
final String? Function(String?)? validator;
final Function(String?)? onChanged;
final Function(String?)? onFieldSubmitted;
final bool enabled;
final InputDecoration? decoration;
const FlutterFormInputPassword({
Key? key,
super.key,
this.label,
this.focusNode,
this.style,
@ -33,7 +22,18 @@ class FlutterFormInputPassword extends StatefulWidget {
this.onFieldSubmitted,
this.enabled = true,
this.decoration,
}) : super(key: key);
});
final Widget? label;
final FocusNode? focusNode;
final TextStyle? style;
final String? initialValue;
final List<TextInputFormatter>? inputFormatters;
final Function(String?)? onSaved;
final String? Function(String?)? validator;
final Function(String?)? onChanged;
final Function(String?)? onFieldSubmitted;
final bool enabled;
final InputDecoration? decoration;
@override
State<FlutterFormInputPassword> createState() => _PasswordTextFieldState();

View file

@ -7,7 +7,7 @@ import 'package:flutter/services.dart';
class FlutterFormInputPlainText extends StatelessWidget {
const FlutterFormInputPlainText({
Key? key,
super.key,
this.label,
this.focusNode,
this.decoration,
@ -27,9 +27,7 @@ class FlutterFormInputPlainText extends StatelessWidget {
this.enabled = true,
this.textCapitalization = TextCapitalization.none,
this.obscureText = false,
}) : super(
key: key,
);
});
final InputDecoration? decoration;
final TextAlignVertical? textAlignVertical;
@ -53,9 +51,9 @@ class FlutterFormInputPlainText extends StatelessWidget {
@override
Widget build(BuildContext context) {
InputDecoration inputDecoration = decoration ??
var inputDecoration = decoration ??
InputDecoration(
label: label ?? const Text("Plain text"),
label: label ?? const Text('Plain text'),
);
return TextFormField(
@ -83,7 +81,7 @@ class FlutterFormInputPlainText extends StatelessWidget {
class FlutterFormInputMultiLine extends StatelessWidget {
const FlutterFormInputMultiLine({
Key? key,
super.key,
this.label,
this.focusNode,
this.hint,
@ -98,7 +96,7 @@ class FlutterFormInputMultiLine extends StatelessWidget {
this.validator,
this.onFieldSubmitted,
this.textCapitalization = TextCapitalization.sentences,
}) : super(key: key);
});
final Widget? label;
final FocusNode? focusNode;
@ -117,8 +115,7 @@ class FlutterFormInputMultiLine extends StatelessWidget {
final TextCapitalization textCapitalization;
@override
Widget build(BuildContext context) {
return Column(
Widget build(BuildContext context) => Column(
children: [
Expanded(
child: FlutterFormInputPlainText(
@ -155,4 +152,3 @@ class FlutterFormInputMultiLine extends StatelessWidget {
],
);
}
}

View file

@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: BSD-3-Clause
class EmailValidator {
mixin EmailValidator {
static bool isValid(String? email) {
if (email == null) {
return false;

View file

@ -1,6 +1,6 @@
name: flutter_input_library
description: A new Flutter package project.
version: 2.7.0
version: 2.7.1
repository: https://github.com/Iconica-Development/flutter_input_library
environment:
@ -15,6 +15,9 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 6.0.0
flutter: