fix: page style issues (#5317)

* fix: 7 emojis per line

* fix: remove shadow of the icons using presets cover

* fix: expand appbar buttons size

* fix: save new title name when it changed

* feat: add cover image preview

* fix: dismiss unsplash and presets panel auto

* feat: add selected color for cover image and layout section

* fix: selected icon size too small

* fix: dismiss page style panel before pushing to emoji and font selector

* chore: update back button icon

* chore: bump version 0.5.7
This commit is contained in:
Lucas.Xu
2024-05-13 13:26:19 +08:00
committed by GitHub
parent cdcb393efd
commit 4cfd83cbc4
14 changed files with 294 additions and 139 deletions

View File

@ -149,14 +149,11 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
fontSize: 28.0,
fontWeight: FontWeight.w700,
fontFamily: fontFamily,
color: state.cover.type == PageStyleCoverImageType.none
? null
: Colors.white,
color:
state.cover.isNone || state.cover.isPresets ? null : Colors.white,
),
onSubmitted: (value) {
scrollController.position.jumpTo(0);
context.read<ViewBloc>().add(ViewEvent.rename(value));
},
onChanged: _rename,
onSubmitted: _rename,
);
}
@ -250,4 +247,9 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
focusNode.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
}
}
void _rename(String name) {
scrollController.position.jumpTo(0);
context.read<ViewBloc>().add(ViewEvent.rename(name));
}
}

View File

