[flutter]: fix some bugs

This commit is contained in:
appflowy 2021-11-02 13:00:27 +08:00
parent 4697acde48
commit 6c1bc2cbd9
10 changed files with 210 additions and 193 deletions

View File

@ -28,7 +28,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
final viewOrFailed = await appManager.createView(name: value.name, desc: value.desc, viewType: value.viewType);
yield viewOrFailed.fold(
(view) => state.copyWith(
selectedView: view,
latestCreatedView: view,
successOrFailure: left(unit),
),
(error) {
@ -71,12 +71,12 @@ class AppBloc extends Bloc<AppEvent, AppState> {
}
Stream<AppState> handleDidReceiveViews(List<View> views) async* {
final selectedView = state.selectedView;
final latestCreatedView = state.latestCreatedView;
AppState newState = state.copyWith(views: views);
if (selectedView != null) {
final index = views.indexWhere((element) => element.id == selectedView.id);
if (latestCreatedView != null) {
final index = views.indexWhere((element) => element.id == latestCreatedView.id);
if (index == -1) {
newState = newState.copyWith(selectedView: null);
newState = newState.copyWith(latestCreatedView: null);
}
}
@ -111,7 +111,7 @@ class AppState with _$AppState {
required App app,
required bool isLoading,
required List<View>? views,
View? selectedView,
View? latestCreatedView,
required Either<Unit, WorkspaceError> successOrFailure,
}) = _AppState;
@ -119,7 +119,7 @@ class AppState with _$AppState {
app: app,
isLoading: false,
views: null,
selectedView: null,
latestCreatedView: null,
successOrFailure: left(unit),
);
}

View File

@ -873,13 +873,13 @@ class _$AppStateTearOff {
{required App app,
required bool isLoading,
required List<View>? views,
View? selectedView,
View? latestCreatedView,
required Either<Unit, WorkspaceError> successOrFailure}) {
return _AppState(
app: app,
isLoading: isLoading,
views: views,
selectedView: selectedView,
latestCreatedView: latestCreatedView,
successOrFailure: successOrFailure,
);
}
@ -893,7 +893,7 @@ mixin _$AppState {
App get app => throw _privateConstructorUsedError;
bool get isLoading => throw _privateConstructorUsedError;
List<View>? get views => throw _privateConstructorUsedError;
View? get selectedView => throw _privateConstructorUsedError;
View? get latestCreatedView => throw _privateConstructorUsedError;
Either<Unit, WorkspaceError> get successOrFailure =>
throw _privateConstructorUsedError;
@ -910,7 +910,7 @@ abstract class $AppStateCopyWith<$Res> {
{App app,
bool isLoading,
List<View>? views,
View? selectedView,
View? latestCreatedView,
Either<Unit, WorkspaceError> successOrFailure});
}
@ -927,7 +927,7 @@ class _$AppStateCopyWithImpl<$Res> implements $AppStateCopyWith<$Res> {
Object? app = freezed,
Object? isLoading = freezed,
Object? views = freezed,
Object? selectedView = freezed,
Object? latestCreatedView = freezed,
Object? successOrFailure = freezed,
}) {
return _then(_value.copyWith(
@ -943,9 +943,9 @@ class _$AppStateCopyWithImpl<$Res> implements $AppStateCopyWith<$Res> {
? _value.views
: views // ignore: cast_nullable_to_non_nullable
as List<View>?,
selectedView: selectedView == freezed
? _value.selectedView
: selectedView // ignore: cast_nullable_to_non_nullable
latestCreatedView: latestCreatedView == freezed
? _value.latestCreatedView
: latestCreatedView // ignore: cast_nullable_to_non_nullable
as View?,
successOrFailure: successOrFailure == freezed
? _value.successOrFailure
@ -964,7 +964,7 @@ abstract class _$AppStateCopyWith<$Res> implements $AppStateCopyWith<$Res> {
{App app,
bool isLoading,
List<View>? views,
View? selectedView,
View? latestCreatedView,
Either<Unit, WorkspaceError> successOrFailure});
}
@ -982,7 +982,7 @@ class __$AppStateCopyWithImpl<$Res> extends _$AppStateCopyWithImpl<$Res>
Object? app = freezed,
Object? isLoading = freezed,
Object? views = freezed,
Object? selectedView = freezed,
Object? latestCreatedView = freezed,
Object? successOrFailure = freezed,
}) {
return _then(_AppState(
@ -998,9 +998,9 @@ class __$AppStateCopyWithImpl<$Res> extends _$AppStateCopyWithImpl<$Res>
? _value.views
: views // ignore: cast_nullable_to_non_nullable
as List<View>?,
selectedView: selectedView == freezed
? _value.selectedView
: selectedView // ignore: cast_nullable_to_non_nullable
latestCreatedView: latestCreatedView == freezed
? _value.latestCreatedView
: latestCreatedView // ignore: cast_nullable_to_non_nullable
as View?,
successOrFailure: successOrFailure == freezed
? _value.successOrFailure
@ -1017,7 +1017,7 @@ class _$_AppState implements _AppState {
{required this.app,
required this.isLoading,
required this.views,
this.selectedView,
this.latestCreatedView,
required this.successOrFailure});
@override
@ -1027,13 +1027,13 @@ class _$_AppState implements _AppState {
@override
final List<View>? views;
@override
final View? selectedView;
final View? latestCreatedView;
@override
final Either<Unit, WorkspaceError> successOrFailure;
@override
String toString() {
return 'AppState(app: $app, isLoading: $isLoading, views: $views, selectedView: $selectedView, successOrFailure: $successOrFailure)';
return 'AppState(app: $app, isLoading: $isLoading, views: $views, latestCreatedView: $latestCreatedView, successOrFailure: $successOrFailure)';
}
@override
@ -1047,9 +1047,9 @@ class _$_AppState implements _AppState {
.equals(other.isLoading, isLoading)) &&
(identical(other.views, views) ||
const DeepCollectionEquality().equals(other.views, views)) &&
(identical(other.selectedView, selectedView) ||
(identical(other.latestCreatedView, latestCreatedView) ||
const DeepCollectionEquality()
.equals(other.selectedView, selectedView)) &&
.equals(other.latestCreatedView, latestCreatedView)) &&
(identical(other.successOrFailure, successOrFailure) ||
const DeepCollectionEquality()
.equals(other.successOrFailure, successOrFailure)));
@ -1061,7 +1061,7 @@ class _$_AppState implements _AppState {
const DeepCollectionEquality().hash(app) ^
const DeepCollectionEquality().hash(isLoading) ^
const DeepCollectionEquality().hash(views) ^
const DeepCollectionEquality().hash(selectedView) ^
const DeepCollectionEquality().hash(latestCreatedView) ^
const DeepCollectionEquality().hash(successOrFailure);
@JsonKey(ignore: true)
@ -1075,7 +1075,7 @@ abstract class _AppState implements AppState {
{required App app,
required bool isLoading,
required List<View>? views,
View? selectedView,
View? latestCreatedView,
required Either<Unit, WorkspaceError> successOrFailure}) = _$_AppState;
@override
@ -1085,7 +1085,7 @@ abstract class _AppState implements AppState {
@override
List<View>? get views => throw _privateConstructorUsedError;
@override
View? get selectedView => throw _privateConstructorUsedError;
View? get latestCreatedView => throw _privateConstructorUsedError;
@override
Either<Unit, WorkspaceError> get successOrFailure =>
throw _privateConstructorUsedError;

View File

@ -8,16 +8,19 @@ import 'widget/style_widgets/style_widgets.dart';
DefaultStyles customStyles(BuildContext context) {
const baseSpacing = Tuple2<double, double>(6, 0);
final theme = context.watch<AppTheme>();
final themeData = theme.themeData;
final fontFamily = makeFontFamily(themeData);
final defaultTextStyle = DefaultTextStyle.of(context);
final baseStyle = defaultTextStyle.style.copyWith(
fontSize: 18,
height: 1.3,
fontWeight: FontWeight.w300,
letterSpacing: 0.6,
fontFamily: fontFamily,
);
final theme = context.watch<AppTheme>();
final themeData = theme.themeData;
final fontFamily = makeFontFamily(themeData);
return DefaultStyles(
h1: DefaultTextBlockStyle(
@ -111,7 +114,7 @@ String makeFontFamily(ThemeData themeData) {
switch (themeData.platform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
fontFamily = 'Menlo';
fontFamily = 'Mulish';
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:

View File

@ -5,8 +5,10 @@ import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:expandable/expandable.dart';
import 'package:flowy_infra/time/duration.dart';
@ -58,7 +60,8 @@ class HomeMenu extends StatelessWidget {
return MultiBlocProvider(
providers: [
BlocProvider<MenuBloc>(
create: (context) => getIt<MenuBloc>(param1: user, param2: workspaceId)..add(const MenuEvent.initial())),
create: (context) => getIt<MenuBloc>(param1: user, param2: workspaceId)..add(const MenuEvent.initial()),
),
],
child: MultiBlocListener(
listeners: [
@ -91,7 +94,7 @@ class HomeMenu extends StatelessWidget {
children: [
_renderTopBar(context),
const VSpace(32),
_renderMenuList(context),
_renderApps(context),
],
).padding(horizontal: Insets.l),
),
@ -104,9 +107,41 @@ class HomeMenu extends StatelessWidget {
);
}
Widget _renderMenuList(BuildContext context) {
return MenuList(
menuItems: buildMenuItems(context.read<MenuBloc>().state.apps),
Widget _renderTopBar(BuildContext context) {
return const MenuTopBar();
}
Widget _renderApps(BuildContext context) {
final apps = context.read<MenuBloc>().state.apps;
List<Widget> menuItems = [];
menuItems.add(MenuUser(user));
List<MenuApp> appWidgets = apps.foldRight([], (apps, _) => apps.map((app) => MenuApp(app)).toList());
menuItems.addAll(appWidgets);
return ExpandableTheme(
data: ExpandableThemeData(useInkWell: true, animationDuration: Durations.medium),
child: Expanded(
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: ChangeNotifierProvider(
create: (_) => MenuSharedState(),
child: ListView.separated(
itemCount: menuItems.length,
separatorBuilder: (context, index) {
if (index == 0) {
return const VSpace(29);
} else {
return VSpace(MenuAppSizes.appVPadding);
}
},
physics: StyledScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return menuItems[index];
},
),
),
),
),
);
}
@ -119,62 +154,40 @@ class HomeMenu extends StatelessWidget {
press: (appName) => context.read<MenuBloc>().add(MenuEvent.createApp(appName, desc: "")),
);
}
}
Widget _renderTopBar(BuildContext context) {
return const MenuTopBar();
class MenuSharedState extends ChangeNotifier {
View? _view;
View? _forcedOpenView;
void addForcedOpenViewListener(void Function(View) callback) {
super.addListener(() {
if (_forcedOpenView != null) {
callback(_forcedOpenView!);
}
});
}
List<MenuItem> buildMenuItems(Option<List<App>> apps) {
List<MenuItem> items = [];
items.add(MenuUser(user));
List<MenuItem> appWidgets = apps.foldRight([], (apps, _) => apps.map((app) => MenuApp(app)).toList());
items.addAll(appWidgets);
return items;
void addSelectedViewListener(void Function(View?) callback) {
super.addListener(() {
callback(_view);
});
}
}
enum MenuItemType {
userProfile,
dashboard,
favorites,
app,
}
abstract class MenuItem extends StatelessWidget {
const MenuItem({Key? key}) : super(key: key);
MenuItemType get type;
}
class MenuList extends StatelessWidget {
final List<MenuItem> menuItems;
const MenuList({required this.menuItems, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ExpandableTheme(
data: ExpandableThemeData(useInkWell: true, animationDuration: Durations.medium),
child: Expanded(
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: ListView.separated(
itemCount: menuItems.length,
separatorBuilder: (context, index) {
if (index == 0) {
return const VSpace(29);
} else {
return VSpace(MenuAppSizes.appVPadding);
}
},
physics: StyledScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return menuItems[index];
},
),
),
),
);
set forcedOpenView(View? view) {
if (_forcedOpenView != view) {
_forcedOpenView = view;
selectedView = view;
notifyListeners();
}
}
set selectedView(View? view) {
if (_view != view) {
_view = view;
notifyListeners();
}
}
View? get selecedtView => _view;
}

View File

@ -66,30 +66,35 @@ class MenuAppHeader extends StatelessWidget {
}
Widget _renderTitle(BuildContext context) {
return BlocSelector<AppBloc, AppState, App>(
selector: (state) => state.app,
builder: (context, state) {
return Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
ExpandableController.of(context, rebuildOnChange: false, required: true)?.toggle();
},
onSecondaryTap: () {
final actionList = AppDisclosureActions(onSelected: (action) => _handleAction(context, action));
actionList.show(
context,
context,
anchorDirection: AnchorDirection.bottomWithCenterAligned,
);
},
child: FlowyText.medium(
state.name,
return Expanded(
child: BlocListener<AppBloc, AppState>(
listenWhen: (p, c) => (p.latestCreatedView == null && c.latestCreatedView != null),
listener: (context, state) {
final expandableController = ExpandableController.of(context, rebuildOnChange: false, required: true)!;
if (!expandableController.expanded) {
expandableController.toggle();
}
},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => ExpandableController.of(context, rebuildOnChange: false, required: true)?.toggle(),
onSecondaryTap: () {
final actionList = AppDisclosureActions(onSelected: (action) => _handleAction(context, action));
actionList.show(
context,
context,
anchorDirection: AnchorDirection.bottomWithCenterAligned,
);
},
child: BlocSelector<AppBloc, AppState, App>(
selector: (state) => state.app,
builder: (context, app) => FlowyText.medium(
app.name,
fontSize: 12,
),
),
);
},
),
),
);
}

View File

@ -1,3 +1,4 @@
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/header/header.dart';
import 'package:expandable/expandable.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
@ -6,24 +7,28 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/app/app_bloc.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'section/section.dart';
class MenuApp extends MenuItem {
class MenuApp extends StatefulWidget {
final App app;
final notifier = AppDataNotifier();
MenuApp(this.app, {Key? key}) : super(key: ValueKey("${app.id}${app.version}"));
@override
State<MenuApp> createState() => _MenuAppState();
}
class _MenuAppState extends State<MenuApp> {
final notifier = AppDataNotifier();
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AppBloc>(
create: (context) {
final appBloc = getIt<AppBloc>(param1: app);
final appBloc = getIt<AppBloc>(param1: widget.app);
appBloc.add(const AppEvent.initial());
return appBloc;
},
@ -31,7 +36,9 @@ class MenuApp extends MenuItem {
],
child: BlocSelector<AppBloc, AppState, AppDataNotifier>(
selector: (state) {
notifier.selectView = state.selectedView;
if (state.latestCreatedView != null) {
Provider.of<MenuSharedState>(context, listen: false).forcedOpenView = state.latestCreatedView;
}
notifier.views = state.views;
return notifier;
},
@ -58,7 +65,7 @@ class MenuApp extends MenuItem {
iconPadding: EdgeInsets.zero,
hasIcon: false,
),
header: MenuAppHeader(app),
header: MenuAppHeader(widget.app),
expanded: child,
collapsed: const SizedBox(),
),
@ -76,9 +83,6 @@ class MenuApp extends MenuItem {
}),
);
}
@override
MenuItemType get type => MenuItemType.app;
}
class MenuAppSizes {
@ -93,7 +97,6 @@ class MenuAppSizes {
class AppDataNotifier extends ChangeNotifier {
List<View> _views = [];
View? _selectedView;
AppDataNotifier();
set views(List<View>? items) {
@ -111,14 +114,5 @@ class AppDataNotifier extends ChangeNotifier {
}
}
set selectView(View? view) {
if (_selectedView != view) {
_selectedView = view;
notifyListeners();
}
}
get selectedView => _selectedView;
List<View> get views => _views;
}

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/domain/view_ext.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/menu_app.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flutter/foundation.dart';
@ -11,58 +12,6 @@ import 'package:styled_widget/styled_widget.dart';
import 'item.dart';
import 'package:async/async.dart';
class ViewSectionNotifier with ChangeNotifier {
List<View> _views;
View? _selectedView;
CancelableOperation? _notifyListenerOperation;
ViewSectionNotifier(List<View> views) : _views = views;
set views(List<View> views) {
if (_views != views) {
_views = views;
_notifyListeners();
}
}
List<View> get views => _views;
set selectView(View? view) {
if (_selectedView == view) {
return;
}
if (view != null) {
_selectedView = view;
WidgetsBinding.instance?.addPostFrameCallback((_) {
getIt<HomeStackManager>().setStack(view.stackContext());
});
_notifyListeners();
} else {
// WidgetsBinding.instance?.addPostFrameCallback((_) {
// getIt<HomeStackManager>().setStack(BlankStackContext());
// });
}
}
View? get selectedView => _selectedView;
void update(AppDataNotifier notifier) {
views = notifier.views;
selectView = notifier.selectedView;
}
void _notifyListeners() {
_notifyListenerOperation?.cancel();
_notifyListenerOperation = CancelableOperation.fromFuture(
Future.delayed(const Duration(milliseconds: 30), () {}),
).then((_) {
notifyListeners();
});
}
}
class ViewSection extends StatelessWidget {
const ViewSection({Key? key}) : super(key: key);
@ -72,7 +21,7 @@ class ViewSection extends StatelessWidget {
return ChangeNotifierProxyProvider<AppDataNotifier, ViewSectionNotifier>(
create: (_) {
final views = Provider.of<AppDataNotifier>(context, listen: false).views;
return ViewSectionNotifier(views);
return ViewSectionNotifier(views, context);
},
update: (_, notifier, controller) => controller!..update(notifier),
child: Consumer(builder: (context, ViewSectionNotifier notifier, child) {
@ -87,7 +36,8 @@ class ViewSection extends StatelessWidget {
view: view,
isSelected: _isViewSelected(context, view.id),
onSelected: (view) {
context.read<ViewSectionNotifier>().selectView = view;
context.read<ViewSectionNotifier>().selectedView = view;
Provider.of<MenuSharedState>(context, listen: false).selectedView = view;
},
).padding(vertical: 4),
);
@ -103,3 +53,61 @@ class ViewSection extends StatelessWidget {
return view.id == viewId;
}
}
class ViewSectionNotifier with ChangeNotifier {
List<View> _views;
View? _selectedView;
CancelableOperation? _notifyListenerOperation;
ViewSectionNotifier(List<View> views, BuildContext context) : _views = views {
final menuSharedState = Provider.of<MenuSharedState>(context, listen: false);
menuSharedState.addForcedOpenViewListener((forcedOpenView) {
selectedView = forcedOpenView;
});
menuSharedState.addSelectedViewListener((currentSelectedView) {
if (currentSelectedView != selectedView) {
selectedView = null;
}
});
}
set views(List<View> views) {
if (_views != views) {
_views = views;
_notifyListeners();
}
}
List<View> get views => _views;
set selectedView(View? view) {
if (_selectedView == view) {
return;
}
_selectedView = view;
_notifyListeners();
if (view != null) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
getIt<HomeStackManager>().setStack(view.stackContext());
});
} else {
// do nothing
}
}
View? get selectedView => _selectedView;
void update(AppDataNotifier notifier) {
views = notifier.views;
}
void _notifyListeners() {
_notifyListenerOperation?.cancel();
_notifyListenerOperation = CancelableOperation.fromFuture(
Future.delayed(const Duration(milliseconds: 30), () {}),
).then((_) {
notifyListeners();
});
}
}

View File

@ -1,14 +1,10 @@
import 'package:flutter/material.dart';
import '../../menu.dart';
class FavoriteHeader extends MenuItem {
class FavoriteHeader extends StatelessWidget {
const FavoriteHeader({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
throw UnimplementedError();
}
@override
MenuItemType get type => MenuItemType.favorites;
}

View File

@ -1,6 +1,5 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/menu/menu_user_bloc.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
@ -9,11 +8,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
class MenuUser extends MenuItem {
class MenuUser extends StatelessWidget {
final UserProfile user;
MenuUser(this.user, {Key? key}) : super(key: ValueKey(user.id));
@override
MenuItemType get type => MenuItemType.userProfile;
@override
Widget build(BuildContext context) {

View File

@ -43,6 +43,7 @@ class FlowyText extends StatelessWidget {
color: textColor,
fontWeight: fontWeight,
fontSize: fontSize + 2,
fontFamily: 'Mulish',
));
}
}