feat: implement personal / favorites folder on the mobile platform (#3723)

This commit is contained in:
Lucas.Xu 2023-10-23 18:35:07 +08:00 committed by GitHub
parent a57ca5c0cb
commit d51c7f382f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 2625 additions and 250 deletions

View File

@ -186,6 +186,14 @@ RUST_COMPILE_TARGET = "aarch64-apple-ios"
BUILD_ARCHS = "arm64"
CRATE_TYPE = "staticlib"
[env.production-ios-arm64]
BUILD_FLAG = "release"
TARGET_OS = "ios"
FLUTTER_OUTPUT_DIR = "Release"
RUST_COMPILE_TARGET = "aarch64-apple-ios"
BUILD_ARCHS = "arm64"
CRATE_TYPE = "staticlib"
[env.development-android]
BUILD_FLAG = "debug"
TARGET_OS = "android"

View File

@ -13,22 +13,9 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
9D1D47ADD7F5DE8237063BCA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 197F72694BED43249F1523E8 /* Pods_Runner.framework */; };
FB3C2A642AE0D57700490715 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 197F72694BED43249F1523E8 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
@ -54,7 +41,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9D1D47ADD7F5DE8237063BCA /* Pods_Runner.framework in Frameworks */,
FB3C2A642AE0D57700490715 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -121,7 +108,6 @@
580A1ED8E012CA1552E5EFD3 /* Pods-Runner.release.xcconfig */,
4C2CB38DA64605A62D45B098 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
@ -137,7 +123,6 @@
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
08FAA63113168DEC7FB74204 /* [CP] Embed Pods Frameworks */,
);
@ -359,11 +344,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = io.appflowy.appflowy;
PRODUCT_NAME = "$(TARGET_NAME)";
STRIP_STYLE = "non-global";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -483,11 +475,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = io.appflowy.appflowy;
PRODUCT_NAME = "$(TARGET_NAME)";
STRIP_STYLE = "non-global";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -502,11 +501,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = io.appflowy.appflowy;
PRODUCT_NAME = "$(TARGET_NAME)";
STRIP_STYLE = "non-global";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,122 +1,218 @@
{
"images" : [
{
"size" : "20x20",
"filename" : "40.png",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"size" : "20x20",
"filename" : "60.png",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
"scale" : "3x",
"size" : "20x20"
},
{
"size" : "29x29",
"filename" : "29.png",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
"scale" : "1x",
"size" : "29x29"
},
{
"size" : "29x29",
"filename" : "58.png",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "29x29"
},
{
"size" : "29x29",
"filename" : "87.png",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
"scale" : "3x",
"size" : "29x29"
},
{
"size" : "40x40",
"filename" : "80.png",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "40x40"
},
{
"size" : "40x40",
"filename" : "120.png",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
"scale" : "3x",
"size" : "40x40"
},
{
"size" : "60x60",
"filename" : "57.png",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
"scale" : "1x",
"size" : "57x57"
},
{
"size" : "60x60",
"filename" : "114.png",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
"scale" : "2x",
"size" : "57x57"
},
{
"size" : "20x20",
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "20.png",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
"scale" : "1x",
"size" : "20x20"
},
{
"size" : "20x20",
"filename" : "40.png",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "20x20"
},
{
"size" : "29x29",
"filename" : "29.png",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
"scale" : "1x",
"size" : "29x29"
},
{
"size" : "29x29",
"filename" : "58.png",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "29x29"
},
{
"size" : "40x40",
"filename" : "40.png",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
"scale" : "1x",
"size" : "40x40"
},
{
"size" : "40x40",
"filename" : "80.png",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "40x40"
},
{
"size" : "76x76",
"filename" : "50.png",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
"scale" : "1x",
"size" : "50x50"
},
{
"size" : "76x76",
"filename" : "100.png",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
"scale" : "2x",
"size" : "50x50"
},
{
"size" : "83.5x83.5",
"filename" : "72.png",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
"scale" : "1x",
"size" : "72x72"
},
{
"size" : "1024x1024",
"filename" : "144.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "72x72"
},
{
"filename" : "76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "1024.png",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename" : "16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "256.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "512.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -11,7 +11,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>appflowy_flutter</string>
<string>AppFlowy</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>

View File

@ -0,0 +1,64 @@
import 'package:appflowy/mobile/presentation/database/mobile_board_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
extension MobileRouter on BuildContext {
Future<void> pushView(ViewPB view) async {
push(
Uri(
path: view.routeName,
queryParameters: view.queryParameters,
).toString(),
);
}
}
extension on ViewPB {
String get routeName {
switch (layout) {
case ViewLayoutPB.Document:
return MobileEditorScreen.routeName;
case ViewLayoutPB.Grid:
return MobileGridScreen.routeName;
case ViewLayoutPB.Calendar:
return MobileCalendarScreen.routeName;
case ViewLayoutPB.Board:
return MobileBoardScreen.routeName;
default:
throw UnimplementedError('routeName for $this is not implemented');
}
}
Map<String, dynamic> get queryParameters {
switch (layout) {
case ViewLayoutPB.Document:
return {
MobileEditorScreen.viewId: id,
MobileEditorScreen.viewTitle: name,
};
case ViewLayoutPB.Grid:
return {
MobileGridScreen.viewId: id,
MobileGridScreen.viewTitle: name,
};
case ViewLayoutPB.Calendar:
return {
MobileCalendarScreen.viewId: id,
MobileCalendarScreen.viewTitle: name,
};
case ViewLayoutPB.Board:
return {
MobileBoardScreen.viewId: id,
MobileBoardScreen.viewTitle: name,
};
default:
throw UnimplementedError(
'queryParameters for $this is not implemented',
);
}
}
}

View File

@ -1,7 +1,9 @@
// ThemeData in mobile
import 'package:flutter/material.dart';
ThemeData getMobileThemeData() {
ThemeData getMobileThemeData(
Theme theme,
) {
const mobileColorTheme = ColorScheme(
brightness: Brightness.light,
primary: Color(0xFF2DA2F6), //primary 100
@ -106,7 +108,7 @@ ThemeData getMobileThemeData() {
),
// body2 14 Regular
bodyMedium: TextStyle(
color: Color(0xFFC5C7CB),
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.20,

View File

@ -0,0 +1,83 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/error/error_page.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:dartz/dartz.dart' hide State;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class MobileViewPage extends StatefulWidget {
const MobileViewPage({
super.key,
required this.id,
this.title,
required this.viewLayout,
});
/// view id
final String id;
final String? title;
final ViewLayoutPB viewLayout;
@override
State<MobileViewPage> createState() => _MobileViewPageState();
}
class _MobileViewPageState extends State<MobileViewPage> {
late final Future<Either<ViewPB, FlowyError>> future;
@override
void initState() {
super.initState();
future = ViewBackendService.getView(widget.id);
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, state) {
Widget body;
String? title;
if (state.connectionState != ConnectionState.done) {
body = const Center(
child: CircularProgressIndicator(),
);
} else if (!state.hasData) {
body = MobileErrorPage(
message: LocaleKeys.error_loadingViewError.tr(),
);
} else {
body = state.data!.fold((view) {
title = view.name;
return view.plugin().widgetBuilder.buildWidget(shrinkWrap: false);
}, (error) {
return MobileErrorPage(
message: error.toString(),
);
});
}
return Scaffold(
appBar: AppBar(
titleSpacing: 0,
title: FlowyText(
title ?? widget.title ?? '',
fontSize: 14.0,
),
leading: BackButton(
onPressed: () => context.pop(),
),
),
body: SafeArea(
child: body,
),
);
},
);
}
}

View File

@ -0,0 +1,98 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/mobile_bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_slide_action_button.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
enum MobilePaneActionType {
delete,
addToFavorites,
removeFromFavorites,
more;
MobileSlideActionButton actionButton(
BuildContext context,
) {
switch (this) {
case MobilePaneActionType.delete:
return MobileSlideActionButton(
backgroundColor: Colors.red,
svg: FlowySvgs.delete_s,
size: 30.0,
onPressed: (context) =>
context.read<ViewBloc>().add(const ViewEvent.delete()),
);
case MobilePaneActionType.removeFromFavorites:
return MobileSlideActionButton(
backgroundColor: Colors.red,
svg: FlowySvgs.favorite_s,
onPressed: (context) => context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
);
case MobilePaneActionType.addToFavorites:
return MobileSlideActionButton(
backgroundColor: Colors.orange,
svg: FlowySvgs.m_favorite_unselected_lg,
size: 34.0,
onPressed: (context) => context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
);
case MobilePaneActionType.more:
return MobileSlideActionButton(
backgroundColor: Colors.grey,
svg: FlowySvgs.three_dots_vertical_s,
size: 28.0,
onPressed: (context) {
final viewBloc = context.read<ViewBloc>();
final favoriteBloc = context.read<FavoriteBloc>();
showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8.0),
topRight: Radius.circular(8.0),
),
),
builder: (context) {
return MultiBlocProvider(
providers: [
BlocProvider.value(value: viewBloc),
BlocProvider.value(value: favoriteBloc),
],
child: BlocBuilder<ViewBloc, ViewState>(
builder: (context, state) {
return MobileViewItemBottomSheet(
view: viewBloc.view,
);
},
),
);
},
);
},
);
}
}
}
ActionPane buildEndActionPane(
BuildContext context,
List<MobilePaneActionType> actions,
) {
return ActionPane(
motion: const ScrollMotion(),
extentRatio: actions.length / 5,
children: actions
.map(
(action) => action.actionButton(context),
)
.toList(),
);
}

