Compare commits

...

13 commits

Author SHA1 Message Date
Freek van de Ven
f32ad75235
Merge pull request #6 from Iconica-Development/doc--improve-documentation
doc: create documentation for files
2024-03-05 15:58:37 +01:00
Vick Top
c7dd72a245 doc: create documentation for files 2024-03-05 15:20:14 +01:00
Freek van de Ven
c26d9de2ca
Merge pull request #5 from Iconica-Development/update-component-documentation-workflow-correct
Add component-documentation.yml correct
2024-02-14 08:01:01 +01:00
Vick Top
8e458231f7 feat(documentation): Create component-documentation.yml workflow file 2024-02-13 13:34:12 +01:00
Vick Top
00450d9e06 chore: Remove old component-documentation.yml 2024-02-13 13:34:12 +01:00
Freek van de Ven
15aacb0809
Merge pull request #4 from Iconica-Development/update-component-documentation-workflow
Add component-documentation.yml
2024-02-12 20:10:31 +01:00
Vick Top
ef4a966e33 feat(documentation): Create component-documentation.yml workflow file 2024-02-12 19:05:36 +01:00
Freek van de Ven
362be1e683
Merge pull request #3 from Iconica-Development/fix/add_ci_linter
fix: added ci and linter
2024-02-07 09:42:50 +01:00
mike doornenbal
2f9b55b3a3 fix: added ci and linter 2024-02-07 09:38:50 +01:00
FlutterJoey
8e58a78e20
Merge pull request #2 from Iconica-Development/hotfix/web-support-flutter-3.10
fix: disable interactive selection for hidden inputs to prevent overw…
2023-05-15 15:04:04 +02:00
88b91c7337 fix: disable interactive selection for hidden inputs to prevent overwrite on change 2023-05-15 14:14:40 +02:00
213559d958 Add BSD-3-Clause license 2022-11-01 08:38:55 +01:00
Brighton van den End
44c838092f
Merge pull request #1 from Iconica-Development/feature/single-character-input
Feature/single character input
2022-10-12 16:43:33 +02:00
15 changed files with 342 additions and 424 deletions

14
.github/workflows/component-ci.yml vendored Normal file
View file

@ -0,0 +1,14 @@
name: Iconica Standard Component CI Workflow
# Workflow Caller version: 2.0.0
on:
pull_request:
workflow_dispatch:
jobs:
call-global-iconica-workflow:
uses: Iconica-Development/.github/.github/workflows/component-ci.yml@master
secrets: inherit
permissions: write-all
with:
subfolder: "." # add optional subfolder to run workflow in

View file

@ -0,0 +1,14 @@
name: Iconica Standard Component Documentation Workflow
# Workflow Caller version: 1.0.0
on:
release:
types: [published]
workflow_dispatch:
jobs:
call-iconica-component-documentation-workflow:
uses: Iconica-Development/.github/.github/workflows/component-documentation.yml@master
secrets: inherit
permissions: write-all

View file

@ -1,3 +1,11 @@
## [0.0.3] - 7 february 2024
- Add CI and linter
## [0.0.2] - 15 may 2023
- Fix text input for web on flutter 3.10
## [0.0.1] - 12 October 2022
- Initial release.

10
LICENSE
View file

@ -1 +1,9 @@
TODO: Add your license here.
Copyright (c) 2022 Iconica, All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,5 +1,9 @@
include: package:flutter_lints/flutter.yaml
include: package:flutter_iconica_analysis/analysis_options.yaml
# Possible to overwrite the rules from the package
analyzer:
errors:
todo: ignore
exclude: [lib/generated_plugin_registrant.dart]
exclude:
linter:
rules:

View file

