chore: optimize row card page UI (#5995)

* chore: adjust buttons padding in row record page

* fix: disable more button in row page

* fix: upload image button ui on mobile

* fix: embed link button ui on mobile

* fix: add missing border for ai text field and ai translate field

* fix: delete AI can make mistakes on mobile

* chore: disable sentry

* fix: invite error toast

* fix: add member limit hint text in invite member screen

* feat: show toast after opening workspace on mobile

* chore: remove sentry

* chore: filter row page in recent views

* feat: support display field name as row page title

* chore: remove scroll bar on home page

* chore: remove legacy code

* chore: optimize mobile speed

* Revert "chore: remove sentry"

This reverts commit 73b45e2590.

* fix: reduce document page rebuild time

* chore: improve tooltip style
This commit is contained in:
Lucas.Xu 2024-08-19 11:06:34 +08:00 committed by GitHub
parent e460120a1c
commit 7113269802
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 619 additions and 295 deletions

View File

@ -18,14 +18,25 @@ extension MobileRouter on BuildContext {
ViewPB view, { ViewPB view, {
Map<String, dynamic>? arguments, Map<String, dynamic>? arguments,
bool addInRecent = true, bool addInRecent = true,
bool showMoreButton = true,
String? fixedTitle,
}) async { }) async {
// set the current view before pushing the new view // set the current view before pushing the new view
getIt<MenuSharedState>().latestOpenView = view; getIt<MenuSharedState>().latestOpenView = view;
unawaited(getIt<CachedRecentService>().updateRecentViews([view.id], true)); unawaited(getIt<CachedRecentService>().updateRecentViews([view.id], true));
final queryParameters = view.queryParameters(arguments);
if (view.layout == ViewLayoutPB.Document) {
queryParameters[MobileDocumentScreen.viewShowMoreButton] =
showMoreButton.toString();
if (fixedTitle != null) {
queryParameters[MobileDocumentScreen.viewFixedTitle] = fixedTitle;
}
}
final uri = Uri( final uri = Uri(
path: view.routeName, path: view.routeName,
queryParameters: view.queryParameters(arguments), queryParameters: queryParameters,
).toString(); ).toString();
await push(uri); await push(uri);
} }

View File

@ -1,7 +1,5 @@
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
import 'package:appflowy/plugins/document/application/document_listener.dart'; import 'package:appflowy/plugins/document/application/document_listener.dart';
import 'package:appflowy/plugins/document/application/document_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart';
@ -113,7 +111,6 @@ class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
); );
} }
final _service = DocumentService();
final ViewPB view; final ViewPB view;
final DocumentListener _documentListener; final DocumentListener _documentListener;
final ViewListener _viewListener; final ViewListener _viewListener;
@ -124,16 +121,6 @@ class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
// for the version under 0.5.5 // for the version under 0.5.5
Future<(CoverType, String?)> getCoverV1() async { Future<(CoverType, String?)> getCoverV1() async {
final result = await _service.getDocument(documentId: view.id);
final document = result.fold((s) => s.toDocument(), (f) => null);
if (document != null) {
final coverType = CoverType.fromString(
document.root.attributes[DocumentHeaderBlockKeys.coverType],
);
final coverValue = document
.root.attributes[DocumentHeaderBlockKeys.coverDetails] as String?;
return (coverType, coverValue);
}
return (CoverType.none, null); return (CoverType.none, null);
} }

View File