View File

@ -0,0 +1,116 @@
import 'package:appflowy/mobile/presentation/bottom_sheet/mobile_bottom_sheet_body.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/mobile_bottom_sheet_header.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/mobile_bottom_sheet_rename_widget.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum MobileBottomSheetType {
view,
rename,
}
class MobileViewItemBottomSheet extends StatefulWidget {
const MobileViewItemBottomSheet({
super.key,
required this.view,
});
final ViewPB view;
@override
State<MobileViewItemBottomSheet> createState() =>
_MobileViewItemBottomSheetState();
}
class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
MobileBottomSheetType type = MobileBottomSheetType.view;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// drag handler
Padding(
padding: const EdgeInsets.only(top: 12, bottom: 12.0),
child: Container(
width: 64,
height: 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.0),
color: Colors.grey,
),
),
),
// header
MobileBottomSheetHeader(
showBackButton: type != MobileBottomSheetType.view,
view: widget.view,
onBack: () {
setState(() {
type = MobileBottomSheetType.view;
});
},
),
const VSpace(8.0),
const Divider(),
// body
_buildBody(),
const VSpace(12.0),
],
);
}
Widget _buildBody() {
switch (type) {
case MobileBottomSheetType.view:
return MobileViewItemBottomSheetBody(
isFavorite: widget.view.isFavorite,
onAction: (action) {
switch (action) {
case MobileViewItemBottomSheetBodyAction.rename:
setState(() {
type = MobileBottomSheetType.rename;
});
break;
case MobileViewItemBottomSheetBodyAction.duplicate:
context.read<ViewBloc>().add(const ViewEvent.duplicate());
Navigator.pop(context);
break;
case MobileViewItemBottomSheetBodyAction.share:
// unimplemented
Navigator.pop(context);
break;
case MobileViewItemBottomSheetBodyAction.delete:
context.read<ViewBloc>().add(const ViewEvent.delete());
Navigator.pop(context);
break;
case MobileViewItemBottomSheetBodyAction.addToFavorites:
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(widget.view));
Navigator.pop(context);
break;
}
},
);
case MobileBottomSheetType.rename:
return MobileBottomSheetRenameWidget(
name: widget.view.name,
onRename: (name) {
if (name != widget.view.name) {
context.read<ViewBloc>().add(ViewEvent.rename(name));
}
Navigator.pop(context);
},
);
}
}
}

View File

@ -0,0 +1,53 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class BottomSheetActionWidget extends StatelessWidget {
const BottomSheetActionWidget({
super.key,
required this.svg,
required this.text,
required this.onTap,
});
final FlowySvgData svg;
final String text;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
),
borderRadius: BorderRadius.circular(8.0),
),
child: InkWell(
onTap: onTap,
enableFeedback: true,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12.0,
horizontal: 16.0,
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
FlowySvg(
svg,
size: const Size.square(22.0),
blendMode: BlendMode.dst,
),
const HSpace(6.0),
FlowyText(text),
const Spacer(),
],
),
),
),
);
}
}

View File

@ -0,0 +1,100 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/mobile_bottom_sheet_action_widget.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
enum MobileViewItemBottomSheetBodyAction {
rename,
duplicate,
share,
delete,
addToFavorites,
removeFromFavorites,
}
class MobileViewItemBottomSheetBody extends StatelessWidget {
const MobileViewItemBottomSheetBody({
super.key,
this.isFavorite = false,
required this.onAction,
});
final bool isFavorite;
final void Function(MobileViewItemBottomSheetBodyAction action) onAction;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// rename, duplicate
Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: BottomSheetActionWidget(
svg: FlowySvgs.m_rename_m,
text: LocaleKeys.button_rename.tr(),
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.rename,
),
),
),
Expanded(
child: BottomSheetActionWidget(
svg: FlowySvgs.m_duplicate_m,
text: LocaleKeys.button_duplicate.tr(),
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.duplicate,
),
),
),
],
),
// share, delete
Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: BottomSheetActionWidget(
svg: FlowySvgs.m_share_m,
text: LocaleKeys.button_share.tr(),
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.share,
),
),
),
Expanded(
child: BottomSheetActionWidget(
svg: FlowySvgs.m_delete_m,
text: LocaleKeys.button_delete.tr(),
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.delete,
),
),
),
],
),
// remove from favorites
BottomSheetActionWidget(
svg: isFavorite
? FlowySvgs.m_favorite_selected_lg
: FlowySvgs.m_favorite_unselected_lg,
text: isFavorite
? LocaleKeys.button_removeFromFavorites.tr()
: LocaleKeys.button_addToFavorites.tr(),
onTap: () => onAction(
isFavorite
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites,
),
)
],
);
}
}

View File

@ -0,0 +1,45 @@
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class MobileBottomSheetHeader extends StatelessWidget {
const MobileBottomSheetHeader({
super.key,
required this.view,
required this.showBackButton,
required this.onBack,
});
final ViewPB view;
final bool showBackButton;
final VoidCallback onBack;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// back button,
showBackButton
? InkWell(
onTap: onBack,
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Icon(
Icons.arrow_back_ios_new_rounded,
size: 24.0,
),
),
)
: const HSpace(40.0),
// title
FlowyText.regular(
view.name,
fontSize: 16.0,
),
// placeholder, ensure the title is centered
const HSpace(40.0),
],
);
}
}

View File

@ -0,0 +1,76 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class MobileBottomSheetRenameWidget extends StatefulWidget {
const MobileBottomSheetRenameWidget({
super.key,
required this.name,
required this.onRename,
});
final String name;
final void Function(String name) onRename;
@override
State<MobileBottomSheetRenameWidget> createState() =>
_MobileBottomSheetRenameWidgetState();
}
class _MobileBottomSheetRenameWidgetState
extends State<MobileBottomSheetRenameWidget> {
late final TextEditingController controller;
@override
void initState() {
super.initState();
controller = TextEditingController(text: widget.name);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4.0,
vertical: 16.0,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const HSpace(8.0),
Expanded(
child: SizedBox(
height: 44.0,
child: FlowyTextField(
controller: controller,
),
),
),
const HSpace(12.0),
FlowyTextButton(
LocaleKeys.button_edit.tr(),
padding: const EdgeInsets.symmetric(
vertical: 12.0,
horizontal: 16.0,
),
fontColor: Colors.white,
fillColor: Colors.lightBlue.shade300,
onPressed: () {
widget.onRename(controller.text);
},
),
const HSpace(8.0),
],
),
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:appflowy/mobile/presentation/base/mobile_view_page.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter/material.dart';
class MobileBoardScreen extends StatelessWidget {
static const routeName = '/board';
static const viewId = 'id';
static const viewTitle = 'title';
const MobileBoardScreen({
super.key,
required this.id,
this.title,
});
/// view id
final String id;
final String? title;
@override
Widget build(BuildContext context) {
return MobileViewPage(
id: id,
title: title,
viewLayout: ViewLayoutPB.Document,
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:appflowy/mobile/presentation/base/mobile_view_page.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter/material.dart';
class MobileCalendarScreen extends StatelessWidget {
static const routeName = '/calendar';
static const viewId = 'id';
static const viewTitle = 'title';
const MobileCalendarScreen({
super.key,
required this.id,
this.title,
});
/// view id
final String id;
final String? title;
@override
Widget build(BuildContext context) {
return MobileViewPage(
id: id,
title: title,
viewLayout: ViewLayoutPB.Document,
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:appflowy/mobile/presentation/base/mobile_view_page.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter/material.dart';
class MobileGridScreen extends StatelessWidget {
static const routeName = '/grid';
static const viewId = 'id';
static const viewTitle = 'title';
const MobileGridScreen({
super.key,
required this.id,
this.title,
});
/// view id
final String id;
final String? title;
@override
Widget build(BuildContext context) {
return MobileViewPage(
id: id,
title: title,
viewLayout: ViewLayoutPB.Document,
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:appflowy/mobile/presentation/base/mobile_view_page.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter/material.dart';
class MobileEditorScreen extends StatelessWidget {
static const routeName = '/docs';
static const viewId = 'id';
static const viewTitle = 'title';
const MobileEditorScreen({
super.key,
required this.id,
this.title,
});
/// view id
final String id;
final String? title;
@override
Widget build(BuildContext context) {
return MobileViewPage(
id: id,
title: title,
viewLayout: ViewLayoutPB.Document,
);
}
}

View File

@ -0,0 +1,49 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class MobileErrorPage extends StatelessWidget {
const MobileErrorPage({
super.key,
this.header,
this.title,
required this.message,
});
final Widget? header;
final String? title;
final String message;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
header != null
? header!
: const FlowyText.semibold(
'😔',
fontSize: 50,
),
const VSpace(14.0),
FlowyText.semibold(
title ?? LocaleKeys.error_weAreSorry.tr(),
fontSize: 32,
textAlign: TextAlign.center,
),
const VSpace(4.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: FlowyText.regular(
message,
fontSize: 16,
maxLines: 100,
color: Colors.grey, // FIXME: use theme color
textAlign: TextAlign.center,
),
),
],
);
}
}

View File

@ -0,0 +1,71 @@
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class MobileFavoritePageFolder extends StatelessWidget {
const MobileFavoritePageFolder({
super.key,
required this.userProfile,
required this.workspaceSetting,
});
final UserProfilePB userProfile;
final WorkspaceSettingPB workspaceSetting;
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => MenuBloc(
user: userProfile,
workspace: workspaceSetting.workspace,
)..add(const MenuEvent.initial()),
),
BlocProvider(
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
)
],
child: MultiBlocListener(
listeners: [
BlocListener<MenuBloc, MenuState>(
listenWhen: (p, c) =>
p.lastCreatedView?.id != c.lastCreatedView?.id,
listener: (context, state) =>
context.pushView(state.lastCreatedView!),
),
],
child: Builder(
builder: (context) {
final favoriteState = context.watch<FavoriteBloc>().state;
if (favoriteState.views.isEmpty) {
return const Center(
// todo: i18n
child: FlowyText('No favorite pages'),
);
}
return SlidableAutoCloseBehavior(
child: Column(
children: [
MobileFavoriteFolder(
showHeader: false,
forceExpanded: true,
views: favoriteState.views,
),
const VSpace(100.0),
],
),
);
},
),
),
);
}
}

View File

@ -0,0 +1,100 @@
import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_folder.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:flutter/material.dart';
class MobileFavoriteScreen extends StatelessWidget {
const MobileFavoriteScreen({
super.key,
});
static const routeName = '/favorite';
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([
FolderEventGetCurrentWorkspace().send(),
getIt<AuthService>().getUser(),
]),
builder: (context, snapshots) {
if (!snapshots.hasData) {
return const Center(child: CircularProgressIndicator.adaptive());
}
final workspaceSetting = snapshots.data?[0].fold(
(workspaceSettingPB) {
return workspaceSettingPB as WorkspaceSettingPB?;
},
(error) => null,
);
final userProfile =
snapshots.data?[1].fold((error) => null, (userProfilePB) {
return userProfilePB as UserProfilePB?;
});
// In the unlikely case either of the above is null, eg.
// when a workspace is already open this can happen.
if (workspaceSetting == null || userProfile == null) {
return const WorkspaceFailedScreen();
}
return Scaffold(
body: SafeArea(
child: MobileFavoritePage(
userProfile: userProfile,
workspaceSetting: workspaceSetting,
),
),
);
},
);
}
}
class MobileFavoritePage extends StatelessWidget {
const MobileFavoritePage({
super.key,
required this.userProfile,
required this.workspaceSetting,
});
final UserProfilePB userProfile;
final WorkspaceSettingPB workspaceSetting;
@override
Widget build(BuildContext context) {
return Column(
children: [
// Header
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: MobileHomePageHeader(
userProfile: userProfile,
),
),
const Divider(),
// Folder
Expanded(
child: Scrollbar(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: MobileFavoritePageFolder(
userProfile: userProfile,
workspaceSetting: workspaceSetting,
),
),
),
),
),
],
);
}
}