@ -1,214 +1,9 @@
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
todo: ignore
exclude: [lib/generated_plugin_registrant.dart]
linter:
# https://dart.dev/tools/linter-rules#lints
rules:
# error rules
always_use_package_imports: false
avoid_dynamic_calls: true
avoid_empty_else: true
avoid_print: true
avoid_relative_lib_imports: true
avoid_returning_null_for_future: true
avoid_slow_async_io: true
avoid_type_to_string: true
avoid_types_as_parameter_names: true
avoid_web_libraries_in_flutter: true
cancel_subscriptions: true
close_sinks: true
comment_references: false
control_flow_in_finally: true
diagnostic_describe_all_properties: false
empty_statements: true
hash_and_equals: true
invariant_booleans: true
iterable_contains_unrelated_type: true
list_remove_unrelated_type: true
literal_only_boolean_expressions: true
no_adjacent_strings_in_list: true
no_duplicate_case_values: true
no_logic_in_create_state: true
prefer_relative_imports: false
prefer_void_to_null: true
test_types_in_equals: true
throw_in_finally: true
unnecessary_statements: true
unrelated_type_equality_checks: true
unsafe_html: true
use_build_context_synchronously: true
use_key_in_widget_constructors: true
valid_regexps: true
# style rules
always_declare_return_types: true
always_put_control_body_on_new_line: true
always_put_required_named_parameters_first: true
always_require_non_null_named_parameters: true
always_specify_types: false
annotate_overrides: true
avoid_annotating_with_dynamic: false
avoid_bool_literals_in_conditional_expressions: true
avoid_catches_without_on_clauses: false
avoid_catching_errors: false
avoid_classes_with_only_static_members: true
avoid_double_and_int_checks: true
avoid_equals_and_hash_code_on_mutable_classes: false
avoid_escaping_inner_quotes: false
avoid_field_initializers_in_const_classes: true
avoid_final_parameters: true
avoid_function_literals_in_foreach_calls: true
avoid_implementing_value_types: true
avoid_init_to_null: true
avoid_js_rounded_ints: true
avoid_multiple_declarations_per_line: true
avoid_null_checks_in_equality_operators: true
avoid_positional_boolean_parameters: true
avoid_private_typedef_functions: true
avoid_redundant_argument_values: false
avoid_renaming_method_parameters: true
avoid_return_types_on_setters: true
avoid_returning_null: true
avoid_returning_null_for_void: true
avoid_returning_this: true
avoid_setters_without_getters: true
avoid_shadowing_type_parameters: true
avoid_single_cascade_in_expression_statements: true
avoid_types_on_closure_parameters: false
avoid_unnecessary_containers: false
avoid_unused_constructor_parameters: true
avoid_void_async: true
await_only_futures: true
camel_case_extensions: true
camel_case_types: true
cascade_invocations: true
cast_nullable_to_non_nullable: true
conditional_uri_does_not_exist: true
constant_identifier_names: true
curly_braces_in_flow_control_structures: true
deprecated_consistency: true
directives_ordering: true
do_not_use_environment: true
empty_catches: true
empty_constructor_bodies: true
eol_at_end_of_file: true
exhaustive_cases: true
file_names: true
flutter_style_todos: true
implementation_imports: true
join_return_with_assignment: true
leading_newlines_in_multiline_strings: true
library_names: true
library_prefixes: true
library_private_types_in_public_api: true
lines_longer_than_80_chars: true
missing_whitespace_between_adjacent_strings: true
no_default_cases: true
no_leading_underscores_for_library_prefixes: true
no_leading_underscores_for_local_identifiers: true
no_runtimeType_toString: true
non_constant_identifier_names: true
noop_primitive_operations: true
null_check_on_nullable_type_parameter: true
null_closures: true
omit_local_variable_types: true
one_member_abstracts: true
only_throw_errors: true
overridden_fields: true
package_api_docs: true
package_prefixed_library_names: true
parameter_assignments: true
prefer_adjacent_string_concatenation: true
prefer_asserts_in_initializer_lists: true
prefer_asserts_with_message: true
prefer_collection_literals: true
prefer_conditional_assignment: true
prefer_const_constructors: true
prefer_const_constructors_in_immutables: true
prefer_const_declarations: false
prefer_const_literals_to_create_immutables: false
prefer_constructors_over_static_methods: true
prefer_contains: true
prefer_double_quotes: false
prefer_equal_for_default_values: true
prefer_expression_function_bodies: false
prefer_final_fields: true
prefer_final_in_for_each: false
prefer_final_locals: false
prefer_final_parameters: false
prefer_for_elements_to_map_fromIterable: true
prefer_foreach: true
prefer_function_declarations_over_variables: true
prefer_generic_function_type_aliases: true
prefer_if_elements_to_conditional_expressions: true
prefer_if_null_operators: true
prefer_initializing_formals: true
prefer_inlined_adds: true
prefer_int_literals: false
prefer_interpolation_to_compose_strings: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_is_not_operator: true
prefer_iterable_whereType: true
prefer_mixin: true
prefer_null_aware_method_calls: true
prefer_null_aware_operators: true
prefer_single_quotes: true
prefer_spread_collections: true
prefer_typing_uninitialized_variables: true
provide_deprecation_message: true
public_member_api_docs: false
recursive_getters: true
require_trailing_commas: true
sized_box_for_whitespace: true
sized_box_shrink_expand: true
slash_for_doc_comments: true
sort_child_properties_last: true
sort_constructors_first: true
sort_unnamed_constructors_first: true
tighten_type_of_initializing_formals: true
type_annotate_public_apis: true
type_init_formals: true
unawaited_futures: true
unnecessary_await_in_return: true
unnecessary_brace_in_string_interps: true
unnecessary_const: false
unnecessary_constructor_name: true
unnecessary_final: true
unnecessary_getters_setters: true
unnecessary_lambdas: true
unnecessary_late: true
unnecessary_new: true
unnecessary_null_aware_assignments: true
unnecessary_null_checks: true
unnecessary_null_in_if_null_operators: true
unnecessary_nullable_for_final_variable_declarations: true
unnecessary_overrides: true
unnecessary_parenthesis: true
unnecessary_raw_strings: true
unnecessary_string_escapes: true
unnecessary_string_interpolations: true
unnecessary_this: true
use_decorated_box: true
use_full_hex_values_for_flutter_colors: true
use_function_type_syntax_for_parameters: true
use_if_null_to_convert_nulls_to_bools: true
use_is_even_rather_than_modulo: true
use_late_for_private_fields_and_variables: true
use_named_constants: true
use_raw_strings: false
use_rethrow_when_possible: true
use_setters_to_change_properties: true
use_string_buffers: true
use_test_throws_matchers: true
use_to_and_as_if_applicable: true
void_checks: true
# pub rules
depend_on_referenced_packages: true
lowercase_with_underscores: true
secure_pubspec_urls: false
sort_pub_dependencies: false
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,3 +1,7 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:flutter_single_character_input/single_character_input.dart';
@ -6,7 +10,7 @@ void main() {
}
class FlutterSingleCharacterInputDemo extends StatefulWidget {
const FlutterSingleCharacterInputDemo({Key? key}) : super(key: key);
const FlutterSingleCharacterInputDemo({super.key});
@override
State<FlutterSingleCharacterInputDemo> createState() =>
@ -30,82 +34,78 @@ class _FlutterSingleCharacterInputDemoState
};
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SingleCharacterInput(
characters: [
InputCharacter(
hint: '1',
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
Widget build(BuildContext context) => Scaffold(
body: Center(
child: SingleCharacterInput(
characters: [
InputCharacter(
hint: '1',
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
),
formatter: _numberFormatter,
),
formatter: _numberFormatter,
),
InputCharacter(
hint: '2',
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
InputCharacter(
hint: '2',
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
),
formatter: _numberFormatter,
),
formatter: _numberFormatter,
),
InputCharacter(
hint: '3',
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
InputCharacter(
hint: '3',
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
),
formatter: _numberFormatter,
),
formatter: _numberFormatter,
),
InputCharacter(
hint: '4',
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
InputCharacter(
hint: '4',
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
),
formatter: _numberFormatter,
),
formatter: _numberFormatter,
),
InputCharacter(
hint: 'A',
keyboardType: TextInputType.name,
formatter: _textFormatter,
),
InputCharacter(
hint: 'B',
keyboardType: TextInputType.name,
formatter: _textFormatter,
),
],
textStyle: Theme.of(context).textTheme.headline1?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 28,
InputCharacter(
hint: 'A',
keyboardType: TextInputType.name,
formatter: _textFormatter,
),
inputDecoration: InputDecoration(
hintStyle: Theme.of(context).textTheme.bodyText1?.copyWith(
InputCharacter(
hint: 'B',
keyboardType: TextInputType.name,
formatter: _textFormatter,
),
],
textStyle: Theme.of(context).textTheme.displayLarge?.copyWith(
fontWeight: FontWeight.w400,
color: const Color(0xFFBBBBBB),
fontSize: 28,
),
isDense: true,
isCollapsed: true,
),
buildDecoration: (context, input) {
return Container(
inputDecoration: InputDecoration(
hintStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w400,
color: const Color(0xFFBBBBBB),
fontSize: 28,
),
isDense: true,
isCollapsed: true,
),
buildDecoration: (context, input) => Container(
margin: const EdgeInsets.all(5),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 15),
width: 32,
child: input,
),
);
},
onChanged: (value, finished) {
// setState(() {});
},
),
onChanged: (value, finished) {
// setState(() {});
},
),
),
),
);
}
);
}