@ -3,6 +3,7 @@ import 'package:appflowy/mobile/application/base/mobile_view_page_bloc.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart'; import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart'; import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart';
import 'package:appflowy/plugins/document/presentation/document_collaborators.dart'; import 'package:appflowy/plugins/document/presentation/document_collaborators.dart';
import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/shared/feature_flags.dart';
@ -26,6 +27,8 @@ class MobileViewPage extends StatefulWidget {
required this.viewLayout, required this.viewLayout,
this.title, this.title,
this.arguments, this.arguments,
this.fixedTitle,
this.showMoreButton = true,
}); });
/// view id /// view id
@ -33,6 +36,10 @@ class MobileViewPage extends StatefulWidget {
final ViewLayoutPB viewLayout; final ViewLayoutPB viewLayout;
final String? title; final String? title;
final Map<String, dynamic>? arguments; final Map<String, dynamic>? arguments;
final bool showMoreButton;
// only used in row page
final String? fixedTitle;
@override @override
State<MobileViewPage> createState() => _MobileViewPageState(); State<MobileViewPage> createState() => _MobileViewPageState();
@ -163,6 +170,9 @@ class _MobileViewPageState extends State<MobileViewPage> {
return plugin.widgetBuilder.buildWidget( return plugin.widgetBuilder.buildWidget(
shrinkWrap: false, shrinkWrap: false,
context: PluginContext(userProfile: state.userProfilePB), context: PluginContext(userProfile: state.userProfilePB),
data: {
MobileDocumentScreen.viewFixedTitle: widget.fixedTitle,
},
); );
}, },
(error) { (error) {
@ -215,6 +225,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
]); ]);
} }
if (widget.showMoreButton) {
actions.addAll([ actions.addAll([
MobileViewPageMoreButton( MobileViewPageMoreButton(
view: view, view: view,
@ -222,6 +233,11 @@ class _MobileViewPageState extends State<MobileViewPage> {
appBarOpacity: _appBarOpacity, appBarOpacity: _appBarOpacity,
), ),
]); ]);
} else {
actions.addAll([
const HSpace(18.0),
]);
}
return actions; return actions;
} }
@ -241,7 +257,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
], ],
Expanded( Expanded(
child: FlowyText.medium( child: FlowyText.medium(
view?.name ?? widget.title ?? '', widget.fixedTitle ?? view?.name ?? widget.title ?? '',
fontSize: 15.0, fontSize: 15.0,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
figmaLineHeight: 18.0, figmaLineHeight: 18.0,

View File

@ -295,6 +295,7 @@ class MobileRowDetailPageContentState
RowCache get rowCache => widget.databaseController.rowCache; RowCache get rowCache => widget.databaseController.rowCache;
FieldController get fieldController => FieldController get fieldController =>
widget.databaseController.fieldController; widget.databaseController.fieldController;
ValueNotifier<String> primaryFieldId = ValueNotifier('');
@override @override
void initState() { void initState() {
@ -327,7 +328,13 @@ class MobileRowDetailPageContentState
fieldController: fieldController, fieldController: fieldController,
rowMeta: rowController.rowMeta, rowMeta: rowController.rowMeta,
)..add(const RowBannerEvent.initial()), )..add(const RowBannerEvent.initial()),
child: BlocBuilder<RowBannerBloc, RowBannerState>( child: BlocConsumer<RowBannerBloc, RowBannerState>(
listener: (context, state) {
if (state.primaryField == null) {
return;
}
primaryFieldId.value = state.primaryField!.id;
},
builder: (context, state) { builder: (context, state) {
if (state.primaryField == null) { if (state.primaryField == null) {
return const SizedBox.shrink(); return const SizedBox.shrink();
@ -367,8 +374,22 @@ class MobileRowDetailPageContentState
if (rowDetailState.numHiddenFields != 0) ...[ if (rowDetailState.numHiddenFields != 0) ...[
const ToggleHiddenFieldsVisibilityButton(), const ToggleHiddenFieldsVisibilityButton(),
], ],
OpenRowPageButton( const VSpace(8.0),
ValueListenableBuilder(
valueListenable: primaryFieldId,
builder: (context, primaryFieldId, child) {
if (primaryFieldId.isEmpty) {
return const SizedBox.shrink();
}
return OpenRowPageButton(
databaseController: widget.databaseController,
cellContext: CellContext(
rowId: rowController.rowId,
fieldId: primaryFieldId,
),
documentId: rowController.rowMeta.documentId, documentId: rowController.rowMeta.documentId,
);
},
), ),
MobileRowDetailCreateFieldButton( MobileRowDetailCreateFieldButton(
viewId: viewId, viewId: viewId,

View File

@ -22,7 +22,7 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minWidth: double.infinity, minWidth: double.infinity,
minHeight: GridSize.headerHeight, maxHeight: GridSize.headerHeight,
), ),
child: TextButton.icon( child: TextButton.icon(
style: Theme.of(context).textButtonTheme.style?.copyWith( style: Theme.of(context).textButtonTheme.style?.copyWith(
@ -37,7 +37,7 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory, splashFactory: NoSplash.splashFactory,
padding: const WidgetStatePropertyAll( padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(vertical: 14, horizontal: 6), EdgeInsets.symmetric(horizontal: 6, vertical: 2),
), ),
), ),
label: FlowyText.medium( label: FlowyText.medium(

View File

@ -3,6 +3,10 @@ import 'dart:async';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
@ -11,20 +15,33 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class OpenRowPageButton extends StatefulWidget { class OpenRowPageButton extends StatefulWidget {
const OpenRowPageButton({ const OpenRowPageButton({
super.key, super.key,
required this.documentId, required this.documentId,
required this.databaseController,
required this.cellContext,
}); });
final String documentId; final String documentId;
final DatabaseController databaseController;
final CellContext cellContext;
@override @override
State<OpenRowPageButton> createState() => _OpenRowPageButtonState(); State<OpenRowPageButton> createState() => _OpenRowPageButtonState();
} }
class _OpenRowPageButtonState extends State<OpenRowPageButton> { class _OpenRowPageButtonState extends State<OpenRowPageButton> {
late final cellBloc = TextCellBloc(
cellController: makeCellController(
widget.databaseController,
widget.cellContext,
).as(),
);
ViewPB? view; ViewPB? view;
@override @override
@ -36,10 +53,13 @@ class _OpenRowPageButtonState extends State<OpenRowPageButton> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<TextCellBloc, TextCellState>(
bloc: cellBloc,
builder: (context, state) {
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minWidth: double.infinity, minWidth: double.infinity,
minHeight: GridSize.headerHeight, maxHeight: GridSize.buttonHeight,
), ),
child: TextButton.icon( child: TextButton.icon(
style: Theme.of(context).textButtonTheme.style?.copyWith( style: Theme.of(context).textButtonTheme.style?.copyWith(
@ -54,7 +74,7 @@ class _OpenRowPageButtonState extends State<OpenRowPageButton> {
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory, splashFactory: NoSplash.splashFactory,
padding: const WidgetStatePropertyAll( padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(vertical: 14, horizontal: 6), EdgeInsets.symmetric(horizontal: 6),
), ),
), ),
label: FlowyText.medium( label: FlowyText.medium(
@ -68,12 +88,17 @@ class _OpenRowPageButtonState extends State<OpenRowPageButton> {
size: Size.square(16.0), size: Size.square(16.0),
), ),
), ),
onPressed: () => _openRowPage(context), onPressed: () {
final name = state.content;
_openRowPage(context, name);
},
), ),
); );
},
);
} }
Future<void> _openRowPage(BuildContext context) async { Future<void> _openRowPage(BuildContext context, String fieldName) async {
Log.info('Open row page(${widget.documentId})'); Log.info('Open row page(${widget.documentId})');
if (view == null) { if (view == null) {
@ -89,6 +114,8 @@ class _OpenRowPageButtonState extends State<OpenRowPageButton> {
await context.pushView( await context.pushView(
view!, view!,
addInRecent: false, addInRecent: false,
showMoreButton: false,
fixedTitle: fieldName,
); );
} }
} }

View File

@ -7,15 +7,21 @@ class MobileDocumentScreen extends StatelessWidget {
super.key, super.key,
required this.id, required this.id,
this.title, this.title,
this.showMoreButton = true,
this.fixedTitle,
}); });
/// view id /// view id
final String id; final String id;
final String? title; final String? title;
final bool showMoreButton;
final String? fixedTitle;
static const routeName = '/docs'; static const routeName = '/docs';
static const viewId = 'id'; static const viewId = 'id';
static const viewTitle = 'title'; static const viewTitle = 'title';
static const viewShowMoreButton = 'show_more_button';
static const viewFixedTitle = 'fixed_title';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -23,6 +29,8 @@ class MobileDocumentScreen extends StatelessWidget {
id: id, id: id,
title: title, title: title,
viewLayout: ViewLayoutPB.Document, viewLayout: ViewLayoutPB.Document,
showMoreButton: showMoreButton,
fixedTitle: fixedTitle,
); );
} }
} }

View File

@ -96,8 +96,7 @@ class _FavoriteViews extends StatelessWidget {
final borderColor = Theme.of(context).isLightMode final borderColor = Theme.of(context).isLightMode
? const Color(0xFFE9E9EC) ? const Color(0xFFE9E9EC)
: const Color(0x1AFFFFFF); : const Color(0x1AFFFFFF);
return Scrollbar( return ListView.separated(
child: ListView.separated(
key: const PageStorageKey('favorite_views_page_storage_key'), key: const PageStorageKey('favorite_views_page_storage_key'),
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: HomeSpaceViewSizes.mVerticalPadding + bottom: HomeSpaceViewSizes.mVerticalPadding +
@ -125,7 +124,6 @@ class _FavoriteViews extends StatelessWidget {
}, },
separatorBuilder: (context, index) => const HSpace(8), separatorBuilder: (context, index) => const HSpace(8),
itemCount: favoriteViews.length, itemCount: favoriteViews.length,
),
); );
} }
} }