View File

@ -0,0 +1,80 @@
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder_header.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileFavoriteFolder extends StatelessWidget {
const MobileFavoriteFolder({
super.key,
required this.views,
this.showHeader = true,
this.forceExpanded = false,
});
final bool showHeader;
final bool forceExpanded;
final List<ViewPB> views;
@override
Widget build(BuildContext context) {
if (views.isEmpty) {
return const SizedBox.shrink();
}
return BlocProvider<FolderBloc>(
create: (context) => FolderBloc(type: FolderCategoryType.favorite)
..add(
const FolderEvent.initial(),
),
child: BlocBuilder<FolderBloc, FolderState>(
builder: (context, state) {
return Column(
children: [
if (showHeader) ...[
MobileFavoriteFolderHeader(
isExpanded: context.read<FolderBloc>().state.isExpanded,
onPressed: () => context
.read<FolderBloc>()
.add(const FolderEvent.expandOrUnExpand()),
onAdded: () => context.read<FolderBloc>().add(
const FolderEvent.expandOrUnExpand(isExpanded: true),
),
),
const VSpace(8.0),
const Divider(
height: 1,
),
],
if (forceExpanded || state.isExpanded)
...views.map(
(view) => MobileViewItem(
key: ValueKey(
'${FolderCategoryType.favorite.name} ${view.id}',
),
categoryType: FolderCategoryType.favorite,
isDraggable: false,
isFirstChild: view.id == views.first.id,
isFeedback: false,
view: view,
level: 0,
onSelected: (view) async {
await context.pushView(view);
},
endActionPane: (context) => buildEndActionPane(context, [
MobilePaneActionType.removeFromFavorites,
MobilePaneActionType.more,
]),
),
),
],
);
},
),
);
}
}

View File

@ -0,0 +1,60 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class MobileFavoriteFolderHeader extends StatefulWidget {
const MobileFavoriteFolderHeader({
super.key,
required this.onPressed,
required this.onAdded,
required this.isExpanded,
});
final VoidCallback onPressed;
final VoidCallback onAdded;
final bool isExpanded;
@override
State<MobileFavoriteFolderHeader> createState() =>
_MobileFavoriteFolderHeaderState();
}
class _MobileFavoriteFolderHeaderState
extends State<MobileFavoriteFolderHeader> {
double _turns = 0;
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: FlowyButton(
text: FlowyText.semibold(
LocaleKeys.sideBar_favorites.tr(),
fontSize: 20.0,
),
margin: const EdgeInsets.symmetric(vertical: 8),
expandText: false,
mainAxisAlignment: MainAxisAlignment.start,
rightIcon: AnimatedRotation(
duration: const Duration(milliseconds: 200),
turns: _turns,
child: const Icon(
Icons.keyboard_arrow_down_rounded,
color: Colors.grey,
),
),
onTap: () {
setState(() {
_turns = widget.isExpanded ? -0.25 : 0;
});
widget.onPressed();
},
),
),
],
);
}
}

View File

@ -0,0 +1,74 @@
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart';
import 'package:appflowy/mobile/presentation/home/personal_folder/mobile_home_personal_folder.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class MobileFolders extends StatelessWidget {
const MobileFolders({
super.key,
required this.user,
required this.workspaceSetting,
required this.showFavorite,
});
final UserProfilePB user;
final WorkspaceSettingPB workspaceSetting;
final bool showFavorite;
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => MenuBloc(
user: user,
workspace: workspaceSetting.workspace,
)..add(const MenuEvent.initial()),
),
BlocProvider(
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
)
],
child: MultiBlocListener(
listeners: [
BlocListener<MenuBloc, MenuState>(
listenWhen: (p, c) =>
p.lastCreatedView?.id != c.lastCreatedView?.id,
listener: (context, state) =>
context.pushView(state.lastCreatedView!),
),
],
child: Builder(
builder: (context) {
final menuState = context.watch<MenuBloc>().state;
final favoriteState = context.watch<FavoriteBloc>().state;
return SlidableAutoCloseBehavior(
child: Column(
children: [
// TODO: Uncomment this when we have favorite folder in home page
if (showFavorite && favoriteState.views.isNotEmpty) ...[
MobileFavoriteFolder(
views: favoriteState.views,
),
const VSpace(18.0),
],
MobilePersonalFolder(
views: menuState.views,
),
const VSpace(8.0),
],
),
);
},
),
),
);
}
}

View File

@ -1,12 +1,14 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/mobile/presentation/home/mobile_folders.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_page_recent_files.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:flutter/material.dart';
// TODO(yijing): This is just a placeholder for now.
class MobileHomeScreen extends StatelessWidget {
const MobileHomeScreen({super.key});
@ -34,43 +36,18 @@ class MobileHomeScreen extends StatelessWidget {
snapshots.data?[1].fold((error) => null, (userProfilePB) {
return userProfilePB as UserProfilePB?;
});
// TODO(yijing): implement home page later
// In the unlikely case either of the above is null, eg.
// when a workspace is already open this can happen.
if (workspaceSetting == null || userProfile == null) {
return const WorkspaceFailedScreen();
}
return Scaffold(
key: ValueKey(userProfile?.id),
// TODO(yijing):Need to change to workspace when it is ready
appBar: AppBar(
title: Text(
userProfile?.email.toString() ?? 'No email found',
),
actions: [
IconButton(
onPressed: () {
// TODO(yijing): Navigate to setting page
},
icon: const FlowySvg(
FlowySvgs.m_setting_m,
),
)
],
),
body: Center(
child: Column(
children: [
const Text(
'User',
),
Text(
userProfile.toString(),
),
Text('Workspace name: ${workspaceSetting?.workspace.name}'),
ElevatedButton(
onPressed: () async {
await getIt<AuthService>().signOut();
runAppFlowy();
},
child: const Text('Logout'),
)
],
body: SafeArea(
child: MobileHomePage(
userProfile: userProfile,
workspaceSetting: workspaceSetting,
),
),
);
@ -78,3 +55,61 @@ class MobileHomeScreen extends StatelessWidget {
);
}
}
class MobileHomePage extends StatelessWidget {
const MobileHomePage({
super.key,
required this.userProfile,
required this.workspaceSetting,
});
final UserProfilePB userProfile;
final WorkspaceSettingPB workspaceSetting;
@override
Widget build(BuildContext context) {
return Column(
children: [
// TODO: header + option icon button
// Header
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: MobileHomePageHeader(
userProfile: userProfile,
),
),
const Divider(),
// Folder
Expanded(
child: Scrollbar(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
// Recent files
const MobileHomePageRecentFilesWidget(),
const Divider(),
// Folders
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: MobileFolders(
showFavorite: false,
user: userProfile,
workspaceSetting: workspaceSetting,
),
),
],
),
),
),
),
),
// TODO: Trash
],
);
}
}

