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,13 +225,19 @@ class _MobileViewPageState extends State<MobileViewPage> {
]); ]);
} }
actions.addAll([ if (widget.showMoreButton) {
MobileViewPageMoreButton( actions.addAll([
view: view, MobileViewPageMoreButton(
isImmersiveMode: isImmersiveMode, view: view,
appBarOpacity: _appBarOpacity, isImmersiveMode: isImmersiveMode,
), 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),
documentId: rowController.rowMeta.documentId, 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,
);
},
), ),
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,44 +53,52 @@ class _OpenRowPageButtonState extends State<OpenRowPageButton> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ConstrainedBox( return BlocBuilder<TextCellBloc, TextCellState>(
constraints: BoxConstraints( bloc: cellBloc,
minWidth: double.infinity, builder: (context, state) {
minHeight: GridSize.headerHeight, return ConstrainedBox(
), constraints: BoxConstraints(
child: TextButton.icon( minWidth: double.infinity,
style: Theme.of(context).textButtonTheme.style?.copyWith( maxHeight: GridSize.buttonHeight,
shape: WidgetStateProperty.all<RoundedRectangleBorder>( ),
RoundedRectangleBorder( child: TextButton.icon(
borderRadius: BorderRadius.circular(12.0), style: Theme.of(context).textButtonTheme.style?.copyWith(
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
),
overlayColor: WidgetStateProperty.all<Color>(
Theme.of(context).hoverColor,
),
alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory,
padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 6),
),
), ),
), label: FlowyText.medium(
overlayColor: WidgetStateProperty.all<Color>( LocaleKeys.grid_field_openRowDocument.tr(),
Theme.of(context).hoverColor, fontSize: 15,
), ),
alignment: AlignmentDirectional.centerStart, icon: const Padding(
splashFactory: NoSplash.splashFactory, padding: EdgeInsets.all(4.0),
padding: const WidgetStatePropertyAll( child: FlowySvg(
EdgeInsets.symmetric(vertical: 14, horizontal: 6), FlowySvgs.full_view_s,
size: Size.square(16.0),
), ),
), ),
label: FlowyText.medium( onPressed: () {
LocaleKeys.grid_field_openRowDocument.tr(), final name = state.content;
fontSize: 15, _openRowPage(context, name);
), },
icon: const Padding(
padding: EdgeInsets.all(4.0),
child: FlowySvg(
FlowySvgs.full_view_s,
size: Size.square(16.0),
), ),
), );
onPressed: () => _openRowPage(context), },
),
); );
} }
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,36 +96,34 @@ 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 + MediaQuery.of(context).padding.bottom,
MediaQuery.of(context).padding.bottom, ),
), itemBuilder: (context, index) {
itemBuilder: (context, index) { final view = favoriteViews[index];
final view = favoriteViews[index]; return Container(
return Container( padding: const EdgeInsets.symmetric(vertical: 24.0),
padding: const EdgeInsets.symmetric(vertical: 24.0), decoration: BoxDecoration(
decoration: BoxDecoration( border: Border(
border: Border( bottom: BorderSide(
bottom: BorderSide( color: borderColor,
color: borderColor, width: 0.5,
width: 0.5,
),
), ),
), ),
child: MobileViewPage( ),
key: ValueKey(view.item.id), child: MobileViewPage(
view: view.item, key: ValueKey(view.item.id),
timestamp: view.timestamp, view: view.item,
type: MobilePageCardType.favorite, timestamp: view.timestamp,
), type: MobilePageCardType.favorite,
); ),
}, );
separatorBuilder: (context, index) => const HSpace(8), },
itemCount: favoriteViews.length, separatorBuilder: (context, index) => const HSpace(8),
), itemCount: favoriteViews.length,
); );
} }
} }

View File

