Created
November 1, 2023 22:01
-
-
Save rodydavis/e1cf79e38fc438625a202491870d2b71 to your computer and use it in GitHub Desktop.
Flutter input, output, preview
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'two_pane.dart'; | |
class InputOutputPreview extends StatefulWidget { | |
const InputOutputPreview({ | |
super.key, | |
required this.title, | |
required this.input, | |
required this.output, | |
required this.preview, | |
required this.placeholder, | |
this.actions = const [], | |
this.codeTitle = 'Code', | |
this.previewTitle = 'Preview', | |
this.loading = false, | |
this.lazy = false, | |
this.previewSize = const Size(300, 700), | |
}); | |
final ( | |
String, | |
ValueChanged<(TextEditingController, TextEditingController)> | |
) input, output; | |
final Widget? preview; | |
final Widget placeholder; | |
final String title; | |
final List<Widget> actions; | |
final String codeTitle, previewTitle; | |
final Size? previewSize; | |
final bool loading; | |
final bool lazy; | |
@override | |
State<InputOutputPreview> createState() => _InputOutputPreviewState(); | |
} | |
class _InputOutputPreviewState extends State<InputOutputPreview> { | |
final input = TextEditingController(); | |
final output = TextEditingController(); | |
String? lastInput; | |
String? lastOutput; | |
@override | |
void initState() { | |
super.initState(); | |
if (!widget.lazy) input.addListener(onInput); | |
output.addListener(onOutput); | |
} | |
@override | |
void dispose() { | |
super.dispose(); | |
if (!widget.lazy) input.removeListener(onInput); | |
output.removeListener(onOutput); | |
input.dispose(); | |
output.dispose(); | |
} | |
void onInput() { | |
final (_, update) = widget.input; | |
final str = input.text; | |
if (lastInput == str) return; | |
update((input, output)); | |
lastInput = str; | |
} | |
void onOutput() { | |
final (_, update) = widget.output; | |
final str = output.text; | |
if (lastOutput == str) return; | |
update((output, input)); | |
lastOutput = str; | |
} | |
@override | |
Widget build(BuildContext context) { | |
final (inputTitle, _) = widget.input; | |
final (outputTitle, _) = widget.output; | |
return TwoPane( | |
title: widget.title, | |
actions: widget.actions, | |
loading: widget.loading, | |
primary: ( | |
widget.codeTitle, | |
(context) => SizedBox( | |
height: double.infinity, | |
child: Column( | |
children: [ | |
Flexible( | |
child: Padding( | |
padding: const EdgeInsets.all(8), | |
child: Card( | |
child: ListTile( | |
title: Text(inputTitle), | |
subtitle: TextField( | |
maxLines: null, | |
controller: input, | |
expands: true, | |
decoration: InputDecoration( | |
isCollapsed: true, | |
border: InputBorder.none, | |
suffix: widget.lazy | |
? IconButton( | |
onPressed: onInput, | |
icon: const Icon(Icons.save), | |
tooltip: 'Submit', | |
) | |
: null, | |
), | |
), | |
), | |
), | |
), | |
), | |
Flexible( | |
child: Padding( | |
padding: const EdgeInsets.all(8), | |
child: Card( | |
child: ListTile( | |
title: Text(outputTitle), | |
subtitle: TextField( | |
maxLines: null, | |
controller: output, | |
expands: true, | |
decoration: const InputDecoration( | |
isCollapsed: true, | |
border: InputBorder.none, | |
), | |
), | |
), | |
), | |
), | |
), | |
], | |
), | |
), | |
), | |
secondary: ( | |
widget.previewTitle, | |
(context) => Container( | |
color: Theme.of(context).colorScheme.surfaceVariant, | |
child: Builder(builder: (context) { | |
if (widget.previewSize == null) { | |
return widget.preview ?? widget.placeholder; | |
} | |
return Center( | |
child: Material( | |
elevation: 8, | |
child: SizedBox.fromSize( | |
size: widget.previewSize, | |
child: widget.preview ?? widget.placeholder, | |
), | |
), | |
); | |
}), | |
) | |
), | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
class TwoPane extends StatefulWidget { | |
const TwoPane({ | |
super.key, | |
required this.primary, | |
required this.secondary, | |
required this.title, | |
this.actions = const [], | |
this.loading = false, | |
}); | |
final (String, WidgetBuilder) primary, secondary; | |
final List<Widget> actions; | |
final String title; | |
final bool loading; | |
@override | |
State<TwoPane> createState() => _TwoPaneState(); | |
} | |
class _TwoPaneState extends State<TwoPane> { | |
bool darkMode = false; | |
void toggleDarkMode() { | |
if (mounted) { | |
setState(() { | |
darkMode = !darkMode; | |
}); | |
} | |
} | |
ThemeData theme(Color color, Brightness brightness) { | |
return ThemeData( | |
brightness: brightness, | |
colorScheme: ColorScheme.fromSeed( | |
seedColor: color, | |
brightness: brightness, | |
), | |
useMaterial3: true, | |
); | |
} | |
@override | |
void didUpdateWidget(covariant TwoPane oldWidget) { | |
if (oldWidget.loading != widget.loading || | |
oldWidget.title != widget.title || | |
oldWidget.actions != widget.actions) { | |
if (mounted) setState(() {}); | |
} | |
super.didUpdateWidget(oldWidget); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Theme( | |
data: theme(Colors.purple, darkMode ? Brightness.dark : Brightness.light), | |
child: Builder(builder: (context) { | |
final (primaryTitle, primaryBuilder) = widget.primary; | |
final (secondaryTitle, secondaryBuilder) = widget.secondary; | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(widget.title), | |
centerTitle: false, | |
actions: [ | |
IconButton( | |
tooltip: 'Toggle dark mode', | |
onPressed: toggleDarkMode, | |
icon: Icon(darkMode ? Icons.light_mode : Icons.dark_mode), | |
), | |
...widget.actions, | |
], | |
), | |
body: LayoutBuilder( | |
builder: (context, dimens) { | |
if (dimens.maxWidth > 800 && dimens.maxHeight > 600) { | |
return Column( | |
children: [ | |
if (widget.loading) const LinearProgressIndicator(), | |
Expanded( | |
child: Row( | |
children: [ | |
Flexible( | |
flex: 1, | |
child: primaryBuilder(context), | |
), | |
Flexible( | |
flex: 1, | |
child: secondaryBuilder(context), | |
), | |
], | |
), | |
), | |
], | |
); | |
} | |
return DefaultTabController( | |
length: 2, | |
child: Column( | |
children: [ | |
SizedBox( | |
height: kToolbarHeight, | |
width: double.infinity, | |
child: TabBar( | |
tabs: [ | |
Tab(text: primaryTitle), | |
Tab(text: secondaryTitle), | |
], | |
), | |
), | |
if (widget.loading) const LinearProgressIndicator(), | |
Expanded( | |
child: TabBarView( | |
children: [ | |
primaryBuilder(context), | |
secondaryBuilder(context), | |
], | |
), | |
), | |
], | |
), | |
); | |
}, | |
)); | |
}), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment