From 7c41ff4c94e397f299fdc6f9b9b3a4e1965b2803 Mon Sep 17 00:00:00 2001 From: Ravi Singh Lodhi Date: Sun, 19 Nov 2023 12:29:00 +0530 Subject: [PATCH 1/3] [SuperTextField] Support light and dark mode --- .../example/lib/main_super_text_field.dart | 97 ++++++++++++++++--- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/super_editor/example/lib/main_super_text_field.dart b/super_editor/example/lib/main_super_text_field.dart index 1575b1e8f6..067158159e 100644 --- a/super_editor/example/lib/main_super_text_field.dart +++ b/super_editor/example/lib/main_super_text_field.dart @@ -6,6 +6,7 @@ import 'package:super_editor/super_text_field.dart'; void main() { runApp( MaterialApp( + debugShowCheckedModeBanner: false, home: _SuperTextFieldDemo(), ), ); @@ -19,23 +20,81 @@ class _SuperTextFieldDemo extends StatefulWidget { } class _SuperTextFieldDemoState extends State<_SuperTextFieldDemo> { + final _darkBackground = const Color(0xFF222222); + final _lightBackground = Colors.white; + final _brightness = ValueNotifier(Brightness.light); + @override Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 500), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, + return ValueListenableBuilder( + valueListenable: _brightness, + builder: (context, brightness, child) { + return Theme( + data: ThemeData(brightness: brightness), + child: child!, + ); + }, + child: Builder(builder: (context) { + final isLight = Theme.of(context).brightness == Brightness.light; + + return Scaffold( + body: ColoredBox( + color: isLight ? _lightBackground : _darkBackground, + child: Stack( children: [ - _SingleLineTextField(), - const SizedBox(height: 16), - _MultiLineTextField(), + Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _SingleLineTextField(), + const SizedBox(height: 16), + _MultiLineTextField(), + ], + ), + ), + ), + Align( + alignment: Alignment.bottomRight, + child: ListenableBuilder( + listenable: _brightness, + builder: (context, child) { + return child!; + }, + child: _buildCornerFabs(), + ), + ), ], ), - ), - ), + )); + }), + ); + } + + Widget _buildCornerFabs() { + return Padding( + padding: const EdgeInsets.only(right: 16, bottom: 16), + child: _buildLightAndDarkModeToggle(), + ); + } + + Widget _buildLightAndDarkModeToggle() { + return FloatingActionButton( + backgroundColor: _brightness.value == Brightness.light ? _darkBackground : _lightBackground, + foregroundColor: _brightness.value == Brightness.light ? _lightBackground : _darkBackground, + elevation: 5, + onPressed: () { + _brightness.value = _brightness.value == Brightness.light ? Brightness.dark : Brightness.light; + }, + child: _brightness.value == Brightness.light + ? const Icon( + Icons.dark_mode, + ) + : const Icon( + Icons.light_mode, + ), ); } } @@ -62,6 +121,8 @@ class _SingleLineTextFieldState extends State<_SingleLineTextField> { @override Widget build(BuildContext context) { + final isLight = Theme.of(context).brightness == Brightness.light; + return TapRegion( groupId: "textfields", onTapOutside: (_) => _focusNode.unfocus(), @@ -71,7 +132,8 @@ class _SingleLineTextFieldState extends State<_SingleLineTextField> { child: SuperTextField( focusNode: _focusNode, textController: _textController, - textStyleBuilder: _textStyleBuilder, + controlsColor: isLight ? Colors.black : Colors.white, + textStyleBuilder: (_) => _textStyleBuilder(_, isLight), hintBuilder: _createHintBuilder("Enter single line text..."), padding: const EdgeInsets.all(4), minLines: 1, @@ -105,6 +167,8 @@ class _MultiLineTextFieldState extends State<_MultiLineTextField> { @override Widget build(BuildContext context) { + final isLight = Theme.of(context).brightness == Brightness.light; + return TapRegion( groupId: "textfields", onTapOutside: (_) => _focusNode.unfocus(), @@ -113,8 +177,9 @@ class _MultiLineTextFieldState extends State<_MultiLineTextField> { borderBuilder: _borderBuilder, child: SuperTextField( focusNode: _focusNode, + controlsColor: isLight ? Colors.black : Colors.white, textController: _textController, - textStyleBuilder: _textStyleBuilder, + textStyleBuilder: (_) => _textStyleBuilder(_, isLight), hintBuilder: _createHintBuilder("Type some text..."), padding: const EdgeInsets.all(4), minLines: 5, @@ -140,9 +205,9 @@ BoxDecoration _borderBuilder(TextFieldBorderState borderState) { ); } -TextStyle _textStyleBuilder(Set attributions) { +TextStyle _textStyleBuilder(Set attributions, bool isLight) { return defaultTextFieldStyleBuilder(attributions).copyWith( - color: Colors.black, + color: isLight ? Colors.black : Colors.white, ); } From 62de5443798b1884599e100025b57e151264619e Mon Sep 17 00:00:00 2001 From: Ravi Singh Lodhi Date: Sun, 19 Nov 2023 13:57:14 +0530 Subject: [PATCH 2/3] [SuperTextField] Add dynamic SuperTextField that can change line counts and colors to demo app --- .../example/lib/main_super_text_field.dart | 242 +++++++++++++++--- 1 file changed, 212 insertions(+), 30 deletions(-) diff --git a/super_editor/example/lib/main_super_text_field.dart b/super_editor/example/lib/main_super_text_field.dart index 067158159e..bd9d0e6878 100644 --- a/super_editor/example/lib/main_super_text_field.dart +++ b/super_editor/example/lib/main_super_text_field.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:super_editor/super_editor.dart'; import 'package:super_editor/super_text_field.dart'; @@ -12,6 +13,11 @@ void main() { ); } +const _headingTextStyle = TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, +); + class _SuperTextFieldDemo extends StatefulWidget { const _SuperTextFieldDemo(); @@ -38,37 +44,56 @@ class _SuperTextFieldDemoState extends State<_SuperTextFieldDemo> { final isLight = Theme.of(context).brightness == Brightness.light; return Scaffold( + appBar: AppBar( + title: Text('SuperTextField Demo'), + ), body: ColoredBox( - color: isLight ? _lightBackground : _darkBackground, - child: Stack( - children: [ - Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 500), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _SingleLineTextField(), - const SizedBox(height: 16), - _MultiLineTextField(), - ], + color: isLight ? _lightBackground : _darkBackground, + child: Stack( + children: [ + Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), + children: [ + Text( + 'Single Line SuperTextField', + style: _headingTextStyle, + ), + const SizedBox(height: 8), + _SingleLineTextField(), + const SizedBox(height: 16), + Text( + 'Multi Line SuperTextField', + style: _headingTextStyle, + ), + const SizedBox(height: 8), + _MultiLineTextField(), + const SizedBox(height: 16), + Text( + 'Dynamic SuperTextField', + style: _headingTextStyle, + ), + const SizedBox(height: 8), + _DynamicTextField(), + ], + ), + ), ), - ), - ), - Align( - alignment: Alignment.bottomRight, - child: ListenableBuilder( - listenable: _brightness, - builder: (context, child) { - return child!; - }, - child: _buildCornerFabs(), - ), + Align( + alignment: Alignment.bottomRight, + child: ListenableBuilder( + listenable: _brightness, + builder: (context, child) { + return child!; + }, + child: _buildCornerFabs(), + ), + ), + ], ), - ], - ), - )); + )); }), ); } @@ -191,7 +216,7 @@ class _MultiLineTextFieldState extends State<_MultiLineTextField> { } } -BoxDecoration _borderBuilder(TextFieldBorderState borderState) { +BoxDecoration _borderBuilder(TextFieldBorderState borderState, {Color? borderColor}) { return BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all( @@ -199,7 +224,7 @@ BoxDecoration _borderBuilder(TextFieldBorderState borderState) { ? Colors.red : borderState.hasFocus ? Colors.blue - : Colors.grey.shade300, + : (borderColor ?? Colors.grey.shade300), width: borderState.hasError ? 2 : 1, ), ); @@ -219,3 +244,160 @@ WidgetBuilder _createHintBuilder(String hintText) { ); }; } + +class _DynamicTextField extends StatefulWidget { + const _DynamicTextField({super.key}); + + @override + State<_DynamicTextField> createState() => __DynamicTextFieldState(); +} + +class __DynamicTextFieldState extends State<_DynamicTextField> { + final _focusNode = FocusNode(); + final _textController = ImeAttributedTextEditingController( + controller: AttributedTextEditingController(), + ); + int _minLines = 1; + int _maxLines = 1; + final _formKey = GlobalKey(); + final _maxController = TextEditingController(text: '1'); + final _minController = TextEditingController(text: '1'); + + Color _borderColor = Colors.grey; + + @override + void dispose() { + _textController.dispose(); + _focusNode.dispose(); + _maxController.dispose(); + _minController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final isLight = Theme.of(context).brightness == Brightness.light; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TapRegion( + groupId: "textfields", + onTapOutside: (_) => _focusNode.unfocus(), + child: TextFieldBorder( + focusNode: _focusNode, + borderBuilder: (_) => _borderBuilder(_, borderColor: _borderColor), + child: SuperTextField( + focusNode: _focusNode, + textController: _textController, + controlsColor: isLight ? Colors.black : Colors.white, + textStyleBuilder: (_) => _textStyleBuilder(_, isLight), + hintBuilder: _createHintBuilder("Enter single line text..."), + padding: const EdgeInsets.all(4), + minLines: _minLines, + maxLines: _maxLines, + inputSource: TextInputSource.ime, + ), + ), + ), + const SizedBox(height: 16), + Text( + 'Change below parameters', + style: _headingTextStyle, + ), + const SizedBox(height: 8), + Form( + key: _formKey, + child: Column( + children: [ + _linesSelector('Min Lines', _minController), + const SizedBox(height: 8), + _linesSelector('Max Lines', _maxController), + ], + ), + ), + const SizedBox(height: 8), + Text( + 'Change border color', + style: _headingTextStyle, + ), + const SizedBox(height: 8), + Wrap( + children: [Colors.blue, Colors.green, Colors.orange, Colors.red].map((color) => _colorTile(color)).toList(), + ), + ], + ); + } + + Widget _colorTile(Color color) { + return GestureDetector( + onTap: () { + setState(() { + _borderColor = color; + }); + }, + child: Container( + height: 40, + width: 40, + margin: const EdgeInsets.symmetric(horizontal: 4), + color: color, + ), + ); + } + + Row _linesSelector(String title, TextEditingController controller) { + return Row( + children: [ + Text(title), + const SizedBox(width: 8), + Expanded( + child: _numberTextField(controller), + ), + ], + ); + } + + TextFormField _numberTextField(TextEditingController controller) { + return TextFormField( + controller: controller, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.fromLTRB(8, 4, 8, 4), + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + ), + ), + validator: (userInput) { + if (userInput == null) { + return 'Invalid number'; + } + + int value = int.parse(userInput); + if (value <= 0 || value >= 10) { + return 'Please select a value between (1, 5)'; + } + + int min = int.parse(_minController.text); + int max = int.parse(_maxController.text); + + if (min > max) { + return 'Min lines cannot be greater than max lines'; + } + + return null; + }, + onChanged: (userInput) { + if (_formKey.currentState!.validate()) { + setState(() { + _minLines = int.parse(_minController.text); + _maxLines = int.parse(_maxController.text); + }); + } + }, + ); + } +} From 77e05ad0baaee5f459aadc393399a4cc8722bcbd Mon Sep 17 00:00:00 2001 From: Ravi Singh Lodhi Date: Sun, 19 Nov 2023 14:19:59 +0530 Subject: [PATCH 3/3] [SuperTextField] Bugfix: Max lines validation --- super_editor/example/lib/main_super_text_field.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/super_editor/example/lib/main_super_text_field.dart b/super_editor/example/lib/main_super_text_field.dart index bd9d0e6878..837a8dc986 100644 --- a/super_editor/example/lib/main_super_text_field.dart +++ b/super_editor/example/lib/main_super_text_field.dart @@ -377,7 +377,7 @@ class __DynamicTextFieldState extends State<_DynamicTextField> { } int value = int.parse(userInput); - if (value <= 0 || value >= 10) { + if (value <= 0 || value > 5) { return 'Please select a value between (1, 5)'; }