mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
e460120a1c
commit
7113269802
@ -18,14 +18,25 @@ extension MobileRouter on BuildContext {
|
||||
ViewPB view, {
|
||||
Map<String, dynamic>? arguments,
|
||||
bool addInRecent = true,
|
||||
bool showMoreButton = true,
|
||||
String? fixedTitle,
|
||||
}) async {
|
||||
// set the current view before pushing the new view
|
||||
getIt<MenuSharedState>().latestOpenView = view;
|
||||
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(
|
||||
path: view.routeName,
|
||||
queryParameters: view.queryParameters(arguments),
|
||||
queryParameters: queryParameters,
|
||||
).toString();
|
||||
await push(uri);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
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_service.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/view_ext.dart';
|
||||
@ -113,7 +111,6 @@ class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
|
||||
);
|
||||
}
|
||||
|
||||
final _service = DocumentService();
|
||||
final ViewPB view;
|
||||
final DocumentListener _documentListener;
|
||||
final ViewListener _viewListener;
|
||||
@ -124,16 +121,6 @@ class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
|
||||
|
||||
// for the version under 0.5.5
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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/presentation/base/app_bar/app_bar.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/plugins/document/presentation/document_collaborators.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
@ -26,6 +27,8 @@ class MobileViewPage extends StatefulWidget {
|
||||
required this.viewLayout,
|
||||
this.title,
|
||||
this.arguments,
|
||||
this.fixedTitle,
|
||||
this.showMoreButton = true,
|
||||
});
|
||||
|
||||
/// view id
|
||||
@ -33,6 +36,10 @@ class MobileViewPage extends StatefulWidget {
|
||||
final ViewLayoutPB viewLayout;
|
||||
final String? title;
|
||||
final Map<String, dynamic>? arguments;
|
||||
final bool showMoreButton;
|
||||
|
||||
// only used in row page
|
||||
final String? fixedTitle;
|
||||
|
||||
@override
|
||||
State<MobileViewPage> createState() => _MobileViewPageState();
|
||||
@ -163,6 +170,9 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
return plugin.widgetBuilder.buildWidget(
|
||||
shrinkWrap: false,
|
||||
context: PluginContext(userProfile: state.userProfilePB),
|
||||
data: {
|
||||
MobileDocumentScreen.viewFixedTitle: widget.fixedTitle,
|
||||
},
|
||||
);
|
||||
},
|
||||
(error) {
|
||||
@ -215,13 +225,19 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
]);
|
||||
}
|
||||
|
||||
actions.addAll([
|
||||
MobileViewPageMoreButton(
|
||||
view: view,
|
||||
isImmersiveMode: isImmersiveMode,
|
||||
appBarOpacity: _appBarOpacity,
|
||||
),
|
||||
]);
|
||||
if (widget.showMoreButton) {
|
||||
actions.addAll([
|
||||
MobileViewPageMoreButton(
|
||||
view: view,
|
||||
isImmersiveMode: isImmersiveMode,
|
||||
appBarOpacity: _appBarOpacity,
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
actions.addAll([
|
||||
const HSpace(18.0),
|
||||
]);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
@ -241,7 +257,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
],
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
view?.name ?? widget.title ?? '',
|
||||
widget.fixedTitle ?? view?.name ?? widget.title ?? '',
|
||||
fontSize: 15.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
figmaLineHeight: 18.0,
|
||||
|
@ -295,6 +295,7 @@ class MobileRowDetailPageContentState
|
||||
RowCache get rowCache => widget.databaseController.rowCache;
|
||||
FieldController get fieldController =>
|
||||
widget.databaseController.fieldController;
|
||||
ValueNotifier<String> primaryFieldId = ValueNotifier('');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -327,7 +328,13 @@ class MobileRowDetailPageContentState
|
||||
fieldController: fieldController,
|
||||
rowMeta: rowController.rowMeta,
|
||||
)..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) {
|
||||
if (state.primaryField == null) {
|
||||
return const SizedBox.shrink();
|
||||
@ -367,8 +374,22 @@ class MobileRowDetailPageContentState
|
||||
if (rowDetailState.numHiddenFields != 0) ...[
|
||||
const ToggleHiddenFieldsVisibilityButton(),
|
||||
],
|
||||
OpenRowPageButton(
|
||||
documentId: rowController.rowMeta.documentId,
|
||||
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,
|
||||
);
|
||||
},
|
||||
),
|
||||
MobileRowDetailCreateFieldButton(
|
||||
viewId: viewId,
|
||||
|
@ -22,7 +22,7 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: double.infinity,
|
||||
minHeight: GridSize.headerHeight,
|
||||
maxHeight: GridSize.headerHeight,
|
||||
),
|
||||
child: TextButton.icon(
|
||||
style: Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
@ -37,7 +37,7 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
padding: const WidgetStatePropertyAll(
|
||||
EdgeInsets.symmetric(vertical: 14, horizontal: 6),
|
||||
EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
),
|
||||
),
|
||||
label: FlowyText.medium(
|
||||
|
@ -3,6 +3,10 @@ import 'dart:async';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.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/workspace/application/view/prelude.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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class OpenRowPageButton extends StatefulWidget {
|
||||
const OpenRowPageButton({
|
||||
super.key,
|
||||
required this.documentId,
|
||||
required this.databaseController,
|
||||
required this.cellContext,
|
||||
});
|
||||
|
||||
final String documentId;
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final CellContext cellContext;
|
||||
|
||||
@override
|
||||
State<OpenRowPageButton> createState() => _OpenRowPageButtonState();
|
||||
}
|
||||
|
||||
class _OpenRowPageButtonState extends State<OpenRowPageButton> {
|
||||
late final cellBloc = TextCellBloc(
|
||||
cellController: makeCellController(
|
||||
widget.databaseController,
|
||||
widget.cellContext,
|
||||
).as(),
|
||||
);
|
||||
|
||||
ViewPB? view;
|
||||
|
||||
@override
|
||||
@ -36,44 +53,52 @@ class _OpenRowPageButtonState extends State<OpenRowPageButton> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: double.infinity,
|
||||
minHeight: GridSize.headerHeight,
|
||||
),
|
||||
child: TextButton.icon(
|
||||
style: Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
return BlocBuilder<TextCellBloc, TextCellState>(
|
||||
bloc: cellBloc,
|
||||
builder: (context, state) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: double.infinity,
|
||||
maxHeight: GridSize.buttonHeight,
|
||||
),
|
||||
child: TextButton.icon(
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
overlayColor: WidgetStateProperty.all<Color>(
|
||||
Theme.of(context).hoverColor,
|
||||
),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
padding: const WidgetStatePropertyAll(
|
||||
EdgeInsets.symmetric(vertical: 14, horizontal: 6),
|
||||
label: FlowyText.medium(
|
||||
LocaleKeys.grid_field_openRowDocument.tr(),
|
||||
fontSize: 15,
|
||||
),
|
||||
icon: const Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.full_view_s,
|
||||
size: Size.square(16.0),
|
||||
),
|
||||
),
|
||||
label: FlowyText.medium(
|
||||
LocaleKeys.grid_field_openRowDocument.tr(),
|
||||
fontSize: 15,
|
||||
),
|
||||
icon: const Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.full_view_s,
|
||||
size: Size.square(16.0),
|
||||
onPressed: () {
|
||||
final name = state.content;
|
||||
_openRowPage(context, name);
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: () => _openRowPage(context),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openRowPage(BuildContext context) async {
|
||||
Future<void> _openRowPage(BuildContext context, String fieldName) async {
|
||||
Log.info('Open row page(${widget.documentId})');
|
||||
|
||||
if (view == null) {
|
||||
@ -89,6 +114,8 @@ class _OpenRowPageButtonState extends State<OpenRowPageButton> {
|
||||
await context.pushView(
|
||||
view!,
|
||||
addInRecent: false,
|
||||
showMoreButton: false,
|
||||
fixedTitle: fieldName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,21 @@ class MobileDocumentScreen extends StatelessWidget {
|
||||
super.key,
|
||||
required this.id,
|
||||
this.title,
|
||||
this.showMoreButton = true,
|
||||
this.fixedTitle,
|
||||
});
|
||||
|
||||
/// view id
|
||||
final String id;
|
||||
final String? title;
|
||||
final bool showMoreButton;
|
||||
final String? fixedTitle;
|
||||
|
||||
static const routeName = '/docs';
|
||||
static const viewId = 'id';
|
||||
static const viewTitle = 'title';
|
||||
static const viewShowMoreButton = 'show_more_button';
|
||||
static const viewFixedTitle = 'fixed_title';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -23,6 +29,8 @@ class MobileDocumentScreen extends StatelessWidget {
|
||||
id: id,
|
||||
title: title,
|
||||
viewLayout: ViewLayoutPB.Document,
|
||||
showMoreButton: showMoreButton,
|
||||
fixedTitle: fixedTitle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -96,36 +96,34 @@ class _FavoriteViews extends StatelessWidget {
|
||||
final borderColor = Theme.of(context).isLightMode
|
||||
? const Color(0xFFE9E9EC)
|
||||
: const Color(0x1AFFFFFF);
|
||||
return Scrollbar(
|
||||
child: ListView.separated(
|
||||
key: const PageStorageKey('favorite_views_page_storage_key'),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final view = favoriteViews[index];
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: borderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
return ListView.separated(
|
||||
key: const PageStorageKey('favorite_views_page_storage_key'),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final view = favoriteViews[index];
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: borderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
child: MobileViewPage(
|
||||
key: ValueKey(view.item.id),
|
||||
view: view.item,
|
||||
timestamp: view.timestamp,
|
||||
type: MobilePageCardType.favorite,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const HSpace(8),
|
||||
itemCount: favoriteViews.length,
|
||||
),
|
||||
),
|
||||
child: MobileViewPage(
|
||||
key: ValueKey(view.item.id),
|
||||
view: view.item,
|
||||
timestamp: view.timestamp,
|
||||
type: MobilePageCardType.favorite,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const HSpace(8),
|
||||
itemCount: favoriteViews.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -25,19 +25,17 @@ class _MobileHomeSpaceState extends State<MobileHomeSpace>
|
||||
final workspaceId =
|
||||
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
|
||||
'';
|
||||
return Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: HomeSpaceViewSizes.mVerticalPadding,
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: MobileFolders(
|
||||
user: widget.userProfile,
|
||||
workspaceId: workspaceId,
|
||||
showFavorite: false,
|
||||
),
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: HomeSpaceViewSizes.mVerticalPadding,
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: MobileFolders(
|
||||
user: widget.userProfile,
|
||||
workspaceId: workspaceId,
|
||||
showFavorite: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -35,6 +35,9 @@ class MobileFolders extends StatelessWidget {
|
||||
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
|
||||
'';
|
||||
return BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.currentWorkspace?.workspaceId !=
|
||||
current.currentWorkspace?.workspaceId,
|
||||
listener: (context, state) {
|
||||
context.read<SidebarSectionsBloc>().add(
|
||||
SidebarSectionsEvent.initial(
|
||||
|
@ -1,8 +1,10 @@
|
||||
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/tab/mobile_space_tab.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/user/application/auth/auth_service.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/home_sizes.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/log.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-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_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sentry/sentry.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
class MobileHomeScreen extends StatelessWidget {
|
||||
const MobileHomeScreen({super.key});
|
||||
@ -103,6 +109,8 @@ class MobileHomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MobileHomePageState extends State<MobileHomePage> {
|
||||
Loading? loadingIndicator;
|
||||
|
||||
@override
|
||||
void 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});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
State<_HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<_HomePage> {
|
||||
Loading? loadingIndicator;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
@ -161,6 +176,8 @@ class _HomePage extends StatelessWidget {
|
||||
listener: (context, state) {
|
||||
getIt<CachedRecentService>().reset();
|
||||
mCurrentWorkspace.value = state.currentWorkspace;
|
||||
|
||||
_showResultDialog(context, state);
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state.currentWorkspace == null) {
|
||||
@ -170,6 +187,7 @@ class _HomePage extends StatelessWidget {
|
||||
final workspaceId = state.currentWorkspace!.workspaceId;
|
||||
|
||||
return Column(
|
||||
key: ValueKey('mobile_home_page_$workspaceId'),
|
||||
children: [
|
||||
// Header
|
||||
Padding(
|
||||
@ -179,7 +197,7 @@ class _HomePage extends StatelessWidget {
|
||||
top: Platform.isAndroid ? 8.0 : 0.0,
|
||||
),
|
||||
child: MobileHomePageHeader(
|
||||
userProfile: userProfile,
|
||||
userProfile: widget.userProfile,
|
||||
),
|
||||
),
|
||||
|
||||
@ -194,7 +212,7 @@ class _HomePage extends StatelessWidget {
|
||||
create: (_) => SidebarSectionsBloc()
|
||||
..add(
|
||||
SidebarSectionsEvent.initial(
|
||||
userProfile,
|
||||
widget.userProfile,
|
||||
workspaceId,
|
||||
),
|
||||
),
|
||||
@ -207,7 +225,7 @@ class _HomePage extends StatelessWidget {
|
||||
create: (_) => SpaceBloc()
|
||||
..add(
|
||||
SpaceEvent.initial(
|
||||
userProfile,
|
||||
widget.userProfile,
|
||||
workspaceId,
|
||||
openFirstPage: false,
|
||||
),
|
||||
@ -215,7 +233,7 @@ class _HomePage extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,36 +68,34 @@ class _RecentViews extends StatelessWidget {
|
||||
? const Color(0xFFE9E9EC)
|
||||
: const Color(0x1AFFFFFF);
|
||||
return SlidableAutoCloseBehavior(
|
||||
child: Scrollbar(
|
||||
child: ListView.separated(
|
||||
key: const PageStorageKey('recent_views_page_storage_key'),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final sectionView = recentViews[index];
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: borderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
child: ListView.separated(
|
||||
key: const PageStorageKey('recent_views_page_storage_key'),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final sectionView = recentViews[index];
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: borderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
child: MobileViewPage(
|
||||
key: ValueKey(sectionView.item.id),
|
||||
view: sectionView.item,
|
||||
timestamp: sectionView.timestamp,
|
||||
type: MobilePageCardType.recent,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const HSpace(8),
|
||||
itemCount: recentViews.length,
|
||||
),
|
||||
),
|
||||
child: MobileViewPage(
|
||||
key: ValueKey(sectionView.item.id),
|
||||
view: sectionView.item,
|
||||
timestamp: sectionView.timestamp,
|
||||
type: MobilePageCardType.recent,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const HSpace(8),
|
||||
itemCount: recentViews.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||
import 'package:appflowy/startup/startup.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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -74,10 +75,14 @@ class SupportSettingGroup extends StatelessWidget {
|
||||
actionButtonTitle: LocaleKeys.button_yes.tr(),
|
||||
onActionButtonPressed: () async {
|
||||
await getIt<FlowyCacheManager>().clearAllCache();
|
||||
// check the workspace and space health
|
||||
await WorkspaceDataManager.checkViewHealth(
|
||||
dryRun: false,
|
||||
);
|
||||
if (context.mounted) {
|
||||
showSnackBarMessage(
|
||||
showToastNotification(
|
||||
context,
|
||||
LocaleKeys.settings_files_clearCacheSuccess.tr(),
|
||||
message: LocaleKeys.settings_files_clearCacheSuccess.tr(),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -48,6 +48,7 @@ class _InviteMemberPage extends StatefulWidget {
|
||||
class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
final emailController = TextEditingController();
|
||||
late final Future<UserProfilePB?> userProfile;
|
||||
bool exceededLimit = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -131,6 +132,15 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
),
|
||||
),
|
||||
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(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
@ -197,6 +207,9 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
|
||||
? LocaleKeys.settings_appearance_members_memberLimitExceeded.tr()
|
||||
: LocaleKeys.settings_appearance_members_failedToAddMember.tr();
|
||||
setState(() {
|
||||
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
|
||||
});
|
||||
showToastNotification(
|
||||
context,
|
||||
type: ToastificationType.error,
|
||||
@ -220,6 +233,9 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
.tr()
|
||||
: LocaleKeys.settings_appearance_members_failedToInviteMember
|
||||
.tr();
|
||||
setState(() {
|
||||
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
|
||||
});
|
||||
showToastNotification(
|
||||
context,
|
||||
type: ToastificationType.error,
|
||||
@ -255,6 +271,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
if (!isEmail(email)) {
|
||||
return showToastNotification(
|
||||
context,
|
||||
type: ToastificationType.error,
|
||||
message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
|
||||
);
|
||||
}
|
||||
|
@ -425,13 +425,14 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
},
|
||||
),
|
||||
const VSpace(6),
|
||||
Opacity(
|
||||
opacity: 0.6,
|
||||
child: FlowyText(
|
||||
LocaleKeys.chat_aiMistakePrompt.tr(),
|
||||
fontSize: 12,
|
||||
if (PlatformExtension.isDesktop)
|
||||
Opacity(
|
||||
opacity: 0.6,
|
||||
child: FlowyText(
|
||||
LocaleKeys.chat_aiMistakePrompt.tr(),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ class GridSize {
|
||||
|
||||
static double get scrollBarSize => 8 * scale;
|
||||
static double get headerHeight => 40 * scale;
|
||||
static double get buttonHeight => 38 * scale;
|
||||
static double get footerHeight => 40 * scale;
|
||||
static double get horizontalHeaderPadding =>
|
||||
PlatformExtension.isDesktop ? 40 * scale : 16 * scale;
|
||||
|
@ -188,8 +188,14 @@ class _RowMenuButtonState extends State<RowMenuButton> {
|
||||
richTooltipText: widget.isDragEnabled
|
||||
? TextSpan(
|
||||
children: [
|
||||
TextSpan(text: '${LocaleKeys.tooltip_dragRow.tr()}\n'),
|
||||
TextSpan(text: LocaleKeys.tooltip_openMenu.tr()),
|
||||
TextSpan(
|
||||
text: '${LocaleKeys.tooltip_dragRow.tr()}\n',
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
TextSpan(
|
||||
text: LocaleKeys.tooltip_openMenu.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
|
@ -13,42 +13,54 @@ class MobileRowDetailSummaryCellSkin extends IEditableSummaryCellSkin {
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
) {
|
||||
return Column(
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
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,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(14)),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -13,42 +13,54 @@ class MobileRowDetailTranslateCellSkin extends IEditableTranslateCellSkin {
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
) {
|
||||
return Column(
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
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,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(14)),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
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/locale_keys.g.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:flowy_infra/theme_extension.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 '../cell/editable_cell_builder.dart';
|
||||
|
||||
import 'accessory/cell_accessory.dart';
|
||||
|
||||
/// 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,
|
||||
richMessage: TextSpan(
|
||||
text: LocaleKeys.grid_rowPage_fieldDragElementTooltip.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -2,6 +2,7 @@ library document_plugin;
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.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/document_page.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(
|
||||
value: bloc,
|
||||
child: BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
|
||||
@ -126,6 +129,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
|
||||
view: view,
|
||||
onDeleted: () => context.onDeleted?.call(view, deletedViewIndex),
|
||||
initialSelection: initialSelection,
|
||||
fixedTitle: fixedTitle,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_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:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@ -42,11 +41,13 @@ class DocumentPage extends StatefulWidget {
|
||||
required this.view,
|
||||
required this.onDeleted,
|
||||
this.initialSelection,
|
||||
this.fixedTitle,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final VoidCallback onDeleted;
|
||||
final Selection? initialSelection;
|
||||
final String? fixedTitle;
|
||||
|
||||
@override
|
||||
State<DocumentPage> createState() => _DocumentPageState();
|
||||
@ -103,6 +104,7 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
BlocProvider.value(value: documentBloc),
|
||||
],
|
||||
child: BlocBuilder<DocumentBloc, DocumentState>(
|
||||
buildWhen: _shouldRebuildDocument,
|
||||
builder: (context, state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator.adaptive());
|
||||
@ -261,6 +263,7 @@ class _DocumentPageState extends State<DocumentPage>
|
||||
|
||||
if (PlatformExtension.isMobile) {
|
||||
return DocumentImmersiveCover(
|
||||
fixedTitle: widget.fixedTitle,
|
||||
view: widget.view,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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_editor/appflowy_editor.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/services.dart';
|
||||
|
||||
@ -31,16 +32,19 @@ class BlockAddButton extends StatelessWidget {
|
||||
children: [
|
||||
TextSpan(
|
||||
text: LocaleKeys.blockActions_addBelowTooltip.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
const TextSpan(text: '\n'),
|
||||
TextSpan(
|
||||
text: Platform.isMacOS
|
||||
? LocaleKeys.blockActions_addAboveMacCmd.tr()
|
||||
: LocaleKeys.blockActions_addAboveCmd.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
const TextSpan(text: ' '),
|
||||
TextSpan(
|
||||
text: LocaleKeys.blockActions_addAboveTooltip.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -21,7 +21,6 @@ class BlockActionButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
child: FlowyTooltip(
|
||||
preferBelow: false,
|
||||
richMessage: richMessage,
|
||||
child: MouseRegion(
|
||||
cursor: Platform.isWindows
|
||||
|
@ -7,6 +7,7 @@ import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.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_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -67,11 +68,14 @@ class BlockOptionButton extends StatelessWidget {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
buildChild: (controller) => _buildOptionButton(controller),
|
||||
buildChild: (controller) => _buildOptionButton(context, controller),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOptionButton(PopoverController controller) {
|
||||
Widget _buildOptionButton(
|
||||
BuildContext context,
|
||||
PopoverController controller,
|
||||
) {
|
||||
return BlockActionButton(
|
||||
svg: FlowySvgs.drag_element_s,
|
||||
richMessage: TextSpan(
|
||||
@ -79,9 +83,11 @@ class BlockOptionButton extends StatelessWidget {
|
||||
TextSpan(
|
||||
// todo: customize the color to highlight the text.
|
||||
text: LocaleKeys.document_plugins_optionAction_click.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
TextSpan(
|
||||
text: LocaleKeys.document_plugins_optionAction_toOpenMenu.tr(),
|
||||
style: context.tooltipTextStyle(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -34,10 +34,12 @@ class DocumentImmersiveCover extends StatefulWidget {
|
||||
super.key,
|
||||
required this.view,
|
||||
required this.userProfilePB,
|
||||
this.fixedTitle,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final UserProfilePB userProfilePB;
|
||||
final String? fixedTitle;
|
||||
|
||||
@override
|
||||
State<DocumentImmersiveCover> createState() => _DocumentImmersiveCoverState();
|
||||
@ -143,6 +145,18 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
|
||||
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(
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
|
@ -121,38 +121,36 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
|
||||
final type = values[currentTabIndex];
|
||||
switch (type) {
|
||||
case UploadImageType.local:
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
constraints: constraints,
|
||||
child: Column(
|
||||
children: [
|
||||
UploadImageFileWidget(
|
||||
allowMultipleImages: widget.allowMultipleImages,
|
||||
onPickFiles: widget.onSelectedLocalImages,
|
||||
),
|
||||
],
|
||||
Widget child = UploadImageFileWidget(
|
||||
allowMultipleImages: widget.allowMultipleImages,
|
||||
onPickFiles: widget.onSelectedLocalImages,
|
||||
);
|
||||
if (PlatformExtension.isDesktop) {
|
||||
child = Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
constraints: constraints,
|
||||
child: child,
|
||||
),
|
||||
// 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:
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -22,14 +23,27 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
||||
|
||||
@override
|
||||
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(
|
||||
children: [
|
||||
const VSpace(12),
|
||||
FlowyTextField(
|
||||
hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(),
|
||||
onChanged: (value) => inputText = value,
|
||||
onEditingComplete: submit,
|
||||
),
|
||||
PlatformExtension.isDesktop
|
||||
? textField
|
||||
: SizedBox(
|
||||
height: 42,
|
||||
child: textField,
|
||||
),
|
||||
if (!isUrlValid) ...[
|
||||
const VSpace(12),
|
||||
FlowyText(
|
||||
@ -39,18 +53,23 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
||||
],
|
||||
const VSpace(20),
|
||||
SizedBox(
|
||||
height: 32,
|
||||
height: PlatformExtension.isMobile ? 36 : 32,
|
||||
width: 300,
|
||||
child: FlowyButton(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9),
|
||||
showDefaultBoxDecorationOnMobile: true,
|
||||
radius:
|
||||
PlatformExtension.isMobile ? BorderRadius.circular(8) : null,
|
||||
margin: const EdgeInsets.all(5),
|
||||
text: FlowyText(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
lineHeight: 1,
|
||||
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,
|
||||
),
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/permission/permission_checker.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_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class UploadImageFileWidget extends StatelessWidget {
|
||||
@ -25,8 +24,9 @@ class UploadImageFileWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = FlowyButton(
|
||||
Widget child = FlowyButton(
|
||||
showDefaultBoxDecorationOnMobile: true,
|
||||
radius: PlatformExtension.isMobile ? BorderRadius.circular(8.0) : null,
|
||||
text: Container(
|
||||
margin: const EdgeInsets.all(4.0),
|
||||
alignment: Alignment.center,
|
||||
@ -38,7 +38,12 @@ class UploadImageFileWidget extends StatelessWidget {
|
||||
);
|
||||
|
||||
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;
|
||||
|
@ -8,12 +8,22 @@ class Loading {
|
||||
BuildContext? loadingContext;
|
||||
final BuildContext context;
|
||||
|
||||
bool hasStopped = false;
|
||||
|
||||
void start() => unawaited(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
loadingContext = context;
|
||||
|
||||
if (hasStopped) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Navigator.of(loadingContext!).pop();
|
||||
loadingContext = null;
|
||||
});
|
||||
}
|
||||
|
||||
return const SimpleDialog(
|
||||
elevation: 0.0,
|
||||
backgroundColor:
|
||||
@ -33,6 +43,8 @@ class Loading {
|
||||
Navigator.of(loadingContext!).pop();
|
||||
loadingContext = null;
|
||||
}
|
||||
|
||||
hasStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,9 +493,20 @@ GoRoute _mobileEditorScreenRoute() {
|
||||
pageBuilder: (context, state) {
|
||||
final id = state.uri.queryParameters[MobileDocumentScreen.viewId]!;
|
||||
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(
|
||||
child: MobileDocumentScreen(id: id, title: title),
|
||||
child: MobileDocumentScreen(
|
||||
id: id,
|
||||
title: title,
|
||||
showMoreButton: showMoreButton ?? true,
|
||||
fixedTitle: fixedTitle,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -33,7 +33,7 @@ class CachedRecentService {
|
||||
final _listener = RecentViewsListener();
|
||||
|
||||
Future<List<SectionViewPB>> recentViews() async {
|
||||
if (_isInitialized) return _recentViews;
|
||||
if (_isInitialized || _completer.isCompleted) return _recentViews;
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
@ -76,7 +76,10 @@ class CachedRecentService {
|
||||
(recentViews) {
|
||||
return FlowyResult.success(
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -68,6 +68,8 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: (userProfile, workspaceId, openFirstPage) async {
|
||||
this.openFirstPage = openFirstPage;
|
||||
|
||||
_initial(userProfile, workspaceId);
|
||||
|
||||
final (spaces, publicViews, privateViews) = await _getSpaces();
|
||||
@ -305,7 +307,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
SpaceEvent.initial(
|
||||
userProfile,
|
||||
workspaceId,
|
||||
openFirstPage: true,
|
||||
openFirstPage: openFirstPage,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -353,6 +355,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
String? _workspaceId;
|
||||
late UserProfilePB userProfile;
|
||||
WorkspaceSectionsListener? _listener;
|
||||
bool openFirstPage = false;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
|
@ -118,6 +118,8 @@ class WorkspaceDataManager {
|
||||
final List<ViewPB> unlistedChildViews = [];
|
||||
// Views whose parent is not in allViews
|
||||
final List<ViewPB> orphanViews = [];
|
||||
// Row pages
|
||||
final List<ViewPB> rowPageViews = [];
|
||||
|
||||
try {
|
||||
if (workspace == null || allViews == null) {
|
||||
@ -145,6 +147,11 @@ class WorkspaceDataManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parentView.id == view.id) {
|
||||
rowPageViews.add(view);
|
||||
continue;
|
||||
}
|
||||
|
||||
final childViewsOfParent =
|
||||
await ViewBackendService.getChildViews(viewId: parentView.id)
|
||||
.getOrThrow();
|
||||
@ -165,7 +172,11 @@ class WorkspaceDataManager {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -353,34 +353,37 @@ class _MToast extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hintText = FlowyText.regular(
|
||||
message,
|
||||
fontSize: 16.0,
|
||||
figmaLineHeight: 18.0,
|
||||
color: Colors.white,
|
||||
maxLines: 10,
|
||||
);
|
||||
return Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
padding: const EdgeInsets.only(bottom: 100),
|
||||
padding: const EdgeInsets.only(bottom: 100, left: 16, right: 16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
color: const Color(0xE5171717),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (type == ToastificationType.success) ...[
|
||||
const FlowySvg(
|
||||
FlowySvgs.success_s,
|
||||
blendMode: null,
|
||||
),
|
||||
const HSpace(8.0),
|
||||
],
|
||||
FlowyText.regular(
|
||||
message,
|
||||
fontSize: 16.0,
|
||||
figmaLineHeight: 18.0,
|
||||
color: Colors.white,
|
||||
maxLines: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: type == ToastificationType.success
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (type == ToastificationType.success) ...[
|
||||
const FlowySvg(
|
||||
FlowySvgs.success_s,
|
||||
blendMode: null,
|
||||
),
|
||||
const HSpace(8.0),
|
||||
],
|
||||
hintText,
|
||||
],
|
||||
)
|
||||
: hintText,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -263,10 +263,12 @@ class FlowyButton extends StatelessWidget {
|
||||
(Platform.isIOS || Platform.isAndroid)
|
||||
? BoxDecoration(
|
||||
border: Border.all(
|
||||
color: borderColor ??
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
width: 1.0,
|
||||
))
|
||||
color: borderColor ??
|
||||
Theme.of(context).colorScheme.outline,
|
||||
width: 1.0,
|
||||
),
|
||||
borderRadius: radius,
|
||||
)
|
||||
: null);
|
||||
|
||||
return Container(
|
||||
|
@ -8,6 +8,7 @@ skip_pub_get=false
|
||||
skip_pub_packages_get=false
|
||||
verbose=false
|
||||
exclude_packages=false
|
||||
show_loading=false
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@ -28,6 +29,10 @@ while [[ $# -gt 0 ]]; do
|
||||
exclude_packages=true
|
||||
shift
|
||||
;;
|
||||
--show-loading)
|
||||
show_loading=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
@ -111,11 +116,13 @@ fi
|
||||
# Get the PID of the background process
|
||||
build_pid=$!
|
||||
|
||||
# Start the loading animation
|
||||
display_loading $build_pid &
|
||||
if [ "$show_loading" = true ]; then
|
||||
# Start the loading animation
|
||||
display_loading $build_pid &
|
||||
|
||||
# Get the PID of the loading animation
|
||||
loading_pid=$!
|
||||
# Get the PID of the loading animation
|
||||
loading_pid=$!
|
||||
fi
|
||||
|
||||
# Wait for the build_runner to finish
|
||||
wait $build_pid
|
||||
|
@ -64,7 +64,7 @@ cd ..
|
||||
cd freezed
|
||||
# Allow execution permissions on CI
|
||||
chmod +x ./generate_freezed.sh
|
||||
./generate_freezed.sh "${args[@]}"
|
||||
./generate_freezed.sh "${args[@]}" --show-loading
|
||||
|
||||
# Return to the original directory
|
||||
cd "$original_dir"
|
||||
|
Loading…
Reference in New Issue
Block a user