@ -25,19 +25,17 @@ 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, bottom: HomeSpaceViewSizes.mVerticalPadding +
bottom: HomeSpaceViewSizes.mVerticalPadding + MediaQuery.of(context).padding.bottom,
MediaQuery.of(context).padding.bottom, ),
), child: MobileFolders(
child: MobileFolders( user: widget.userProfile,
user: widget.userProfile, workspaceId: workspaceId,
workspaceId: workspaceId, 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,36 +68,34 @@ 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( bottom: HomeSpaceViewSizes.mVerticalPadding +
bottom: HomeSpaceViewSizes.mVerticalPadding + MediaQuery.of(context).padding.bottom,
MediaQuery.of(context).padding.bottom, ),
), itemBuilder: (context, index) {
itemBuilder: (context, index) { final sectionView = recentViews[index];
final sectionView = recentViews[index]; return Container(
return Container( padding: const EdgeInsets.symmetric(vertical: 24.0),
padding: const EdgeInsets.symmetric(vertical: 24.0), decoration: BoxDecoration(
decoration: BoxDecoration( border: Border(
border: Border( bottom: BorderSide(
bottom: BorderSide( color: borderColor,
color: borderColor, width: 0.5,
width: 0.5,
),
), ),
), ),
child: MobileViewPage( ),
key: ValueKey(sectionView.item.id), child: MobileViewPage(
view: sectionView.item, key: ValueKey(sectionView.item.id),
timestamp: sectionView.timestamp, view: sectionView.item,
type: MobilePageCardType.recent, timestamp: sectionView.timestamp,
), type: MobilePageCardType.recent,
); ),
}, );
separatorBuilder: (context, index) => const HSpace(8), },
itemCount: recentViews.length, separatorBuilder: (context, index) => const HSpace(8),
), 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,13 +425,14 @@ class _ChatContentPageState extends State<_ChatContentPage> {
}, },
), ),
const VSpace(6), const VSpace(6),
Opacity( if (PlatformExtension.isDesktop)
opacity: 0.6, Opacity(
child: FlowyText( opacity: 0.6,
LocaleKeys.chat_aiMistakePrompt.tr(), child: FlowyText(
fontSize: 12, LocaleKeys.chat_aiMistakePrompt.tr(),
fontSize: 12,
),
), ),
),
], ],
); );
}, },

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,42 +13,54 @@ class MobileRowDetailSummaryCellSkin extends IEditableSummaryCellSkin {
FocusNode focusNode, FocusNode focusNode,
TextEditingController textEditingController, TextEditingController textEditingController,
) { ) {
return Column( return Container(
children: [ decoration: BoxDecoration(
TextField( border: Border.fromBorderSide(
controller: textEditingController, BorderSide(color: Theme.of(context).colorScheme.outline),
readOnly: true,
focusNode: focusNode,
onEditingComplete: () => focusNode.unfocus(),
onSubmitted: (_) => focusNode.unfocus(),
style: Theme.of(context).textTheme.bodyMedium,
textInputAction: TextInputAction.done,
maxLines: null,
minLines: 1,
decoration: InputDecoration(
contentPadding: GridSize.cellContentInsets,
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
isDense: true,
),
), ),
Row( borderRadius: const BorderRadius.all(Radius.circular(14)),
children: [ ),
const Spacer(), padding: const EdgeInsets.symmetric(
Padding( horizontal: 4,
padding: const EdgeInsets.all(8.0), vertical: 2,
child: SummaryCellAccessory( ),
viewId: bloc.cellController.viewId, child: Column(
fieldId: bloc.cellController.fieldId, children: [
rowId: bloc.cellController.rowId, TextField(
), controller: textEditingController,
readOnly: true,
focusNode: focusNode,
onEditingComplete: () => focusNode.unfocus(),
onSubmitted: (_) => focusNode.unfocus(),
style: Theme.of(context).textTheme.bodyMedium,
textInputAction: TextInputAction.done,
maxLines: null,
minLines: 1,
decoration: InputDecoration(
contentPadding: GridSize.cellContentInsets,
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
isDense: true,
), ),
], ),
), Row(
], children: [
const Spacer(),
Padding(
padding: const EdgeInsets.all(8.0),
child: SummaryCellAccessory(
viewId: bloc.cellController.viewId,
fieldId: bloc.cellController.fieldId,
rowId: bloc.cellController.rowId,
),
),
],
),
],
),
); );
} }
} }

View File