@ -131,12 +131,16 @@ class _UnsplashImages extends StatelessWidget {
};
final mainAxisSpacing = switch (type) {
UnsplashImageType.halfScreen => 16.0,
UnsplashImageType.fullScreen => 8.0,
UnsplashImageType.fullScreen => 16.0,
};
final crossAxisSpacing = switch (type) {
UnsplashImageType.halfScreen => 10.0,
UnsplashImageType.fullScreen => 16.0,
};
return GridView.count(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: 10.0,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: 4 / 3,
children: photos
.map(
@ -197,28 +201,31 @@ class _UnsplashImage extends StatelessWidget {
}
Widget _buildFullScreenImage(BuildContext context) {
return Stack(
children: [
LayoutBuilder(
builder: (context, constraints) {
return Image.network(
photo.urls.thumb.toString(),
fit: BoxFit.cover,
width: constraints.maxWidth,
height: constraints.maxHeight,
);
},
),
Positioned(
bottom: 6,
left: 6,
child: FlowyText.medium(
photo.name,
fontSize: 10.0,
color: Colors.white,
return ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Stack(
children: [
LayoutBuilder(
builder: (context, constraints) {
return Image.network(
photo.urls.thumb.toString(),
fit: BoxFit.cover,
width: constraints.maxWidth,
height: constraints.maxHeight,
);
},
),
),
],
Positioned(
bottom: 9,
left: 10,
child: FlowyText.medium(
photo.name,
fontSize: 13.0,
color: Colors.white,
),
),
],
),
);
}
}

View File

@ -1,16 +1,21 @@
import 'dart:async';
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
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/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_cover_bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/shared/feedback_gesture_detector.dart';
import 'package:appflowy/shared/flowy_gradient_colors.dart';
import 'package:appflowy/shared/permission/permission_checker.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/util/string_extension.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
@ -19,6 +24,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
class PageStyleCoverImage extends StatelessWidget {
@ -33,13 +39,11 @@ class PageStyleCoverImage extends StatelessWidget {
final backgroundColor = context.pageStyleBackgroundColor;
return BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
builder: (context, state) {
return Row(
return Column(
children: [
_buildOptionGroup(
context,
backgroundColor,
state,
),
_buildOptionGroup(context, backgroundColor, state),
const VSpace(16.0),
_buildPreview(context, state),
],
);
},
@ -51,46 +55,124 @@ class PageStyleCoverImage extends StatelessWidget {
Color backgroundColor,
DocumentPageStyleState state,
) {
return Expanded(
child: Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: const BorderRadius.horizontal(
left: Radius.circular(12),
right: Radius.circular(12),
return Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: const BorderRadius.horizontal(
left: Radius.circular(12),
right: Radius.circular(12),
),
),
padding: const EdgeInsets.all(4.0),
child: Row(
children: [
_CoverOptionButton(
showLeftCorner: true,
showRightCorner: false,
selected: state.coverImage.isPresets,
onTap: () => _showPresets(context),
child: const _PresetCover(),
),
),
padding: const EdgeInsets.all(4.0),
child: Row(
children: [
_CoverOptionButton(
showLeftCorner: true,
showRightCorner: false,
selected: state.coverImage.isPresets,
onTap: () => _showPresets(context),
child: const _PresetCover(),
),
_CoverOptionButton(
showLeftCorner: false,
showRightCorner: false,
selected: state.coverImage.isPhoto,
onTap: () => _pickImage(context),
child: const _PhotoCover(),
),
_CoverOptionButton(
showLeftCorner: false,
showRightCorner: true,
selected: state.coverImage.isUnsplashImage,
onTap: () => _showUnsplash(context),
child: const _UnsplashCover(),
),
],
),
_CoverOptionButton(
showLeftCorner: false,
showRightCorner: false,
selected: state.coverImage.isPhoto,
onTap: () => _pickImage(context),
child: const _PhotoCover(),
),
_CoverOptionButton(
showLeftCorner: false,
showRightCorner: true,
selected: state.coverImage.isUnsplashImage,
onTap: () => _showUnsplash(context),
child: const _UnsplashCover(),
),
],
),
);
}
Widget _buildPreview(
BuildContext context,
DocumentPageStyleState state,
) {
final cover = state.coverImage;
if (cover.isNone) {
return const SizedBox.shrink();
}
final value = cover.value;
final type = cover.type;
Widget preview = const SizedBox.shrink();
if (type == PageStyleCoverImageType.customImage ||
type == PageStyleCoverImageType.unsplashImage) {
final userProfilePB =
context.read<MobileViewPageBloc>().state.userProfilePB;
preview = FlowyNetworkImage(
url: value,
userProfilePB: userProfilePB,
);
}
if (type == PageStyleCoverImageType.builtInImage) {
preview = Image.asset(
PageStyleCoverImageType.builtInImagePath(value),
fit: BoxFit.cover,
);
}
if (type == PageStyleCoverImageType.pureColor) {
final color = value.coverColor(context);
if (color != null) {
preview = ColoredBox(
color: color,
);
}
}
if (type == PageStyleCoverImageType.gradientColor) {
preview = Container(
decoration: BoxDecoration(
gradient: FlowyGradientColor.fromId(value).linear,
),
);
}
if (type == PageStyleCoverImageType.localImage) {
preview = Image.file(
File(value),
fit: BoxFit.cover,
);
}
return Row(
children: [
FlowyText(LocaleKeys.pageStyle_image.tr()),
const Spacer(),
Container(
width: 40,
height: 28,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
border: Border.all(color: const Color(0x1F222533)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(5.0)),
child: preview,
),
),
],
);
}
void _showPresets(BuildContext context) {
final pageStyleBloc = context.read<DocumentPageStyleBloc>();
context.pop();
showMobileBottomSheet(
context,
showDragHandle: true,
@ -99,18 +181,18 @@ class PageStyleCoverImage extends StatelessWidget {
showHeader: true,
showRemoveButton: true,
onRemove: () {
context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
);
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
);
},
title: LocaleKeys.pageStyle_pageCover.tr(),
title: LocaleKeys.pageStyle_presets.tr(),
barrierColor: Colors.transparent,
backgroundColor: Theme.of(context).colorScheme.background,
builder: (_) {
return BlocProvider.value(
value: context.read<DocumentPageStyleBloc>(),
value: pageStyleBloc,
child: const PageCoverBottomSheet(),
);
},
@ -174,6 +256,9 @@ class PageStyleCoverImage extends StatelessWidget {
}
void _showUnsplash(BuildContext context) {
final pageStyleBloc = context.read<DocumentPageStyleBloc>();
context.pop();
showMobileBottomSheet(
context,
showDragHandle: true,
@ -181,15 +266,15 @@ class PageStyleCoverImage extends StatelessWidget {
showDoneButton: true,
showHeader: true,
showRemoveButton: true,
title: LocaleKeys.pageStyle_coverImage.tr(),
title: LocaleKeys.pageStyle_unsplash.tr(),
barrierColor: Colors.transparent,
backgroundColor: Theme.of(context).colorScheme.background,
onRemove: () {
context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
);
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
);
},
builder: (_) {
return ConstrainedBox(
@ -204,14 +289,14 @@ class PageStyleCoverImage extends StatelessWidget {
child: UnsplashImageWidget(
type: UnsplashImageType.fullScreen,
onSelectUnsplashImage: (url) {
context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover(
type: PageStyleCoverImageType.unsplashImage,
value: url,
),
),
);
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover(
type: PageStyleCoverImageType.unsplashImage,
value: url,
),
),
);
},
),
),
@ -308,6 +393,7 @@ class _CoverOptionButton extends StatelessWidget {
duration: Durations.medium1,
decoration: selected
? ShapeDecoration(
color: const Color(0x141AC3F2),
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.50,

View File

@ -11,8 +11,9 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:go_router/go_router.dart';
class PageStyleIcon extends StatelessWidget {
class PageStyleIcon extends StatefulWidget {
const PageStyleIcon({
super.key,
required this.view,
@ -20,10 +21,15 @@ class PageStyleIcon extends StatelessWidget {
final ViewPB view;
@override
State<PageStyleIcon> createState() => _PageStyleIconState();
}
class _PageStyleIconState extends State<PageStyleIcon> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => PageStyleIconBloc(view: view)
create: (_) => PageStyleIconBloc(view: widget.view)
..add(const PageStyleIconEvent.initial()),
child: BlocBuilder<PageStyleIconBloc, PageStyleIconState>(
builder: (context, state) {
@ -60,6 +66,10 @@ class PageStyleIcon extends StatelessWidget {
}
void _showIconSelector(BuildContext context, String selectedIcon) {
context.pop();
final pageStyleIconBloc = PageStyleIconBloc(view: widget.view)
..add(const PageStyleIconEvent.initial());
showMobileBottomSheet(
context,
showDragHandle: true,
@ -75,13 +85,13 @@ class PageStyleIcon extends StatelessWidget {
initialChildSize: 0.61,
showRemoveButton: true,
onRemove: () {
context.read<PageStyleIconBloc>().add(
const PageStyleIconEvent.updateIcon('', true),
);
pageStyleIconBloc.add(
const PageStyleIconEvent.updateIcon('', true),
);
},
scrollableWidgetBuilder: (_, controller) {
return BlocProvider.value(
value: context.read<PageStyleIconBloc>(),
value: pageStyleIconBloc,
child: Expanded(
child: Scrollbar(
controller: controller,
@ -112,6 +122,8 @@ class _IconSelectorState extends State<_IconSelector> {
EmojiData? emojiData;
List<String> availableEmojis = [];
PageStyleIconBloc? pageStyleIconBloc;
@override
void initState() {
super.initState();
@ -131,6 +143,14 @@ class _IconSelectorState extends State<_IconSelector> {
},
);
}
pageStyleIconBloc = context.read<PageStyleIconBloc>();
}
@override
void dispose() {
pageStyleIconBloc?.close();
super.dispose();
}
@override
@ -146,7 +166,7 @@ class _IconSelectorState extends State<_IconSelector> {
_buildSearchBar(context),
Expanded(
child: GridView.count(
crossAxisCount: _getEmojiPerLine(context),
crossAxisCount: 7,
controller: widget.scrollController,
children: [
for (final emoji in availableEmojis)
@ -165,27 +185,33 @@ class _IconSelectorState extends State<_IconSelector> {
String emoji,
String? selectedEmoji,
) {
Widget child = Center(
child: FlowyText.emoji(
emoji,
fontSize: 24,
Widget child = SizedBox.square(
dimension: 24.0,
child: Center(
child: FlowyText.emoji(
emoji,
fontSize: 24,
),
),
);
if (emoji == selectedEmoji) {
child = Container(
margin: const EdgeInsets.all(8.0),
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.50,
strokeAlign: BorderSide.strokeAlignOutside,
color: Color(0xFF00BCF0),
child = Center(
child: Container(
width: 40,
height: 40,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.40,
strokeAlign: BorderSide.strokeAlignOutside,
color: Color(0xFF00BCF0),
),
borderRadius: BorderRadius.circular(10),
),
borderRadius: BorderRadius.circular(9),
),
child: child,
),
child: child,
);
}
@ -208,11 +234,6 @@ class _IconSelectorState extends State<_IconSelector> {
return availableEmojis;
}
int _getEmojiPerLine(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width ~/ 48.0; // the size of the emoji
}
Widget _buildSearchBar(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(

View File

@ -11,6 +11,7 @@ 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';
import 'package:go_router/go_router.dart';
const kPageStyleLayoutHeight = 52.0;
@ -117,6 +118,7 @@ class _OptionGroup<T> extends StatelessWidget {
duration: Durations.medium1,
decoration: selected
? ShapeDecoration(
color: const Color(0x141AC3F2),
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.50,
@ -180,7 +182,10 @@ class _FontButton extends StatelessWidget {
const HSpace(16.0),
FlowyText(LocaleKeys.titleBar_font.tr()),
const Spacer(),
FlowyText(fontFamilyDisplayName),
FlowyText(
fontFamilyDisplayName,
color: context.pageStyleTextColor,
),
const HSpace(6.0),
const FlowySvg(FlowySvgs.m_page_style_arrow_right_s),
const HSpace(12.0),
@ -193,6 +198,9 @@ class _FontButton extends StatelessWidget {
}
void _showFontSelector(BuildContext context) {
final pageStyleBloc = context.read<DocumentPageStyleBloc>();
context.pop();
showMobileBottomSheet(
context,
showDragHandle: true,
@ -208,7 +216,7 @@ class _FontButton extends StatelessWidget {
initialChildSize: 0.61,
scrollableWidgetBuilder: (_, controller) {
return BlocProvider.value(
value: context.read<DocumentPageStyleBloc>(),
value: pageStyleBloc,
child: BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
builder: (context, state) {
return Expanded(
@ -219,11 +227,11 @@ class _FontButton extends StatelessWidget {
selectedFontFamilyName:
state.fontFamily ?? defaultFontFamily,
onFontFamilySelected: (fontFamilyName) {
context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateFontFamily(
fontFamilyName,
),
);
pageStyleBloc.add(
DocumentPageStyleEvent.updateFontFamily(
fontFamilyName,
),
);
},
),
),

View File

@ -25,7 +25,7 @@ class PageStyleBottomSheet extends StatelessWidget {
children: [
// cover image
FlowyText(
LocaleKeys.pageStyle_backgroundImage.tr(),
LocaleKeys.pageStyle_coverImage.tr(),
color: context.pageStyleTextColor,
fontSize: 14.0,
),