View File

@ -25,8 +25,7 @@ class _MobileHomeSpaceState extends State<MobileHomeSpace>
final workspaceId = final workspaceId =
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ?? context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
''; '';
return Scrollbar( return SingleChildScrollView(
child: SingleChildScrollView(
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: HomeSpaceViewSizes.mVerticalPadding, top: HomeSpaceViewSizes.mVerticalPadding,
@ -39,7 +38,6 @@ class _MobileHomeSpaceState extends State<MobileHomeSpace>
showFavorite: false, showFavorite: false,
), ),
), ),
),
); );
} }
} }

View File

@ -35,6 +35,9 @@ class MobileFolders extends StatelessWidget {
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ?? context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
''; '';
return BlocListener<UserWorkspaceBloc, UserWorkspaceState>( return BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
listenWhen: (previous, current) =>
previous.currentWorkspace?.workspaceId !=
current.currentWorkspace?.workspaceId,
listener: (context, state) { listener: (context, state) {
context.read<SidebarSectionsBloc>().add( context.read<SidebarSectionsBloc>().add(
SidebarSectionsEvent.initial( SidebarSectionsEvent.initial(

View File

@ -1,8 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart'; import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart'; import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart';
import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart'; import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
@ -14,15 +16,19 @@ import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart'; import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:sentry/sentry.dart'; import 'package:sentry/sentry.dart';
import 'package:toastification/toastification.dart';
class MobileHomeScreen extends StatelessWidget { class MobileHomeScreen extends StatelessWidget {
const MobileHomeScreen({super.key}); const MobileHomeScreen({super.key});
@ -103,6 +109,8 @@ class MobileHomePage extends StatefulWidget {
} }
class _MobileHomePageState extends State<MobileHomePage> { class _MobileHomePageState extends State<MobileHomePage> {
Loading? loadingIndicator;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -147,11 +155,18 @@ class _MobileHomePageState extends State<MobileHomePage> {
} }
} }
class _HomePage extends StatelessWidget { class _HomePage extends StatefulWidget {
const _HomePage({required this.userProfile}); const _HomePage({required this.userProfile});
final UserProfilePB userProfile; final UserProfilePB userProfile;
@override
State<_HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<_HomePage> {
Loading? loadingIndicator;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>( return BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
@ -161,6 +176,8 @@ class _HomePage extends StatelessWidget {
listener: (context, state) { listener: (context, state) {
getIt<CachedRecentService>().reset(); getIt<CachedRecentService>().reset();
mCurrentWorkspace.value = state.currentWorkspace; mCurrentWorkspace.value = state.currentWorkspace;
_showResultDialog(context, state);
}, },
builder: (context, state) { builder: (context, state) {
if (state.currentWorkspace == null) { if (state.currentWorkspace == null) {
@ -170,6 +187,7 @@ class _HomePage extends StatelessWidget {
final workspaceId = state.currentWorkspace!.workspaceId; final workspaceId = state.currentWorkspace!.workspaceId;
return Column( return Column(
key: ValueKey('mobile_home_page_$workspaceId'),
children: [ children: [
// Header // Header
Padding( Padding(
@ -179,7 +197,7 @@ class _HomePage extends StatelessWidget {
top: Platform.isAndroid ? 8.0 : 0.0, top: Platform.isAndroid ? 8.0 : 0.0,
), ),
child: MobileHomePageHeader( child: MobileHomePageHeader(
userProfile: userProfile, userProfile: widget.userProfile,
), ),
), ),
@ -194,7 +212,7 @@ class _HomePage extends StatelessWidget {
create: (_) => SidebarSectionsBloc() create: (_) => SidebarSectionsBloc()
..add( ..add(
SidebarSectionsEvent.initial( SidebarSectionsEvent.initial(
userProfile, widget.userProfile,
workspaceId, workspaceId,
), ),
), ),
@ -207,7 +225,7 @@ class _HomePage extends StatelessWidget {
create: (_) => SpaceBloc() create: (_) => SpaceBloc()
..add( ..add(
SpaceEvent.initial( SpaceEvent.initial(
userProfile, widget.userProfile,
workspaceId, workspaceId,
openFirstPage: false, openFirstPage: false,
), ),
@ -215,7 +233,7 @@ class _HomePage extends StatelessWidget {
), ),
], ],
child: MobileSpaceTab( child: MobileSpaceTab(
userProfile: userProfile, userProfile: widget.userProfile,
), ),
), ),
), ),
@ -224,4 +242,59 @@ class _HomePage extends StatelessWidget {
}, },
); );
} }
void _showResultDialog(BuildContext context, UserWorkspaceState state) {
final actionResult = state.actionResult;
if (actionResult == null) {
return;
}
final actionType = actionResult.actionType;
final result = actionResult.result;
final isLoading = actionResult.isLoading;
if (isLoading) {
loadingIndicator ??= Loading(context)..start();
return;
} else {
loadingIndicator?.stop();
loadingIndicator = null;
}
if (result == null) {
return;
}
result.onFailure((f) {
Log.error(
'[Workspace] Failed to perform ${actionType.toString()} action: $f',
);
});
final String? message;
ToastificationType toastType = ToastificationType.success;
switch (actionType) {
case UserWorkspaceActionType.open:
message = result.fold(
(s) {
toastType = ToastificationType.success;
return LocaleKeys.workspace_openSuccess.tr();
},
(e) {
toastType = ToastificationType.error;
return '${LocaleKeys.workspace_openFailed.tr()}: ${e.msg}';
},
);
break;
default:
message = null;
toastType = ToastificationType.error;
break;
}
if (message != null) {
showToastNotification(context, message: message, type: toastType);
}
}
} }

View File

@ -68,7 +68,6 @@ class _RecentViews extends StatelessWidget {
? const Color(0xFFE9E9EC) ? const Color(0xFFE9E9EC)
: const Color(0x1AFFFFFF); : const Color(0x1AFFFFFF);
return SlidableAutoCloseBehavior( return SlidableAutoCloseBehavior(
child: Scrollbar(
child: ListView.separated( child: ListView.separated(
key: const PageStorageKey('recent_views_page_storage_key'), key: const PageStorageKey('recent_views_page_storage_key'),
padding: EdgeInsets.only( padding: EdgeInsets.only(
@ -98,7 +97,6 @@ class _RecentViews extends StatelessWidget {
separatorBuilder: (context, index) => const HSpace(8), separatorBuilder: (context, index) => const HSpace(8),
itemCount: recentViews.length, itemCount: recentViews.length,
), ),
),
); );
} }
} }

View File

@ -7,7 +7,8 @@ import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/shared/appflowy_cache_manager.dart'; import 'package:appflowy/shared/appflowy_cache_manager.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/share_log_files.dart'; import 'package:appflowy/util/share_log_files.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:appflowy/workspace/presentation/settings/pages/fix_data_widget.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -74,10 +75,14 @@ class SupportSettingGroup extends StatelessWidget {
actionButtonTitle: LocaleKeys.button_yes.tr(), actionButtonTitle: LocaleKeys.button_yes.tr(),
onActionButtonPressed: () async { onActionButtonPressed: () async {
await getIt<FlowyCacheManager>().clearAllCache(); await getIt<FlowyCacheManager>().clearAllCache();
// check the workspace and space health
await WorkspaceDataManager.checkViewHealth(
dryRun: false,
);
if (context.mounted) { if (context.mounted) {
showSnackBarMessage( showToastNotification(
context, context,
LocaleKeys.settings_files_clearCacheSuccess.tr(), message: LocaleKeys.settings_files_clearCacheSuccess.tr(),
); );
} }
}, },

View File

@ -48,6 +48,7 @@ class _InviteMemberPage extends StatefulWidget {
class _InviteMemberPageState extends State<_InviteMemberPage> { class _InviteMemberPageState extends State<_InviteMemberPage> {
final emailController = TextEditingController(); final emailController = TextEditingController();
late final Future<UserProfilePB?> userProfile; late final Future<UserProfilePB?> userProfile;
bool exceededLimit = false;
@override @override
void initState() { void initState() {
@ -131,6 +132,15 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
), ),
), ),
const VSpace(16), const VSpace(16),
if (exceededLimit) ...[
FlowyText.regular(
LocaleKeys.settings_appearance_members_inviteFailedMemberLimit.tr(),
fontSize: 14.0,
maxLines: 3,
color: Theme.of(context).colorScheme.error,
),
const VSpace(16),
],
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
@ -197,6 +207,9 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
? LocaleKeys.settings_appearance_members_memberLimitExceeded.tr() ? LocaleKeys.settings_appearance_members_memberLimitExceeded.tr()
: LocaleKeys.settings_appearance_members_failedToAddMember.tr(); : LocaleKeys.settings_appearance_members_failedToAddMember.tr();
setState(() {
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
});
showToastNotification( showToastNotification(
context, context,
type: ToastificationType.error, type: ToastificationType.error,
@ -220,6 +233,9 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
.tr() .tr()
: LocaleKeys.settings_appearance_members_failedToInviteMember : LocaleKeys.settings_appearance_members_failedToInviteMember
.tr(); .tr();
setState(() {
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
});
showToastNotification( showToastNotification(
context, context,
type: ToastificationType.error, type: ToastificationType.error,
@ -255,6 +271,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
if (!isEmail(email)) { if (!isEmail(email)) {
return showToastNotification( return showToastNotification(
context, context,
type: ToastificationType.error,
message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(), message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
); );
} }

View File

@ -425,6 +425,7 @@ class _ChatContentPageState extends State<_ChatContentPage> {
}, },
), ),
const VSpace(6), const VSpace(6),
if (PlatformExtension.isDesktop)
Opacity( Opacity(
opacity: 0.6, opacity: 0.6,
child: FlowyText( child: FlowyText(

View File

@ -6,6 +6,7 @@ class GridSize {
static double get scrollBarSize => 8 * scale; static double get scrollBarSize => 8 * scale;
static double get headerHeight => 40 * scale; static double get headerHeight => 40 * scale;
static double get buttonHeight => 38 * scale;
static double get footerHeight => 40 * scale; static double get footerHeight => 40 * scale;
static double get horizontalHeaderPadding => static double get horizontalHeaderPadding =>
PlatformExtension.isDesktop ? 40 * scale : 16 * scale; PlatformExtension.isDesktop ? 40 * scale : 16 * scale;

View File

@ -188,8 +188,14 @@ class _RowMenuButtonState extends State<RowMenuButton> {
richTooltipText: widget.isDragEnabled richTooltipText: widget.isDragEnabled
? TextSpan( ? TextSpan(
children: [ children: [
TextSpan(text: '${LocaleKeys.tooltip_dragRow.tr()}\n'), TextSpan(
TextSpan(text: LocaleKeys.tooltip_openMenu.tr()), text: '${LocaleKeys.tooltip_dragRow.tr()}\n',
style: context.tooltipTextStyle(),
),
TextSpan(
text: LocaleKeys.tooltip_openMenu.tr(),
style: context.tooltipTextStyle(),
),
], ],
) )
: null, : null,

View File

@ -13,7 +13,18 @@ class MobileRowDetailSummaryCellSkin extends IEditableSummaryCellSkin {
FocusNode focusNode, FocusNode focusNode,
TextEditingController textEditingController, TextEditingController textEditingController,
) { ) {
return Column( return Container(
decoration: BoxDecoration(
border: Border.fromBorderSide(
BorderSide(color: Theme.of(context).colorScheme.outline),
),
borderRadius: const BorderRadius.all(Radius.circular(14)),
),
padding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 2,
),
child: Column(
children: [ children: [
TextField( TextField(
controller: textEditingController, controller: textEditingController,
@ -49,6 +60,7 @@ class MobileRowDetailSummaryCellSkin extends IEditableSummaryCellSkin {
], ],
), ),
], ],
),
); );
} }
} }

View File

@ -13,7 +13,18 @@ class MobileRowDetailTranslateCellSkin extends IEditableTranslateCellSkin {
FocusNode focusNode, FocusNode focusNode,
TextEditingController textEditingController, TextEditingController textEditingController,
) { ) {
return Column( return Container(
decoration: BoxDecoration(
border: Border.fromBorderSide(
BorderSide(color: Theme.of(context).colorScheme.outline),
),
borderRadius: const BorderRadius.all(Radius.circular(14)),
),
padding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 2,
),
child: Column(
children: [ children: [
TextField( TextField(
readOnly: true, readOnly: true,
@ -49,6 +60,7 @@ class MobileRowDetailTranslateCellSkin extends IEditableTranslateCellSkin {
], ],
), ),
], ],
),
); );
} }
} }

View File

@ -1,8 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
@ -21,10 +18,11 @@ import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../cell/editable_cell_builder.dart'; import '../cell/editable_cell_builder.dart';
import 'accessory/cell_accessory.dart'; import 'accessory/cell_accessory.dart';
/// Display the row properties in a list. Only used in [RowDetailPage]. /// Display the row properties in a list. Only used in [RowDetailPage].
@ -165,6 +163,7 @@ class _PropertyCellState extends State<_PropertyCell> {
svg: FlowySvgs.drag_element_s, svg: FlowySvgs.drag_element_s,
richMessage: TextSpan( richMessage: TextSpan(
text: LocaleKeys.grid_rowPage_fieldDragElementTooltip.tr(), text: LocaleKeys.grid_rowPage_fieldDragElementTooltip.tr(),
style: context.tooltipTextStyle(),
), ),
), ),
), ),

View File

@ -2,6 +2,7 @@ library document_plugin;
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/document_page.dart'; import 'package:appflowy/plugins/document/document_page.dart';
import 'package:appflowy/plugins/document/presentation/document_collaborators.dart'; import 'package:appflowy/plugins/document/presentation/document_collaborators.dart';
@ -118,6 +119,8 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
} }
}); });
final fixedTitle = data?[MobileDocumentScreen.viewFixedTitle];
return BlocProvider<ViewInfoBloc>.value( return BlocProvider<ViewInfoBloc>.value(
value: bloc, value: bloc,
child: BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>( child: BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
@ -126,6 +129,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
view: view, view: view,
onDeleted: () => context.onDeleted?.call(view, deletedViewIndex), onDeleted: () => context.onDeleted?.call(view, deletedViewIndex),
initialSelection: initialSelection, initialSelection: initialSelection,
fixedTitle: fixedTitle,
), ),
), ),
); );

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart';
@ -26,6 +24,7 @@ import 'package:cross_file/cross_file.dart';
import 'package:desktop_drop/desktop_drop.dart'; import 'package:desktop_drop/desktop_drop.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -42,11 +41,13 @@ class DocumentPage extends StatefulWidget {
required this.view, required this.view,
required this.onDeleted, required this.onDeleted,
this.initialSelection, this.initialSelection,
this.fixedTitle,
}); });
final ViewPB view; final ViewPB view;
final VoidCallback onDeleted; final VoidCallback onDeleted;
final Selection? initialSelection; final Selection? initialSelection;
final String? fixedTitle;
@override @override
State<DocumentPage> createState() => _DocumentPageState(); State<DocumentPage> createState() => _DocumentPageState();
@ -103,6 +104,7 @@ class _DocumentPageState extends State<DocumentPage>
BlocProvider.value(value: documentBloc), BlocProvider.value(value: documentBloc),
], ],
child: BlocBuilder<DocumentBloc, DocumentState>( child: BlocBuilder<DocumentBloc, DocumentState>(
buildWhen: _shouldRebuildDocument,
builder: (context, state) { builder: (context, state) {
if (state.isLoading) { if (state.isLoading) {
return const Center(child: CircularProgressIndicator.adaptive()); return const Center(child: CircularProgressIndicator.adaptive());
@ -261,6 +263,7 @@ class _DocumentPageState extends State<DocumentPage>
if (PlatformExtension.isMobile) { if (PlatformExtension.isMobile) {
return DocumentImmersiveCover( return DocumentImmersiveCover(
fixedTitle: widget.fixedTitle,
view: widget.view, view: widget.view,
userProfilePB: userProfilePB, userProfilePB: userProfilePB,
); );
@ -308,4 +311,31 @@ class _DocumentPageState extends State<DocumentPage>
} }
} }
} }
bool _shouldRebuildDocument(DocumentState previous, DocumentState current) {
// only rebuild the document page when the below fields are changed
// this is to prevent unnecessary rebuilds
//
// If you confirm the newly added fields should be rebuilt, please update
// this function.
if (previous.editorState != current.editorState) {
return true;
}
if (previous.forceClose != current.forceClose ||
previous.isDeleted != current.isDeleted) {
return true;
}
if (previous.userProfilePB != current.userProfilePB) {
return true;
}
if (previous.isLoading != current.isLoading ||
previous.error != current.error) {
return true;
}
return false;
}
} }

