Skip to content

Instantly share code, notes, and snippets.

@hawkkiller
Created October 15, 2023 17:22
Show Gist options
  • Save hawkkiller/f57c79126a12e0921591b851cc99cba1 to your computer and use it in GitHub Desktop.
Save hawkkiller/f57c79126a12e0921591b851cc99cba1 to your computer and use it in GitHub Desktop.
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