@ -13,42 +13,54 @@ class MobileRowDetailTranslateCellSkin extends IEditableTranslateCellSkin {
FocusNode focusNode, FocusNode focusNode,
TextEditingController textEditingController, TextEditingController textEditingController,
) { ) {
return Column( return Container(
children: [ decoration: BoxDecoration(
TextField( border: Border.fromBorderSide(
readOnly: true, BorderSide(color: Theme.of(context).colorScheme.outline),
controller: textEditingController,
focusNode: focusNode,
onEditingComplete: () => focusNode.unfocus(),
onSubmitted: (_) => focusNode.unfocus(),
style: Theme.of(context).textTheme.bodyMedium,
textInputAction: TextInputAction.done,
maxLines: null,
minLines: 1,
decoration: InputDecoration(
contentPadding: GridSize.cellContentInsets,
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
isDense: true,
),
), ),
Row( borderRadius: const BorderRadius.all(Radius.circular(14)),
children: [ ),
const Spacer(), padding: const EdgeInsets.symmetric(
Padding( horizontal: 4,
padding: const EdgeInsets.all(8.0), vertical: 2,
child: TranslateCellAccessory( ),
viewId: bloc.cellController.viewId, child: Column(
fieldId: bloc.cellController.fieldId, children: [
rowId: bloc.cellController.rowId, TextField(
), readOnly: true,
controller: textEditingController,
focusNode: focusNode,
onEditingComplete: () => focusNode.unfocus(),
onSubmitted: (_) => focusNode.unfocus(),
style: Theme.of(context).textTheme.bodyMedium,
textInputAction: TextInputAction.done,
maxLines: null,
minLines: 1,
decoration: InputDecoration(
contentPadding: GridSize.cellContentInsets,
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
isDense: true,
), ),
], ),
), Row(
], children: [
const Spacer(),
Padding(
padding: const EdgeInsets.all(8.0),
child: TranslateCellAccessory(
viewId: bloc.cellController.viewId,
fieldId: bloc.cellController.fieldId,
rowId: bloc.cellController.rowId,
),
),
],
),
],
),
); );
} }
} }

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,38 +121,36 @@ 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,
padding: const EdgeInsets.all(8.0), );
child: Container( if (PlatformExtension.isDesktop) {
alignment: Alignment.center, child = Padding(
decoration: BoxDecoration( padding: const EdgeInsets.all(8.0),
borderRadius: BorderRadius.circular(8), child: Container(
border: Border.all( alignment: Alignment.center,
color: Theme.of(context).colorScheme.outline, decoration: BoxDecoration(
), borderRadius: BorderRadius.circular(8),
), border: Border.all(
constraints: constraints, color: Theme.of(context).colorScheme.outline,
child: Column(
children: [
UploadImageFileWidget(
allowMultipleImages: widget.allowMultipleImages,
onPickFiles: widget.onSelectedLocalImages,
),
],
), ),
), ),
constraints: constraints,
child: child,
), ),
// if (widget.limitMaximumImageSize) ...[ );
// FlowyText( } else {
// LocaleKeys.document_imageBlock_maximumImageSize.tr(), child = Padding(
// fontSize: 10.0, padding: const EdgeInsets.symmetric(
// color: Theme.of(context).hintColor, 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,14 +23,27 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textField = FlowyTextField(
hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(),
onChanged: (value) => inputText = value,
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( return Column(
children: [ children: [
const VSpace(12), const VSpace(12),
FlowyTextField( PlatformExtension.isDesktop
hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(), ? textField
onChanged: (value) => inputText = value, : SizedBox(
onEditingComplete: submit, height: 42,
), child: textField,
),
if (!isUrlValid) ...[ if (!isUrlValid) ...[
const VSpace(12), const VSpace(12),
FlowyText( FlowyText(
@ -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,34 +353,37 @@ 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
mainAxisSize: MainAxisSize.min, ? Row(
children: [ mainAxisSize: MainAxisSize.min,
if (type == ToastificationType.success) ...[ children: [
const FlowySvg( if (type == ToastificationType.success) ...[
FlowySvgs.success_s, const FlowySvg(
blendMode: null, FlowySvgs.success_s,
), blendMode: null,
const HSpace(8.0), ),
], const HSpace(8.0),
FlowyText.regular( ],
message, hintText,
fontSize: 16.0, ],
figmaLineHeight: 18.0, )
color: Colors.white, : hintText,
maxLines: 3,
),
],
),
), ),
); );
} }

View File

@ -263,10 +263,12 @@ class FlowyButton extends StatelessWidget {
(Platform.isIOS || Platform.isAndroid) (Platform.isIOS || Platform.isAndroid)
? 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=$!
# Start the loading animation if [ "$show_loading" = true ]; then
display_loading $build_pid & # Start the loading animation
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"