View File

@ -5,6 +5,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -31,16 +32,19 @@ class BlockAddButton extends StatelessWidget {
children: [ children: [
TextSpan( TextSpan(
text: LocaleKeys.blockActions_addBelowTooltip.tr(), text: LocaleKeys.blockActions_addBelowTooltip.tr(),
style: context.tooltipTextStyle(),
), ),
const TextSpan(text: '\n'), const TextSpan(text: '\n'),
TextSpan( TextSpan(
text: Platform.isMacOS text: Platform.isMacOS
? LocaleKeys.blockActions_addAboveMacCmd.tr() ? LocaleKeys.blockActions_addAboveMacCmd.tr()
: LocaleKeys.blockActions_addAboveCmd.tr(), : LocaleKeys.blockActions_addAboveCmd.tr(),
style: context.tooltipTextStyle(),
), ),
const TextSpan(text: ' '), const TextSpan(text: ' '),
TextSpan( TextSpan(
text: LocaleKeys.blockActions_addAboveTooltip.tr(), text: LocaleKeys.blockActions_addAboveTooltip.tr(),
style: context.tooltipTextStyle(),
), ),
], ],
), ),

View File

@ -21,7 +21,6 @@ class BlockActionButton extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Align( return Align(
child: FlowyTooltip( child: FlowyTooltip(
preferBelow: false,
richMessage: richMessage, richMessage: richMessage,
child: MouseRegion( child: MouseRegion(
cursor: Platform.isWindows cursor: Platform.isWindows

View File

@ -7,6 +7,7 @@ import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -67,11 +68,14 @@ class BlockOptionButton extends StatelessWidget {
controller.close(); controller.close();
} }
}, },
buildChild: (controller) => _buildOptionButton(controller), buildChild: (controller) => _buildOptionButton(context, controller),
); );
} }
Widget _buildOptionButton(PopoverController controller) { Widget _buildOptionButton(
BuildContext context,
PopoverController controller,
) {
return BlockActionButton( return BlockActionButton(
svg: FlowySvgs.drag_element_s, svg: FlowySvgs.drag_element_s,
richMessage: TextSpan( richMessage: TextSpan(
@ -79,9 +83,11 @@ class BlockOptionButton extends StatelessWidget {
TextSpan( TextSpan(
// todo: customize the color to highlight the text. // todo: customize the color to highlight the text.
text: LocaleKeys.document_plugins_optionAction_click.tr(), text: LocaleKeys.document_plugins_optionAction_click.tr(),
style: context.tooltipTextStyle(),
), ),
TextSpan( TextSpan(
text: LocaleKeys.document_plugins_optionAction_toOpenMenu.tr(), text: LocaleKeys.document_plugins_optionAction_toOpenMenu.tr(),
style: context.tooltipTextStyle(),
), ),
], ],
), ),

View File

@ -34,10 +34,12 @@ class DocumentImmersiveCover extends StatefulWidget {
super.key, super.key,
required this.view, required this.view,
required this.userProfilePB, required this.userProfilePB,
this.fixedTitle,
}); });
final ViewPB view; final ViewPB view;
final UserProfilePB userProfilePB; final UserProfilePB userProfilePB;
final String? fixedTitle;
@override @override
State<DocumentImmersiveCover> createState() => _DocumentImmersiveCoverState(); State<DocumentImmersiveCover> createState() => _DocumentImmersiveCoverState();
@ -143,6 +145,18 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
fontFamily = getGoogleFontSafely(documentFontFamily).fontFamily; fontFamily = getGoogleFontSafely(documentFontFamily).fontFamily;
} }
if (widget.fixedTitle != null) {
return FlowyText(
widget.fixedTitle!,
fontSize: 28.0,
fontWeight: FontWeight.w700,
fontFamily: fontFamily,
color:
state.cover.isNone || state.cover.isPresets ? null : Colors.white,
overflow: TextOverflow.ellipsis,
);
}
return AutoSizeTextField( return AutoSizeTextField(
controller: textEditingController, controller: textEditingController,
focusNode: focusNode, focusNode: focusNode,

View File

@ -121,9 +121,12 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
final type = values[currentTabIndex]; final type = values[currentTabIndex];
switch (type) { switch (type) {
case UploadImageType.local: case UploadImageType.local:
return Column( Widget child = UploadImageFileWidget(
children: [ allowMultipleImages: widget.allowMultipleImages,
Padding( onPickFiles: widget.onSelectedLocalImages,
);
if (PlatformExtension.isDesktop) {
child = Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
@ -134,25 +137,20 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
), ),
), ),
constraints: constraints, constraints: constraints,
child: Column( child: child,
children: [
UploadImageFileWidget(
allowMultipleImages: widget.allowMultipleImages,
onPickFiles: widget.onSelectedLocalImages,
), ),
],
),
),
),
// if (widget.limitMaximumImageSize) ...[
// FlowyText(
// LocaleKeys.document_imageBlock_maximumImageSize.tr(),
// fontSize: 10.0,
// color: Theme.of(context).hintColor,
// ),
// ],
],
); );
} else {
child = Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 12.0,
),
child: child,
);
}
return child;
case UploadImageType.url: case UploadImageType.url:
return Container( return Container(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),

View File

@ -1,5 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/patterns/common_patterns.dart'; import 'package:appflowy/shared/patterns/common_patterns.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -22,13 +23,26 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( final textField = FlowyTextField(
children: [
const VSpace(12),
FlowyTextField(
hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(), hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(),
onChanged: (value) => inputText = value, onChanged: (value) => inputText = value,
onEditingComplete: submit, onEditingComplete: submit,
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 14,
),
hintStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).hintColor,
fontSize: 14,
),
);
return Column(
children: [
const VSpace(12),
PlatformExtension.isDesktop
? textField
: SizedBox(
height: 42,
child: textField,
), ),
if (!isUrlValid) ...[ if (!isUrlValid) ...[
const VSpace(12), const VSpace(12),
@ -39,18 +53,23 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
], ],
const VSpace(20), const VSpace(20),
SizedBox( SizedBox(
height: 32, height: PlatformExtension.isMobile ? 36 : 32,
width: 300, width: 300,
child: FlowyButton( child: FlowyButton(
backgroundColor: Theme.of(context).colorScheme.primary, backgroundColor: Theme.of(context).colorScheme.primary,
hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9), hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9),
showDefaultBoxDecorationOnMobile: true, showDefaultBoxDecorationOnMobile: true,
radius:
PlatformExtension.isMobile ? BorderRadius.circular(8) : null,
margin: const EdgeInsets.all(5), margin: const EdgeInsets.all(5),
text: FlowyText( text: FlowyText(
LocaleKeys.document_imageBlock_embedLink_label.tr(), LocaleKeys.document_imageBlock_embedLink_label.tr(),
lineHeight: 1, lineHeight: 1,
textAlign: TextAlign.center, textAlign: TextAlign.center,
color: Theme.of(context).colorScheme.onPrimary, color: PlatformExtension.isMobile
? null
: Theme.of(context).colorScheme.onPrimary,
fontSize: PlatformExtension.isMobile ? 14 : null,
), ),
onTap: submit, onTap: submit,
), ),

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/permission/permission_checker.dart'; import 'package:appflowy/shared/permission/permission_checker.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
@ -9,6 +7,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart'; import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
class UploadImageFileWidget extends StatelessWidget { class UploadImageFileWidget extends StatelessWidget {
@ -25,8 +24,9 @@ class UploadImageFileWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final child = FlowyButton( Widget child = FlowyButton(
showDefaultBoxDecorationOnMobile: true, showDefaultBoxDecorationOnMobile: true,
radius: PlatformExtension.isMobile ? BorderRadius.circular(8.0) : null,
text: Container( text: Container(
margin: const EdgeInsets.all(4.0), margin: const EdgeInsets.all(4.0),
alignment: Alignment.center, alignment: Alignment.center,
@ -38,7 +38,12 @@ class UploadImageFileWidget extends StatelessWidget {
); );
if (PlatformExtension.isDesktopOrWeb) { if (PlatformExtension.isDesktopOrWeb) {
return FlowyHover(child: child); child = FlowyHover(child: child);
} else {
child = Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: child,
);
} }
return child; return child;

View File

@ -8,12 +8,22 @@ class Loading {
BuildContext? loadingContext; BuildContext? loadingContext;
final BuildContext context; final BuildContext context;
bool hasStopped = false;
void start() => unawaited( void start() => unawaited(
showDialog<void>( showDialog<void>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (BuildContext context) { builder: (BuildContext context) {
loadingContext = context; loadingContext = context;
if (hasStopped) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(loadingContext!).pop();
loadingContext = null;
});
}
return const SimpleDialog( return const SimpleDialog(
elevation: 0.0, elevation: 0.0,
backgroundColor: backgroundColor:
@ -33,6 +43,8 @@ class Loading {
Navigator.of(loadingContext!).pop(); Navigator.of(loadingContext!).pop();
loadingContext = null; loadingContext = null;
} }
hasStopped = true;
} }
} }

View File

@ -493,9 +493,20 @@ GoRoute _mobileEditorScreenRoute() {
pageBuilder: (context, state) { pageBuilder: (context, state) {
final id = state.uri.queryParameters[MobileDocumentScreen.viewId]!; final id = state.uri.queryParameters[MobileDocumentScreen.viewId]!;
final title = state.uri.queryParameters[MobileDocumentScreen.viewTitle]; final title = state.uri.queryParameters[MobileDocumentScreen.viewTitle];
final showMoreButton = bool.tryParse(
state.uri.queryParameters[MobileDocumentScreen.viewShowMoreButton] ??
'true',
);
final fixedTitle =
state.uri.queryParameters[MobileDocumentScreen.viewFixedTitle];
return MaterialExtendedPage( return MaterialExtendedPage(
child: MobileDocumentScreen(id: id, title: title), child: MobileDocumentScreen(
id: id,
title: title,
showMoreButton: showMoreButton ?? true,
fixedTitle: fixedTitle,
),
); );
}, },
); );

View File

@ -33,7 +33,7 @@ class CachedRecentService {
final _listener = RecentViewsListener(); final _listener = RecentViewsListener();
Future<List<SectionViewPB>> recentViews() async { Future<List<SectionViewPB>> recentViews() async {
if (_isInitialized) return _recentViews; if (_isInitialized || _completer.isCompleted) return _recentViews;
_isInitialized = true; _isInitialized = true;
@ -76,7 +76,10 @@ class CachedRecentService {
(recentViews) { (recentViews) {
return FlowyResult.success( return FlowyResult.success(
RepeatedRecentViewPB( RepeatedRecentViewPB(
items: recentViews.items.where((e) => !e.item.isSpace), // filter the space view and the orphan view
items: recentViews.items.where(
(e) => !e.item.isSpace && e.item.id != e.item.parentViewId,
),
), ),
); );
}, },

View File

@ -68,6 +68,8 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
(event, emit) async { (event, emit) async {
await event.when( await event.when(
initial: (userProfile, workspaceId, openFirstPage) async { initial: (userProfile, workspaceId, openFirstPage) async {
this.openFirstPage = openFirstPage;
_initial(userProfile, workspaceId); _initial(userProfile, workspaceId);
final (spaces, publicViews, privateViews) = await _getSpaces(); final (spaces, publicViews, privateViews) = await _getSpaces();
@ -305,7 +307,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
SpaceEvent.initial( SpaceEvent.initial(
userProfile, userProfile,
workspaceId, workspaceId,
openFirstPage: true, openFirstPage: openFirstPage,
), ),
); );
}, },
@ -353,6 +355,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
String? _workspaceId; String? _workspaceId;
late UserProfilePB userProfile; late UserProfilePB userProfile;
WorkspaceSectionsListener? _listener; WorkspaceSectionsListener? _listener;
bool openFirstPage = false;
@override @override
Future<void> close() async { Future<void> close() async {

View File

@ -118,6 +118,8 @@ class WorkspaceDataManager {
final List<ViewPB> unlistedChildViews = []; final List<ViewPB> unlistedChildViews = [];
// Views whose parent is not in allViews // Views whose parent is not in allViews
final List<ViewPB> orphanViews = []; final List<ViewPB> orphanViews = [];
// Row pages
final List<ViewPB> rowPageViews = [];
try { try {
if (workspace == null || allViews == null) { if (workspace == null || allViews == null) {
@ -145,6 +147,11 @@ class WorkspaceDataManager {
continue; continue;
} }
if (parentView.id == view.id) {
rowPageViews.add(view);
continue;
}
final childViewsOfParent = final childViewsOfParent =
await ViewBackendService.getChildViews(viewId: parentView.id) await ViewBackendService.getChildViews(viewId: parentView.id)
.getOrThrow(); .getOrThrow();
@ -165,7 +172,11 @@ class WorkspaceDataManager {
} }
for (final view in orphanViews) { for (final view in orphanViews) {
Log.debug('[workspace] orphanViews: ${view.toProto3Json()}'); Log.info('[workspace] orphanViews: ${view.toProto3Json()}');
}
for (final view in rowPageViews) {
Log.info('[workspace] rowPageViews: ${view.toProto3Json()}');
} }
if (!dryRun && unlistedChildViews.isNotEmpty) { if (!dryRun && unlistedChildViews.isNotEmpty) {

View File

@ -353,16 +353,24 @@ class _MToast extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final hintText = FlowyText.regular(
message,
fontSize: 16.0,
figmaLineHeight: 18.0,
color: Colors.white,
maxLines: 10,
);
return Container( return Container(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
padding: const EdgeInsets.only(bottom: 100), padding: const EdgeInsets.only(bottom: 100, left: 16, right: 16),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0), padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12.0),
color: const Color(0xE5171717), color: const Color(0xE5171717),
), ),
child: Row( child: type == ToastificationType.success
? Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (type == ToastificationType.success) ...[ if (type == ToastificationType.success) ...[
@ -372,15 +380,10 @@ class _MToast extends StatelessWidget {
), ),
const HSpace(8.0), const HSpace(8.0),
], ],
FlowyText.regular( hintText,
message,
fontSize: 16.0,
figmaLineHeight: 18.0,
color: Colors.white,
maxLines: 3,
),
], ],
), )
: hintText,
), ),
); );
} }

View File

@ -264,9 +264,11 @@ class FlowyButton extends StatelessWidget {
? BoxDecoration( ? BoxDecoration(
border: Border.all( border: Border.all(
color: borderColor ?? color: borderColor ??
Theme.of(context).colorScheme.surfaceContainerHighest, Theme.of(context).colorScheme.outline,
width: 1.0, width: 1.0,
)) ),
borderRadius: radius,
)
: null); : null);
return Container( return Container(

View File

@ -8,6 +8,7 @@ skip_pub_get=false
skip_pub_packages_get=false skip_pub_packages_get=false
verbose=false verbose=false
exclude_packages=false exclude_packages=false
show_loading=false
# Parse command line arguments # Parse command line arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
@ -28,6 +29,10 @@ while [[ $# -gt 0 ]]; do
exclude_packages=true exclude_packages=true
shift shift
;; ;;
--show-loading)
show_loading=true
shift
;;
*) *)
echo "Unknown option: $1" echo "Unknown option: $1"
exit 1 exit 1
@ -111,11 +116,13 @@ fi
# Get the PID of the background process # Get the PID of the background process
build_pid=$! build_pid=$!
if [ "$show_loading" = true ]; then
# Start the loading animation # Start the loading animation
display_loading $build_pid & display_loading $build_pid &
# Get the PID of the loading animation # Get the PID of the loading animation
loading_pid=$! loading_pid=$!
fi
# Wait for the build_runner to finish # Wait for the build_runner to finish
wait $build_pid wait $build_pid

View File

@ -64,7 +64,7 @@ cd ..
cd freezed cd freezed
# Allow execution permissions on CI # Allow execution permissions on CI
chmod +x ./generate_freezed.sh chmod +x ./generate_freezed.sh
./generate_freezed.sh "${args[@]}" ./generate_freezed.sh "${args[@]}" --show-loading
# Return to the original directory # Return to the original directory
cd "$original_dir" cd "$original_dir"