View file

@ -5,49 +5,56 @@ packages:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.9.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.2.1"
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.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev"
source: hosted
version: "1.16.0"
version: "1.17.1"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
url: "https://pub.dev"
source: hosted
version: "1.0.5"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
flutter:
@ -59,7 +66,8 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
url: "https://pub.dartlang.org"
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
url: "https://pub.dev"
source: hosted
version: "2.0.1"
flutter_single_character_input:
@ -74,41 +82,54 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
lints:
dependency: transitive
description:
name: lints
url: "https://pub.dartlang.org"
sha256: "5cfd6509652ff5e7fe149b6df4859e687fca9048437857cb2e65c8d780f396e3"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev"
source: hosted
version: "0.12.12"
version: "0.12.15"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted
version: "0.1.5"
version: "0.2.0"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
version: "1.9.1"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.2"
version: "1.8.3"
sky_engine:
dependency: transitive
description: flutter
@ -118,51 +139,58 @@ packages:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.9.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
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.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
url: "https://pub.dev"
source: hosted
version: "0.4.12"
version: "0.5.1"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
sdks:
dart: ">=2.18.2 <3.0.0"
dart: ">=3.0.0-0 <4.0.0"
flutter: ">=1.17.0"

View file

@ -19,8 +19,10 @@ 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:
uses-material-design: true