View File

@ -0,0 +1,50 @@
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class MobileHomePageHeader extends StatelessWidget {
const MobileHomePageHeader({
super.key,
required this.userProfile,
});
final UserProfilePB userProfile;
@override
Widget build(BuildContext context) {
// TODO: implement the details later.
return SizedBox(
height: 80,
child: Row(
children: [
const FlowyText(
'🐻',
fontSize: 26,
),
const HSpace(14),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FlowyText.medium(
'AppFlowy',
fontSize: 18,
),
const VSpace(4.0),
FlowyText.regular(
userProfile.email,
fontSize: 12,
color: Colors.grey,
)
],
),
const Spacer(),
IconButton(
icon: const Icon(Icons.more_horiz_outlined),
onPressed: () {},
),
],
),
);
}
}

View File

@ -0,0 +1,59 @@
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
const rainbowColors = <Color>[
Color(0xFFFF0064),
Color(0xFFFF7600),
Color(0xFFFFD500),
Color(0xFF8CFE00),
Color(0xFF00E86C),
Color(0xFF00F4F2),
Color(0xFF00CCFF),
Color(0xFF70A2FF),
Color(0xFFA96CFF),
];
class MobileHomePageRecentFilesWidget extends StatelessWidget {
const MobileHomePageRecentFilesWidget({super.key});
@override
Widget build(BuildContext context) {
// TODO: implement the details later.
return SizedBox(
height: 160,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: FlowyText.semibold(
'Recent',
fontSize: 20.0,
),
),
Expanded(
child: ListView.separated(
separatorBuilder: (context, index) => const HSpace(20),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
),
scrollDirection: Axis.horizontal,
itemCount: rainbowColors.length,
itemBuilder: (context, index) {
return Container(
alignment: Alignment.center,
width: 144,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: rainbowColors[index],
),
);
},
),
),
],
),
);
}
}

View File

@ -0,0 +1,72 @@
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
import 'package:appflowy/mobile/presentation/home/personal_folder/mobile_home_personal_folder_header.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobilePersonalFolder extends StatelessWidget {
const MobilePersonalFolder({
super.key,
required this.views,
});
final List<ViewPB> views;
@override
Widget build(BuildContext context) {
return BlocProvider<FolderBloc>(
create: (context) => FolderBloc(type: FolderCategoryType.personal)
..add(
const FolderEvent.initial(),
),
child: BlocBuilder<FolderBloc, FolderState>(
builder: (context, state) {
return Column(
children: [
MobilePersonalFolderHeader(
isExpanded: context.read<FolderBloc>().state.isExpanded,
onPressed: () => context
.read<FolderBloc>()
.add(const FolderEvent.expandOrUnExpand()),
onAdded: () => context.read<FolderBloc>().add(
const FolderEvent.expandOrUnExpand(isExpanded: true),
),
),
const VSpace(8.0),
const Divider(
height: 1,
),
if (state.isExpanded)
...views.map(
(view) => MobileViewItem(
key: ValueKey(
'${FolderCategoryType.personal.name} ${view.id}',
),
isDraggable: false,
categoryType: FolderCategoryType.personal,
isFirstChild: view.id == views.first.id,
view: view,
level: 0,
leftPadding: 16,
isFeedback: false,
onSelected: (view) async {
await context.pushView(view);
},
endActionPane: (context) => buildEndActionPane(context, [
MobilePaneActionType.delete,
MobilePaneActionType.addToFavorites,
MobilePaneActionType.more,
]),
),
)
],
);
},
),
);
}
}

View File

@ -0,0 +1,82 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/menu/menu_bloc.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 MobilePersonalFolderHeader extends StatefulWidget {
const MobilePersonalFolderHeader({
super.key,
required this.onPressed,
required this.onAdded,
required this.isExpanded,
});
final VoidCallback onPressed;
final VoidCallback onAdded;
final bool isExpanded;
@override
State<MobilePersonalFolderHeader> createState() =>
_MobilePersonalFolderHeaderState();
}
class _MobilePersonalFolderHeaderState
extends State<MobilePersonalFolderHeader> {
double _turns = 0;
@override
Widget build(BuildContext context) {
const iconSize = 32.0;
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: FlowyButton(
text: FlowyText.semibold(
LocaleKeys.sideBar_personal.tr(),
fontSize: 20.0,
),
margin: const EdgeInsets.symmetric(vertical: 8),
expandText: false,
mainAxisAlignment: MainAxisAlignment.start,
rightIcon: AnimatedRotation(
duration: const Duration(milliseconds: 200),
turns: _turns,
child: const Icon(
Icons.keyboard_arrow_down_rounded,
color: Colors.grey,
),
),
onTap: () {
setState(() {
_turns = widget.isExpanded ? -0.25 : 0;
});
widget.onPressed();
},
),
),
FlowyIconButton(
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
iconPadding: const EdgeInsets.all(2),
height: iconSize,
width: iconSize,
icon: const FlowySvg(
FlowySvgs.add_s,
size: Size.square(iconSize),
),
onPressed: () {
context.read<MenuBloc>().add(
MenuEvent.createApp(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
index: 0,
),
);
},
),
],
);
}
}

View File

@ -0,0 +1,35 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class MobileSlideActionButton extends StatelessWidget {
const MobileSlideActionButton({
super.key,
required this.svg,
this.size = 32.0,
this.backgroundColor = Colors.transparent,
required this.onPressed,
});
final FlowySvgData svg;
final double size;
final Color backgroundColor;
final SlidableActionCallback onPressed;
@override
Widget build(BuildContext context) {
return CustomSlidableAction(
backgroundColor: backgroundColor,
onPressed: (context) {
HapticFeedback.mediumImpact();
onPressed(context);
},
child: FlowySvg(
svg,
size: Size.square(size),
color: Colors.white,
),
);
}
}

View File

