feat: implement personal / favorites folder on the mobile platform (#3723)
@ -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"
|
||||
|
@ -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";
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 616 B |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 969 B |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 5.3 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 564 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.5 KiB |
@ -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>
|
||||
|
@ -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',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
);
|
||||
}
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
|
@ -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,
|
||||
]),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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),
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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: '/',
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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 |
3
frontend/resources/flowy_icons/24x/m_delete.svg
Normal 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 |
3
frontend/resources/flowy_icons/24x/m_duplicate.svg
Normal 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 |
3
frontend/resources/flowy_icons/24x/m_rename.svg
Normal 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 |
3
frontend/resources/flowy_icons/24x/m_share.svg
Normal 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 |
3
frontend/resources/flowy_icons/24x/m_unfavorite.svg
Normal 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 |
@ -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."
|
||||
}
|
||||
}
|
||||
|
@ -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 ../
|
||||
""",
|
||||
]
|
||||
|