Created
October 15, 2023 17:22
-
-
Save hawkkiller/f57c79126a12e0921591b851cc99cba1 to your computer and use it in GitHub Desktop.
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/cupertino.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:google_fonts/google_fonts.dart'; | |
void main() { | |
runApp(const MainApp()); | |
} | |
class MainApp extends StatelessWidget { | |
const MainApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData.light(useMaterial3: true), | |
home: const _DialogWidget(), | |
); | |
} | |
} | |
class _DialogWidget extends StatefulWidget { | |
const _DialogWidget(); | |
@override | |
State<_DialogWidget> createState() => _DialogWidgetState(); | |
} | |
class _DialogWidgetState extends State<_DialogWidget> { | |
String? _activeText; | |
bool inProgress = false; | |
void _submit() { | |
setState(() { | |
inProgress = true; | |
}); | |
Future.delayed(const Duration(seconds: 5), () { | |
setState(() { | |
inProgress = false; | |
}); | |
}); | |
} | |
@override | |
Widget build(BuildContext context) => Scaffold( | |
backgroundColor: Theme.of(context).colorScheme.primaryContainer, | |
body: Center( | |
child: DecoratedBox( | |
decoration: BoxDecoration( | |
borderRadius: const BorderRadius.all( | |
Radius.circular(25), | |
), | |
color: Theme.of(context).colorScheme.surface, | |
boxShadow: [ | |
BoxShadow( | |
color: Theme.of(context).colorScheme.shadow.withOpacity(.2), | |
blurRadius: 10, | |
offset: const Offset(0, 5), | |
), | |
], | |
), | |
child: SizedBox( | |
width: 400, | |
child: Padding( | |
padding: const EdgeInsets.all(24), | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
const _TopBar(), | |
Divider( | |
color: Theme.of(context).colorScheme.outlineVariant, | |
), | |
Padding( | |
padding: const EdgeInsets.only(top: 24), | |
child: Text( | |
'How are you feeling?', | |
style: GoogleFonts.gabriela( | |
fontSize: 24, | |
fontWeight: FontWeight.w600, | |
color: Theme.of(context).colorScheme.onSurface, | |
), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.only(top: 8), | |
child: Text( | |
'We\'d love to hear your feedback', | |
style: GoogleFonts.montserrat( | |
fontSize: 12, | |
fontWeight: FontWeight.w500, | |
color: Theme.of(context).colorScheme.outline, | |
), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.only(top: 32), | |
child: _SentimentsRow( | |
text: _activeText, | |
onTap: (text) { | |
setState(() { | |
_activeText = text; | |
}); | |
}, | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.only(top: 24), | |
child: TextField( | |
maxLines: 5, | |
style: GoogleFonts.montserrat( | |
fontSize: 14, | |
fontWeight: FontWeight.w500, | |
color: Theme.of(context).colorScheme.onSurface, | |
), | |
decoration: InputDecoration( | |
hintText: 'Tell us more', | |
filled: true, | |
hintStyle: GoogleFonts.montserrat( | |
fontSize: 14, | |
fontWeight: FontWeight.w500, | |
color: Theme.of(context).colorScheme.outline, | |
), | |
focusedBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(16), | |
borderSide: BorderSide( | |
color: Theme.of(context).colorScheme.primary, | |
), | |
), | |
enabledBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(16), | |
borderSide: BorderSide( | |
color: Theme.of(context).colorScheme.outlineVariant, | |
), | |
), | |
border: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(16), | |
borderSide: BorderSide( | |
color: Theme.of(context).colorScheme.outlineVariant, | |
), | |
), | |
focusedErrorBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(16), | |
borderSide: BorderSide( | |
color: Theme.of(context).colorScheme.error, | |
), | |
), | |
errorBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(16), | |
borderSide: BorderSide( | |
color: Theme.of(context).colorScheme.error, | |
), | |
), | |
), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.only(top: 24), | |
child: RepaintBoundary( | |
child: IgnorePointer( | |
ignoring: inProgress, | |
child: FilledButton.icon( | |
icon: SizedBox.square( | |
dimension: 24, | |
child: AnimatedSwitcher( | |
duration: const Duration(milliseconds: 200), | |
child: Visibility( | |
key: ValueKey(inProgress), | |
visible: !inProgress, | |
replacement: _AdaptiveIndicator( | |
color: Theme.of(context).colorScheme.onPrimary, | |
), | |
child: Icon( | |
Icons.send_rounded, | |
color: Theme.of(context).colorScheme.onPrimary, | |
), | |
), | |
), | |
), | |
onPressed: _submit, | |
label: Text( | |
'Submit', | |
style: GoogleFonts.montserrat( | |
fontSize: 14, | |
fontWeight: FontWeight.w600, | |
color: Theme.of(context).colorScheme.onPrimary, | |
), | |
), | |
), | |
), | |
), | |
), | |
], | |
), | |
), | |
), | |
), | |
), | |
); | |
} | |
class _AdaptiveIndicator extends StatelessWidget { | |
const _AdaptiveIndicator({this.color}); | |
final Color? color; | |
@override | |
Widget build(BuildContext context) { | |
final isCupertino = Theme.of(context).platform == TargetPlatform.iOS || | |
Theme.of(context).platform == TargetPlatform.macOS; | |
return isCupertino | |
? CupertinoActivityIndicator( | |
color: color, | |
) | |
: CircularProgressIndicator( | |
strokeWidth: 2, | |
color: color, | |
); | |
} | |
} | |
class _SentimentsRow extends StatefulWidget { | |
const _SentimentsRow({ | |
required this.text, | |
required this.onTap, | |
}); | |
final String? text; | |
final ValueChanged<String> onTap; | |
@override | |
State<_SentimentsRow> createState() => __SentimentsRowState(); | |
} | |
class __SentimentsRowState extends State<_SentimentsRow> { | |
@override | |
Widget build(BuildContext context) { | |
return Row( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: [ | |
_SentimentItem( | |
icon: Icons.sentiment_very_dissatisfied_rounded, | |
text: 'Bad', | |
activeText: widget.text, | |
onTap: widget.onTap, | |
), | |
_SentimentItem( | |
icon: Icons.sentiment_dissatisfied_rounded, | |
text: 'Okay', | |
activeText: widget.text, | |
onTap: widget.onTap, | |
), | |
_SentimentItem( | |
icon: Icons.sentiment_neutral_rounded, | |
text: 'Good', | |
activeText: widget.text, | |
onTap: widget.onTap, | |
), | |
_SentimentItem( | |
icon: Icons.sentiment_satisfied_rounded, | |
text: 'Great', | |
activeText: widget.text, | |
onTap: widget.onTap, | |
), | |
_SentimentItem( | |
icon: Icons.sentiment_very_satisfied_rounded, | |
text: 'Awesome', | |
activeText: widget.text, | |
onTap: widget.onTap, | |
), | |
], | |
); | |
} | |
} | |
class _SentimentItem extends StatefulWidget { | |
const _SentimentItem({ | |
required this.icon, | |
required this.text, | |
required this.activeText, | |
required this.onTap, | |
}); | |
final IconData icon; | |
final String text; | |
final String? activeText; | |
final ValueChanged<String> onTap; | |
@override | |
State<_SentimentItem> createState() => _SentimentItemState(); | |
} | |
class _SentimentItemState extends State<_SentimentItem> with SingleTickerProviderStateMixin { | |
late final AnimationController _controller; | |
late Animation<Color?> _colorAnimation; | |
@override | |
void initState() { | |
_controller = AnimationController( | |
duration: const Duration(milliseconds: 100), | |
vsync: this, | |
value: widget.activeText == widget.text ? 1 : 0, | |
); | |
super.initState(); | |
} | |
@override | |
void dispose() { | |
_controller.dispose(); | |
super.dispose(); | |
} | |
@override | |
void didChangeDependencies() { | |
_colorAnimation = ColorTween( | |
begin: Theme.of(context).colorScheme.surfaceVariant, | |
end: Theme.of(context).colorScheme.primary, | |
).animate(_controller); | |
super.didChangeDependencies(); | |
} | |
@override | |
void didUpdateWidget(covariant _SentimentItem oldWidget) { | |
if (widget.activeText != oldWidget.activeText) { | |
if (widget.activeText == widget.text) { | |
_controller.forward(); | |
} else { | |
_controller.reverse(); | |
} | |
} | |
super.didUpdateWidget(oldWidget); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return RepaintBoundary( | |
child: Column( | |
children: [ | |
AnimatedBuilder( | |
animation: _colorAnimation, | |
child: Icon( | |
widget.icon, | |
color: Theme.of(context).colorScheme.onPrimary, | |
), | |
builder: (context, icon) { | |
return IconButton.filled( | |
style: IconButton.styleFrom( | |
backgroundColor: _colorAnimation.value, | |
), | |
onPressed: () => widget.onTap(widget.text), | |
icon: icon!, | |
); | |
}, | |
), | |
Padding( | |
padding: const EdgeInsets.only(top: 8), | |
child: Text( | |
widget.text, | |
style: GoogleFonts.montserrat( | |
fontSize: 12, | |
fontWeight: FontWeight.w500, | |
color: Theme.of(context).colorScheme.onSurface, | |
), | |
), | |
), | |
], | |
), | |
); | |
} | |
} | |
class _TopBar extends StatelessWidget { | |
const _TopBar(); | |
@override | |
Widget build(BuildContext context) { | |
return Row( | |
children: [ | |
DecoratedBox( | |
decoration: BoxDecoration( | |
borderRadius: const BorderRadius.all( | |
Radius.circular(25), | |
), | |
color: Theme.of(context).colorScheme.primary, | |
), | |
child: SizedBox.square( | |
dimension: 32, | |
child: Icon( | |
Icons.message_rounded, | |
color: Theme.of(context).colorScheme.onPrimary, | |
size: 20, | |
), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.only(left: 8), | |
child: Text( | |
'Feedback', | |
style: GoogleFonts.montserrat( | |
fontSize: 16, | |
fontWeight: FontWeight.w600, | |
color: Theme.of(context).colorScheme.onSurface, | |
), | |
), | |
), | |
const Spacer(), | |
IconButton( | |
onPressed: () {}, | |
icon: const Icon(Icons.close), | |
), | |
], | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment