Skip to content

Commit

Permalink
feat: added popup instructions
Browse files Browse the repository at this point in the history
feat: added custom icon button

feat: added custom colour palette theme extension
  • Loading branch information
SethCohen committed Mar 26, 2023
1 parent fe06a77 commit 5bd4e7e
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 69 deletions.
179 changes: 119 additions & 60 deletions src/lib/themes/comfy.dart
Original file line number Diff line number Diff line change
@@ -1,66 +1,125 @@
import 'package:flutter/material.dart';

// Palette: https://coolors.co/4a5b6e-425366-f8cdc6-9ec1cc-f5efee
// background: Color(0xFF4A5B6E)
// surface: Color(0xFF425366)
// selected: Color(0xFFF8CDC6)
// unselected: Color(0xFF9EC1CC)
// text/hover: Color(0xFFF5EFEE)
// error: Color(0xFFC9465E)
@immutable
class CustomPalette extends ThemeExtension<CustomPalette> {
const CustomPalette({
required this.background,
required this.surface,
required this.selected,
required this.unselected,
required this.text,
required this.hover,
required this.error,
});

final Color? background;
final Color? surface;
final Color? selected;
final Color? unselected;
final Color? text;
final Color? hover;
final Color? error;

@override
CustomPalette copyWith(
{Color? background,
Color? surface,
Color? selected,
Color? unselected,
Color? text,
Color? hover,
Color? error}) {
return CustomPalette(
background: background ?? this.background,
surface: surface ?? this.surface,
selected: selected ?? this.selected,
unselected: unselected ?? this.unselected,
text: text ?? this.text,
hover: hover ?? this.hover,
error: error ?? this.error,
);
}

@override
CustomPalette lerp(CustomPalette? other, double t) {
if (other is! CustomPalette) {
return this;
}
return CustomPalette(
background: Color.lerp(background, other.background, t),
surface: Color.lerp(surface, other.surface, t),
selected: Color.lerp(selected, other.selected, t),
unselected: Color.lerp(unselected, other.unselected, t),
text: Color.lerp(text, other.text, t),
hover: Color.lerp(hover, other.hover, t),
error: Color.lerp(error, other.error, t),
);
}
}

final comfyTheme = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFFF8CDC6),
background: const Color(0xFF4A5B6E),
),
appBarTheme: const AppBarTheme(
elevation: 0,
backgroundColor: Colors.transparent,
foregroundColor: Color(0xFF9EC1CC),
titleTextStyle: TextStyle(
color: Color(0xFFF5EFEE),
fontSize: 22,
fontWeight: FontWeight.w500,
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFFF8CDC6),
background: const Color(0xFF4A5B6E),
),
),
tabBarTheme: TabBarTheme(
labelColor: const Color(0xFFF8CDC6),
unselectedLabelColor: const Color(0xFF9EC1CC),
indicator: const BoxDecoration(),
splashFactory: NoSplash.splashFactory,
overlayColor: MaterialStateProperty.all<Color>(Colors.transparent),
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
foregroundColor:
MaterialStateProperty.all<Color>(const Color(0xFFF8CDC6)),
overlayColor: MaterialStateProperty.all<Color>(const Color(0xFF425366)),
),
),
cardTheme: const CardTheme(
appBarTheme: const AppBarTheme(
elevation: 0,
color: Color(0xFF425366),
surfaceTintColor: Colors.transparent),
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: Color(0xFFF8CDC6),
linearTrackColor: Color(0xFF4A5B6E),
),
textTheme: const TextTheme(
displayLarge: TextStyle(color: Color(0xFFF5EFEE)),
displayMedium: TextStyle(color: Color(0xFFF5EFEE)),
displaySmall: TextStyle(color: Color(0xFFF5EFEE)),
headlineLarge: TextStyle(color: Color(0xFFF5EFEE)),
headlineMedium: TextStyle(color: Color(0xFFF5EFEE)),
headlineSmall: TextStyle(color: Color(0xFFF5EFEE)),
titleLarge: TextStyle(color: Color(0xFFF5EFEE)),
titleMedium: TextStyle(color: Color(0xFFF5EFEE)),
titleSmall: TextStyle(color: Color(0xFFF5EFEE)),
labelLarge: TextStyle(color: Color(0xFFF5EFEE)),
labelMedium: TextStyle(color: Color(0xFFF5EFEE)),
labelSmall: TextStyle(color: Color(0xFFF5EFEE)),
bodyLarge: TextStyle(color: Color(0xFFF5EFEE)),
bodyMedium: TextStyle(color: Color(0xFFF5EFEE)),
bodySmall: TextStyle(color: Color(0xFFF5EFEE)),
),
);
backgroundColor: Colors.transparent,
foregroundColor: Color(0xFF9EC1CC),
titleTextStyle: TextStyle(
color: Color(0xFFF5EFEE),
fontSize: 22,
fontWeight: FontWeight.w500,
),
),
tabBarTheme: TabBarTheme(
labelColor: const Color(0xFFF8CDC6),
unselectedLabelColor: const Color(0xFF9EC1CC),
indicator: const BoxDecoration(),
splashFactory: NoSplash.splashFactory,
overlayColor: MaterialStateProperty.all<Color>(Colors.transparent),
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
foregroundColor:
MaterialStateProperty.all<Color>(const Color(0xFFF8CDC6)),
overlayColor: MaterialStateProperty.all<Color>(const Color(0xFF425366)),
),
),
cardTheme: const CardTheme(
elevation: 0,
color: Color(0xFF425366),
surfaceTintColor: Colors.transparent),
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: Color(0xFFF8CDC6),
linearTrackColor: Color(0xFF4A5B6E),
),
textTheme: const TextTheme(
displayLarge: TextStyle(color: Color(0xFFF5EFEE)),
displayMedium: TextStyle(color: Color(0xFFF5EFEE)),
displaySmall: TextStyle(color: Color(0xFFF5EFEE)),
headlineLarge: TextStyle(color: Color(0xFFF5EFEE)),
headlineMedium: TextStyle(color: Color(0xFFF5EFEE)),
headlineSmall: TextStyle(color: Color(0xFFF5EFEE)),
titleLarge: TextStyle(color: Color(0xFFF5EFEE)),
titleMedium: TextStyle(color: Color(0xFFF5EFEE)),
titleSmall: TextStyle(color: Color(0xFFF5EFEE)),
labelLarge: TextStyle(color: Color(0xFFF5EFEE)),
labelMedium: TextStyle(color: Color(0xFFF5EFEE)),
labelSmall: TextStyle(color: Color(0xFFF5EFEE)),
bodyLarge: TextStyle(color: Color(0xFFF5EFEE)),
bodyMedium: TextStyle(color: Color(0xFFF5EFEE)),
bodySmall: TextStyle(color: Color(0xFFF5EFEE)),
),
extensions: const <ThemeExtension<dynamic>>[
CustomPalette(
background: Color(0xFF4A5B6E),
surface: Color(0xFF425366),
selected: Color(0xFFF8CDC6),
unselected: Color(0xFF9EC1CC),
text: Color(0xFFF5EFEE),
hover: Color(0xFFF5EFEE),
error: Color(0xFFC9465E),
),
]);
41 changes: 41 additions & 0 deletions src/lib/widgets/custom_iconbutton.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:asl/themes/comfy.dart';
import 'package:flutter/material.dart';