View file

@ -1 +1,3 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
///
library single_character_input;
export 'src/input_character.dart';

View file

@ -1,6 +1,15 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
/// Defines a character input configuration.
class InputCharacter {
/// Creates an [InputCharacter] configuration.
///
/// The [keyboardType] parameter is required.
/// The [readOnly], [hint], and [formatter] parameters are optional.
const InputCharacter({
required this.keyboardType,
this.readOnly = false,
@ -8,14 +17,23 @@ class InputCharacter {
this.formatter,
});
/// The type of keyboard to display for the input.
final TextInputType keyboardType;
final CharacterFormatter? formatter;
/// A flag indicating whether the input is read-only.
final bool readOnly;
/// The optional hint text displayed inside the input field.
final String hint;
String format(String value) {
return formatter?.call(value) ?? value;
}
/// The optional formatter function to format the input value.
final CharacterFormatter? formatter;
/// Formats the input value using the provided formatter function.
///
/// If no formatter is provided, the input value remains unchanged.
String format(String value) => formatter?.call(value) ?? value;
}
/// Defines a function signature for formatting input characters.
typedef CharacterFormatter = String Function(String);

View file

@ -1,10 +1,16 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_single_character_input/src/input_character.dart';
/// A StatefulWidget that represents a single character input.
class SingleCharacterInput extends StatefulWidget {
/// Creates a SingleCharacterInput widget.
const SingleCharacterInput({
required this.characters,
required this.onChanged,
@ -15,9 +21,11 @@ class SingleCharacterInput extends StatefulWidget {
this.inputDecoration,
this.spacing = 0,
this.textStyle,
Key? key,
}) : super(key: key);
super.key,
});
/// A function that builds a custom input widget using
/// the provided context and input widgets.
final Widget Function(BuildContext context, List<Widget> inputs)?
buildCustomInput;
@ -26,16 +34,25 @@ class SingleCharacterInput extends StatefulWidget {
/// Gets called when the value is changed.
/// Passes the changed value and if all inputs are filled in.
// ignore: avoid_positional_boolean_parameters
final void Function(String value, bool isComplete) onChanged;
/// The decoration to be applied to the input widget.
final InputDecoration? inputDecoration;
/// List of all character fields which are used to create inputs.
final List<InputCharacter> characters;
/// The alignment of the input text.
final TextAlign textAlign;
/// The alignment of the cursor.
final TextAlign cursorTextAlign;
/// The spacing between input fields.
final double spacing;
/// The style of the input text.
final TextStyle? textStyle;
@override
@ -101,92 +118,91 @@ class _SingleCharacterInputState extends State<SingleCharacterInput>
}
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
WidgetsBinding.instance.addPostFrameCallback((_) {
_mainNodes[_currentKeyboard]?.unfocus();
_mainNodes[_currentKeyboard]?.requestFocus();
});
setState(() {});
},
child: Wrap(
direction: Axis.horizontal,
children: [
Offstage(
child: Column(
children: _mainNodes
.map((key, value) {
return MapEntry(
key,
TextField(
focusNode: value,
controller: _mainController,
textCapitalization: TextCapitalization.characters,
keyboardType: key,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp('[a-zA-Z0-9]'),
),
LengthLimitingTextInputFormatter(
widget.characters.length,
),
],
onChanged: (String value) {
if (value.length > _currentIndex) {
var result = widget.characters[_currentIndex]
.format(value[_currentIndex]);
if (value[_currentIndex] != result) {
value = value.replaceRange(
_currentIndex,
_currentIndex + 1,
result,
);
_mainController.value =
_mainController.value.copyWith(
text: value,
selection: TextSelection.collapsed(
offset: value.length,
),
);
Widget build(BuildContext context) => GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
WidgetsBinding.instance.addPostFrameCallback((_) {
_mainNodes[_currentKeyboard]?.unfocus();
_mainNodes[_currentKeyboard]?.requestFocus();
});
setState(() {});
},
child: Wrap(
direction: Axis.horizontal,
children: [
Offstage(
child: Column(
children: _mainNodes
.map(
(key, value) => MapEntry(
key,
TextField(
enableInteractiveSelection: false,
focusNode: value,
controller: _mainController,
textCapitalization: TextCapitalization.characters,
keyboardType: key,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp('[a-zA-Z0-9]'),
),
LengthLimitingTextInputFormatter(
widget.characters.length,
),
],
onChanged: (String value) {
if (value.length > _currentIndex) {
var result = widget.characters[_currentIndex]
.format(value[_currentIndex]);
if (value[_currentIndex] != result) {
value = value.replaceRange(
_currentIndex,
_currentIndex + 1,
result,
);
_mainController.value =
_mainController.value.copyWith(
text: value,
selection: TextSelection.collapsed(
offset: value.length,
),
);
}
}
}
_onChanged(value);
},
_onChanged(value);
},
),
),
);
})
.values
.toList(),
)
.values
.toList(),
),
),
),
if (widget.buildCustomInput != null) ...[
widget.buildCustomInput!.call(
context,
widget.characters
.asMap()
.map(
(key, value) => MapEntry(key, _createCharacter(key)),
)
.values
.toList(),
),
] else ...[
for (var i = 0; i < widget.characters.length; i++) ...[
_createCharacter(i),
if (i < widget.characters.length - 1 && widget.spacing > 0) ...[
SizedBox(
height: widget.spacing,
width: widget.spacing,
),
]
if (widget.buildCustomInput != null) ...[
widget.buildCustomInput!.call(
context,
widget.characters
.asMap()
.map(
(key, value) => MapEntry(key, _createCharacter(key)),
)
.values
.toList(),
),
] else ...[
for (var i = 0; i < widget.characters.length; i++) ...[
_createCharacter(i),
if (i < widget.characters.length - 1 && widget.spacing > 0) ...[
SizedBox(
height: widget.spacing,
width: widget.spacing,
),
],
],
],
],
],
),
);
}
),
);
void _onChanged(String value) {
widget.onChanged.call(value, value.length == widget.characters.length);
@ -205,6 +221,8 @@ class _SingleCharacterInputState extends State<SingleCharacterInput>
if (_currentKeyboard != nextKeyboard) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_mainNodes[nextKeyboard]?.requestFocus();
_mainController.selection =
TextSelection.collapsed(offset: _currentIndex);
});
setState(() {
_currentKeyboard = nextKeyboard;
@ -253,9 +271,7 @@ class _SingleCharacterInputState extends State<SingleCharacterInput>
return '';
}
bool _hasFocus() {
return _mainNodes.values.any((element) => element.hasFocus);
}
bool _hasFocus() => _mainNodes.values.any((element) => element.hasFocus);
Widget _createLabel(int index) {
if (index < _currentValue.length) {
@ -264,19 +280,17 @@ class _SingleCharacterInputState extends State<SingleCharacterInput>
if (index == _currentValue.length && _hasFocus()) {
return AnimatedBuilder(
animation: _cursorAnimation,
builder: (context, _) {
return Opacity(
opacity: _cursorAnimation.value,
child: Container(
alignment: _getAlignment(widget.cursorTextAlign),
child: Text(
'|',
style: widget.textStyle,
textAlign: widget.cursorTextAlign,
),
builder: (context, _) => Opacity(
opacity: _cursorAnimation.value,
child: Container(
alignment: _getAlignment(widget.cursorTextAlign),
child: Text(
'|',
style: widget.textStyle,
textAlign: widget.cursorTextAlign,
),
);
},
),
),
);
} else {
return Container(

View file

@ -1,6 +1,6 @@
name: flutter_single_character_input
description: A new Flutter package project.
version: 0.0.1
version: 0.0.3
environment:
sdk: ">=2.18.0 <3.0.0"
@ -13,4 +13,7 @@ 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

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_test/flutter_test.dart';
void main() {