Skip to content

Commit f87cf8f

Browse files
committed
Working on injection framework
1 parent d6d00ae commit f87cf8f

File tree

7 files changed

+451
-14
lines changed

7 files changed

+451
-14
lines changed

lib/addons.dart

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ import 'main.dart';
77
import 'src/theme.dart';
88
import 'package:flutter/material.dart';
99
import 'package:styled_logger/styled_logger.dart';
10+
import 'package:dictionaries/core/context.dart';
1011

11-
export 'src/widgetframework.dart';
12-
export 'src/menubar.dart' show DictionariesMenuBarEntry;
13-
export 'src/nodes.dart' show RootNode;
14-
export 'src/theme.dart' show DictionariesTheme;
12+
export 'package:dictionaries/core/context.dart' show AddonContext;
1513

1614
/// This class is what you base your entire addon around.
1715
/// You will make a new class that inherits this class and provides the info and permissions.
@@ -162,11 +160,6 @@ class AddonLogger {
162160
}
163161
}
164162

165-
class AddonContext {
166-
final String id;
167-
const AddonContext.fromId(this.id);
168-
}
169-
170163
extension DictionariesThemeInjection on DictionariesTheme {
171164
void inject(AddonContext context, String id) {
172165
addInjectedTheme("${context.id}.$id", context.id, this);

lib/core/context.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class DictionariesContext {
2+
final AddonContext? addonContext;
3+
4+
const DictionariesContext({this.addonContext});
5+
6+
bool get isAddon => addonContext != null;
7+
}
8+
9+
class AddonContext {
10+
final String id;
11+
12+
const AddonContext.fromId(this.id);
13+
}

lib/injections.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import 'package:dictionaries/addons.dart';
2+
3+
class InjectionCollection {
4+
InjectionCollection();
5+
static late InjectionCollection instance;
6+
7+
List<Injection<DictionariesMenuBarInjection>> menuBarInjections = [];
8+
List<Injection<DictionariesMenuBarDeletion>> menuBarDeletions = [];
9+
}
10+
11+
class Injection<T> {
12+
final AddonContext context;
13+
final T injection;
14+
15+
const Injection(this.context, this.injection);
16+
}

lib/ui/header.dart

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import 'package:collection/collection.dart';
2+
import 'package:dictionaries/addons.dart';
3+
import 'package:dictionaries/injections.dart';
4+
import 'package:flutter_environments_plus/flutter_environments_plus.dart';
5+
import 'package:flutter_riverpod/legacy.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:menu_bar/menu_bar.dart';
8+
import 'package:styled_logger/styled_logger.dart';
9+
import 'package:menu_bar/src/entry.dart';
10+
11+
final menuBarProvider = StateProvider<List<DictionariesMenuBarEntry>>((r) => []);
12+
final headerTitleProvider = StateProvider<String?>((r) => null);
13+
14+
bool get isPlatformMenuBarSupported {
15+
return Environment.isMacos;
16+
}
17+
18+
List<BarButton> generateBarButtonsFromEntries(BuildContext context, List<DictionariesMenuBarEntry> entries) {
19+
return entries.map((x) {
20+
return BarButton(text: Text(x.text), submenu: SubMenu(menuItems: generateMenuBarFromEntries(context, [x.text], x.children)));
21+
}).whereType<BarButton>().toList();
22+
}
23+
24+
List<MenuEntry> generateMenuBarFromEntries(BuildContext context, List<String> keys, List<DictionariesMenuBarEntry> entries) {
25+
return entries.map((x) {
26+
if (InjectionCollection.instance.menuBarDeletions.any((y) => ListEquality().equals(y.injection.keys, [...keys, x.text]))) return null;
27+
28+
if (x.divider) {
29+
return MenuDivider();
30+
} else if (x.onActivate != null) {
31+
return MenuButton(text: Text(x.text), onTap: () => x.onActivate!(context), shortcut: x.shortcut);
32+
} else if (x.children.isNotEmpty) {
33+
return MenuButton(text: Text(x.text), submenu: SubMenu(menuItems: generateMenuBarFromEntries(context, [...keys, x.text], x.children)));
34+
} else {
35+
return MenuButton(text: Text(x.text));
36+
}
37+
}).whereType<MenuEntry>().toList();
38+
}
39+
40+
extension InjectAllMenuBarEntries on List<DictionariesMenuBarEntry> {
41+
List<DictionariesMenuBarEntry> inject(List<DictionariesMenuBarInjection> injections) {
42+
injectAllMenuBarEntries(this, injections);
43+
return this;
44+
}
45+
}
46+
47+
void injectAllMenuBarEntries(List<DictionariesMenuBarEntry> current, List<DictionariesMenuBarInjection> injections) {
48+
for (final injection in injections) {
49+
bool invalid = false;
50+
DictionariesMenuBarEntry? currentEntry;
51+
52+
for (int i = 0; i < injection.keys.length; i++) {
53+
final key = injection.keys[i];
54+
final l = currentEntry?.children ?? current;
55+
int index = l.indexWhere((x) => x.text == key);
56+
57+
if (index < 0) {
58+
// Not found
59+
final e = DictionariesMenuBarEntry(key, children: []);
60+
l.add(e);
61+
currentEntry = e;
62+
} else {
63+
final e = l[index];
64+
65+
if (e.onActivate != null) {
66+
// The thing we're trying to traverse into has an onActivate,
67+
// so we'll warn and ignore.
68+
69+
Logger.warn("Menu bar injection tries to traverse into $key, but $key can't be traversed into! Full path: ${injection.keys.join("/")}");
70+
invalid = true;
71+
break;
72+
} else {
73+
currentEntry = e;
74+
}
75+
}
76+
}
77+
78+
if (invalid) continue;
79+
final l = currentEntry?.children ?? current;
80+
int after = injection.rightAfter != null ? (injection.rightAfter!.leading ? -2 : l.indexWhere((x) => x.text == injection.rightAfter!.text)) : -1;
81+
l.insertAll(after >= 0 ? after + 1 : (after == -2 ? 0 : l.length), injection.entries);
82+
}
83+
}
84+
85+
class DictionariesMenuBarEntry {
86+
final String id;
87+
final String? text;
88+
final void Function(BuildContext context)? onActivate;
89+
final List<DictionariesMenuBarEntry> children;
90+
final MenuSerializableShortcut? shortcut;
91+
final bool divider;
92+
93+
const DictionariesMenuBarEntry(this.id, this.text, {this.onActivate, this.shortcut, this.children = const []}) : divider = false;
94+
const DictionariesMenuBarEntry.divider(this.id) : text = null, onActivate = null, shortcut = null, children = const [], divider = true;
95+
96+
static const String dividerAfterExport = "DividerAfterExport";
97+
static const String beforeDownloadOriginalFile = "BeforeDownloadOriginalFile";
98+
99+
static const String debugAddonOptionsTopDivider = "DebugAddonOptionsTopDivider";
100+
static const String debugAddonOptionsSecondDivider = "DebugAddonOptionsSecondDivider";
101+
static const String debugAddonOptionsThirdDivider = "DebugAddonOptionsThirdDivider";
102+
}
103+
104+
class DictionariesApp extends StatefulWidget {
105+
final Widget child;
106+
final VoidCallback ifUsingPlatformMenuBar;
107+
const DictionariesApp({super.key, required this.child, required this.ifUsingPlatformMenuBar});
108+
109+
@override
110+
State<DictionariesApp> createState() => _DictionariesAppState();
111+
}
112+
113+
class _DictionariesAppState extends State<DictionariesApp> {
114+
String? title;
115+
List<DictionariesMenuBarEntry> items = [];
116+
117+
@override
118+
Widget build(BuildContext context) {
119+
return SizedBox(
120+
height: 60,
121+
child: Column(
122+
children: [
123+
Text(["Dictionaries", title].whereType<String>().join(" - ")),
124+
if (!isPlatformMenuBarSupported) MenuBarWidget(
125+
barButtons: generateBarButtonsFromEntries(context, items),
126+
child: widget.child,
127+
) else widget.child,
128+
],
129+
),
130+
);
131+
}
132+
}

lib/ui/tabs.dart

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import 'package:dictionaries/core/context.dart';
2+
import 'package:flutter/material.dart';
3+
4+
class Sidebar extends StatefulWidget {
5+
const Sidebar({super.key});
6+
7+
@override
8+
State<Sidebar> createState() => _SidebarState();
9+
}
10+
11+
class _SidebarState extends State<Sidebar> {
12+
@override
13+
Widget build(BuildContext context) {
14+
return const Placeholder();
15+
}
16+
}
17+
18+
class SidebarItem {
19+
final String title;
20+
final StatelessWidget icon;
21+
final Widget content;
22+
final SidebarItemPosition position;
23+
final int id;
24+
25+
static int _currentId = 0;
26+
static List<(SidebarItem, AddonContext?)> items = [];
27+
28+
bool get isAddon => position == SidebarItemPosition.addon;
29+
30+
SidebarItem({required this.title, required this.icon, required this.content, required this.position}) : id = _currentId++;
31+
32+
void add([AddonContext? context]) {
33+
if (isAddon && context == null) throw ArgumentError.notNull("context");
34+
items.add((this, context));
35+
}
36+
}
37+
38+
class AddonSidebarItem extends SidebarItem {
39+
AddonSidebarItem({required super.title, required super.icon, required super.content}) : super(position: SidebarItemPosition.addon);
40+
41+
void register(AddonContext context) {
42+
add(context);
43+
}
44+
}
45+
46+
enum SidebarItemPosition {
47+
top,
48+
addon,
49+
bottom,
50+
}

0 commit comments

Comments
 (0)