@ -0,0 +1,417 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item_add_button.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/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';
import 'package:flutter_slidable/flutter_slidable.dart';
typedef ViewItemOnSelected = void Function(ViewPB);
typedef ActionPaneBuilder = ActionPane Function(BuildContext context);
const _itemHeight = 48.0;
class MobileViewItem extends StatelessWidget {
const MobileViewItem({
super.key,
required this.view,
this.parentView,
required this.categoryType,
required this.level,
this.leftPadding = 10,
required this.onSelected,
this.isFirstChild = false,
this.isDraggable = true,
required this.isFeedback,
this.startActionPane,
this.endActionPane,
});
final ViewPB view;
final ViewPB? parentView;
final FolderCategoryType categoryType;
// indicate the level of the view item
// used to calculate the left padding
final int level;
// the left padding of the view item for each level
// the left padding of the each level = level * leftPadding
final double leftPadding;
// Selected by normal conventions
final ViewItemOnSelected onSelected;
// used for indicating the first child of the parent view, so that we can
// add top border to the first child
final bool isFirstChild;
// it should be false when it's rendered as feedback widget inside DraggableItem
final bool isDraggable;
// identify if the view item is rendered as feedback widget inside DraggableItem
final bool isFeedback;
// the actions of the view item, such as favorite, rename, delete, etc.
final ActionPaneBuilder? startActionPane;
final ActionPaneBuilder? endActionPane;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ViewBloc(view: view)..add(const ViewEvent.initial()),
child: BlocConsumer<ViewBloc, ViewState>(
listenWhen: (p, c) =>
c.lastCreatedView != null &&
p.lastCreatedView?.id != c.lastCreatedView!.id,
listener: (context, state) => context.pushView(state.lastCreatedView!),
builder: (context, state) {
// don't remove this code. it's related to the backend service.
view.childViews
..clear()
..addAll(state.childViews);
return InnerMobileViewItem(
view: state.view,
parentView: parentView,
childViews: state.childViews,
categoryType: categoryType,
level: level,
leftPadding: leftPadding,
showActions: true,
isExpanded: state.isExpanded,
onSelected: onSelected,
isFirstChild: isFirstChild,
isDraggable: isDraggable,
isFeedback: isFeedback,
startActionPane: startActionPane,
endActionPane: endActionPane,
);
},
),
);
}
}
class InnerMobileViewItem extends StatelessWidget {
const InnerMobileViewItem({
super.key,
required this.view,
required this.parentView,
required this.childViews,
required this.categoryType,
this.isDraggable = true,
this.isExpanded = true,
required this.level,
required this.leftPadding,
required this.showActions,
required this.onSelected,
this.isFirstChild = false,
required this.isFeedback,
this.startActionPane,
this.endActionPane,
});
final ViewPB view;
final ViewPB? parentView;
final List<ViewPB> childViews;
final FolderCategoryType categoryType;
final bool isDraggable;
final bool isExpanded;
final bool isFirstChild;
// identify if the view item is rendered as feedback widget inside DraggableItem
final bool isFeedback;
final int level;
final double leftPadding;
final bool showActions;
final ViewItemOnSelected onSelected;
final ActionPaneBuilder? startActionPane;
final ActionPaneBuilder? endActionPane;
@override
Widget build(BuildContext context) {
Widget child = SingleMobileInnerViewItem(
key: ValueKey('${categoryType.name} ${view.id} $isExpanded'),
view: view,
parentView: parentView,
level: level,
showActions: showActions,
categoryType: categoryType,
onSelected: onSelected,
isExpanded: isExpanded,
isDraggable: isDraggable,
leftPadding: leftPadding,
isFeedback: isFeedback,
startActionPane: startActionPane,
endActionPane: endActionPane,
);
// if the view is expanded and has child views, render its child views
if (isExpanded) {
if (childViews.isNotEmpty) {
final children = childViews.map((childView) {
return MobileViewItem(
key: ValueKey('${categoryType.name} ${childView.id}'),
parentView: view,
categoryType: categoryType,
isFirstChild: childView.id == childViews.first.id,
view: childView,
level: level + 1,
onSelected: onSelected,
isDraggable: isDraggable,
leftPadding: leftPadding,
isFeedback: isFeedback,
startActionPane: startActionPane,
endActionPane: endActionPane,
);
}).toList();
child = Column(
mainAxisSize: MainAxisSize.min,
children: [
child,
const Divider(
height: 1,
),
...children,
],
);
} else {
child = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
child,
const Divider(
height: 1,
),
Container(
height: _itemHeight,
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.only(left: (level + 2) * leftPadding),
child: FlowyText.medium(
LocaleKeys.noPagesInside.tr(),
color: Colors.grey,
),
),
),
const Divider(
height: 1,
),
],
);
}
} else {
child = Column(
mainAxisSize: MainAxisSize.min,
children: [
child,
const Divider(
height: 1,
),
],
);
}
// wrap the child with DraggableItem if isDraggable is true
if (isDraggable && !isReferencedDatabaseView(view, parentView)) {
child = DraggableViewItem(
isFirstChild: isFirstChild,
view: view,
child: child,
feedback: (context) {
return MobileViewItem(
view: view,
parentView: parentView,
categoryType: categoryType,
level: level,
onSelected: onSelected,
isDraggable: false,
leftPadding: leftPadding,
isFeedback: true,
startActionPane: startActionPane,
endActionPane: endActionPane,
);
},
);
}
return child;
}
}
class SingleMobileInnerViewItem extends StatefulWidget {
const SingleMobileInnerViewItem({
super.key,
required this.view,
required this.parentView,
required this.isExpanded,
required this.level,
required this.leftPadding,
this.isDraggable = true,
required this.categoryType,
required this.showActions,
required this.onSelected,
required this.isFeedback,
this.startActionPane,
this.endActionPane,
});
final ViewPB view;
final ViewPB? parentView;
final bool isExpanded;
// identify if the view item is rendered as feedback widget inside DraggableItem
final bool isFeedback;
final int level;
final double leftPadding;
final bool isDraggable;
final bool showActions;
final ViewItemOnSelected onSelected;
final FolderCategoryType categoryType;
final ActionPaneBuilder? startActionPane;
final ActionPaneBuilder? endActionPane;
@override
State<SingleMobileInnerViewItem> createState() =>
_SingleMobileInnerViewItemState();
}
class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
@override
Widget build(BuildContext context) {
final children = [
// expand icon
_buildLeftIcon(),
const HSpace(4),
// icon
SizedBox.square(
dimension: 22,
child: widget.view.defaultIcon(),
),
const HSpace(12),
// title
Expanded(
child: FlowyText.regular(
widget.view.name,
fontSize: 18.0,
overflow: TextOverflow.ellipsis,
),
)
];
// hover action
// ··· more action button
// children.add(_buildViewMoreActionButton(context));
// only support add button for document layout
if (widget.view.layout == ViewLayoutPB.Document) {
// + button
children.add(_buildViewAddButton(context));
}
Widget child = GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => widget.onSelected(widget.view),
child: SizedBox(
height: _itemHeight,
child: Padding(
padding: EdgeInsets.only(left: widget.level * widget.leftPadding),
child: Row(
children: children,
),
),
),
);
if (widget.startActionPane != null || widget.endActionPane != null) {
child = Slidable(
// Specify a key if the Slidable is dismissible.
key: ValueKey(widget.view.id),
startActionPane: widget.startActionPane?.call(context),
endActionPane: widget.endActionPane?.call(context),
child: child,
);
}
return child;
}
// > button or · button
// show > if the view is expandable.
// show · if the view can't contain child views.
Widget _buildLeftIcon() {
if (isReferencedDatabaseView(widget.view, widget.parentView)) {
return const _DotIconWidget();
}
return GestureDetector(
child: AnimatedRotation(
duration: const Duration(milliseconds: 200),
turns: widget.isExpanded ? 0 : -0.25,
child: const Icon(
Icons.keyboard_arrow_down_rounded,
size: 28,
),
),
onTap: () {
context
.read<ViewBloc>()
.add(ViewEvent.setIsExpanded(!widget.isExpanded));
},
);
}
// + button
Widget _buildViewAddButton(BuildContext context) {
return MobileViewAddButton(
onPressed: () {
context.read<ViewBloc>().add(
ViewEvent.createView(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
ViewLayoutPB.Document,
),
);
},
);
}
}
class _DotIconWidget extends StatelessWidget {
const _DotIconWidget();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(6.0),
child: Container(
width: 4,
height: 4,
decoration: BoxDecoration(
color: Theme.of(context).iconTheme.color,
borderRadius: BorderRadius.circular(2),
),
),
);
}
}
// workaround: we should use view.isEndPoint or something to check if the view can contain child views. But currently, we don't have that field.
bool isReferencedDatabaseView(ViewPB view, ViewPB? parentView) {
if (parentView == null) {
return false;
}
return view.layout.isDatabaseView && parentView.layout.isDatabaseView;
}

View File

@ -0,0 +1,28 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
const _iconSize = 32.0;
class MobileViewAddButton extends StatelessWidget {
const MobileViewAddButton({
super.key,
required this.onPressed,
});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return FlowyIconButton(
iconPadding: const EdgeInsets.all(2),
width: _iconSize,
height: _iconSize,
icon: const FlowySvg(
FlowySvgs.add_s,
size: Size.square(_iconSize),
),
onPressed: onPressed,
);
}
}

View File

@ -1,4 +1,5 @@
export 'home/home.dart';
export 'root_placeholder_page.dart';
export 'details_placeholder_page.dart';
export 'editor/mobile_editor_screen.dart';
export 'home/home.dart';
export 'mobile_bottom_navigation_bar.dart';
export 'root_placeholder_page.dart';

View File

@ -103,7 +103,9 @@ class _DocumentPageState extends State<DocumentPage> {
styleCustomizer: EditorStyleCustomizer(
context: context,
// the 44 is the width of the left action list
padding: const EdgeInsets.only(left: 40, right: 40 + 44),
padding: PlatformExtension.isMobile
? const EdgeInsets.only(left: 20, right: 20)
: const EdgeInsets.only(left: 40, right: 40 + 44),
),
header: _buildCoverAndIcon(context),
);

View File

