feat: go_router refactor and bottom navigation bar in mobile (#3459)
@ -23,7 +23,20 @@ ThemeData getMobileThemeData() {
|
||||
primaryColor: mobileColorTheme.primary, //primary 100
|
||||
primaryColorLight: const Color(0xFF57B5F8), //primary 80
|
||||
dividerColor: mobileColorTheme.outline, //caption
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
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(
|
||||
@ -72,7 +85,6 @@ ThemeData getMobileThemeData() {
|
||||
displayLarge: TextStyle(
|
||||
color: Color(0xFF57B5F8),
|
||||
fontSize: 32,
|
||||
fontFamily: 'Poppins',
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.20,
|
||||
letterSpacing: 0.16,
|
@ -0,0 +1,99 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
// TODO(yijing): delete this after implementing the real screen inside bottom navigation bar.
|
||||
/// For demonstration purposes
|
||||
class DetailsPlaceholderScreen extends StatefulWidget {
|
||||
/// Constructs a [DetailsScreen].
|
||||
const DetailsPlaceholderScreen({
|
||||
required this.label,
|
||||
this.param,
|
||||
this.extra,
|
||||
this.withScaffold = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The label to display in the center of the screen.
|
||||
final String label;
|
||||
|
||||
/// Optional param
|
||||
final String? param;
|
||||
|
||||
/// Optional extra object
|
||||
final Object? extra;
|
||||
|
||||
/// Wrap in scaffold
|
||||
final bool withScaffold;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => DetailsPlaceholderScreenState();
|
||||
}
|
||||
|
||||
/// The state for DetailsScreen
|
||||
class DetailsPlaceholderScreenState extends State<DetailsPlaceholderScreen> {
|
||||
int _counter = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.withScaffold) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Details Screen - ${widget.label}'),
|
||||
),
|
||||
body: _build(context),
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: _build(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Details for ${widget.label} - Counter: $_counter',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(4)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_counter++;
|
||||
});
|
||||
},
|
||||
child: const Text('Increment counter'),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
if (widget.param != null)
|
||||
Text(
|
||||
'Parameter: ${widget.param!}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
if (widget.extra != null)
|
||||
Text(
|
||||
'Extra: ${widget.extra!}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
if (!widget.withScaffold) ...<Widget>[
|
||||
const Padding(padding: EdgeInsets.all(16)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pop();
|
||||
},
|
||||
child: const Text(
|
||||
'< Back',
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export 'mobile_home_page.dart';
|
@ -0,0 +1,80 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.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});
|
||||
|
||||
static const routeName = "/MobileHomeScreen";
|
||||
|
||||
@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?;
|
||||
});
|
||||
// TODO(yijing): implement home page later
|
||||
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'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Builds the "shell" for the app by building a Scaffold with a
|
||||
/// BottomNavigationBar, where [child] is placed in the body of the Scaffold.
|
||||
class MobileBottomNavigationBar extends StatelessWidget {
|
||||
/// Constructs an [MobileBottomNavigationBar].
|
||||
const MobileBottomNavigationBar({
|
||||
required this.navigationShell,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The navigation shell and container for the branch Navigators.
|
||||
final StatefulNavigationShell navigationShell;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
body: navigationShell,
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
// Here, the items of BottomNavigationBar are hard coded. In a real
|
||||
// world scenario, the items would most likely be generated from the
|
||||
// branches of the shell route, which can be fetched using
|
||||
// `navigationShell.route.branches`.
|
||||
type: BottomNavigationBarType.fixed,
|
||||
items: <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
// There is no text shown on the bottom navigation bar, but Exception will be thrown if label is null here.
|
||||
label: 'home',
|
||||
icon: const FlowySvg(FlowySvgs.m_home_unselected_lg),
|
||||
activeIcon: FlowySvg(
|
||||
FlowySvgs.m_home_selected_lg,
|
||||
color: style.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
label: 'favorite',
|
||||
icon: FlowySvg(FlowySvgs.m_favorite_unselected_lg),
|
||||
activeIcon: FlowySvg(
|
||||
FlowySvgs.m_favorite_selected_lg,
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
label: 'add',
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.m_add_circle_xl,
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
label: 'search',
|
||||
icon: const FlowySvg(FlowySvgs.m_search_lg),
|
||||
activeIcon: FlowySvg(
|
||||
FlowySvgs.m_search_lg,
|
||||
color: style.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
label: 'notification',
|
||||
icon: const FlowySvg(FlowySvgs.m_notification_unselected_lg),
|
||||
activeIcon: FlowySvg(
|
||||
FlowySvgs.m_notification_selected_lg,
|
||||
color: style.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
currentIndex: navigationShell.currentIndex,
|
||||
onTap: (int bottomBarIndex) => _onTap(context, bottomBarIndex),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Navigate to the current location of the branch at the provided index when
|
||||
/// tapping an item in the BottomNavigationBar.
|
||||
void _onTap(BuildContext context, int bottomBarIndex) {
|
||||
// When navigating to a new branch, it's recommended to use the goBranch
|
||||
// method, as doing so makes sure the last navigation state of the
|
||||
// Navigator for the branch is restored.
|
||||
navigationShell.goBranch(
|
||||
bottomBarIndex,
|
||||
// A common pattern when using bottom navigation bars is to support
|
||||
// navigating to the initial location when tapping the item that is
|
||||
// already active. This example demonstrates how to support this behavior,
|
||||
// using the initialLocation parameter of goBranch.
|
||||
initialLocation: bottomBarIndex == navigationShell.currentIndex,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.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 MobileHomeScreen extends StatelessWidget {
|
||||
const MobileHomeScreen({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.workspaceSetting,
|
||||
});
|
||||
|
||||
static const routeName = "/MobileHomeScreen";
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceSettingPB workspaceSetting;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("MobileHomeScreen"),
|
||||
),
|
||||
// TODO(yijing): implement home page later
|
||||
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'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export 'home/home.dart';
|
||||
export 'root_placeholder_page.dart';
|
||||
export 'details_placeholder_page.dart';
|
||||
export 'mobile_bottom_navigation_bar.dart';
|
@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Widget for the root/initial pages in the bottom navigation bar.
|
||||
class RootPlaceholderScreen extends StatelessWidget {
|
||||
/// Creates a RootScreen
|
||||
const RootPlaceholderScreen({
|
||||
required this.label,
|
||||
required this.detailsPath,
|
||||
this.secondDetailsPath,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The label
|
||||
final String label;
|
||||
|
||||
/// The path to the detail page
|
||||
final String detailsPath;
|
||||
|
||||
/// The path to another detail page
|
||||
final String? secondDetailsPath;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Root of section $label'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text('$label Page', style: Theme.of(context).textTheme.titleLarge),
|
||||
const Padding(padding: EdgeInsets.all(4)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.go(detailsPath, extra: '$label-XYZ');
|
||||
},
|
||||
child: const Text('View details'),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(4)),
|
||||
if (secondDetailsPath != null)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.go(secondDetailsPath!);
|
||||
},
|
||||
child: const Text('View more details'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/startup/tasks/prelude.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../user/application/user_settings_service.dart';
|
||||
import '../../workspace/application/appearance.dart';
|
||||
@ -73,7 +75,7 @@ class InitAppWidgetTask extends LaunchTask {
|
||||
}
|
||||
}
|
||||
|
||||
class ApplicationWidget extends StatelessWidget {
|
||||
class ApplicationWidget extends StatefulWidget {
|
||||
final Widget child;
|
||||
final AppearanceSettingsPB appearanceSetting;
|
||||
final AppTheme appTheme;
|
||||
@ -86,19 +88,36 @@ class ApplicationWidget extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cubit = AppearanceSettingsCubit(appearanceSetting, appTheme)
|
||||
..readLocaleWhenAppLaunch(context);
|
||||
State<ApplicationWidget> createState() => _ApplicationWidgetState();
|
||||
}
|
||||
|
||||
class _ApplicationWidgetState extends State<ApplicationWidget> {
|
||||
late final GoRouter routerConfig;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// avoid rebuild routerConfig when the appTheme is changed.
|
||||
routerConfig = generateRouter(widget.child);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: cubit),
|
||||
BlocProvider<AppearanceSettingsCubit>(
|
||||
create: (_) => AppearanceSettingsCubit(
|
||||
widget.appearanceSetting,
|
||||
widget.appTheme,
|
||||
)..readLocaleWhenAppLaunch(context),
|
||||
),
|
||||
BlocProvider<DocumentAppearanceCubit>(
|
||||
create: (_) => DocumentAppearanceCubit()..fetch(),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
||||
builder: (context, state) => MaterialApp(
|
||||
builder: (context, state) => MaterialApp.router(
|
||||
builder: overlayManagerBuilder(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: state.lightTheme,
|
||||
@ -108,8 +127,7 @@ class ApplicationWidget extends StatelessWidget {
|
||||
[AppFlowyEditorLocalizations.delegate],
|
||||
supportedLocales: context.supportedLocales,
|
||||
locale: state.locale,
|
||||
navigatorKey: AppGlobals.rootNavKey,
|
||||
home: child,
|
||||
routerConfig: routerConfig,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
296
frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart
Normal file
@ -0,0 +1,296 @@
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/app_widget.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/presentation.dart';
|
||||
import 'package:appflowy/util/platform_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/desktop_home_screen.dart';
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
GoRouter generateRouter(Widget child) {
|
||||
return GoRouter(
|
||||
navigatorKey: AppGlobals.rootNavKey,
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
// Root route is SplashScreen.
|
||||
// It needs LaunchConfiguration as a parameter, so we get it from ApplicationWidget's child.
|
||||
_rootRoute(child),
|
||||
// Routes in both desktop and mobile
|
||||
_signInScreenRoute(),
|
||||
_skipLogInScreenRoute(),
|
||||
_encryptSecretScreenRoute(),
|
||||
_workspaceErrorScreenRoute(),
|
||||
// Desktop only
|
||||
if (!PlatformExtension.isMobile) _desktopHomeScreenRoute(),
|
||||
// Mobile only
|
||||
if (PlatformExtension.isMobile) _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,
|
||||
pageBuilder: (context, state) {
|
||||
final args = state.extra as Map<String, dynamic>;
|
||||
return CustomTransitionPage(
|
||||
child: WorkspaceStartScreen(
|
||||
userProfile: args[WorkspaceStartScreen.argUserProfile],
|
||||
),
|
||||
transitionsBuilder: _buildFadeTransition,
|
||||
transitionDuration: _slowDuration,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: SignUpScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
return CustomTransitionPage(
|
||||
child: SignUpScreen(
|
||||
router: getIt<AuthRouter>(),
|
||||
),
|
||||
transitionsBuilder: _buildFadeTransition,
|
||||
transitionDuration: _slowDuration,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// We use StatefulShellRoute to create a StatefulNavigationShell(ScaffoldWithNavBar) to access to multiple pages, and each page retains its own state.
|
||||
StatefulShellRoute _mobileHomeScreenWithNavigationBarRoute() {
|
||||
return StatefulShellRoute.indexedStack(
|
||||
builder: (
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
StatefulNavigationShell navigationShell,
|
||||
) {
|
||||
// Return the widget that implements the custom shell (in this case
|
||||
// using a BottomNavigationBar). The StatefulNavigationShell is passed
|
||||
// to be able access the state of the shell and to navigate to other
|
||||
// branches in a stateful way.
|
||||
return MobileBottomNavigationBar(navigationShell: navigationShell);
|
||||
},
|
||||
branches: <StatefulShellBranch>[
|
||||
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();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
// 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'],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// The route branch for the third tab of the bottom navigation bar.
|
||||
StatefulShellBranch(
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
// The screen to display as the root in the third tab of the
|
||||
// bottom navigation bar.
|
||||
path: '/c',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
const RootPlaceholderScreen(
|
||||
label: 'Add Document',
|
||||
detailsPath: '/c/details',
|
||||
),
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: 'details',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
DetailsPlaceholderScreen(
|
||||
label: 'Add Document details',
|
||||
extra: state.extra,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: '/d',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
const RootPlaceholderScreen(
|
||||
label: 'Search',
|
||||
detailsPath: '/d/details',
|
||||
),
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: 'details',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
const DetailsPlaceholderScreen(
|
||||
label: 'Search Page details',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: '/e',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
const RootPlaceholderScreen(
|
||||
label: 'Notification',
|
||||
detailsPath: '/e/details',
|
||||
),
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: 'details',
|
||||
builder: (BuildContext context, GoRouterState state) =>
|
||||
const DetailsPlaceholderScreen(
|
||||
label: 'Notification Page details',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _desktopHomeScreenRoute() {
|
||||
return GoRoute(
|
||||
path: DesktopHomeScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
return CustomTransitionPage(
|
||||
child: const DesktopHomeScreen(),
|
||||
transitionsBuilder: _buildFadeTransition,
|
||||
transitionDuration: _slowDuration,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _workspaceErrorScreenRoute() {
|
||||
return GoRoute(
|
||||
path: WorkspaceErrorScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
final args = state.extra as Map<String, dynamic>;
|
||||
return CustomTransitionPage(
|
||||
child: WorkspaceErrorScreen(
|
||||
error: args[WorkspaceErrorScreen.argError],
|
||||
userFolder: args[WorkspaceErrorScreen.argUserFolder],
|
||||
),
|
||||
transitionsBuilder: _buildFadeTransition,
|
||||
transitionDuration: _slowDuration,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _encryptSecretScreenRoute() {
|
||||
return GoRoute(
|
||||
path: EncryptSecretScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
final args = state.extra as Map<String, dynamic>;
|
||||
return CustomTransitionPage(
|
||||
child: EncryptSecretScreen(
|
||||
user: args[EncryptSecretScreen.argUser],
|
||||
key: args[EncryptSecretScreen.argKey],
|
||||
),
|
||||
transitionsBuilder: _buildFadeTransition,
|
||||
transitionDuration: _slowDuration,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _skipLogInScreenRoute() {
|
||||
return GoRoute(
|
||||
path: SkipLogInScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
return CustomTransitionPage(
|
||||
child: const SkipLogInScreen(),
|
||||
transitionsBuilder: _buildFadeTransition,
|
||||
transitionDuration: _slowDuration,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _signInScreenRoute() {
|
||||
return GoRoute(
|
||||
path: SignInScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
return CustomTransitionPage(
|
||||
child: const SignInScreen(),
|
||||
transitionsBuilder: _buildFadeTransition,
|
||||
transitionDuration: _slowDuration,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _rootRoute(Widget child) {
|
||||
return GoRoute(
|
||||
path: '/',
|
||||
redirect: (context, state) async {
|
||||
// Every time before navigating to splash screen, we check if user is already logged in in desktop. It is used to skip showing splash screen when user just changes apperance settings like theme mode.
|
||||
final userResponse = await getIt<AuthService>().getUser();
|
||||
final routeName = userResponse.fold(
|
||||
(error) => null,
|
||||
(user) => DesktopHomeScreen.routeName,
|
||||
);
|
||||
if (routeName != null && !PlatformExtension.isMobile) return routeName;
|
||||
|
||||
return null;
|
||||
},
|
||||
// Root route is SplashScreen.
|
||||
// It needs LaunchConfiguration as a parameter, so we get it from ApplicationWidget's child.
|
||||
pageBuilder: (context, state) => MaterialPage(
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFadeTransition(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) =>
|
||||
FadeTransition(opacity: animation, child: child);
|
||||
|
||||
Duration _slowDuration = Duration(
|
||||
milliseconds: (RouteDurations.slow.inMilliseconds).round(),
|
||||
);
|
@ -7,3 +7,4 @@ export 'platform_error_catcher.dart';
|
||||
export 'windows.dart';
|
||||
export 'localization.dart';
|
||||
export 'supabase_task.dart';
|
||||
export 'generate_router.dart';
|
||||
|
@ -5,17 +5,17 @@ import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void handleSuccessOrFail(
|
||||
Either<UserProfilePB, FlowyError> result,
|
||||
void handleUserProfileResult(
|
||||
Either<UserProfilePB, FlowyError> userProfileResult,
|
||||
BuildContext context,
|
||||
AuthRouter router,
|
||||
AuthRouter authRouter,
|
||||
) {
|
||||
result.fold(
|
||||
(user) {
|
||||
if (user.encryptionType == EncryptionTypePB.Symmetric) {
|
||||
router.pushEncryptionScreen(context, user);
|
||||
userProfileResult.fold(
|
||||
(userProfile) {
|
||||
if (userProfile.encryptionType == EncryptionTypePB.Symmetric) {
|
||||
authRouter.pushEncryptionScreen(context, userProfile);
|
||||
} else {
|
||||
router.pushHomeScreen(context, user);
|
||||
authRouter.goHomeScreen(context, userProfile);
|
||||
}
|
||||
},
|
||||
(error) {
|
@ -1,2 +1,2 @@
|
||||
export 'handle_open_workspace_error.dart';
|
||||
export 'handle_success_or_fail.dart';
|
||||
export 'handle_user_profile_result.dart';
|
||||
|
@ -1,18 +1,15 @@
|
||||
import 'package:appflowy/mobile/presentation/mobile_home_page.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_home_page.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/screens/screens.dart';
|
||||
import 'package:appflowy/user/presentation/screens/workspace_start_screen/workspace_start_screen.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_screen.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/desktop_home_screen.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:flowy_infra_ui/widget/route/animation.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
show UserProfilePB;
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/util/platform_extension.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class AuthRouter {
|
||||
void pushForgetPasswordScreen(BuildContext context) {}
|
||||
@ -25,12 +22,7 @@ class AuthRouter {
|
||||
}
|
||||
|
||||
void pushSignUpScreen(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => SignUpScreen(router: getIt<AuthRouter>()),
|
||||
const RouteSettings(name: SignUpScreen.routeName),
|
||||
),
|
||||
);
|
||||
context.push(SignUpScreen.routeName);
|
||||
}
|
||||
|
||||
/// Navigates to the home screen based on the current workspace and platform.
|
||||
@ -46,39 +38,22 @@ class AuthRouter {
|
||||
/// @param [context] BuildContext for navigating to the appropriate screen.
|
||||
/// @param [userProfile] UserProfilePB object containing the details of the current user.
|
||||
///
|
||||
Future<void> pushHomeScreen(
|
||||
Future<void> goHomeScreen(
|
||||
BuildContext context,
|
||||
UserProfilePB userProfile,
|
||||
) async {
|
||||
final result = await FolderEventGetCurrentWorkspace().send();
|
||||
result.fold(
|
||||
(workspaceSetting) {
|
||||
// Replace SignInScreen or SkipLogInScreen as root page.
|
||||
// If user click back button, it will exit app rather than go back to SignInScreen or SkipLogInScreen
|
||||
if (PlatformExtension.isMobile) {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) => MobileHomeScreen(
|
||||
key: ValueKey(userProfile.id),
|
||||
userProfile: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
),
|
||||
),
|
||||
// pop up all the pages until [SplashScreen]
|
||||
(route) => route.settings.name == SplashScreen.routeName,
|
||||
context.go(
|
||||
MobileHomeScreen.routeName,
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => DesktopHomeScreen(
|
||||
key: ValueKey(userProfile.id),
|
||||
userProfile: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
),
|
||||
const RouteSettings(
|
||||
name: DesktopHomeScreen.routeName,
|
||||
),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
context.go(
|
||||
DesktopHomeScreen.routeName,
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -90,16 +65,13 @@ class AuthRouter {
|
||||
BuildContext context,
|
||||
UserProfilePB userProfile,
|
||||
) async {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => EncryptSecretScreen(
|
||||
user: userProfile,
|
||||
key: ValueKey(userProfile.id),
|
||||
),
|
||||
const RouteSettings(name: EncryptSecretScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
// After log in,push EncryptionScreen on the top SignInScreen
|
||||
context.push(
|
||||
EncryptSecretScreen.routeName,
|
||||
extra: {
|
||||
EncryptSecretScreen.argUser: userProfile,
|
||||
EncryptSecretScreen.argKey: ValueKey(userProfile.id),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -108,38 +80,33 @@ class AuthRouter {
|
||||
UserFolderPB userFolder,
|
||||
FlowyError error,
|
||||
) async {
|
||||
final screen = WorkspaceErrorScreen(
|
||||
userFolder: userFolder,
|
||||
error: error,
|
||||
);
|
||||
await Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => screen,
|
||||
const RouteSettings(name: WorkspaceErrorScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
await context.push(
|
||||
WorkspaceErrorScreen.routeName,
|
||||
extra: {
|
||||
WorkspaceErrorScreen.argUserFolder: userFolder,
|
||||
WorkspaceErrorScreen.argError: error,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SplashRouter {
|
||||
// Unused for now, it was planed to be used in SignUpScreen.
|
||||
// To let user choose workspace than navigate to corresponding home screen.
|
||||
Future<void> pushWorkspaceStartScreen(
|
||||
BuildContext context,
|
||||
UserProfilePB userProfile,
|
||||
) async {
|
||||
final screen = WorkspaceStartScreen(userProfile: userProfile);
|
||||
await Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => screen,
|
||||
const RouteSettings(name: WorkspaceStartScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
await context.push(
|
||||
WorkspaceStartScreen.routeName,
|
||||
extra: {
|
||||
WorkspaceStartScreen.argUserProfile: userProfile,
|
||||
},
|
||||
);
|
||||
|
||||
FolderEventGetCurrentWorkspace().send().then((result) {
|
||||
result.fold(
|
||||
(workspaceSettingPB) =>
|
||||
pushHomeScreen(context, userProfile, workspaceSettingPB),
|
||||
(workspaceSettingPB) => pushHomeScreen(context),
|
||||
(r) => null,
|
||||
);
|
||||
});
|
||||
@ -147,62 +114,29 @@ class SplashRouter {
|
||||
|
||||
void pushHomeScreen(
|
||||
BuildContext context,
|
||||
UserProfilePB userProfile,
|
||||
WorkspaceSettingPB workspaceSetting,
|
||||
) {
|
||||
if (PlatformExtension.isMobile) {
|
||||
Navigator.pushAndRemoveUntil<void>(
|
||||
context,
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) => MobileHomeScreen(
|
||||
key: ValueKey(userProfile.id),
|
||||
userProfile: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
),
|
||||
),
|
||||
// pop up all the pages until [SplashScreen]
|
||||
(route) => route.settings.name == SplashScreen.routeName,
|
||||
context.push(
|
||||
MobileHomeScreen.routeName,
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => DesktopHomeScreen(
|
||||
userProfile: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
key: ValueKey(userProfile.id),
|
||||
),
|
||||
const RouteSettings(
|
||||
name: DesktopHomeScreen.routeName,
|
||||
),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
context.push(
|
||||
DesktopHomeScreen.routeName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void pushSignInScreen(BuildContext context) {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => SignInScreen(router: getIt<AuthRouter>()),
|
||||
const RouteSettings(name: SignInScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void pushSkipLoginScreen(BuildContext context) {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => SkipLogInScreen(
|
||||
router: getIt<AuthRouter>(),
|
||||
authService: getIt<AuthService>(),
|
||||
),
|
||||
const RouteSettings(name: SkipLogInScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
void goHomeScreen(
|
||||
BuildContext context,
|
||||
) {
|
||||
if (PlatformExtension.isMobile) {
|
||||
context.go(
|
||||
MobileHomeScreen.routeName,
|
||||
);
|
||||
} else {
|
||||
context.go(
|
||||
DesktopHomeScreen.routeName,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../application/encrypt_secret_bloc.dart';
|
||||
|
||||
class EncryptSecretScreen extends StatefulWidget {
|
||||
static const routeName = "/EncryptSecretScreen";
|
||||
static const routeName = '/EncryptSecretScreen';
|
||||
// arguments names to used in GoRouter
|
||||
static const argUser = 'user';
|
||||
static const argKey = 'key';
|
||||
|
||||
final UserProfilePB user;
|
||||
const EncryptSecretScreen({required this.user, super.key});
|
||||
|
||||
|
@ -4,3 +4,4 @@ export 'splash_screen.dart';
|
||||
export 'sign_up_screen.dart';
|
||||
export 'encrypt_secret_screen.dart';
|
||||
export 'workspace_error_screen.dart';
|
||||
export 'workspace_start_screen/workspace_start_screen.dart';
|
||||
|
@ -12,11 +12,9 @@ import '../../helpers/helpers.dart';
|
||||
class SignInScreen extends StatelessWidget {
|
||||
const SignInScreen({
|
||||
super.key,
|
||||
required this.router,
|
||||
});
|
||||
|
||||
static const routeName = '/SignInScreen';
|
||||
final AuthRouter router;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -26,7 +24,11 @@ class SignInScreen extends StatelessWidget {
|
||||
listener: (context, state) {
|
||||
state.successOrFail.fold(
|
||||
() => null,
|
||||
(result) => handleSuccessOrFail(result, context, router),
|
||||
(userProfileResult) => handleUserProfileResult(
|
||||
userProfileResult,
|
||||
context,
|
||||
getIt<AuthRouter>(),
|
||||
),
|
||||
);
|
||||
},
|
||||
builder: (context, state) {
|
||||
|
@ -21,14 +21,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class SkipLogInScreen extends StatefulWidget {
|
||||
final AuthRouter router;
|
||||
final AuthService authService;
|
||||
static const routeName = '/SkipLogInScreen';
|
||||
|
||||
const SkipLogInScreen({
|
||||
super.key,
|
||||
required this.router,
|
||||
required this.authService,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -86,13 +82,13 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
|
||||
}
|
||||
|
||||
Future<void> _autoRegister(BuildContext context) async {
|
||||
final result = await widget.authService.signUpAsGuest();
|
||||
final result = await getIt<AuthService>().signUpAsGuest();
|
||||
result.fold(
|
||||
(error) {
|
||||
Log.error(error);
|
||||
},
|
||||
(user) {
|
||||
widget.router.pushHomeScreen(context, user);
|
||||
getIt<AuthRouter>().goHomeScreen(context, user);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -6,11 +6,13 @@ import 'package:appflowy/user/application/splash_bloc.dart';
|
||||
import 'package:appflowy/user/domain/auth_state.dart';
|
||||
import 'package:appflowy/user/presentation/helpers/helpers.dart';
|
||||
import 'package:appflowy/user/presentation/router.dart';
|
||||
import 'package:appflowy/user/presentation/screens/screens.dart';
|
||||
import 'package:appflowy/util/platform_extension.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
// [[diagram: splash screen]]
|
||||
// ┌────────────────┐1.get user ┌──────────┐ ┌────────────┐ 2.send UserEventCheckUser
|
||||
@ -23,12 +25,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
// └───────────┘ └─────────────┘ └────────┘
|
||||
// 4. Show HomeScreen or SignIn 3.return AuthState
|
||||
class SplashScreen extends StatelessWidget {
|
||||
/// Root Page of the app.
|
||||
const SplashScreen({
|
||||
super.key,
|
||||
required this.autoRegister,
|
||||
});
|
||||
|
||||
static const routeName = '/SplashScreen';
|
||||
final bool autoRegister;
|
||||
|
||||
@override
|
||||
@ -50,9 +52,8 @@ class SplashScreen extends StatelessWidget {
|
||||
|
||||
BlocProvider<SplashBloc> _buildChild(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
return getIt<SplashBloc>()..add(const SplashEvent.getUser());
|
||||
},
|
||||
create: (context) =>
|
||||
getIt<SplashBloc>()..add(const SplashEvent.getUser()),
|
||||
child: Scaffold(
|
||||
body: BlocListener<SplashBloc, SplashState>(
|
||||
listener: (context, state) {
|
||||
@ -87,10 +88,9 @@ class SplashScreen extends StatelessWidget {
|
||||
final result = await FolderEventGetCurrentWorkspace().send();
|
||||
result.fold(
|
||||
(workspaceSetting) {
|
||||
getIt<SplashRouter>().pushHomeScreen(
|
||||
// After login, replace Splash screen by corresponding home screen
|
||||
getIt<SplashRouter>().goHomeScreen(
|
||||
context,
|
||||
userProfile,
|
||||
workspaceSetting,
|
||||
);
|
||||
},
|
||||
(error) => handleOpenWorkspaceError(context, error),
|
||||
@ -107,11 +107,12 @@ class SplashScreen extends StatelessWidget {
|
||||
Log.trace(
|
||||
'_handleUnauthenticated -> Supabase is enabled: $isSupabaseEnabled',
|
||||
);
|
||||
// if the env is not configured, we will skip to the 'skip login screen'.
|
||||
// replace Splash screen as root page
|
||||
if (isSupabaseEnabled) {
|
||||
getIt<SplashRouter>().pushSignInScreen(context);
|
||||
context.go(SignInScreen.routeName);
|
||||
} else {
|
||||
getIt<SplashRouter>().pushSkipLoginScreen(context);
|
||||
// if the env is not configured, we will skip to the 'skip login screen'.
|
||||
context.go(SkipLogInScreen.routeName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,10 @@ import '../../application/workspace_error_bloc.dart';
|
||||
|
||||
class WorkspaceErrorScreen extends StatelessWidget {
|
||||
static const routeName = "/WorkspaceErrorScreen";
|
||||
// arguments names to used in GoRouter
|
||||
static const argError = "error";
|
||||
static const argUserFolder = "userFolder";
|
||||
|
||||
final FlowyError error;
|
||||
final UserFolderPB userFolder;
|
||||
const WorkspaceErrorScreen({
|
||||
|
@ -7,6 +7,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class DesktopWorkspaceStartScreen extends StatelessWidget {
|
||||
const DesktopWorkspaceStartScreen({super.key, required this.workspaceState});
|
||||
@ -101,6 +102,5 @@ Widget _renderCreateButton(BuildContext context) {
|
||||
// same method as in mobile
|
||||
void _popToWorkspace(BuildContext context, WorkspacePB workspace) {
|
||||
context.read<WorkspaceBloc>().add(WorkspaceEvent.openWorkspace(workspace));
|
||||
|
||||
Navigator.of(context).pop(workspace.id);
|
||||
context.pop(workspace.id);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
// TODO(yijing): needs refactor when multiple workspaces are supported
|
||||
class MobileWorkspaceStartScreen extends StatefulWidget {
|
||||
@ -139,6 +140,5 @@ class _MobileWorkspaceStartScreenState
|
||||
// same method as in desktop
|
||||
void _popToWorkspace(BuildContext context, WorkspacePB workspace) {
|
||||
context.read<WorkspaceBloc>().add(WorkspaceEvent.openWorkspace(workspace));
|
||||
|
||||
Navigator.of(context).pop(workspace.id);
|
||||
context.pop(workspace.id);
|
||||
}
|
||||
|
@ -9,8 +9,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// For future use
|
||||
class WorkspaceStartScreen extends StatelessWidget {
|
||||
final UserProfilePB userProfile;
|
||||
static const routeName = "/WorkspaceStartScreen";
|
||||
static const argUserProfile = "userProfile";
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
/// To choose which screen is going to open
|
||||
const WorkspaceStartScreen({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
|
@ -3,7 +3,7 @@ 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/workspace/application/mobile_theme_data.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_theme_data.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:appflowy/plugins/blank/blank.dart';
|
||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_service.dart';
|
||||
@ -11,6 +12,7 @@ import 'package:appflowy/workspace/presentation/home/hotkeys.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/edit_panel/panel_animation.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
@ -25,84 +27,102 @@ import '../widgets/edit_panel/edit_panel.dart';
|
||||
import 'home_layout.dart';
|
||||
import 'home_stack.dart';
|
||||
|
||||
class DesktopHomeScreen extends StatefulWidget {
|
||||
class DesktopHomeScreen extends StatelessWidget {
|
||||
static const routeName = '/DesktopHomeScreen';
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceSettingPB workspaceSetting;
|
||||
const DesktopHomeScreen({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.workspaceSetting,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DesktopHomeScreen> createState() => _DesktopHomeScreenState();
|
||||
}
|
||||
const DesktopHomeScreen({super.key});
|
||||
|
||||
class _DesktopHomeScreenState extends State<DesktopHomeScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<TabsBloc>.value(value: getIt<TabsBloc>()),
|
||||
BlocProvider<HomeBloc>(
|
||||
create: (context) {
|
||||
return HomeBloc(widget.userProfile, widget.workspaceSetting)
|
||||
..add(const HomeEvent.initial());
|
||||
},
|
||||
),
|
||||
BlocProvider<HomeSettingBloc>(
|
||||
create: (context) {
|
||||
return HomeSettingBloc(
|
||||
widget.userProfile,
|
||||
widget.workspaceSetting,
|
||||
context.read<AppearanceSettingsCubit>(),
|
||||
)..add(const HomeSettingEvent.initial());
|
||||
},
|
||||
),
|
||||
],
|
||||
child: HomeHotKeys(
|
||||
child: Scaffold(
|
||||
body: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<HomeBloc, HomeState>(
|
||||
listenWhen: (p, c) => p.latestView != c.latestView,
|
||||
listener: (context, state) {
|
||||
final view = state.latestView;
|
||||
if (view != null) {
|
||||
// Only open the last opened view if the [TabsState.currentPageManager] current opened plugin is blank and the last opened view is not null.
|
||||
// All opened widgets that display on the home screen are in the form of plugins. There is a list of built-in plugins defined in the [PluginType] enum, including board, grid and trash.
|
||||
final currentPageManager =
|
||||
context.read<TabsBloc>().state.currentPageManager;
|
||||
return FutureBuilder(
|
||||
future: Future.wait([
|
||||
FolderEventGetCurrentWorkspace().send(),
|
||||
getIt<AuthService>().getUser(),
|
||||
]),
|
||||
builder: (context, snapshots) {
|
||||
if (!snapshots.hasData) {
|
||||
return const Center(child: CircularProgressIndicator.adaptive());
|
||||
}
|
||||
|
||||
if (currentPageManager.plugin.pluginType ==
|
||||
PluginType.blank) {
|
||||
getIt<TabsBloc>().add(
|
||||
TabsEvent.openPlugin(
|
||||
plugin: view.plugin(listenOnViewChanged: true),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<HomeSettingBloc, HomeSettingState>(
|
||||
buildWhen: (previous, current) => previous != current,
|
||||
builder: (context, state) {
|
||||
return FlowyContainer(
|
||||
Theme.of(context).colorScheme.surface,
|
||||
child: _buildBody(context),
|
||||
);
|
||||
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;
|
||||
});
|
||||
return MultiBlocProvider(
|
||||
key: ValueKey(userProfile!.id),
|
||||
providers: [
|
||||
BlocProvider<TabsBloc>.value(value: getIt<TabsBloc>()),
|
||||
BlocProvider<HomeBloc>(
|
||||
create: (context) {
|
||||
return HomeBloc(userProfile, workspaceSetting!)
|
||||
..add(const HomeEvent.initial());
|
||||
},
|
||||
),
|
||||
BlocProvider<HomeSettingBloc>(
|
||||
create: (context) {
|
||||
return HomeSettingBloc(
|
||||
userProfile,
|
||||
workspaceSetting!,
|
||||
context.read<AppearanceSettingsCubit>(),
|
||||
)..add(const HomeSettingEvent.initial());
|
||||
},
|
||||
),
|
||||
],
|
||||
child: HomeHotKeys(
|
||||
child: Scaffold(
|
||||
body: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<HomeBloc, HomeState>(
|
||||
listenWhen: (p, c) => p.latestView != c.latestView,
|
||||
listener: (context, state) {
|
||||
final view = state.latestView;
|
||||
if (view != null) {
|
||||
// Only open the last opened view if the [TabsState.currentPageManager] current opened plugin is blank and the last opened view is not null.
|
||||
// All opened widgets that display on the home screen are in the form of plugins. There is a list of built-in plugins defined in the [PluginType] enum, including board, grid and trash.
|
||||
final currentPageManager =
|
||||
context.read<TabsBloc>().state.currentPageManager;
|
||||
|
||||
if (currentPageManager.plugin.pluginType ==
|
||||
PluginType.blank) {
|
||||
getIt<TabsBloc>().add(
|
||||
TabsEvent.openPlugin(
|
||||
plugin: view.plugin(listenOnViewChanged: true),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<HomeSettingBloc, HomeSettingState>(
|
||||
buildWhen: (previous, current) => previous != current,
|
||||
builder: (context, state) {
|
||||
return FlowyContainer(
|
||||
Theme.of(context).colorScheme.surface,
|
||||
child:
|
||||
_buildBody(context, userProfile, workspaceSetting!),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(BuildContext context) {
|
||||
Widget _buildBody(
|
||||
BuildContext context,
|
||||
UserProfilePB userProfile,
|
||||
WorkspaceSettingPB workspaceSetting,
|
||||
) {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
final layout = HomeLayout(context, constraints);
|
||||
@ -115,6 +135,8 @@ class _DesktopHomeScreenState extends State<DesktopHomeScreen> {
|
||||
final menu = _buildHomeSidebar(
|
||||
layout: layout,
|
||||
context: context,
|
||||
userProfile: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
);
|
||||
final homeMenuResizer = _buildHomeMenuResizer(context: context);
|
||||
final editPanel = _buildEditPanel(
|
||||
@ -137,10 +159,11 @@ class _DesktopHomeScreenState extends State<DesktopHomeScreen> {
|
||||
Widget _buildHomeSidebar({
|
||||
required HomeLayout layout,
|
||||
required BuildContext context,
|
||||
required UserProfilePB userProfile,
|
||||
required WorkspaceSettingPB workspaceSetting,
|
||||
}) {
|
||||
final workspaceSetting = widget.workspaceSetting;
|
||||
final homeMenu = HomeSideBar(
|
||||
user: widget.userProfile,
|
||||
user: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
);
|
||||
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));
|
@ -1,5 +1,4 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||
|
@ -66,7 +66,7 @@ class SidebarUser extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return BlocProvider<DocumentAppearanceCubit>.value(
|
||||
value: BlocProvider.of<DocumentAppearanceCubit>(context),
|
||||
value: BlocProvider.of<DocumentAppearanceCubit>(dialogContext),
|
||||
child: SettingsDialog(
|
||||
userProfile,
|
||||
didLogout: () async {
|
||||
|
@ -604,6 +604,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
go_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: "5668e6d3dbcb2d0dfa25f7567554b88c57e1e3f3c440b672b24d4a9477017d5b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.2"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -107,6 +107,7 @@ dependencies:
|
||||
hive: ^2.2.3
|
||||
hive_flutter: ^1.1.0
|
||||
super_clipboard: ^0.6.3
|
||||
go_router: ^10.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^2.0.1
|
||||
|
3
frontend/resources/flowy_icons/24x/m_setting.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="M8.96704 2.45522C7.54904 2.90522 6.32605 3.63923 5.24805 4.64323C4.91505 4.95323 4.81305 5.46123 5.02905 5.86123C5.83005 7.34223 4.99204 8.92723 3.18504 9.01823C2.74304 9.04023 2.35205 9.36823 2.24805 9.79923C2.06905 10.5442 1.99805 11.1682 1.99805 11.9862C1.99805 12.6732 2.07204 13.4512 2.21704 14.1432C2.30704 14.5752 2.68305 14.8862 3.12305 14.9242C4.94105 15.0812 5.84105 16.4682 5.02905 18.2362C4.84905 18.6292 4.93105 19.0992 5.24805 19.3932C6.31005 20.3752 7.53004 21.0682 8.96704 21.5182C9.37704 21.6462 9.84004 21.4912 10.092 21.1432C11.204 19.6042 12.817 19.5992 13.873 21.1432C14.122 21.5062 14.578 21.6812 14.998 21.5492C16.385 21.1122 17.677 20.3692 18.748 19.3932C19.078 19.0922 19.165 18.6052 18.967 18.2052C18.135 16.5262 19.0921 14.9852 20.8101 14.9552C21.2661 14.9472 21.6721 14.6482 21.7791 14.2052C21.9521 13.4882 21.998 12.8642 21.998 11.9862C21.998 11.2322 21.909 10.4892 21.748 9.76823C21.646 9.31123 21.2471 8.98723 20.7791 8.98623C19.0881 8.98323 18.14 7.32123 18.967 5.86123C19.197 5.45523 19.1251 4.95723 18.7791 4.64323C17.6891 3.65323 16.36 2.87922 14.967 2.45522C14.539 2.32522 14.084 2.48523 13.842 2.86123C12.876 4.36223 11.072 4.38823 10.123 2.89323C9.88005 2.50923 9.39904 2.31822 8.96704 2.45522ZM15.045 4.57923C15.728 4.86523 16.267 5.16423 16.886 5.63323C16.16 7.93023 17.391 10.3162 19.941 10.8972C20.004 11.3102 19.998 11.5602 19.998 11.9862C19.998 12.4952 20.0051 12.6742 19.9471 13.0422C17.4081 13.5682 16.152 15.8872 16.859 18.3382C16.251 18.7792 15.816 19.0852 15.053 19.3802C13.261 17.5562 10.7731 17.4762 8.94305 19.3922C8.22905 19.0782 7.68006 18.7992 7.12506 18.3302C7.81306 15.8412 6.65005 13.6922 4.06805 13.0402C3.95305 12.5842 3.99905 11.2962 4.06505 10.9052C6.73505 10.2652 7.78705 7.90223 7.12405 5.62623C7.71005 5.18523 8.23706 4.86423 8.92206 4.58623C10.6471 6.34123 13.237 6.51623 15.045 4.57923ZM11.998 7.98623C9.78905 7.98623 7.99805 9.77723 7.99805 11.9862C7.99805 14.1962 9.78905 15.9862 11.998 15.9862C14.207 15.9862 15.998 14.1962 15.998 11.9862C15.998 9.77723 14.207 7.98623 11.998 7.98623ZM11.998 9.98623C13.103 9.98623 13.998 10.8822 13.998 11.9862C13.998 13.0912 13.103 13.9862 11.998 13.9862C10.893 13.9862 9.99805 13.0912 9.99805 11.9862C9.99805 10.8822 10.893 9.98623 11.998 9.98623Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.7777 3.34827C14.1116 3.34803 13.4314 3.76244 13.0286 4.58727L10.5507 9.68875L4.90265 10.4904C3.08627 10.7431 2.4978 12.525 3.80948 13.806L7.8906 17.7785L6.94327 23.354C6.6313 25.1589 8.12825 26.2497 9.7491 25.3945C10.3754 25.0632 13.5955 23.3949 14.7777 22.7707L19.8062 25.3945C21.4289 26.2497 22.9306 25.16 22.6121 23.354L21.6282 17.7785L25.7095 13.806C27.0273 12.5297 26.4692 10.7482 24.6527 10.4904L18.9682 9.68875L16.5267 4.58727C16.1245 3.76209 15.4437 3.34862 14.7777 3.34827Z" fill="#FFCE00"/>
|
||||
</svg>
|
After Width: | Height: | Size: 617 B |
@ -0,0 +1,3 @@
|
||||
<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.7777 3.34827C14.1116 3.34803 13.4315 3.76244 13.0286 4.58727L10.5507 9.68875L4.90265 10.4904C3.08627 10.7431 2.4978 12.525 3.80948 13.806L7.8906 17.7785L6.94327 23.354C6.6313 25.1589 8.12825 26.2497 9.7491 25.3945C10.3754 25.0632 13.5955 23.3949 14.7777 22.7707L19.8062 25.3945C21.429 26.2497 22.9306 25.16 22.6121 23.354L21.6282 17.7785L25.7095 13.806C27.0273 12.5297 26.4692 10.7482 24.6527 10.4904L18.9682 9.68875L16.5267 4.58727C16.1245 3.76209 15.4437 3.34862 14.7777 3.34827ZM14.7777 6.37273L17.1826 11.2921C17.3523 11.6401 17.6739 11.8572 18.0572 11.9116L23.5231 12.7129L19.5512 16.5395C19.2732 16.809 19.1561 17.1789 19.2233 17.5592L20.1707 22.9527L15.3243 20.4024C14.9838 20.2227 14.5714 20.2227 14.2311 20.4024C13.6264 20.7209 10.671 22.2737 9.38464 22.9527L10.2957 17.5965C10.3609 17.2174 10.2423 16.8079 9.96774 16.5395L6.03222 12.7129L11.4617 11.948C11.846 11.8946 12.2024 11.6407 12.3727 11.2921L14.7777 6.37273Z" fill="#676666"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
3
frontend/resources/flowy_icons/32x/m_home_selected.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="M4.99805 2.92969C3.89305 2.92969 2.99805 3.82469 2.99805 4.92969V10.9297C2.99805 12.0347 3.89305 12.9297 4.99805 12.9297H7.99805C9.10305 12.9297 9.99805 12.0347 9.99805 10.9297V4.92969C9.99805 3.82469 9.10305 2.92969 7.99805 2.92969H4.99805ZM13.998 3.92969C12.893 3.92969 11.998 4.82469 11.998 5.92969V9.92969C11.998 11.0347 12.893 11.9297 13.998 11.9297H17.998C19.103 11.9297 19.998 11.0347 19.998 9.92969V5.92969C19.998 4.82469 19.103 3.92969 17.998 3.92969H13.998ZM13.998 13.9297C12.893 13.9297 11.998 14.8247 11.998 15.9297V17.9297C11.998 19.0347 12.893 19.9297 13.998 19.9297H18.998C20.103 19.9297 20.998 19.0347 20.998 17.9297V15.9297C20.998 14.8247 20.103 13.9297 18.998 13.9297H13.998ZM5.65405 14.9297C4.73405 14.9297 3.99805 15.6657 3.99805 16.5857V19.2737C3.99805 20.1937 4.73405 20.9297 5.65405 20.9297H8.34204C9.26204 20.9297 9.99805 20.1937 9.99805 19.2737V16.5857C9.99805 15.6657 9.26204 14.9297 8.34204 14.9297H5.65405Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
3
frontend/resources/flowy_icons/32x/m_home_unselected.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="28" height="29" viewBox="0 0 28 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.83138 3.91803C4.54221 3.91803 3.49805 4.9622 3.49805 6.25136V13.2514C3.49805 14.5405 4.54221 15.5847 5.83138 15.5847H9.33138C10.6205 15.5847 11.6647 14.5405 11.6647 13.2514V6.25136C11.6647 4.9622 10.6205 3.91803 9.33138 3.91803H5.83138ZM16.3314 5.0847C15.0422 5.0847 13.998 6.12886 13.998 7.41803V12.0847C13.998 13.3739 15.0422 14.418 16.3314 14.418H20.998C22.2872 14.418 23.3314 13.3739 23.3314 12.0847V7.41803C23.3314 6.12886 22.2872 5.0847 20.998 5.0847H16.3314ZM5.83138 6.25136H9.33138V13.2514H5.83138V6.25136ZM16.3314 7.41803H20.998V12.0847H16.3314V7.41803ZM16.3314 16.7514C15.0422 16.7514 13.998 17.7955 13.998 19.0847V21.418C13.998 22.7072 15.0422 23.7514 16.3314 23.7514H22.1647C23.4539 23.7514 24.498 22.7072 24.498 21.418V19.0847C24.498 17.7955 23.4539 16.7514 22.1647 16.7514H16.3314ZM6.59672 17.918C5.52339 17.918 4.66471 18.7767 4.66471 19.85V22.986C4.66471 24.0594 5.52339 24.918 6.59672 24.918H9.73271C10.806 24.918 11.6647 24.0594 11.6647 22.986V19.85C11.6647 18.7767 10.806 17.918 9.73271 17.918H6.59672ZM16.3314 19.0847H22.1647V21.418H16.3314V19.0847ZM6.99805 20.2514H9.33138V22.5847H6.99805V20.2514Z" fill="#5C5C5C"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -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.9909 1.99902C8.43887 1.99902 5.99087 4.44703 5.99087 7.99904C5.99087 8.48404 5.99087 8.99905 5.99087 9.99905C5.99087 10.9991 5.82887 11.5391 5.27187 11.9991C5.20887 12.0511 4.94487 12.278 4.86587 12.3431C3.60087 13.3891 2.97987 14.3691 2.99087 15.9991C2.99887 17.0771 3.88387 18.0101 4.99087 17.9991H9.18186C9.18186 17.9991 8.99087 18.6101 8.99087 18.9991C8.99087 20.6561 10.3339 21.9991 11.9909 21.9991C13.6479 21.9991 14.9909 20.6561 14.9909 18.9991C14.9909 18.6101 14.8079 17.9991 14.8079 17.9991H18.9909C20.0949 18.0021 20.9899 17.0831 20.9909 15.9991C20.9929 14.3811 20.3649 13.3841 19.1159 12.3431C19.0339 12.274 18.7439 12.0531 18.6789 11.9991C18.1339 11.5451 17.9909 10.9991 17.9909 9.99905C17.9909 8.74904 17.9909 7.99904 17.9909 7.99904C17.9909 4.44703 15.5429 1.99902 11.9909 1.99902ZM11.9909 17.9991C12.5429 17.9991 12.9909 18.4471 12.9909 18.9991C12.9909 19.5511 12.5429 19.9991 11.9909 19.9991C11.4389 19.9991 10.9909 19.5511 10.9909 18.9991C10.9909 18.4471 11.4389 17.9991 11.9909 17.9991Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="28" height="29" viewBox="0 0 28 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.9895 2.83203C9.84515 2.83203 6.9895 5.68804 6.9895 9.83205C6.9895 10.3979 6.9895 12.1304 6.9895 12.1654C6.9895 12.5411 6.7998 12.7966 6.1509 13.3321C6.07728 13.3927 5.7687 13.6576 5.677 13.7334C4.2007 14.9537 3.4741 16.0971 3.4895 17.9987C3.50501 19.9214 5.07232 21.4766 6.9895 21.4988H10.6948C10.6948 21.4988 10.4895 22.2116 10.4895 22.6654C10.4895 24.5986 12.0563 26.1654 13.9895 26.1654C15.9227 26.1654 17.4895 24.5986 17.4895 22.6654C17.4895 22.2116 17.2935 21.4988 17.2935 21.4988H20.9895C22.9028 21.5058 24.4918 19.9296 24.4895 17.9987C24.4872 16.1111 23.7592 14.9479 22.302 13.7334C22.2063 13.6529 21.868 13.3951 21.791 13.3321C21.1563 12.8024 20.9895 12.5364 20.9895 12.1654C20.9895 10.7071 20.9895 9.83205 20.9895 9.83205C20.9895 5.68804 18.1335 2.83203 13.9895 2.83203ZM13.9895 5.16537C16.8455 5.16537 18.6562 6.97604 18.6562 9.83205C18.6562 9.83205 18.6562 10.7071 18.6562 12.1654C18.6562 13.4044 19.1788 14.1861 20.2965 15.1182C20.3852 15.1917 20.7597 15.4857 20.8437 15.5557C21.8213 16.3712 22.155 16.9231 22.1562 17.9987C22.1573 18.6334 21.6137 19.1677 20.9895 19.1654C20.769 19.1642 17.3728 19.1642 13.9895 19.1654C10.6065 19.1666 7.23742 19.1677 6.9895 19.1654C6.33827 19.1572 5.82808 18.6462 5.82283 17.9987C5.8142 16.9254 6.14775 16.3724 7.13533 15.5557C7.21595 15.4892 7.56093 15.1882 7.64575 15.1182C8.78115 14.1802 9.32283 13.4161 9.32283 12.1654C9.32283 12.1304 9.32283 10.3979 9.32283 9.83205C9.32283 6.97604 11.1339 5.16537 13.9895 5.16537ZM13.9895 21.4988C14.6335 21.4988 15.1562 22.0214 15.1562 22.6654C15.1562 23.3094 14.6335 23.8321 13.9895 23.8321C13.3455 23.8321 12.8228 23.3094 12.8228 22.6654C12.8228 22.0214 13.3455 21.4988 13.9895 21.4988Z" fill="#676666"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
3
frontend/resources/flowy_icons/32x/m_search.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.9332 2.77869C6.7785 2.77869 2.59985 6.9577 2.59985 12.112C2.59985 17.2664 6.7785 21.4454 11.9332 21.4454C14.076 21.4454 16.1071 20.7092 17.6824 19.4959L22.7612 24.6176C23.2169 25.0726 23.9828 25.0726 24.4385 24.6176C24.894 24.1614 24.894 23.3961 24.4385 22.9399L19.3244 17.8532C20.5386 16.2782 21.2665 14.2552 21.2665 12.112C21.2665 6.9577 17.0879 2.77869 11.9332 2.77869ZM11.9332 5.11203C15.7992 5.11203 18.9332 8.2457 18.9332 12.112C18.9332 15.9784 15.7992 19.1121 11.9332 19.1121C8.0672 19.1121 4.93319 15.9784 4.93319 12.112C4.93319 8.2457 8.0672 5.11203 11.9332 5.11203Z" fill="#676666"/>
|
||||
</svg>
|
After Width: | Height: | Size: 710 B |
4
frontend/resources/flowy_icons/40x/m_add_circle.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="41" height="41" viewBox="0 0 41 41" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" y="0.5" width="40" height="40" rx="20" fill="#333333"/>
|
||||
<path d="M30.0703 20.9907C30.0703 20.4387 29.6223 19.9907 29.0703 19.9907L22.0703 19.9907L22.0703 12.9907C22.0703 12.4387 21.6223 11.9907 21.0703 11.9907C20.5183 11.9907 20.0703 12.4387 20.0703 12.9907L20.0703 19.9907L13.0703 19.9907C12.5183 19.9907 12.0703 20.4387 12.0703 20.9907C12.0703 21.5427 12.5183 21.9907 13.0703 21.9907L20.0703 21.9907L20.0703 28.9907C20.0703 29.5427 20.5183 29.9907 21.0703 29.9907C21.6223 29.9907 22.0703 29.5427 22.0703 28.9907L22.0703 21.9907L29.0703 21.9907C29.6223 21.9907 30.0703 21.5427 30.0703 20.9907Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 728 B |