Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: NesScaffold #139

Merged
merged 3 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
- feat: adding different frames to `NesDialog`s
- feat: allow `NesFileExplorer` to have custom icons
- feat: adding `NesIcons.alien`
- feat: allow `NesScaffold` and allow snackbars to be shown on them.

# 0.19.0
- feat: adding `NesSectionHeader`
Expand Down
69 changes: 69 additions & 0 deletions example/lib/advanced/scaffold/scaffold.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:nes_ui/nes_ui.dart';

class NesScaffoldExample extends StatefulWidget {
const NesScaffoldExample({super.key});

static Route<void> route() {
return MaterialPageRoute<void>(
builder: (_) => const NesScaffoldExample(),
);
}

@override
State<NesScaffoldExample> createState() => _NesScaffoldExampleState();
}

class _NesScaffoldExampleState extends State<NesScaffoldExample> {
var _counter = 0;

@override
Widget build(BuildContext context) {
return Scaffold(
body: NesScaffold(
body: Builder(
builder: (context) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
NesButton(
type: NesButtonType.primary,
onPressed: () {
NesScaffoldMessenger.of(context).showSnackBar(
NesSnackbar(
text: 'Hello, World! ${_counter++}',
type: _counter.isOdd
? NesSnackbarType.success
: NesSnackbarType.warning,
),
alignment: Alignment.topRight,
);
},
child: const Text('Show Snackbar top Right'),
),
const SizedBox(width: 8),
NesButton(
type: NesButtonType.primary,
onPressed: () {
NesScaffoldMessenger.of(context).showSnackBar(
NesSnackbar(
text: 'Hello, World! ${_counter++}',
type: _counter.isOdd
? NesSnackbarType.success
: NesSnackbarType.warning,
),
alignment: Alignment.topLeft,
);
},
child: const Text('Show Snackbar top Left'),
),
],
),
);
},
),
),
);
}
}
2 changes: 1 addition & 1 deletion example/lib/gallery/gallery_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class GalleryPage extends StatelessWidget {

@override
Widget build(BuildContext context) {
return NesScaffold(
return NesExampleScaffold(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
Expand Down
15 changes: 13 additions & 2 deletions example/lib/widgets/nes_scaffold.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import 'package:example/advanced/container_playground/container_playground.dart';
import 'package:example/advanced/fixed_viewport/page/fixed_viewport_page.dart';
import 'package:example/advanced/rpg_menu/rpg_menu.dart';
import 'package:example/advanced/scaffold/scaffold.dart';
import 'package:example/advanced/window_manager/window_manager.dart';
import 'package:example/app/app.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nes_ui/nes_ui.dart';

class NesScaffold extends StatelessWidget {
const NesScaffold({
class NesExampleScaffold extends StatelessWidget {
const NesExampleScaffold({
super.key,
required this.child,
});
Expand All @@ -23,6 +24,16 @@ class NesScaffold extends StatelessWidget {
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
children: [
NesButton(
type: NesButtonType.normal,
onPressed: () {
Navigator.of(context).push(
NesScaffoldExample.route(),
);
},
child: NesIcon(iconData: NesIcons.rail),
),
const SizedBox(height: 16),
NesButton(
type: NesButtonType.normal,
onPressed: () {
Expand Down
251 changes: 251 additions & 0 deletions lib/src/widgets/nes_scaffold.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:nes_ui/nes_ui.dart';

/// {@template nes_scaffold_messenger}
/// Manages widgets displayed by a [NesScaffold].
/// {@endtemplate}
class NesScaffoldMessenger extends StatefulWidget {
/// {@macro nes_scaffold_messenger}
const NesScaffoldMessenger({
required this.body,
super.key,
});

/// The primary content of the scaffold.
final Widget body;

/// Returns the [NesScaffoldMessengerState] in the scope.
static NesScaffoldMessengerState of(BuildContext context) {
return _NesScaffoldMessengerScope.of(context);
}

@override
NesScaffoldMessengerState createState() => NesScaffoldMessengerState();
}

class _SnackbarMessage {
const _SnackbarMessage({
required this.id,
required this.snackbar,
required this.alignment,
required this.offset,
this.markForRemoval = false,
});

final int id;
final NesSnackbar snackbar;
final Alignment alignment;
final Offset offset;
final bool markForRemoval;

_SnackbarMessage copyWith({
int? id,
Alignment? alignment,
Offset? offset,
bool? markForRemoval,
}) {
return _SnackbarMessage(
id: id ?? this.id,
snackbar: snackbar,
alignment: alignment ?? this.alignment,
offset: offset ?? this.offset,
markForRemoval: markForRemoval ?? this.markForRemoval,
);
}
}

/// Handles the state of a [NesScaffoldMessenger].
///
/// This class allows the user to show snackbars and etc.
class NesScaffoldMessengerState extends State<NesScaffoldMessenger> {
late int _snackbarId = 0;
final List<Timer> _timers = [];

Future<void> _waitABit() async {
final completer = Completer<void>();

late final Timer timer;

timer = Timer(const Duration(seconds: 3), () {
completer.complete();
_timers.remove(timer);
timer.cancel();
});

_timers.add(timer);

return completer.future;
}

/// Adds a [NesSnackbar] to the [NesScaffold].
void showSnackBar(
NesSnackbar snackbar, {
Alignment alignment = Alignment.bottomCenter,
}) {
final message = _SnackbarMessage(
id: _snackbarId++,
snackbar: snackbar,
alignment: alignment,
offset: Offset(alignment.x, 0),
);
_snackbars.value = [
..._snackbars.value,
message,
];

WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_snackbars.value = _snackbars.value.map((e) {
if (e.id == message.id) {
return e.copyWith(
offset: Offset.zero,
);
}
return e;
}).toList();
});

_waitABit().then((_) {
_snackbars.value = _snackbars.value.map((e) {
if (e.id == message.id) {
return e.copyWith(
markForRemoval: true,
offset: Offset(alignment.x, 0),
);
}
return e;
}).toList();
});

_overlayController.show();
}

late final _snackbars = ValueNotifier<List<_SnackbarMessage>>([]);
late final _overlayController = OverlayPortalController();

void _checkForRemovals() {
_snackbars.value = _snackbars.value.where((element) {
return !element.markForRemoval;
}).toList();
}

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

for (final timer in _timers) {
timer.cancel();
}
}

@override
Widget build(BuildContext context) {
return _NesScaffoldMessengerScope(
state: this,
child: OverlayPortal(
overlayChildBuilder: (context) {
return ValueListenableBuilder<List<_SnackbarMessage>>(
valueListenable: _snackbars,
builder: (context, snackbars, child) {
final values = List<_SnackbarMessage>.from(snackbars)
..sort((a, b) {
return a.id.compareTo(b.id);
});
return Stack(
children: [
for (var i = 0; i < values.length; i++)
Align(
alignment: values[i].alignment,
child: AnimatedSlide(
key: ValueKey('slide_${values[i].id}'),
onEnd: _checkForRemovals,
duration: const Duration(milliseconds: 300),
offset: values[i].offset,
child: AnimatedPadding(
key: ValueKey('padding_${values[i].id}'),
duration: const Duration(milliseconds: 300),
padding: EdgeInsets.only(
top: 32 * i + 16,
left: 16,
right: 16,
),
child: NesDropshadow(
child: snackbars[i].snackbar,
),
),
),
),
],
);
},
);
},
controller: _overlayController,
child: widget.body,
),
);
}
}

class _NesScaffoldMessengerScope extends InheritedWidget {
const _NesScaffoldMessengerScope({
required super.child,
required this.state,
});

final NesScaffoldMessengerState state;

/// Returns the current message.
static NesScaffoldMessengerState of(BuildContext context) {
final result = context
.dependOnInheritedWidgetOfExactType<_NesScaffoldMessengerScope>()
?.state;

if (result == null) {
throw FlutterError(
'NesScaffoldMessenger.of() called with a context that does not contain '
'a NesScaffoldMessenger.\n'
'No NesScaffoldMessengerState ancestor could be found starting from '
'the context that was passed to NesScaffoldMessenger.of().\n'
'The context used was:\n'
' $context',
);
}

return result;
}

@override
bool updateShouldNotify(_NesScaffoldMessengerScope oldWidget) =>
state != oldWidget.state;
}

/// {@template nes_scaffold}
/// A Nes UI system scaffold widget.
///
/// Right now it doesn't replace the [Scaffold] widget, so we
/// advise to use it in combination with Material's [Scaffold].
///
/// That might change as we develop the library.
///
/// To interact with the [NesScaffold] state, use a [NesScaffoldMessenger].
///
/// {@endtemplate}
class NesScaffold extends StatelessWidget {
/// {@macro nes_scaffold}
const NesScaffold({
required this.body,
super.key,
});

/// The primary content of the scaffold.
final Widget body;

@override
Widget build(BuildContext context) {
return NesScaffoldMessenger(
body: body,
);
}
}
10 changes: 6 additions & 4 deletions lib/src/widgets/nes_snackbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ class NesSnackbar extends StatelessWidget {
SnackBar(
backgroundColor: Colors.transparent,
elevation: 0,
content: NesSnackbar(
text: text,
type: type,
content: SizedBox(
width: double.infinity,
child: NesSnackbar(
text: text,
type: type,
),
),
),
);
Expand Down Expand Up @@ -73,7 +76,6 @@ class NesSnackbar extends StatelessWidget {
}

return NesContainer(
width: double.infinity,
backgroundColor: color,
child: Text(
text,
Expand Down
Loading
Loading