TextFiledThemeScreen.dart
class TextFiledThemeScreen extends StatefulWidget {
final bool showAppBar;
const TextFiledThemeScreen({super.key, this.showAppBar = false});
@override
State<TextFiledThemeScreen> createState() => _TextFiledThemeScreenState();
}
class _TextFiledThemeScreenState extends State<TextFiledThemeScreen> {
ThemeController themeController = Get.find();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.background,
appBar: widget.showAppBar
? AppBar(
title: const Text(
"TextField Theme",
),
)
: null,
body: Center(
child: Container(
constraints: const BoxConstraints(
maxWidth: 1000,
),
alignment: Alignment.topCenter,
padding: const EdgeInsets.all(16),
child: Stack(
alignment: Alignment.topCenter,
children: [
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 95,),
_buildCommonContainer(
child: ThemeColorSelector(controller: themeController,),
title: "Theme Color",
description: "Select the theme mode you prefer.",
),
const ListTileReveal(
leading: Icon(Icons.text_fields_outlined),
title: Text('TextField'),
subtitle: Text('By default Switch are themed to style and '
'height align with OutlinedButton and FilledButton, shown '
'here for comparison.\n'),
),
ListTileReveal(
title: const Text(
'Use Material 3 ',
),
subtitle: const Text(
'Update settings below to match M3 default '
'values. Also sets text selection style.\n'
'Can also be used in M2 mode, but the result is different from '
'when used in actual M3 mode.'),
trailing: FilledButton(
onPressed: () async {
await _handleSetToM3(context);
},
child: const Text('Set to M3'),
),
onTap: () async {
await _handleSetToM3(context);
},
),
const SizedBox(height: 20,),
_textFieldShowcase(),
const SizedBox(height: 10,),
const Divider(),
const SizedBox(height: 10,),
Obx(
() => CustomContainer(
title: '${themeController.themeMode.value==ThemeMode.dark?'Dark':'Light'} Theme base color',
description: 'Base color',
child: ThemePopupMenu3(
schemeIndex: themeController.textFiledSchemeIndex.value,
onChanged: themeController.setTextFiledSchemeColor),
),
),
const SizedBox(
height: 10,
),
Obx(
()=> SwitchListTileReveal(
title: const Text('Base color as background',style: TextStyle(fontSize: 14),),
subtitleDense: true,
subtitle: const Text('TIP: If you leave this OFF, you can still '
'theme the fill color and turn it ON using widget level '
"'filled: true' property, and wise versa.\n"),
value: themeController.inputDecoratorIsFilled.value,
onChanged: themeController.setInputDecoratorIsFilled
,
),
),
const SizedBox(
height: 10,
),
Obx(
() => CustomContainer(
title: '${themeController.themeMode.value==ThemeMode.dark?'Dark':'Light'} theme focused prefix icon color',
description: ' prefix icon color',
child: ThemePopupMenu3(
schemeIndex:
themeController.textFiledPrefixIconSchemeIndex.value,
onChanged: themeController.setTextFiledPrefixIconSchemeColor),
),
),
const SizedBox(
height: 20,
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(
() => Flexible(
child: _buildCommonContainer(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: SliderComponentShape.noOverlay,
),
child: SizedBox(
width: 1000,
child: Slider(
value: themeController
.textFiledMainColorOpacity.value,
min: -1,
max: 100,
divisions: 25,
inactiveColor: Colors.transparent,
onChanged: (double value) {
themeController
.setTextFiledMainColorOpacity(value);
if (kDebugMode) {
print(value);
}
},
),
),
),
title: 'TextFiled Main Color Opacity',
description:
'Adjust the textFiled main color opacity to your preference.',
),
),
),
Padding(
padding: const EdgeInsets.only(left: 10.0, top: 28),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text('Opacity'),
Obx(
() {
final borderRadius = themeController
.textFiledMainColorOpacity.value;
final integerPart =
double.parse(borderRadius.toStringAsFixed(0))
.toInt();
return SizedBox(
width: 55,
child: Text(
textAlign: TextAlign.center,
maxLines: 2,
softWrap: true,
themeController
.textFiledMainColorOpacity.value ==
-1
? 'default opacity'
: integerPart.toString(),
style: TextStyles.titleSmall
.copyWith(fontSize: 12),
),
);
},
)
],
),
)
],
),
const SizedBox(
height: 20,
),
const Divider(),
const SizedBox(height: 20,),
Obx(
() => CustomContainer(
title: '${themeController.themeMode.value==ThemeMode.dark?'Dark':'Light'} Theme Border color',
description: 'Border color',
child: ThemePopupMenu3(
schemeIndex: themeController.textFiledBorderSchemeIndex.value,
onChanged: themeController.setTextFiledBorderSchemeColor),
),
),
const SizedBox(
height: 10,
),
Obx(
()=> SwitchListTile(
title: const Text(
'Border style',
),
subtitle: const Text(
'ON for outline | OFF for underline',
),
value: themeController.inputDecoratorBorderType.value ==
FlexInputBorderType.outline,
onChanged:
(bool isOn) {
if (isOn) {
themeController.setInputDecoratorBorderType(
FlexInputBorderType.outline);
} else {
themeController.setInputDecoratorBorderType(
FlexInputBorderType.underline);
}
},
),
),
const SizedBox(
height: 10,
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(
() => Flexible(
child: _buildCommonContainer(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: SliderComponentShape.noOverlay,
),
child: SizedBox(
width: 1000,
child: Slider(
value: themeController
.textFiledBorderRadius.value,
min: -1,
max: 40,
divisions: 20,
inactiveColor: Colors.transparent,
onChanged: (double value) {
themeController
.setTextFiledBorderRadius(value);
if (kDebugMode) {
print(value);
}
},
),
),
),
title: 'TextFiled Border Radius',
description:
'Adjust the textFiled border radius to your preference.',
),
),
),
Padding(
padding: const EdgeInsets.only(left: 10.0, top: 28),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text('Radius'),
Obx(
() {
final borderRadius = themeController
.textFiledBorderRadius.value;
final integerPart =
double.parse(borderRadius.toStringAsFixed(0))
.toInt();
return SizedBox(
width: 55,
child: Text(
textAlign: TextAlign.center,
maxLines: 2,
softWrap: true,
themeController
.textFiledBorderRadius.value ==
-1
? 'default opacity'
: integerPart.toString(),
style: TextStyles.titleSmall
.copyWith(fontSize: 12),
),
);
},
)
],
),
)
],
),
const SizedBox(
height: 20,
),
const Divider(),
Obx(()=> SwitchListTile(
title: const Text('Focused field has a border'),
value: themeController.inputDecoratorFocusedHasBorder.value,
onChanged: themeController.setInputDecoratorFocusedHasBorder,
),
),
const SizedBox(height: 10,),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(
() => Flexible(
child: _buildCommonContainer(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: SliderComponentShape.noOverlay,
),
child: SizedBox(
width: 1000,
child: Slider(
value:themeController.inputDecoratorFocusedHasBorder.value
? themeController.textFiledFocusedBorderWidth.value
: 0 ,
min: 0,
max: 5,
divisions: 11,
inactiveColor: Colors.transparent,
onChanged:themeController.inputDecoratorFocusedHasBorder.value
? (double value) {
themeController.setTextFiledFocusedBorderWidth(value);
}
: null,
),
),
),
title: 'TextFiled Focused Border Width',
description:
'Adjust the textFiled focused border width to your preference.',
),
),
),
Padding(
padding: const EdgeInsets.only(left: 10.0, top: 28),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text('Width'),
Obx(
() {
// Get the integer part
final textToShow = themeController.inputDecoratorFocusedHasBorder.value
? (themeController.textFiledFocusedBorderWidth.value ) <= 0
? 'Default'
: (themeController.textFiledFocusedBorderWidth.value
.toStringAsFixed(1))
: themeController.inputDecoratorFocusedHasBorder.value
? 'default 2'
: 'none';
return SizedBox(
width: 55,
child: Text(
textToShow,
textAlign: TextAlign.center,
maxLines: 2,
softWrap: true,
style: TextStyles.titleSmall
.copyWith(fontSize: 12),
),
);
},
),
],
),
)
],
),
const SizedBox(height: 20,),
const Divider(),
const SizedBox(height: 10,),
Obx(
()=> SwitchListTile(
title: const Text('Unfocused field has a border'),
value: themeController.inputDecoratorUnfocusedHasBorder.value ,
onChanged: themeController.setInputDecoratorUnfocusedHasBorder
,
),
),
Obx(
()=> SwitchListTile(
title: const Text('Unfocused border is colored'),
value: themeController.inputDecoratorUnfocusedBorderIsColored.value &&
themeController.inputDecoratorUnfocusedHasBorder.value ,
onChanged: themeController.inputDecoratorUnfocusedHasBorder.value
? themeController.setInputDecoratorUnfocusedBorderIsColored
: null,
),
),
const SizedBox(height: 10,),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(
() => Flexible(
child: _buildCommonContainer(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: SliderComponentShape.noOverlay,
),
child: SizedBox(
width: 1000,
child: Slider(
value: themeController.inputDecoratorUnfocusedHasBorder.value
? themeController.textFiledUnFocusedBorderWidth.value
: 0,
min: 0,
max: 5,
divisions: 11,
inactiveColor: Colors.transparent,
onChanged:themeController.inputDecoratorUnfocusedHasBorder.value
? (double value) {
themeController
.setTextFiledUnFocusedBorderWidth(value);
if (kDebugMode) {
print(value);
}
}:null,
),
),
),
title: 'TextFiled Focused Border Width',
description:
'Adjust the textFiled focused border width to your preference.',
),
),
),
Padding(
padding: const EdgeInsets.only(left: 10.0, top: 28),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text('Width'),
Obx(
() {
String textToShow = themeController.inputDecoratorUnfocusedHasBorder.value
? (themeController.textFiledUnFocusedBorderWidth.value ) <= 0
? 'Default'
: (themeController.textFiledUnFocusedBorderWidth.value.toStringAsFixed(1))
: themeController.inputDecoratorUnfocusedHasBorder.value
? 'default 1'
: 'none';
return SizedBox(
width: 55,
child: Text(
textToShow,
textAlign: TextAlign.center,
maxLines: 2,
softWrap: true,
style: TextStyles.titleSmall
.copyWith(fontSize: 12),
),
);
},
),
],
),
)
],
),
const SizedBox(height: 20,),
const Divider(),
const SizedBox(height: 20,),
Obx(
() => CustomContainer(
title: '${themeController.themeMode.value==ThemeMode.dark?'Dark':'Light'} theme cursor',
description: 'Cursor color',
child: ThemePopupMenu3(
schemeIndex: themeController.textFiledCursorSchemeIndex.value,
onChanged: themeController.setTextFiledCursorSchemeColor),
),
),
const SizedBox(
height: 20,
),
Obx(
() => CustomContainer(
title: '${themeController.themeMode.value==ThemeMode.dark?'Dark':'Light'} theme Selection text color',
description: 'Selection Text color',
child: ThemePopupMenu3(
schemeIndex: themeController.textFiledTextSelectionSchemeIndex.value,
onChanged: themeController.setTextFiledTextSelectionSchemeColor),
),
),
const SizedBox(
height: 20,
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(
() => Flexible(
child: _buildCommonContainer(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
overlayShape: SliderComponentShape.noOverlay,
),
child: SizedBox(
width: 1000,
child: Slider(
value: themeController
.textFiledSelectedTextOpacity.value,
min: -1,
max: 100,
divisions: 25,
inactiveColor: Colors.transparent,
onChanged: (double value) {
themeController
.setTextFiledSelectedTextOpacity(value);
if (kDebugMode) {
print(value);
}
},
),
),
),
title: '${themeController.themeMode.value==ThemeMode.dark?'Dark':'Light'} theme text selection opacity',
description:
'Adjust the text selection opacity to your preference.',
),
),
),
Padding(
padding: const EdgeInsets.only(left: 10.0, top: 28),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text('Opacity'),
Obx(
() {
final borderRadius = themeController
.textFiledSelectedTextOpacity.value;
final integerPart =
double.parse(borderRadius.toStringAsFixed(0))
.toInt();
return SizedBox(
width: 55,
child: Text(
textAlign: TextAlign.center,
maxLines: 2,
softWrap: true,
themeController
.textFiledSelectedTextOpacity.value ==
-1
? 'default opacity'
: integerPart.toString(),
style: TextStyles.titleSmall
.copyWith(fontSize: 12),
),
);
},
)
],
),
)
],
),
const SizedBox(
height: 20,
),
Obx(
() => CustomContainer(
title: '${themeController.themeMode.value==ThemeMode.dark?'Dark':'Light'} theme text selection handles',
description: 'text selection handles color',
child: ThemePopupMenu3(
schemeIndex: themeController.textFiledSelectionHandleColorIndex.value,
onChanged: themeController.setTextFiledSelectionHandleColor),
),
),
const SizedBox(
height: 20,
),
const TextField(
showCursor: true,
decoration: InputDecoration(
hintText: 'Write something and try the text selection style',
labelText: 'Label: TextField for text selection',
),
),
const SizedBox(
height: 20,
),
],
),
),
Container(
height:90,
width: double.infinity,
color: Theme.of(context).colorScheme.background,
child: const Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
key: Key('TextField3'),
// controller: textController3,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Hint: Write something...',
labelText: 'Label: Outline border set by Widget if not defined',
prefixIcon: Icon(Icons.search),
suffixIcon: Icon(Icons.info),
// errorText: errorState3.value
// ? "Any entry without an 'a' will trigger this error"
// : null,
),
),
Divider()
],
),
),
],
),
),
),
);
}
Future<void> _handleSetToM3(BuildContext context) async {
final bool? reset = await showDialog<bool?>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Set TextField To FlexColorScheme Defaults'),
content:
const Text('Set the text field settings back to its opinionated '
'default values?'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: const Text('Cancel')),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: const Text('Set to FCS defaults')),
],
);
},
);
if (reset ?? false) {
await themeController.setTextFieldToM3();
}
}
Future<void> _handleSetToDefaults(BuildContext context) async {
final bool? reset = await showDialog<bool?>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Set TextField To FlexColorScheme Defaults'),
content:
const Text('Set the text field settings back to its opinionated '
'default values?'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: const Text('Cancel')),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: const Text('Set to FCS defaults')),
],
);
},
);
if (reset ?? false) {
// await themeController.setTextFieldToDefaults();
}
}
Widget _buildCommonContainer(
{required Widget child,
required String title,
required String description}) {
return Column(
children: [
Container(
padding: const EdgeInsets.only(left: 16),
alignment: Alignment.centerLeft,
child: Text(
title,
style: TextStyles.titleSmall,
),
),
const SizedBox(height: 5),
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(10),
boxShadow: [containerShadow(context)],
),
child: child,
),
const SizedBox(height: 5),
Container(
padding: const EdgeInsets.only(left: 16),
alignment: Alignment.centerLeft,
child: Text(
description,
style: TextStyles.bodyMedium,
),
),
],
);
}
Widget _textFieldShowcase() {
TextEditingController textController1 = TextEditingController();
TextEditingController textController2 = TextEditingController();
TextEditingController textController3 = TextEditingController();
TextEditingController textController4 = TextEditingController();
RxBool errorState1 = false.obs;
RxBool errorState2 = false.obs;
RxBool errorState3 = false.obs;
return RepaintBoundary(
child: Column(
children: <Widget>[
Obx(
() => TextField(
onChanged: (String text) {
if (text.contains('a') | text.isEmpty) {
errorState1.value = false;
} else {
errorState1.value = true;
}
},
key: const Key('TextField1'),
controller: textController1,
decoration: InputDecoration(
hintText: 'Hint: Write something...',
labelText: 'Label: Underline border by default if not defined',
errorText: errorState1.value
? "Any entry without an 'a' will trigger this error"
: null,
),
),
),
const SizedBox(height: 16),
Obx(
() => TextField(
onChanged: (String text) {
if (text.contains('a') | text.isEmpty) {
errorState2.value = false;
} else {
errorState2.value = true;
}
},
key: const Key('TextField2'),
controller: textController2,
decoration: InputDecoration(
filled: true,
hintText: 'Hint: Write something...',
labelText: 'Label: Underline border by default if not defined, '
'filled set true by Widget',
errorText: errorState2.value
? "Any entry without an 'a' will trigger this error"
: null,
),
),
),
const SizedBox(height: 16),
Obx(
() => TextField(
onChanged: (String text) {
if (text.contains('a') | text.isEmpty) {
errorState3.value = false;
} else {
errorState3.value = true;
}
},
key: const Key('TextField3'),
controller: textController3,
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: 'Hint: Write something...',
labelText: 'Label: Outline border set by Widget if not defined',
prefixIcon: const Icon(Icons.search),
suffixIcon: const Icon(Icons.info),
errorText: errorState3.value
? "Any entry without an 'a' will trigger this error"
: null,
),
),
),
const SizedBox(height: 16),
TextField(
key: const Key('TextField4'),
controller: textController4,
decoration: const InputDecoration.collapsed(
hintText: 'Hint: Collapsed TextField...',
),
),
const SizedBox(height: 16),
TextField(
controller: TextEditingController(),
enabled: false,
decoration: const InputDecoration(
labelText: 'TextField - Disabled label',
),
),
const SizedBox(height: 16),
TextField(
controller: TextEditingController(text: 'Disabled with text entry'),
enabled: false,
decoration: const InputDecoration(
labelText: 'TextField - Disabled label',
prefixIcon: Icon(Icons.search),
suffixIcon: Icon(Icons.info),
),
),
],
),
);
}
}