@ -196,38 +196,80 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
scrollController: effectiveScrollController,
);
final editor = AppFlowyEditor(
editorState: widget.editorState,
editable: true,
editorScrollController: editorScrollController,
// setup the auto focus parameters
autoFocus: widget.autoFocus ?? autoFocus,
focusedSelection: selection,
// setup the theme
editorStyle: styleCustomizer.style(),
// customize the block builders
blockComponentBuilders: blockComponentBuilders,
// customize the shortcuts
characterShortcutEvents: characterShortcutEvents,
commandShortcutEvents: commandShortcutEvents,
// customize the context menu items
contextMenuItems: customContextMenuItems,
// customize the header and footer.
header: widget.header,
footer: const VSpace(200),
final editor = Directionality(
textDirection: textDirection,
child: AppFlowyEditor(
editorState: widget.editorState,
editable: true,
editorScrollController: editorScrollController,
// setup the auto focus parameters
autoFocus: widget.autoFocus ?? autoFocus,
focusedSelection: selection,
// setup the theme
editorStyle: styleCustomizer.style(),
// customize the block builders
blockComponentBuilders: blockComponentBuilders,
// customize the shortcuts
characterShortcutEvents: characterShortcutEvents,
commandShortcutEvents: commandShortcutEvents,
// customize the context menu items
contextMenuItems: customContextMenuItems,
// customize the header and footer.
header: widget.header,
footer: const VSpace(200),
),
);
final editorState = widget.editorState;
if (PlatformExtension.isMobile) {
return Column(
children: [
Expanded(
child: MobileFloatingToolbar(
editorState: editorState,
editorScrollController: editorScrollController,
toolbarBuilder: (context, anchor) {
return AdaptiveTextSelectionToolbar.editable(
clipboardStatus: ClipboardStatus.pasteable,
onCopy: () => copyCommand.execute(editorState),
onCut: () => cutCommand.execute(editorState),
onPaste: () => pasteCommand.execute(editorState),
onSelectAll: () => selectAllCommand.execute(editorState),
anchors: TextSelectionToolbarAnchors(
primaryAnchor: anchor,
),
);
},
child: editor,
),
),
MobileToolbar(
editorState: editorState,
toolbarItems: [
textDecorationMobileToolbarItem,
buildTextAndBackgroundColorMobileToolbarItem(),
headingMobileToolbarItem,
todoListMobileToolbarItem,
listMobileToolbarItem,
linkMobileToolbarItem,
quoteMobileToolbarItem,
dividerMobileToolbarItem,
codeMobileToolbarItem,
],
),
],
);
}
return Center(
child: FloatingToolbar(
style: styleCustomizer.floatingToolbarStyleBuilder(),
items: toolbarItems,
editorState: widget.editorState,
editorState: editorState,
editorScrollController: editorScrollController,
textDirection: textDirection,
child: Directionality(
textDirection: textDirection,
child: editor,
),
child: editor,
),
);
}
@ -241,7 +283,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
// OptionAction.moveDown,
];
final calloutBGColor = AFThemeExtension.of(context).calloutBGColor;
const calloutBGColor = Colors.black;
// AFThemeExtension.of(context).calloutBGColor;
final configuration = BlockComponentConfiguration(
padding: (_) => const EdgeInsets.symmetric(vertical: 5.0),
@ -417,24 +460,27 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
if (supportAlignBuilderType.contains(entry.key)) ...alignAction,
];
builder.showActions =
(node) => node.parent?.type != TableCellBlockKeys.type;
builder.actionBuilder = (context, state) {
final top = builder.configuration.padding(context.node).top;
final padding = context.node.type == HeadingBlockKeys.type
? EdgeInsets.only(top: top + 8.0)
: EdgeInsets.only(top: top + 2.0);
return Padding(
padding: padding,
child: BlockActionList(
blockComponentContext: context,
blockComponentState: state,
editorState: widget.editorState,
actions: actions,
showSlashMenu: () => showSlashMenu(widget.editorState),
),
);
};
// only show the ... and + button on the desktop platform.
if (PlatformExtension.isDesktop) {
builder.showActions =
(node) => node.parent?.type != TableCellBlockKeys.type;
builder.actionBuilder = (context, state) {
final top = builder.configuration.padding(context.node).top;
final padding = context.node.type == HeadingBlockKeys.type
? EdgeInsets.only(top: top + 8.0)
: EdgeInsets.only(top: top + 2.0);
return Padding(
padding: padding,
child: BlockActionList(
blockComponentContext: context,
blockComponentState: state,
editorState: widget.editorState,
actions: actions,
showSlashMenu: () => showSlashMenu(widget.editorState),
),
);
};
}
}
return builders;

View File

@ -75,40 +75,9 @@ class EditorStyleCustomizer {
}
EditorStyle mobile() {
final theme = Theme.of(context);
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
final fontFamily = context.read<DocumentAppearanceCubit>().state.fontFamily;
return EditorStyle.desktop(
return EditorStyle.mobile(
padding: padding,
cursorColor: theme.colorScheme.primary,
textStyleConfiguration: TextStyleConfiguration(
text: baseTextStyle(fontFamily).copyWith(
fontSize: fontSize,
color: theme.colorScheme.onBackground,
height: 1.5,
),
bold: baseTextStyle(fontFamily).copyWith(
fontWeight: FontWeight.w600,
),
italic: baseTextStyle(fontFamily).copyWith(fontStyle: FontStyle.italic),
underline: baseTextStyle(fontFamily)
.copyWith(decoration: TextDecoration.underline),
strikethrough: baseTextStyle(fontFamily)
.copyWith(decoration: TextDecoration.lineThrough),
href: baseTextStyle(fontFamily).copyWith(
color: theme.colorScheme.primary,
decoration: TextDecoration.underline,
),
code: GoogleFonts.arefRuqaaInk(
textStyle: baseTextStyle(fontFamily).copyWith(
fontSize: fontSize,
fontWeight: FontWeight.normal,
color: Colors.red,
backgroundColor: theme.colorScheme.inverseSurface,
),
),
),
textSpanDecorator: customizeAttributeDecorator,
);
}

View File

@ -1,3 +1,7 @@
import 'package:appflowy/mobile/presentation/database/mobile_board_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart';
import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_page.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/app_widget.dart';
@ -25,10 +29,14 @@ GoRouter generateRouter(Widget child) {
// Desktop only
if (!PlatformExtension.isMobile) _desktopHomeScreenRoute(),
// Mobile only
if (PlatformExtension.isMobile) _mobileHomeScreenWithNavigationBarRoute(),
if (PlatformExtension.isMobile) ...[
_mobileEditorScreenRoute(),
_mobileGridScreenRoute(),
_mobileBoardScreenRoute(),
_mobileCalendarScreenRoute(),
_mobileHomeScreenWithNavigationBarRoute(),
],
// Unused routes for now, it may need to be used in the future.
// TODO(yijing): extract route method like other routes when it comes to be used.
// Desktop and Mobile
GoRoute(
path: WorkspaceStartScreen.routeName,
@ -77,8 +85,6 @@ StatefulShellRoute _mobileHomeScreenWithNavigationBarRoute() {
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
// The screen to display as the root in the first tab of the
// bottom navigation bar.
path: MobileHomeScreen.routeName,
builder: (BuildContext context, GoRouterState state) {
return const MobileHomeScreen();
@ -86,33 +92,14 @@ StatefulShellRoute _mobileHomeScreenWithNavigationBarRoute() {
),
],
),
// TODO(yijing): implement other tabs later
// The following code comes from the example of StatefulShellRoute.indexedStack. I left there just for placeholder purpose. They will be updated in the future.
// The route branch for the second tab of the bottom navigation bar.
StatefulShellBranch(
// It's not necessary to provide a navigatorKey if it isn't also
// needed elsewhere. If not provided, a default key will be used.
routes: <RouteBase>[
GoRoute(
// The screen to display as the root in the second tab of the
// bottom navigation bar.
path: '/b',
builder: (BuildContext context, GoRouterState state) =>
const RootPlaceholderScreen(
label: 'Favorite',
detailsPath: '/b/details/1',
secondDetailsPath: '/b/details/2',
),
routes: <RouteBase>[
GoRoute(
path: 'details/:param',
builder: (BuildContext context, GoRouterState state) =>
DetailsPlaceholderScreen(
label: 'Favorite details',
param: state.pathParameters['param'],
),
),
],
path: MobileFavoriteScreen.routeName,
builder: (BuildContext context, GoRouterState state) {
return const MobileFavoriteScreen();
},
),
],
),
@ -261,6 +248,70 @@ GoRoute _signInScreenRoute() {
);
}
GoRoute _mobileEditorScreenRoute() {
return GoRoute(
path: MobileEditorScreen.routeName,
pageBuilder: (context, state) {
final id = state.uri.queryParameters[MobileEditorScreen.viewId]!;
final title = state.uri.queryParameters[MobileEditorScreen.viewTitle];
return MaterialPage(
child: MobileEditorScreen(
id: id,
title: title,
),
);
},
);
}
GoRoute _mobileGridScreenRoute() {
return GoRoute(
path: MobileGridScreen.routeName,
pageBuilder: (context, state) {
final id = state.uri.queryParameters[MobileGridScreen.viewId]!;
final title = state.uri.queryParameters[MobileGridScreen.viewTitle];
return MaterialPage(
child: MobileGridScreen(
id: id,
title: title,
),
);
},
);
}
GoRoute _mobileBoardScreenRoute() {
return GoRoute(
path: MobileBoardScreen.routeName,
pageBuilder: (context, state) {
final id = state.uri.queryParameters[MobileBoardScreen.viewId]!;
final title = state.uri.queryParameters[MobileBoardScreen.viewTitle];
return MaterialPage(
child: MobileBoardScreen(
id: id,
title: title,
),
);
},
);
}
GoRoute _mobileCalendarScreenRoute() {
return GoRoute(
path: MobileCalendarScreen.routeName,
pageBuilder: (context, state) {
final id = state.uri.queryParameters[MobileCalendarScreen.viewId]!;
final title = state.uri.queryParameters[MobileCalendarScreen.viewTitle]!;
return MaterialPage(
child: MobileCalendarScreen(
id: id,
title: title,
),
);
},
);
}
GoRoute _rootRoute(Widget child) {
return GoRoute(
path: '/',

View File

@ -1,16 +1,15 @@
import 'dart:async';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/workspace/workspace_listener.dart';
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'menu_bloc.freezed.dart';
@ -42,7 +41,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
index: event.index,
);
result.fold(
(app) => emit(state.copyWith(plugin: app.plugin())),
(app) => emit(state.copyWith(lastCreatedView: app)),
(error) {
Log.error(error);
emit(state.copyWith(successOrFailure: right(error)));
@ -120,12 +119,12 @@ class MenuState with _$MenuState {
const factory MenuState({
required List<ViewPB> views,
required Either<Unit, FlowyError> successOrFailure,
required Plugin plugin,
ViewPB? lastCreatedView,
}) = _MenuState;
factory MenuState.initial(WorkspacePB workspace) => MenuState(
views: workspace.views,
successOrFailure: left(unit),
plugin: makePlugin(pluginType: PluginType.blank),
lastCreatedView: null,
);
}

View File

@ -3,7 +3,6 @@ import 'dart:async';
import 'package:appflowy/user/application/user_settings_service.dart';
import 'package:appflowy/util/platform_extension.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart';
import 'package:appflowy/mobile/application/mobile_theme_data.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
@ -405,8 +404,190 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
if (PlatformExtension.isMobile) {
// Mobile version has only one theme(light mode) for now.
// The desktop theme and the mobile theme are independent.
final mobileThemeData = getMobileThemeData();
return mobileThemeData;
const mobileColorTheme = ColorScheme(
brightness: Brightness.light,
primary: Color(0xFF2DA2F6), //primary 100
onPrimary: Colors.white,
// TODO(yijing): add color later
secondary: Colors.white,
onSecondary: Colors.white,
error: Color(0xffFB006D),
onError: Color(0xffFB006D),
background: Colors.white,
onBackground: Color(0xff2F3030), // title text
outline: Color(0xffBDC0C5), //caption
//Snack bar
surface: Colors.white,
onSurface: Color(0xff2F3030), // title text
);
return ThemeData(
// color
primaryColor: mobileColorTheme.primary, //primary 100
primaryColorLight: const Color(0xFF57B5F8), //primary 80
dividerColor: mobileColorTheme.outline, //caption
scaffoldBackgroundColor: mobileColorTheme.background,
appBarTheme: AppBarTheme(
foregroundColor: mobileColorTheme.onBackground,
backgroundColor: mobileColorTheme.background,
elevation: 0,
centerTitle: false,
titleTextStyle: TextStyle(
fontFamily: 'Poppins',
color: mobileColorTheme.onBackground,
fontSize: 18,
fontWeight: FontWeight.w600,
letterSpacing: 0.05,
),
),
// button
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
elevation: MaterialStateProperty.all(0),
shadowColor: MaterialStateProperty.all(null),
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return const Color(0xFF57B5F8);
}
return mobileColorTheme.primary;
},
),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(
mobileColorTheme.onBackground,
),
backgroundColor: MaterialStateProperty.all(Colors.white),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
),
side: MaterialStateProperty.all(
BorderSide(
color: mobileColorTheme.outline,
width: 0.5,
),
),
padding: MaterialStateProperty.all(
const EdgeInsets.symmetric(horizontal: 16),
),
// splash color
overlayColor: MaterialStateProperty.all(
Colors.grey[100],
),
),
),
// text
fontFamily: 'Poppins',
textTheme: const TextTheme(
displayLarge: TextStyle(
color: Color(0xFF57B5F8),
fontSize: 32,
fontWeight: FontWeight.w700,
height: 1.20,
letterSpacing: 0.16,
),
displayMedium: TextStyle(
color: Color(0xff2F3030),
fontSize: 32,
fontWeight: FontWeight.w600,
height: 1.20,
letterSpacing: 0.16,
),
// H1 Semi 26
displaySmall: TextStyle(
color: Color(0xFF2F3030),
fontSize: 26,
fontWeight: FontWeight.w600,
height: 1.10,
letterSpacing: 0.13,
),
// body2 14 Regular
bodyMedium: TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.20,
letterSpacing: 0.07,
),
// blue text button
labelMedium: TextStyle(
color: Color(0xFF2DA2F6),
fontSize: 14,
fontWeight: FontWeight.w500,
height: 1.20,
),
),
inputDecorationTheme: InputDecorationTheme(
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
width: 2,
color: Color(0xFF2DA2F6), //primary 100
),
borderRadius: BorderRadius.all(Radius.circular(6)),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: mobileColorTheme.error),
borderRadius: const BorderRadius.all(Radius.circular(6)),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: mobileColorTheme.error),
borderRadius: const BorderRadius.all(Radius.circular(6)),
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xffBDC0C5), //caption
),
borderRadius: BorderRadius.all(Radius.circular(6)),
),
),
colorScheme: mobileColorTheme,
extensions: [
AFThemeExtension(
warning: theme.yellow,
success: theme.green,
tint1: theme.tint1,
tint2: theme.tint2,
tint3: theme.tint3,
tint4: theme.tint4,
tint5: theme.tint5,
tint6: theme.tint6,
tint7: theme.tint7,
tint8: theme.tint8,
tint9: theme.tint9,
textColor: theme.text,
greyHover: theme.hoverBG1,
greySelect: theme.bg3,
lightGreyHover: theme.hoverBG3,
toggleOffFill: theme.shader5,
progressBarBGColor: theme.progressBarBGColor,
toggleButtonBGColor: theme.toggleButtonBGColor,
calendarWeekendBGColor: theme.calendarWeekendBGColor,
gridRowCountColor: theme.gridRowCountColor,
code: _getFontStyle(
fontFamily: monospaceFontFamily,
fontColor: theme.shader3,
),
callout: _getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s11,
fontColor: theme.shader3,
),
calloutBGColor: theme.hoverBG3,
tableCellBGColor: theme.surface,
caption: _getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s11,
fontWeight: FontWeight.w400,
fontColor: theme.hint,
),
),
],
);
}
// Due to Desktop version has multiple themes, it relies on the current theme to build the ThemeData