class CustomIconButton extends StatefulWidget {
final Icon icon;
final bool? isSelected;
final VoidCallback onPressed;

const CustomIconButton({
super.key,
this.isSelected,
required this.icon,
required this.onPressed,
});

@override
State<CustomIconButton> createState() => _CustomIconButtonState();
}

class _CustomIconButtonState extends State<CustomIconButton> {
bool _isHovering = false;

@override
Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context);

return MouseRegion(
onEnter: (_) => setState(() => _isHovering = true),
onExit: (_) => setState(() => _isHovering = false),
child: IconButton(
icon: widget.icon,
color: _isHovering
? themeData.extension<CustomPalette>()!.hover
: widget.isSelected!
? themeData.extension<CustomPalette>()!.selected
: themeData.extension<CustomPalette>()!.unselected,
onPressed: widget.onPressed,
),
);
}
}
68 changes: 59 additions & 9 deletions src/lib/widgets/flashcard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/flashcard_model.dart';
import '../providers/data_provider.dart';
import 'custom_iconbutton.dart';

class Flashcard extends StatefulWidget {
const Flashcard({
Expand All @@ -22,6 +23,7 @@ class Flashcard extends StatefulWidget {

class _FlashcardState extends State<Flashcard> {
bool _isImageBlurred = true;
OverlayEntry? _popupOverlayEntry;

@override
Widget build(BuildContext context) {
Expand All @@ -37,18 +39,33 @@ class _FlashcardState extends State<Flashcard> {
children: [
_buildTitle(),
// TODO replace network images with controllable apng||video player/frame controller
_buildImage(),
Stack(
children: [
_buildImage(),
if (!_isImageBlurred && !isEmptyInstructions)
_buildInstructionsPopup(context),
],
),
// TODO media controls implementation
_buildMediaControls(),
_buildFlashcardButtons(),
// TODO replace instructions with FAB over and on bottom right of image
if (!_isImageBlurred && !isEmptyInstructions) _buildInstructions()
],
),
),
);
}

Positioned _buildInstructionsPopup(BuildContext context) => Positioned(
bottom: 8,
right: 8,
child: CustomIconButton(
isSelected: _popupOverlayEntry != null,
onPressed: () =>
_popupOverlayEntry == null ? _showPopup(context) : _hidePopup(),
icon: const Icon(Icons.info_outline),
),
);

Widget _buildDifficultyButton(String text, int quality, Color color) =>
TextButton(
style: TextButton.styleFrom(foregroundColor: color),
Expand Down Expand Up @@ -124,13 +141,46 @@ class _FlashcardState extends State<Flashcard> {
);
}

Widget _buildInstructions() => Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
height: 100,
child: SingleChildScrollView(
child: Text(widget.card.instructions),
Widget _buildInstructions(height, width) => Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
height: height,
width: width,
child: SingleChildScrollView(
child: Text(widget.card.instructions),
),
),
),
);

OverlayEntry _createPopup(BuildContext context) {
final RenderBox renderBox = context.findRenderObject() as RenderBox;
final Size size = renderBox.size;
final Offset offset = renderBox.localToGlobal(Offset.zero);

return OverlayEntry(
builder: (BuildContext context) => Positioned(
left: offset.dx + size.width,
top: offset.dy + size.height / 2,
child: _buildInstructions(size.height, size.width),
),
);
}

void _showPopup(BuildContext context) {
_popupOverlayEntry = _createPopup(context);
Overlay.of(context).insert(_popupOverlayEntry!);
}

void _hidePopup() {
_popupOverlayEntry?.remove();
_popupOverlayEntry = null;
}

@override
void dispose() {
_hidePopup();
super.dispose();
}
}

0 comments on commit 5bd4e7e

Please sign in to comment.