Skip to content

Commit b5ad2c9

Browse files
authored
Merge pull request #52 from DutchCodingCompany/feature/showcase
✨ Move showcase_kit into the toolkit
2 parents dfa53c9 + 9fa8299 commit b5ad2c9

22 files changed

Lines changed: 1842 additions & 1 deletion

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 0.1.1
4+
* Added the showcase kit, building blocks for a clickable web demo of an app made from its real screens. Import it with `package:dcc_toolkit/showcase.dart`, see [doc/showcase.md](doc/showcase.md)
5+
* Added the `dcc-toolkit-create-showcase` agent skill for setting up a showcase app
6+
37
## 0.1.0
48
* **Breaking:** Requires Flutter `>=3.47.0` and Dart `^3.13.0`
59
* **Breaking:** Migrated from `package:flutter/material.dart` / `package:flutter/cupertino.dart` to the `material_ui` and `cupertino_ui` packages. Apps must migrate as well, see [doc/migrate_to_material_ui.md](doc/migrate_to_material_ui.md)

‎lib/showcase.dart‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// Building blocks for a clickable demo of an app, made from its real screens.
2+
library;
3+
4+
export 'showcase/finish_prompt.dart';
5+
export 'showcase/highlight.dart';
6+
export 'showcase/highlighted_page.dart';
7+
export 'showcase/out_of_scope.dart';
8+
export 'showcase/showcase_config.dart';
9+
export 'showcase/showcase_navigator.dart';
10+
export 'showcase/showcase_scope.dart';
11+
export 'showcase/showcase_shell.dart';
12+
export 'showcase/showcase_state.dart';
13+
export 'showcase/showcase_strings.dart';

‎lib/showcase/finish_prompt.dart‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import 'dart:async';
2+
3+
import 'package:dcc_toolkit/dcc_toolkit.dart';
4+
import 'package:dcc_toolkit/showcase/showcase_scope.dart';
5+
import 'package:dcc_toolkit/showcase/showcase_state.dart';
6+
import 'package:material_ui/material_ui.dart';
7+
8+
/// Wait a moment, so the visitor sees the result of the last step first.
9+
const _delay = Duration(milliseconds: 700);
10+
11+
/// Offers to start over once the demo is finished and the visitor is back on the page that contains this widget,
12+
/// usually the home of the app.
13+
///
14+
/// Keeping looking around closes the dialog. The demo then starts over by itself at the next
15+
/// [ShowcaseState.begin].
16+
class const FinishPrompt({required final Widget child, super.key}) extends StatefulWidget {
17+
/// Creates a [FinishPrompt].
18+
this;
19+
20+
@override
21+
State<FinishPrompt> createState() => _FinishPromptState();
22+
}
23+
24+
class _FinishPromptState extends State<FinishPrompt> {
25+
bool _isScheduled = false;
26+
27+
Future<void> _showAfterDelay(ShowcaseState state) async {
28+
await Future<void>.delayed(_delay);
29+
_isScheduled = false;
30+
if (!mounted) return;
31+
32+
final strings = ShowcaseScope.stringsOf(context);
33+
await showNativeDialog<void>(
34+
context,
35+
title: strings.finishTitle,
36+
content: strings.finishMessage,
37+
actions: [
38+
DialogAction(text: strings.keepLooking, onTap: () {}),
39+
DialogAction(text: strings.startOver, onTap: state.reset),
40+
],
41+
);
42+
}
43+
44+
@override
45+
Widget build(BuildContext context) {
46+
final state = ShowcaseScope.of<ShowcaseState>(context);
47+
final isCurrent = ModalRoute.of(context)?.isCurrent ?? false;
48+
49+
if (isCurrent && !_isScheduled && state.takeFinishPrompt()) {
50+
_isScheduled = true;
51+
unawaited(_showAfterDelay(state));
52+
}
53+
54+
return widget.child;
55+
}
56+
}

‎lib/showcase/highlight.dart‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import 'package:dcc_toolkit/dcc_toolkit.dart';
2+
import 'package:material_ui/material_ui.dart';
3+
4+
/// Decides whether [widget] is the target of a [Highlight].
5+
typedef WidgetMatcher = bool Function(Widget widget);
6+
7+
/// One explanation shown on top of a screen.
8+
///
9+
/// The real views are not changed for the showcase, so a [target] finds the widget to mark in the widget tree
10+
/// of the screen. Without a target only the explanation is shown. A highlight whose target is not built (yet)
11+
/// waits until it appears, for example behind a loading state.
12+
class const Highlight({
13+
required final String title,
14+
required final String body,
15+
final WidgetMatcher? target,
16+
17+
/// Space between the target and the ring around it.
18+
final EdgeInsets padding = Paddings.all8,
19+
}) {
20+
/// Creates a [Highlight].
21+
this;
22+
23+
/// Matches the first widget of type [T] on the screen.
24+
static WidgetMatcher type<T extends Widget>() =>
25+
(widget) => widget is T;
26+
}

0 commit comments

Comments
 (0)