Lucas 225683562b
fix: 0.7.4 launch review issues on mobile (#6795)
* feat: expand the hit test area for more button in space menu

* fix: contrast issue for the delte dialog text

* fix: stay in space menu after deleting a space

* fix: hide padding when space icon list is scrolled down

* feat: expand the hit test area for toggle button

* feat: remove open workspace success toast

* chore: update translations

* test: stay in space menu after deleting a space

* chore: enable android test

* fix: integration tests
2024-11-15 16:19:02 +08:00

390 lines
11 KiB
Dart

import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
import 'package:appflowy/shared/icon_emoji_picker/colors.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart' hide Icon;
import 'constants.dart';
import 'manage_space_widget.dart';
import 'space_permission_bottom_sheet.dart';
class ManageSpaceNameOption extends StatelessWidget {
const ManageSpaceNameOption({
super.key,
required this.controller,
required this.type,
});
final TextEditingController controller;
final ManageSpaceType type;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 4),
child: FlowyText(
LocaleKeys.space_spaceName.tr(),
fontSize: 14,
figmaLineHeight: 20.0,
fontWeight: FontWeight.w400,
color: Theme.of(context).hintColor,
),
),
FlowyOptionTile.textField(
controller: controller,
autofocus: type == ManageSpaceType.create ? true : false,
textFieldHintText: LocaleKeys.space_spaceNamePlaceholder.tr(),
),
const VSpace(16),
],
);
}
}
class ManageSpacePermissionOption extends StatelessWidget {
const ManageSpacePermissionOption({
super.key,
required this.permission,
});
final ValueNotifier<SpacePermission> permission;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 4),
child: FlowyText(
LocaleKeys.space_permission.tr(),
fontSize: 14,
figmaLineHeight: 20.0,
fontWeight: FontWeight.w400,
color: Theme.of(context).hintColor,
),
),
ValueListenableBuilder(
valueListenable: permission,
builder: (context, value, child) => FlowyOptionTile.text(
height: SpaceUIConstants.itemHeight,
text: value.i18n,
leftIcon: FlowySvg(value.icon),
trailing: const FlowySvg(
FlowySvgs.arrow_right_s,
),
onTap: () {
showMobileBottomSheet(
context,
showHeader: true,
title: LocaleKeys.space_permission.tr(),
showCloseButton: true,
showDivider: false,
showDragHandle: true,
builder: (context) => SpacePermissionBottomSheet(
permission: value,
onAction: (value) {
permission.value = value;
Navigator.pop(context);
},
),
);
},
),
),
const VSpace(16),
],
);
}
}
class ManageSpaceIconOption extends StatefulWidget {
const ManageSpaceIconOption({
super.key,
required this.selectedColor,
required this.selectedIcon,
});
final ValueNotifier<String> selectedColor;
final ValueNotifier<Icon?> selectedIcon;
@override
State<ManageSpaceIconOption> createState() => _ManageSpaceIconOptionState();
}
class _ManageSpaceIconOptionState extends State<ManageSpaceIconOption> {
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
..._buildColorOption(context),
..._buildSpaceIconOption(context),
],
);
}
List<Widget> _buildColorOption(BuildContext context) {
return [
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 4),
child: FlowyText(
LocaleKeys.space_mSpaceIconColor.tr(),
fontSize: 14,
figmaLineHeight: 20.0,
fontWeight: FontWeight.w400,
color: Theme.of(context).hintColor,
),
),
ValueListenableBuilder(
valueListenable: widget.selectedColor,
builder: (context, selectedColor, child) {
return FlowyOptionDecorateBox(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: builtInSpaceColors.map((color) {
return SpaceColorItem(
color: color,
selectedColor: selectedColor,
onSelected: (color) => widget.selectedColor.value = color,
);
}).toList(),
),
),
),
);
},
),
const VSpace(16),
];
}
List<Widget> _buildSpaceIconOption(BuildContext context) {
return [
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 4),
child: FlowyText(
LocaleKeys.space_mSpaceIcon.tr(),
fontSize: 14,
figmaLineHeight: 20.0,
fontWeight: FontWeight.w400,
color: Theme.of(context).hintColor,
),
),
Expanded(
child: SizedBox(
width: double.infinity,
child: ValueListenableBuilder(
valueListenable: widget.selectedColor,
builder: (context, selectedColor, child) {
return ValueListenableBuilder(
valueListenable: widget.selectedIcon,
builder: (context, selectedIcon, child) {
return FlowyOptionDecorateBox(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: _buildIconGroups(
context,
selectedColor,
selectedIcon,
),
),
);
},
);
},
),
),
),
const VSpace(16),
];
}
Widget _buildIconGroups(
BuildContext context,
String selectedColor,
Icon? selectedIcon,
) {
final iconGroups = kIconGroups;
if (iconGroups == null) {
return const SizedBox.shrink();
}
return ListView.builder(
itemCount: iconGroups.length,
itemBuilder: (context, index) {
final iconGroup = iconGroups[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const VSpace(12.0),
FlowyText(
iconGroup.displayName.capitalize(),
fontSize: 12,
figmaLineHeight: 18.0,
color: context.pickerTextColor,
),
const VSpace(4.0),
Center(
child: Wrap(
spacing: 10.0,
runSpacing: 8.0,
children: iconGroup.icons.map((icon) {
return SpaceIconItem(
icon: icon,
isSelected: selectedIcon?.name == icon.name,
selectedColor: selectedColor,
onSelectedIcon: (icon) => widget.selectedIcon.value = icon,
);
}).toList(),
),
),
const VSpace(12.0),
if (index == iconGroups.length - 1) ...[
const StreamlinePermit(),
],
],
);
},
);
}
}
class SpaceIconItem extends StatelessWidget {
const SpaceIconItem({
super.key,
required this.icon,
required this.onSelectedIcon,
required this.isSelected,
required this.selectedColor,
});
final Icon icon;
final void Function(Icon icon) onSelectedIcon;
final bool isSelected;
final String selectedColor;
@override
Widget build(BuildContext context) {
return AnimatedGestureDetector(
onTapUp: () => onSelectedIcon(icon),
child: Container(
width: 36,
height: 36,
decoration: isSelected
? BoxDecoration(
color: Color(int.parse(selectedColor)),
borderRadius: BorderRadius.circular(8.0),
)
: ShapeDecoration(
color: Colors.transparent,
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 0.5,
color: Color(0x661F2329),
),
borderRadius: BorderRadius.circular(8),
),
),
child: Center(
child: FlowySvg.string(
icon.content,
size: const Size.square(18),
color: isSelected
? Theme.of(context).colorScheme.surface
: context.pickerIconColor,
opacity: isSelected ? 1.0 : 0.7,
),
),
),
);
}
}
class SpaceColorItem extends StatelessWidget {
const SpaceColorItem({
super.key,
required this.color,
required this.selectedColor,
required this.onSelected,
});
final String color;
final String selectedColor;
final void Function(String color) onSelected;
@override
Widget build(BuildContext context) {
final child = Center(
child: Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: Color(int.parse(color)),
borderRadius: BorderRadius.circular(14),
),
),
);
final decoration = color != selectedColor
? null
: ShapeDecoration(
color: Colors.transparent,
shape: RoundedRectangleBorder(
side: BorderSide(
width: 1.50,
color: Theme.of(context).colorScheme.primary,
),
borderRadius: BorderRadius.circular(21),
),
);
return AnimatedGestureDetector(
onTapUp: () => onSelected(color),
child: Container(
width: 36,
height: 36,
decoration: decoration,
child: child,
),
);
}
}
extension on SpacePermission {
String get i18n {
switch (this) {
case SpacePermission.publicToAll:
return LocaleKeys.space_publicPermission.tr();
case SpacePermission.private:
return LocaleKeys.space_privatePermission.tr();
}
}
FlowySvgData get icon {
switch (this) {
case SpacePermission.publicToAll:
return FlowySvgs.space_permission_public_s;
case SpacePermission.private:
return FlowySvgs.space_permission_private_s;
}
}
}