Skip to content

Commit 221a6fb

Browse files
committed
Memo sorting functions, modified page switcher
1 parent 1af2e21 commit 221a6fb

12 files changed

+310
-77
lines changed

lib/main.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ class FvmApp extends StatelessWidget {
5757
return const ErrorDBScreen();
5858
}
5959

60+
final settings = SettingsService.read();
61+
6062
return ValueListenableBuilder<Box<SidekickSettings>>(
6163
valueListenable: SettingsService.box.listenable(),
6264
builder: (context, box, widget) {
63-
final settings = SettingsService.read();
64-
6565
return OKToast(
6666
child: MaterialApp(
6767
localizationsDelegates: [

lib/src/components/molecules/version_install_button.dart

+8-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class VersionInstallButton extends HookWidget {
2424
final hovering = useState(false);
2525
final queueProvider = useProvider(fvmQueueProvider);
2626

27+
final isCached = version?.isCached == true;
28+
2729
useEffect(() {
2830
final isInstalling = queueProvider.activeItem != null &&
2931
queueProvider.activeItem.version == version;
@@ -49,7 +51,7 @@ class VersionInstallButton extends HookWidget {
4951
}
5052

5153
Widget installIcon() {
52-
if ((isQueued.value && !version.isCached)) {
54+
if ((isQueued.value && !isCached)) {
5355
return SizedBox(
5456
height: 20,
5557
width: 20,
@@ -60,7 +62,7 @@ class VersionInstallButton extends HookWidget {
6062
);
6163
}
6264

63-
if (version.isCached) {
65+
if (isCached) {
6466
return Icon(
6567
Icons.check,
6668
size: 20,
@@ -87,14 +89,14 @@ class VersionInstallButton extends HookWidget {
8789
}
8890
},
8991
child: Opacity(
90-
opacity: (version?.isCached ?? false) ? 0.3 : 1,
92+
opacity: isCached ? 0.3 : 1,
9193
child: IconButton(
9294
onPressed: onInstall,
9395
splashRadius: 20,
9496
icon: Tooltip(
95-
message:
96-
(version?.isCached ?? false) ? installedMsg : notInstalledMsg,
97-
child: installIcon()),
97+
message: isCached ? installedMsg : notInstalledMsg,
98+
child: installIcon(),
99+
),
98100
),
99101
),
100102
);

lib/src/components/organisms/shortcut_manager.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter/services.dart';
3-
import 'package:flutter_hooks/flutter_hooks.dart';
43
import 'package:hooks_riverpod/hooks_riverpod.dart';
54
import 'package:sidekick/src/modules/navigation/navigation.provider.dart';
65

@@ -16,18 +15,19 @@ class NavigationIntent extends Intent {
1615
}
1716

1817
/// Shorcut manager
19-
class SkShortcutManager extends HookWidget {
18+
class SkShortcutManager extends StatelessWidget {
2019
/// Constructor
2120
const SkShortcutManager({
2221
Key key,
2322
this.child,
23+
@required this.focusNode,
2424
}) : super(key: key);
2525

2626
/// Child
2727
final Widget child;
28+
final FocusNode focusNode;
2829
@override
2930
Widget build(BuildContext context) {
30-
final focusNode = useFocusNode();
3131
// Handles route change
3232
void handleRouteChange(NavigationRoutes route) {
3333
context.read(navigationProvider.notifier).goTo(route);

lib/src/modules/common/app_shell.dart

+29-21
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,16 @@ import 'package:flutter_hooks/flutter_hooks.dart';
55
import 'package:hooks_riverpod/hooks_riverpod.dart';
66
import 'package:i18next/i18next.dart';
77
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
8+
import 'package:sidekick/src/modules/common/utils/indexed_transition_switcher.dart';
89

910
import '../../components/molecules/top_app_bar.dart';
10-
import '../../components/organisms/app_bottom_bar.dart';
1111
import '../../components/organisms/shortcut_manager.dart';
1212
import '../../modules/common/utils/layout_size.dart';
1313
import '../../theme.dart';
1414
import '../fvm/fvm.screen.dart';
1515
import '../navigation/navigation.provider.dart';
1616
import '../projects/projects.screen.dart';
1717
import '../releases/releases.screen.dart';
18-
import '../search/components/search_bar.dart';
19-
import '../selected_detail/components/info_drawer.dart';
2018
import '../selected_detail/selected_detail.provider.dart';
2119
import 'constants.dart';
2220

@@ -33,12 +31,29 @@ class AppShell extends HookWidget {
3331
/// Constructor
3432
const AppShell({Key key}) : super(key: key);
3533

34+
NavigationRailDestination renderNavButton(
35+
BuildContext context,
36+
String label,
37+
IconData iconData,
38+
) {
39+
return NavigationRailDestination(
40+
icon: Icon(iconData, size: 20),
41+
selectedIcon: Icon(
42+
iconData,
43+
size: 20,
44+
color: Theme.of(context).colorScheme.secondary,
45+
),
46+
label: Text(label),
47+
);
48+
}
49+
3650
@override
3751
Widget build(BuildContext context) {
38-
LayoutSize.init(context);
52+
// LayoutSize.init(context);
3953
final navigation = useProvider(navigationProvider.notifier);
4054
final currentRoute = useProvider(navigationProvider);
4155
final selectedInfo = useProvider(selectedDetailProvider).state;
56+
final focusNode = useFocusNode();
4257

4358
// Index of item selected
4459
final selectedIndex = useState(0);
@@ -65,23 +80,12 @@ class AppShell extends HookWidget {
6580
}
6681
});
6782

68-
NavigationRailDestination renderNavButton(String label, IconData iconData) {
69-
return NavigationRailDestination(
70-
icon: Icon(iconData, size: 20),
71-
selectedIcon: Icon(
72-
iconData,
73-
size: 20,
74-
color: Theme.of(context).colorScheme.secondary,
75-
),
76-
label: Text(label),
77-
);
78-
}
79-
8083
return SkShortcutManager(
84+
focusNode: focusNode,
8185
child: Scaffold(
8286
appBar: const SkAppBar(),
83-
bottomNavigationBar: const AppBottomBar(),
84-
endDrawer: const SelectedDetailDrawer(),
87+
// bottomNavigationBar: const AppBottomBar(),
88+
// endDrawer: const SelectedDetailDrawer(),
8589
backgroundColor: platformBackgroundColor(context),
8690
key: _scaffoldKey,
8791
body: Row(
@@ -97,14 +101,17 @@ class AppShell extends HookWidget {
97101
},
98102
destinations: [
99103
renderNavButton(
104+
context,
100105
I18Next.of(context).t('modules:common.navButtonDashboard'),
101106
Icons.category,
102107
),
103108
renderNavButton(
109+
context,
104110
I18Next.of(context).t('modules:common.navButtonProjects'),
105111
MdiIcons.folderMultiple,
106112
),
107113
renderNavButton(
114+
context,
108115
I18Next.of(context).t('modules:common.navButtonExplore'),
109116
Icons.explore,
110117
),
@@ -124,7 +131,7 @@ class AppShell extends HookWidget {
124131
children: <Widget>[
125132
// This is the main content.
126133
Expanded(
127-
child: PageTransitionSwitcher(
134+
child: IndexedTransitionSwitcher(
128135
duration: const Duration(milliseconds: 250),
129136
reverse: selectedIndex.value <
130137
(navigation.previous.index ?? 0),
@@ -142,14 +149,15 @@ class AppShell extends HookWidget {
142149
child: child,
143150
);
144151
},
145-
child: pages[selectedIndex.value],
152+
index: selectedIndex.value,
153+
children: pages,
146154
),
147155
),
148156
],
149157
),
150158
),
151159
),
152-
const SearchBar(),
160+
// const SearchBar(),
153161
],
154162
),
155163
),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import 'package:flutter/widgets.dart';
2+
3+
/// Based on the PageTransitionSwitcher from the animations package, this widget
4+
/// allows you to transition between an array of widgets using entry and exit
5+
/// animations whilst maintaining their state.
6+
class IndexedTransitionSwitcher extends StatefulWidget {
7+
/// Creates an [IndexedTransitionSwitcher].
8+
const IndexedTransitionSwitcher(
9+
{@required this.index,
10+
@required this.children,
11+
@required this.transitionBuilder,
12+
this.reverse = false,
13+
this.duration = const Duration(milliseconds: 300)});
14+
15+
/// The index of the child to show.
16+
final int index;
17+
18+
/// The widgets to switch between.
19+
final List<Widget> children;
20+
21+
/// A function to wrap the child in primary and secondary animations.
22+
///
23+
/// When the index changes, the new child will animate in with the primary
24+
/// animation, and the old widget will animate out with the secondary
25+
/// animation.
26+
final Widget Function(Widget child, Animation<double> primaryAnimation,
27+
Animation<double> secondaryAnimation) transitionBuilder;
28+
29+
/// The duration of the transition.
30+
final Duration duration;
31+
32+
/// Whether or not the transition should be reversed.
33+
///
34+
/// If true, the new child will animate in behind the oWld child with the
35+
/// secondary animation running in reverse, whilst the old child animates
36+
/// out with the primary animation playing in reverse.
37+
final bool reverse;
38+
39+
@override
40+
_IndexedTransitionSwitcherState createState() =>
41+
_IndexedTransitionSwitcherState();
42+
}
43+
44+
class _IndexedTransitionSwitcherState extends State<IndexedTransitionSwitcher>
45+
with TickerProviderStateMixin {
46+
List<_ChildEntry> _childEntries;
47+
48+
@override
49+
void initState() {
50+
super.initState();
51+
// Create the page entries
52+
_childEntries = widget.children
53+
.asMap()
54+
.entries
55+
.map((entry) => _createPageEntry(entry.key, entry.value))
56+
.toList();
57+
}
58+
59+
@override
60+
void didUpdateWidget(IndexedTransitionSwitcher oldWidget) {
61+
super.didUpdateWidget(oldWidget);
62+
// Transition if the index has changed
63+
if (widget.index != oldWidget.index) {
64+
var newChild =
65+
_childEntries.where((entry) => entry.index == widget.index).first;
66+
var oldChild =
67+
_childEntries.where((entry) => entry.index == oldWidget.index).first;
68+
// Animate the children
69+
if (widget.reverse) {
70+
// Animate in the new child
71+
newChild.primaryController.value = 1;
72+
newChild.secondaryController.reverse(from: 1);
73+
// Animate out the old child and unstage it when the animation is complete
74+
oldChild.secondaryController.value = 0;
75+
oldChild.primaryController
76+
.reverse(from: 1)
77+
.then((value) => setState(() {
78+
oldChild.onStage = false;
79+
oldChild.primaryController.reset();
80+
oldChild.secondaryController.reset();
81+
}));
82+
} else {
83+
// Animate in the new child
84+
newChild.secondaryController.value = 0;
85+
newChild.primaryController.forward(from: 0);
86+
// Animate out the old child and unstage it when the animation is complete
87+
oldChild.primaryController.value = 1;
88+
oldChild.secondaryController.forward().then((value) => setState(() {
89+
oldChild.onStage = false;
90+
oldChild.primaryController.reset();
91+
oldChild.secondaryController.reset();
92+
}));
93+
}
94+
// Reorder the stack and set onStage to true for the new child
95+
_childEntries.remove(newChild);
96+
_childEntries.remove(oldChild);
97+
_childEntries
98+
.addAll(widget.reverse ? [newChild, oldChild] : [oldChild, newChild]);
99+
newChild.onStage = true;
100+
}
101+
}
102+
103+
_ChildEntry _createPageEntry(int index, Widget child) {
104+
// Prepare the animation controllers
105+
final primaryController = AnimationController(
106+
value: widget.index == index ? 1.0 : 0,
107+
duration: widget.duration,
108+
vsync: this,
109+
);
110+
final secondaryController = AnimationController(
111+
duration: widget.duration,
112+
vsync: this,
113+
);
114+
// Create the page entry
115+
return _ChildEntry(
116+
key: UniqueKey(),
117+
index: index,
118+
primaryController: primaryController,
119+
secondaryController: secondaryController,
120+
transitionChild: widget.transitionBuilder(
121+
child, primaryController, secondaryController),
122+
onStage: widget.index == index);
123+
}
124+
125+
@override
126+
void dispose() {
127+
// Dispose of the animation controllers
128+
for (var entry in _childEntries) {
129+
entry.dispose();
130+
}
131+
super.dispose();
132+
}
133+
134+
@override
135+
Widget build(BuildContext context) => Stack(
136+
alignment: Alignment.center,
137+
fit: StackFit.expand,
138+
children: _childEntries
139+
.map<Widget>((entry) => Offstage(
140+
key: entry.key,
141+
offstage: !entry.onStage,
142+
child: entry.transitionChild,
143+
))
144+
.toList(),
145+
);
146+
}
147+
148+
/// Internal representation of a child.
149+
class _ChildEntry {
150+
_ChildEntry(
151+
{@required this.index,
152+
@required this.key,
153+
@required this.primaryController,
154+
@required this.secondaryController,
155+
@required this.transitionChild,
156+
@required this.onStage});
157+
158+
/// The child index.
159+
final int index;
160+
161+
/// The key to maintain widget state when moving in the tree.
162+
final Key key;
163+
164+
/// The entry animation controller.
165+
final AnimationController primaryController;
166+
167+
/// The exit animation controller.
168+
final AnimationController secondaryController;
169+
170+
/// The child widget wrapped in the transition.
171+
final Widget transitionChild;
172+
173+
/// Whether or not the child should be rendered.
174+
bool onStage;
175+
176+
/// Dispose of the animation controllers
177+
void dispose() {
178+
primaryController.dispose();
179+
secondaryController.dispose();
180+
}
181+
}

0 commit comments

Comments
 (0)