View File

@ -1,9 +1,10 @@
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_top_menu.dart';
@ -56,10 +57,11 @@ class HomeSideBar extends StatelessWidget {
child: MultiBlocListener(
listeners: [
BlocListener<MenuBloc, MenuState>(
listenWhen: (p, c) => p.plugin.id != c.plugin.id,
listener: (context, state) => context
.read<TabsBloc>()
.add(TabsEvent.openPlugin(plugin: state.plugin)),
listenWhen: (p, c) =>
p.lastCreatedView?.id != c.lastCreatedView?.id,
listener: (context, state) => context.read<TabsBloc>().add(
TabsEvent.openPlugin(plugin: state.lastCreatedView!.plugin()),
),
),
BlocListener<NotificationActionBloc, NotificationActionState>(
listener: (context, state) {

View File

@ -64,7 +64,6 @@ String languageFromLocale(Locale locale) {
case "hin":
return "हिन्दी";
// If not found then the language code will be displayed
default:
return locale.languageCode;

View File

@ -22,6 +22,8 @@ class FlowyButton extends StatelessWidget {
final bool disable;
final double disableOpacity;
final Size? leftIconSize;
final bool expandText;
final MainAxisAlignment mainAxisAlignment;
const FlowyButton({
Key? key,
@ -40,6 +42,8 @@ class FlowyButton extends StatelessWidget {
this.disable = false,
this.disableOpacity = 0.5,
this.leftIconSize = const Size.square(16),
this.expandText = true,
this.mainAxisAlignment = MainAxisAlignment.center,
}) : super(key: key);
@override
@ -79,7 +83,11 @@ class FlowyButton extends StatelessWidget {
children.add(const HSpace(6));
}
children.add(Expanded(child: text));
if (expandText) {
children.add(Expanded(child: text));
} else {
children.add(text);
}
if (rightIcon != null) {
children.add(const HSpace(6));
@ -88,7 +96,7 @@ class FlowyButton extends StatelessWidget {
}
Widget child = Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
);

View File

@ -89,6 +89,10 @@ class FlowyText extends StatelessWidget {
maxLines: maxLines,
textAlign: textAlign,
overflow: overflow ?? TextOverflow.clip,
textHeightBehavior: const TextHeightBehavior(
applyHeightToFirstAscent: false,
applyHeightToLastDescent: false,
),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: fontSize,
fontWeight: fontWeight,

View File

@ -525,6 +525,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.15"
flutter_slidable:
dependency: "direct main"
description:
name: flutter_slidable
sha256: cc4231579e3eae41ae166660df717f4bad1359c87f4a4322ad8ba1befeb3d2be
url: "https://pub.dev"
source: hosted
version: "3.0.0"
flutter_svg:
dependency: "direct main"
description:

View File

@ -116,6 +116,7 @@ dependencies:
# to gather notification handling for all platforms
local_notifier: ^0.1.5
app_links: ^3.4.1
flutter_slidable: ^3.0.0
dev_dependencies:
flutter_lints: ^2.0.1

View File

@ -0,0 +1,3 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.26576 3.43317C9.26576 2.73334 8.69868 2.1665 7.99909 2.1665C7.29951 2.1665 6.73242 2.73334 6.73242 3.43317C6.73242 4.133 7.29951 4.69984 7.99909 4.69984C8.69868 4.69984 9.26576 4.133 9.26576 3.43317ZM9.26576 8.49984C9.26576 7.8 8.69868 7.23317 7.99909 7.23317C7.29951 7.23317 6.73242 7.8 6.73242 8.49984C6.73242 9.19967 7.29951 9.7665 7.99909 9.7665C8.69868 9.7665 9.26576 9.19967 9.26576 8.49984ZM9.26576 13.5665C9.26576 12.8667 8.69868 12.2998 7.99909 12.2998C7.29951 12.2998 6.73242 12.8667 6.73242 13.5665C6.73242 14.2663 7.29951 14.8332 7.99909 14.8332C8.69868 14.8332 9.26576 14.2663 9.26576 13.5665Z" fill="#2F3030"/>
</svg>

After

Width:  |  Height:  |  Size: 740 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.998 1.95312C10.893 1.95312 9.99804 2.84812 9.99804 3.95312H4.99805C4.44605 3.95312 3.99805 4.40112 3.99805 4.95312C3.99805 5.50511 4.44605 5.95311 4.99805 5.95311V17.9531C4.99805 20.1431 6.80304 21.9531 8.99804 21.9531H14.998C17.193 21.9531 18.998 20.1481 18.998 17.9531V5.95311C19.55 5.95311 19.998 5.50511 19.998 4.95312C19.998 4.40112 19.55 3.95312 18.998 3.95312H13.998C13.998 2.84812 13.103 1.95312 11.998 1.95312ZM6.99804 5.95311H16.998V17.9531C16.998 19.0501 16.108 19.9531 15.029 19.9531L8.99804 19.9221C7.89604 19.9221 6.99804 19.0321 6.99804 17.9531V5.95311ZM9.99804 7.95311C9.44604 7.95311 8.99804 8.40111 8.99804 8.9531V16.9531C8.99804 17.5051 9.44604 17.9531 9.99804 17.9531C10.55 17.9531 10.998 17.5051 10.998 16.9531V8.9531C10.998 8.40111 10.55 7.95311 9.99804 7.95311ZM13.998 7.95311C13.446 7.95311 12.998 8.40111 12.998 8.9531V16.9531C12.998 17.5051 13.446 17.9531 13.998 17.9531C14.55 17.9531 14.998 17.5051 14.998 16.9531V8.9531C14.998 8.40111 14.55 7.95311 13.998 7.95311Z" fill="#FB006D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.99805 2.01416C6.43705 2.01416 2.99805 5.41996 2.99805 8.98296C2.99805 8.99326 2.99805 9.00376 2.99805 9.01416C2.99805 11.4742 2.99805 14.4105 2.99805 14.6704C2.99805 16.5342 4.60906 18.0142 6.56006 18.0142H6.99805V18.6704C6.99805 20.5342 8.60906 22.0142 10.5601 22.0142H17.436C19.387 22.0142 20.998 20.5342 20.998 18.6704V9.35796C20.998 7.49416 19.387 6.01416 17.436 6.01416H16.998V5.35796C16.998 3.49416 15.387 2.01416 13.436 2.01416C13.222 2.01416 13.212 2.01416 9.99805 2.01416ZM10.998 4.01416C12.16 4.01416 13.284 4.01416 13.436 4.01416C14.324 4.01416 14.998 4.64436 14.998 5.35796V14.6704C14.998 15.384 14.324 16.0142 13.436 16.0142H6.56006C5.67206 16.0142 4.99805 15.384 4.99805 14.6704C4.99805 14.4564 4.99805 12.0945 4.99805 10.0142H7.43604C9.38603 10.0142 10.998 8.53426 10.998 6.67036V4.01416ZM8.99805 4.26416V6.67036C8.99805 7.38396 8.32404 8.01416 7.43604 8.01416H5.21704C5.67604 6.24696 7.22605 4.73456 8.99805 4.26416ZM16.998 8.01416H17.436C18.324 8.01416 18.998 8.64436 18.998 9.35796V18.6704C18.998 19.384 18.324 20.0142 17.436 20.0142H10.5601C9.67206 20.0142 8.99805 19.384 8.99805 18.6704V18.0142H13.436C15.387 18.0142 16.998 16.5342 16.998 14.6704V8.01416Z" fill="#2F3030"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.9946 1.95312C16.7296 1.95312 16.4636 2.04713 16.2756 2.23413C15.7196 2.79013 13.7186 4.79115 13.2746 5.23615L4.26961 14.2412L3.2686 15.2412C3.1296 15.3812 3.0576 15.5792 3.0186 15.7732L2.0186 20.7762C1.8786 21.4762 2.47461 22.0721 3.17461 21.9331C3.80061 21.8071 7.5526 21.0572 8.1776 20.9322C8.3716 20.8932 8.56961 20.8212 8.70961 20.6822L9.70961 19.6812L18.7146 10.6762C19.1596 10.2322 21.1606 8.23012 21.7166 7.67512C21.9036 7.48712 21.9976 7.22113 21.9976 6.95613C21.9976 5.31912 21.5806 4.14912 20.7156 3.26612C19.8426 2.37511 18.6766 1.95312 16.9946 1.95312ZM17.3886 3.97211C18.2916 4.02511 18.8766 4.23216 19.2776 4.64216C19.6876 5.06016 19.9466 5.64015 20.0026 6.52516C19.4546 7.07216 18.6526 7.86212 17.9956 8.51912C17.2026 7.72612 16.2246 6.74815 15.4316 5.95515C16.0896 5.29815 16.8416 4.51911 17.3886 3.97211ZM13.9936 7.39314L16.5576 9.95717L8.9906 17.5241L6.4266 14.9602L13.9936 7.39314ZM4.9886 16.3982L7.5526 18.9621L7.4586 19.0562C6.7986 19.1882 5.46561 19.4672 4.23761 19.7132L4.89461 16.4922L4.9886 16.3982Z" fill="#2F3030"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.998 1.95312C15.341 1.95312 13.998 3.29613 13.998 4.95312C13.998 5.21012 14.039 5.53013 14.1 5.76813L8.74704 9.50412C8.26804 9.18012 7.61905 8.95312 6.99805 8.95312C5.34105 8.95312 3.99805 10.2961 3.99805 11.9531C3.99805 13.6101 5.34105 14.9531 6.99805 14.9531C7.63005 14.9531 8.25404 14.7181 8.73804 14.3851L14.1021 18.1511C14.0361 18.3981 13.998 18.6851 13.998 18.9531C13.998 20.6101 15.341 21.9531 16.998 21.9531C18.655 21.9531 19.998 20.6101 19.998 18.9531C19.998 17.2961 18.655 15.9531 16.998 15.9531C16.366 15.9531 15.7501 16.1811 15.2671 16.5151L9.88904 12.7521C9.95404 12.5051 9.99805 12.2211 9.99805 11.9531C9.99805 11.6851 9.96004 11.3961 9.89404 11.1491L15.259 7.39713C15.738 7.72013 16.377 7.95312 16.998 7.95312C18.655 7.95312 19.998 6.61013 19.998 4.95312C19.998 3.29613 18.655 1.95312 16.998 1.95312ZM16.998 3.95312C17.55 3.95312 17.998 4.40112 17.998 4.95312C17.998 5.50513 17.55 5.95312 16.998 5.95312C16.446 5.95312 15.998 5.50513 15.998 4.95312C15.998 4.40112 16.446 3.95312 16.998 3.95312ZM6.99805 10.9531C7.55005 10.9531 7.99805 11.4011 7.99805 11.9531C7.99805 12.5051 7.55005 12.9531 6.99805 12.9531C6.44605 12.9531 5.99805 12.5051 5.99805 11.9531C5.99805 11.4011 6.44605 10.9531 6.99805 10.9531ZM16.998 17.9531C17.55 17.9531 17.998 18.4011 17.998 18.9531C17.998 19.5051 17.55 19.9531 16.998 19.9531C16.446 19.9531 15.998 19.5051 15.998 18.9531C15.998 18.4011 16.446 17.9531 16.998 17.9531Z" fill="#2F3030"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.0232 2.44141C11.4522 2.44121 10.8692 2.79641 10.5242 3.50341L8.40012 7.87611L3.55892 8.56321C2.00202 8.77981 1.49763 10.3072 2.62193 11.4052L6.12003 14.8102L5.30802 19.5892C5.04062 21.1362 6.32372 22.0712 7.71302 21.3382C8.24982 21.0542 11.0102 19.6242 12.0232 19.0892L16.3332 21.3382C17.7242 22.0712 19.0112 21.1372 18.7382 19.5892L17.8952 14.8102L21.3932 11.4052C22.5232 10.3112 22.0442 8.78421 20.4872 8.56321L15.6152 7.87611L13.5222 3.50341C13.1772 2.79611 12.5942 2.44171 12.0232 2.44141ZM12.0232 5.03381L14.0842 9.25041C14.2302 9.54871 14.5052 9.73481 14.8342 9.78141L19.5192 10.4682L16.1152 13.7482C15.8762 13.9792 15.7762 14.2962 15.8342 14.6222L16.6462 19.2452L12.4922 17.0592C12.3382 16.9782 12.1672 16.9392 11.9972 16.9442C11.9972 16.0352 12.0232 5.03381 12.0232 5.03381Z" fill="#FFCE00"/>
</svg>

After

Width:  |  Height:  |  Size: 917 B

View File

@ -142,6 +142,7 @@
"defaultNewPageName": "Untitled",
"renameDialog": "Rename"
},
"noPagesInside": "No pages inside",
"toolbar": {
"undo": "Undo",
"redo": "Redo",
@ -212,7 +213,11 @@
"delete": "Delete",
"duplicate": "Duplicate",
"done": "Done",
"putback": "Put Back"
"putback": "Put Back",
"share": "Share",
"removeFromFavorites": "Remove from favorites",
"addToFavorites": "Add to favorites",
"rename": "Rename"
},
"label": {
"welcome": "Welcome!",
@ -860,5 +865,9 @@
"replaceAll": "Replace all",
"noResult": "No results",
"caseSensitive": "Case sensitive"
},
"error": {
"weAreSorry": "We're sorry",
"loadingViewError": "We're having trouble loading this view. Please check your internet connection, refresh the app, and do not hesitate to reach out to the team if the issue continues."
}
}

View File

@ -26,8 +26,13 @@ script = [
"""
cd rust-lib/
rustup show
echo cargo lipo --targets ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
cargo lipo --targets ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
if [ "${BUILD_FLAG}" == "debug" ]; then
echo "🚀 🚀 🚀 Building for debug"
cargo lipo --targets ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
else
echo "🚀 🚀 🚀 Building for release"
cargo lipo --targets ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}" --release
fi
cd ../
""",
]