Skip to content

Commit a384404

Browse files
authored
Merge pull request #521 from AppFlowy-IO/fix/grid_ui_adjust
Fix: grid UI adjust
2 parents 7071db6 + b286276 commit a384404

File tree

24 files changed

+484
-386
lines changed

24 files changed

+484
-386
lines changed

frontend/app_flowy/assets/translations/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@
180180
"row": {
181181
"duplicate": "Duplicate",
182182
"delete": "Delete",
183-
"textPlaceholder": "Empty"
183+
"textPlaceholder": "Empty",
184+
"copyProperty": "Copied property to clipboard"
184185
},
185186
"selectOption": {
186187
"create": "Create",

frontend/app_flowy/lib/startup/deps_resolver.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
1616
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
1717
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
1818
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
19+
import 'package:fluttertoast/fluttertoast.dart';
1920
import 'package:get_it/get_it.dart';
2021

2122
class DependencyResolver {
@@ -46,6 +47,8 @@ void _resolveUserDeps(GetIt getIt) {
4647
}
4748

4849
void _resolveHomeDeps(GetIt getIt) {
50+
getIt.registerSingleton(FToast());
51+
4952
getIt.registerSingleton(MenuSharedState());
5053

5154
getIt.registerFactoryParam<UserListener, UserProfile, void>(

frontend/app_flowy/lib/startup/tasks/app_widget.dart

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -67,40 +67,42 @@ class ApplicationWidget extends StatelessWidget {
6767
}) : super(key: key);
6868

6969
@override
70-
Widget build(BuildContext context) => ChangeNotifierProvider.value(
71-
value: settingModel,
72-
builder: (context, _) {
73-
const ratio = 1.73;
74-
const minWidth = 600.0;
75-
setWindowMinSize(const Size(minWidth, minWidth / ratio));
76-
settingModel.readLocaleWhenAppLaunch(context);
77-
AppTheme theme = context.select<AppearanceSettingModel, AppTheme>(
78-
(value) => value.theme,
79-
);
80-
Locale locale = context.select<AppearanceSettingModel, Locale>(
81-
(value) => value.locale,
82-
);
70+
Widget build(BuildContext context) {
71+
return ChangeNotifierProvider.value(
72+
value: settingModel,
73+
builder: (context, _) {
74+
const ratio = 1.73;
75+
const minWidth = 600.0;
76+
setWindowMinSize(const Size(minWidth, minWidth / ratio));
77+
settingModel.readLocaleWhenAppLaunch(context);
78+
AppTheme theme = context.select<AppearanceSettingModel, AppTheme>(
79+
(value) => value.theme,
80+
);
81+
Locale locale = context.select<AppearanceSettingModel, Locale>(
82+
(value) => value.locale,
83+
);
8384

84-
return MultiProvider(
85-
providers: [
86-
Provider.value(value: theme),
87-
Provider.value(value: locale),
88-
],
89-
builder: (context, _) {
90-
return MaterialApp(
91-
builder: overlayManagerBuilder(),
92-
debugShowCheckedModeBanner: false,
93-
theme: theme.themeData,
94-
localizationsDelegates: context.localizationDelegates,
95-
supportedLocales: context.supportedLocales,
96-
locale: locale,
97-
navigatorKey: AppGlobals.rootNavKey,
98-
home: child,
99-
);
100-
},
101-
);
102-
},
103-
);
85+
return MultiProvider(
86+
providers: [
87+
Provider.value(value: theme),
88+
Provider.value(value: locale),
89+
],
90+
builder: (context, _) {
91+
return MaterialApp(
92+
builder: overlayManagerBuilder(),
93+
debugShowCheckedModeBanner: false,
94+
theme: theme.themeData,
95+
localizationsDelegates: context.localizationDelegates,
96+
supportedLocales: context.supportedLocales,
97+
locale: locale,
98+
navigatorKey: AppGlobals.rootNavKey,
99+
home: child,
100+
);
101+
},
102+
);
103+
},
104+
);
105+
}
104106
}
105107

106108
class AppGlobals {

frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class _GridCellContext<T, D> extends Equatable {
105105
final FieldService _fieldService;
106106

107107
late final CellListener _cellListener;
108-
late final ValueNotifier<T?> _cellDataNotifier;
108+
late final ValueNotifier<T?>? _cellDataNotifier;
109109
bool isListening = false;
110110
VoidCallback? _onFieldChangedFn;
111111
Timer? _loadDataOperation;
@@ -163,19 +163,19 @@ class _GridCellContext<T, D> extends Equatable {
163163
}
164164

165165
onCellChangedFn() {
166-
onCellChanged(_cellDataNotifier.value);
166+
onCellChanged(_cellDataNotifier?.value);
167167

168168
if (cellDataLoader.config.reloadOnCellChanged) {
169169
_loadData();
170170
}
171171
}
172172

173-
_cellDataNotifier.addListener(onCellChangedFn);
173+
_cellDataNotifier?.addListener(onCellChangedFn);
174174
return onCellChangedFn;
175175
}
176176

177177
void removeListener(VoidCallback fn) {
178-
_cellDataNotifier.removeListener(fn);
178+
_cellDataNotifier?.removeListener(fn);
179179
}
180180

181181
T? getCellData({bool loadIfNoCache = true}) {
@@ -211,7 +211,7 @@ class _GridCellContext<T, D> extends Equatable {
211211
_loadDataOperation?.cancel();
212212
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
213213
cellDataLoader.loadData().then((data) {
214-
_cellDataNotifier.value = data;
214+
_cellDataNotifier?.value = data;
215215
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
216216
});
217217
});

frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ import 'dart:io' show Platform;
22

33
import 'package:app_flowy/startup/startup.dart';
44
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
5-
import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
5+
import 'package:app_flowy/workspace/presentation/home/toast.dart';
66
import 'package:flowy_infra/theme.dart';
77
import 'package:flowy_sdk/log.dart';
88
import 'package:flutter/material.dart';
99
import 'package:flutter_bloc/flutter_bloc.dart';
1010
import 'package:provider/provider.dart';
1111
import 'package:time/time.dart';
12-
import 'package:fluttertoast/fluttertoast.dart';
13-
1412
import 'package:app_flowy/plugin/plugin.dart';
1513
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
1614
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
@@ -22,8 +20,6 @@ import 'package:flowy_infra/notifier.dart';
2220

2321
typedef NavigationCallback = void Function(String id);
2422

25-
late FToast fToast;
26-
2723
class HomeStack extends StatelessWidget {
2824
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
2925
// final Size size;
@@ -74,8 +70,7 @@ class _FadingIndexedStackState extends State<FadingIndexedStack> {
7470
@override
7571
void initState() {
7672
super.initState();
77-
fToast = FToast();
78-
fToast.init(HomeScreen.scaffoldKey.currentState!.context);
73+
initToastWithContext(context);
7974
}
8075

8176
@override
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import 'package:app_flowy/startup/startup.dart';
2+
import 'package:flowy_infra_ui/style_widget/text.dart';
3+
import 'package:flutter/material.dart';
4+
import 'package:fluttertoast/fluttertoast.dart';
5+
6+
class FlowyMessageToast extends StatelessWidget {
7+
final String message;
8+
const FlowyMessageToast({required this.message, Key? key}) : super(key: key);
9+
10+
@override
11+
Widget build(BuildContext context) {
12+
return Container(
13+
child: Padding(
14+
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
15+
child: FlowyText.medium(message, color: Colors.white),
16+
),
17+
decoration: const BoxDecoration(
18+
borderRadius: BorderRadius.all(Radius.circular(4)),
19+
color: Colors.black,
20+
),
21+
);
22+
}
23+
}
24+
25+
void initToastWithContext(BuildContext context) {
26+
getIt<FToast>().init(context);
27+
}
28+
29+
void showMessageToast(String message) {
30+
final child = FlowyMessageToast(message: message);
31+
32+
getIt<FToast>().showToast(
33+
child: child,
34+
gravity: ToastGravity.BOTTOM,
35+
toastDuration: const Duration(seconds: 3),
36+
);
37+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import 'package:flowy_infra_ui/style_widget/hover.dart';
2+
import 'package:flutter/widgets.dart';
3+
import 'package:flowy_infra/theme.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:provider/provider.dart';
6+
import 'package:flowy_infra/size.dart';
7+
import 'package:styled_widget/styled_widget.dart';
8+
9+
class GridCellAccessoryBuildContext {
10+
final BuildContext anchorContext;
11+
12+
GridCellAccessoryBuildContext({required this.anchorContext});
13+
}
14+
15+
abstract class GridCellAccessory implements Widget {
16+
void onTap();
17+
}
18+
19+
typedef AccessoryBuilder = List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext);
20+
21+
abstract class AccessoryWidget extends Widget {
22+
const AccessoryWidget({Key? key}) : super(key: key);
23+
24+
// The hover will show if the onFocus's value is true
25+
ValueNotifier<bool>? get isFocus;
26+
27+
AccessoryBuilder? get accessoryBuilder;
28+
}
29+
30+
class AccessoryHover extends StatefulWidget {
31+
final AccessoryWidget child;
32+
final EdgeInsets contentPadding;
33+
const AccessoryHover({
34+
required this.child,
35+
this.contentPadding = EdgeInsets.zero,
36+
Key? key,
37+
}) : super(key: key);
38+
39+
@override
40+
State<AccessoryHover> createState() => _AccessoryHoverState();
41+
}
42+
43+
class _AccessoryHoverState extends State<AccessoryHover> {
44+
late AccessoryHoverState _hoverState;
45+
VoidCallback? _listenerFn;
46+
47+
@override
48+
void initState() {
49+
_hoverState = AccessoryHoverState();
50+
_listenerFn = () => _hoverState.isFocus = widget.child.isFocus?.value ?? false;
51+
widget.child.isFocus?.addListener(_listenerFn!);
52+
53+
super.initState();
54+
}
55+
56+
@override
57+
void dispose() {
58+
_hoverState.dispose();
59+
60+
if (_listenerFn != null) {
61+
widget.child.isFocus?.removeListener(_listenerFn!);
62+
_listenerFn = null;
63+
}
64+
super.dispose();
65+
}
66+
67+
@override
68+
Widget build(BuildContext context) {
69+
List<Widget> children = [
70+
const _Background(),
71+
Padding(padding: widget.contentPadding, child: widget.child),
72+
];
73+
74+
final accessoryBuilder = widget.child.accessoryBuilder;
75+
if (accessoryBuilder != null) {
76+
final accessories = accessoryBuilder((GridCellAccessoryBuildContext(anchorContext: context)));
77+
children.add(
78+
Padding(
79+
padding: const EdgeInsets.only(right: 6),
80+
child: AccessoryContainer(accessories: accessories),
81+
).positioned(right: 0),
82+
);
83+
}
84+
85+
return ChangeNotifierProvider.value(
86+
value: _hoverState,
87+
child: MouseRegion(
88+
cursor: SystemMouseCursors.click,
89+
opaque: false,
90+
onEnter: (p) => setState(() => _hoverState.onHover = true),
91+
onExit: (p) => setState(() => _hoverState.onHover = false),
92+
child: Stack(
93+
fit: StackFit.loose,
94+
alignment: AlignmentDirectional.center,
95+
children: children,
96+
),
97+
),
98+
);
99+
}
100+
}
101+
102+
class AccessoryHoverState extends ChangeNotifier {
103+
bool _onHover = false;
104+
bool _isFocus = false;
105+
106+
set onHover(bool value) {
107+
if (_onHover != value) {
108+
_onHover = value;
109+
notifyListeners();
110+
}
111+
}
112+
113+
bool get onHover => _onHover;
114+
115+
set isFocus(bool value) {
116+
if (_isFocus != value) {
117+
_isFocus = value;
118+
notifyListeners();
119+
}
120+
}
121+
122+
bool get isFocus => _isFocus;
123+
}
124+
125+
class _Background extends StatelessWidget {
126+
const _Background({Key? key}) : super(key: key);
127+
128+
@override
129+
Widget build(BuildContext context) {
130+
final theme = context.watch<AppTheme>();
131+
return Consumer<AccessoryHoverState>(
132+
builder: (context, state, child) {
133+
if (state.onHover || state.isFocus) {
134+
return FlowyHoverContainer(
135+
style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
136+
);
137+
} else {
138+
return const SizedBox();
139+
}
140+
},
141+
);
142+
}
143+
}
144+
145+
class AccessoryContainer extends StatelessWidget {
146+
final List<GridCellAccessory> accessories;
147+
const AccessoryContainer({required this.accessories, Key? key}) : super(key: key);
148+
149+
@override
150+
Widget build(BuildContext context) {
151+
final theme = context.watch<AppTheme>();
152+
final children = accessories.map((accessory) {
153+
final hover = FlowyHover(
154+
style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface),
155+
builder: (_, onHover) => Container(
156+
width: 26,
157+
height: 26,
158+
padding: const EdgeInsets.all(3),
159+
child: accessory,
160+
),
161+
);
162+
return GestureDetector(
163+
child: hover,
164+
behavior: HitTestBehavior.opaque,
165+
onTap: () => accessory.onTap(),
166+
);
167+
}).toList();
168+
169+
return Wrap(children: children, spacing: 6);
170+
}
171+
}

0 commit comments

Comments
 (0)