feat: mobile setting page, trash page and recent file UI with mock data (#3734)

This commit is contained in:
Yijing Huang 2023-10-25 06:29:50 -07:00 committed by GitHub
parent d34eec1d99
commit ad21a61ffb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1475 additions and 461 deletions

View File

@ -25,8 +25,8 @@ void main() {
await tester.openSettingsPage(SettingsPage.user);
await tester.tapButton(find.byType(SettingLogoutButton));
tester.expectToSeeText(LocaleKeys.button_OK.tr());
await tester.tapButtonWithName(LocaleKeys.button_OK.tr());
tester.expectToSeeText(LocaleKeys.button_ok.tr());
await tester.tapButtonWithName(LocaleKeys.button_ok.tr());
// Go to the sign in page again
await tester.pumpAndSettle(const Duration(seconds: 1));

View File

@ -236,7 +236,7 @@ extension CommonOperations on WidgetTester {
final okButton = find.byWidgetPredicate(
(widget) =>
widget is PrimaryTextButton &&
widget.label == LocaleKeys.button_OK.tr(),
widget.label == LocaleKeys.button_ok.tr(),
);
await tapButton(okButton);
}

View File

@ -747,7 +747,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
final field = find.byWidgetPredicate(
(widget) =>
widget is PrimaryTextButton &&
widget.label == LocaleKeys.button_OK.tr(),
widget.label == LocaleKeys.button_ok.tr(),
);
await tapButton(field);
}
@ -1432,7 +1432,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
final field = find.byWidgetPredicate(
(widget) =>
widget is PrimaryTextButton &&
widget.label == LocaleKeys.button_OK.tr(),
widget.label == LocaleKeys.button_ok.tr(),
);
await tapButton(field);
}
@ -1452,7 +1452,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
final okButton = find.byWidgetPredicate(
(widget) =>
widget is PrimaryTextButton &&
widget.label == LocaleKeys.button_OK.tr(),
widget.label == LocaleKeys.button_ok.tr(),
);
await tapButton(okButton);
}

View File

@ -164,7 +164,7 @@ SPEC CHECKSUMS:
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
super_native_extensions: 49e897b6039bb784226e7354e502a3c68e1659b5
super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4

View File

@ -1,48 +1,98 @@
// ThemeData in mobile
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:flowy_infra/colorscheme/colorscheme.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/material.dart';
const _primaryColor = Color(0xFF2DA2F6); //primary 100
const _onBackgroundColor = Color(0xff2F3030); // text/title color
const _onSurfaceColor = Color(0xff676666); // text/body color
const _onSecondaryColor = Color(0xFFC5C7CB); // text/body2 color
// TODO(yijing): improve theme color before release
ThemeData getMobileThemeData(
Theme theme,
Brightness brightness,
FlowyColorScheme theme,
String fontFamily,
String monospaceFontFamily,
) {
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
);
final mobileColorTheme = (brightness == Brightness.light)
? ColorScheme(
brightness: brightness,
primary: _primaryColor,
onPrimary: Colors.white,
// TODO(yijing): add color later
secondary: Colors.white,
onSecondary: _onSecondaryColor,
error: const Color(0xffFB006D),
onError: const Color(0xffFB006D),
background: Colors.white,
onBackground: _onBackgroundColor,
outline: const Color(0xffBDC0C5), //caption
outlineVariant: const Color(0xffCBD5E0).withOpacity(0.24),
//Snack bar
surface: Colors.white,
onSurface: _onSurfaceColor, // text/body color
)
: ColorScheme(
brightness: brightness,
primary: _primaryColor,
onPrimary: Colors.white,
// TODO(yijing): add color later
secondary: Colors.black,
onSecondary: Colors.white,
error: const Color(0xffFB006D),
onError: const Color(0xffFB006D),
background: const Color(0xff1C1C1E), // BG/Secondary color
onBackground: Colors.white,
outline: const Color(0xff96989C), //caption
outlineVariant: Colors.black,
//Snack bar
surface: const Color(0xff2F3030),
onSurface: const Color(0xffC5C6C7), // text/body color
);
return ThemeData(
// color
primaryColor: mobileColorTheme.primary, //primary 100
primaryColorLight: const Color(0xFF57B5F8), //primary 80
dividerColor: mobileColorTheme.outline, //caption
hintColor: mobileColorTheme.outline,
disabledColor: mobileColorTheme.outline,
scaffoldBackgroundColor: mobileColorTheme.background,
appBarTheme: AppBarTheme(
foregroundColor: mobileColorTheme.onBackground,
backgroundColor: mobileColorTheme.background,
elevation: 0,
elevation: 80,
centerTitle: false,
titleTextStyle: TextStyle(
fontFamily: 'Poppins',
color: mobileColorTheme.onBackground,
fontSize: 18,
fontWeight: FontWeight.w600,
letterSpacing: 0.05,
),
shadowColor: mobileColorTheme.outlineVariant,
),
radioTheme: RadioThemeData(
fillColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.selected)) {
return mobileColorTheme.primary;
}
return mobileColorTheme.outline;
}),
),
// button
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
fixedSize: MaterialStateProperty.all(const Size.fromHeight(48)),
elevation: MaterialStateProperty.all(0),
textStyle: MaterialStateProperty.all(
const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
shadowColor: MaterialStateProperty.all(null),
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
@ -57,6 +107,12 @@ ThemeData getMobileThemeData(
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
textStyle: MaterialStateProperty.all(
const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
foregroundColor: MaterialStateProperty.all(
mobileColorTheme.onBackground,
),
@ -81,10 +137,19 @@ ThemeData getMobileThemeData(
),
),
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
textStyle: MaterialStateProperty.all(
const TextStyle(
fontFamily: 'Poppins',
),
),
),
),
// text
fontFamily: 'Poppins',
textTheme: const TextTheme(
displayLarge: TextStyle(
textTheme: TextTheme(
displayLarge: const TextStyle(
color: Color(0xFF57B5F8),
fontSize: 32,
fontWeight: FontWeight.w700,
@ -92,7 +157,7 @@ ThemeData getMobileThemeData(
letterSpacing: 0.16,
),
displayMedium: TextStyle(
color: Color(0xff2F3030),
color: mobileColorTheme.onBackground,
fontSize: 32,
fontWeight: FontWeight.w600,
height: 1.20,
@ -100,33 +165,46 @@ ThemeData getMobileThemeData(
),
// H1 Semi 26
displaySmall: TextStyle(
color: Color(0xFF2F3030),
fontSize: 26,
color: mobileColorTheme.onBackground,
fontWeight: FontWeight.w600,
height: 1.10,
letterSpacing: 0.13,
),
// body2 14 Regular
bodyMedium: TextStyle(
color: Colors.black,
color: mobileColorTheme.onBackground,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.20,
letterSpacing: 0.07,
),
// blue text button
// Trash empty title
labelLarge: TextStyle(
color: mobileColorTheme.onBackground,
fontSize: 22,
fontWeight: FontWeight.w600,
letterSpacing: -0.3,
),
// setting item title
labelMedium: TextStyle(
color: Color(0xFF2DA2F6),
fontSize: 14,
color: mobileColorTheme.onSurface,
fontSize: 18,
fontWeight: FontWeight.w500,
height: 1.20,
),
// setting group title
labelSmall: TextStyle(
color: mobileColorTheme.onBackground,
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: 0.05,
),
),
inputDecorationTheme: InputDecorationTheme(
contentPadding: const EdgeInsets.all(8),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
width: 2,
color: Color(0xFF2DA2F6), //primary 100
color: _primaryColor,
),
borderRadius: BorderRadius.all(Radius.circular(6)),
),
@ -138,13 +216,54 @@ ThemeData getMobileThemeData(
borderSide: BorderSide(color: mobileColorTheme.error),
borderRadius: const BorderRadius.all(Radius.circular(6)),
),
enabledBorder: const OutlineInputBorder(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xffBDC0C5), //caption
color: mobileColorTheme.outline,
),
borderRadius: BorderRadius.all(Radius.circular(6)),
borderRadius: const 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,
),
),
],
);
}

View File

@ -50,16 +50,8 @@ enum MobilePaneActionType {
onPressed: (context) {
final viewBloc = context.read<ViewBloc>();
final favoriteBloc = context.read<FavoriteBloc>();
showModalBottomSheet(
showMobileBottomSheet(
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: [

View File

@ -1,12 +1,32 @@
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/mobile/presentation/bottom_sheet/mobile_bottom_sheet_view_item_header.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:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
Future<void> showMobileBottomSheet({
required BuildContext context,
required WidgetBuilder builder,
}) async {
showModalBottomSheet(
context: context,
isScrollControlled: true,
enableDrag: true,
useSafeArea: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8.0),
topRight: Radius.circular(8.0),
),
),
builder: builder,
);
}
enum MobileBottomSheetType {
view,
@ -16,10 +36,12 @@ enum MobileBottomSheetType {
class MobileViewItemBottomSheet extends StatefulWidget {
const MobileViewItemBottomSheet({
super.key,
required this.view,
this.view,
this.defaultType = MobileBottomSheetType.view,
});
final ViewPB view;
final ViewPB? view;
final MobileBottomSheetType defaultType;
@override
State<MobileViewItemBottomSheet> createState() =>
@ -29,6 +51,18 @@ class MobileViewItemBottomSheet extends StatefulWidget {
class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
MobileBottomSheetType type = MobileBottomSheetType.view;
@override
initState() {
super.initState();
type = widget.defaultType;
if ([MobileBottomSheetType.view, MobileBottomSheetType.rename]
.contains(type)) {
assert(widget.view != null);
}
}
@override
Widget build(BuildContext context) {
return Column(
@ -48,30 +82,39 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
),
// header
MobileBottomSheetHeader(
showBackButton: type != MobileBottomSheetType.view,
view: widget.view,
onBack: () {
setState(() {
type = MobileBottomSheetType.view;
});
},
),
_buildHeader(),
const VSpace(8.0),
const Divider(),
// body
_buildBody(),
const VSpace(12.0),
const VSpace(24.0),
],
);
}
Widget _buildHeader() {
switch (type) {
case MobileBottomSheetType.view:
case MobileBottomSheetType.rename:
// header
return MobileViewItemBottomSheetHeader(
showBackButton: type != MobileBottomSheetType.view,
view: widget.view!,
onBack: () {
setState(() {
type = MobileBottomSheetType.view;
});
},
);
}
}
Widget _buildBody() {
switch (type) {
case MobileBottomSheetType.view:
return MobileViewItemBottomSheetBody(
isFavorite: widget.view.isFavorite,
isFavorite: widget.view!.isFavorite,
onAction: (action) {
switch (action) {
case MobileViewItemBottomSheetBodyAction.rename:
@ -81,34 +124,34 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
break;
case MobileViewItemBottomSheetBodyAction.duplicate:
context.read<ViewBloc>().add(const ViewEvent.duplicate());
Navigator.pop(context);
context.pop();
break;
case MobileViewItemBottomSheetBodyAction.share:
// unimplemented
Navigator.pop(context);
context.pop();
break;
case MobileViewItemBottomSheetBodyAction.delete:
context.read<ViewBloc>().add(const ViewEvent.delete());
Navigator.pop(context);
context.pop();
break;
case MobileViewItemBottomSheetBodyAction.addToFavorites:
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(widget.view));
Navigator.pop(context);
.add(FavoriteEvent.toggle(widget.view!));
context.pop();
break;
}
},
);
case MobileBottomSheetType.rename:
return MobileBottomSheetRenameWidget(
name: widget.view.name,
name: widget.view!.name,
onRename: (name) {
if (name != widget.view.name) {
if (name != widget.view!.name) {
context.read<ViewBloc>().add(ViewEvent.rename(name));
}
Navigator.pop(context);
context.pop();
},
);
}

View File

@ -2,8 +2,8 @@ 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({
class MobileViewItemBottomSheetHeader extends StatelessWidget {
const MobileViewItemBottomSheetHeader({
super.key,
required this.view,
required this.showBackButton,

View File

@ -1 +1,3 @@
export 'mobile_home_page.dart';
export 'mobile_home_setting_page.dart';
export 'mobile_home_trash_page.dart';

View File

@ -1,3 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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';
@ -7,7 +9,11 @@ import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_scr
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:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'home.dart';
class MobileHomeScreen extends StatelessWidget {
const MobileHomeScreen({super.key});
@ -87,6 +93,8 @@ class MobileHomePage extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Recent files
const MobileHomePageRecentFilesWidget(),
@ -101,15 +109,41 @@ class MobileHomePage extends StatelessWidget {
workspaceSetting: workspaceSetting,
),
),
const SizedBox(height: 8),
const _TrashButton(),
],
),
),
),
),
),
// TODO: Trash
],
);
}
}
class _TrashButton extends StatelessWidget {
const _TrashButton();
@override
Widget build(BuildContext context) {
// TODO(yijing): improve style UI later
return SizedBox(
width: double.infinity,
child: TextButton.icon(
onPressed: () {
context.push(MobileHomeTrashPage.routeName);
},
icon: FlowySvg(
FlowySvgs.m_delete_m,
color: Theme.of(context).colorScheme.onSurface,
),
label: Text(
LocaleKeys.trash_text.tr(),
style: Theme.of(context).textTheme.labelMedium,
),
style: const ButtonStyle(alignment: Alignment.centerLeft),
),
);
}
}

View File

@ -1,6 +1,11 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.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:go_router/go_router.dart';
class MobileHomePageHeader extends StatelessWidget {
const MobileHomePageHeader({
@ -12,6 +17,7 @@ class MobileHomePageHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// TODO: implement the details later.
return SizedBox(
height: 80,
@ -22,27 +28,64 @@ class MobileHomePageHeader extends StatelessWidget {
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,
)
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// TODO: replace with the real data
Row(
children: [
const FlowyText.medium(
'AppFlowy',
fontSize: 18,
),
// temporary placeholder for log out icon button
// needs to be replaced with workspace switcher and log out
IconButton(
onPressed: () => showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Log out'),
content:
const Text('Are you sure you want to log out?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () async {
await getIt<AuthService>().signOut();
runAppFlowy();
},
child: const Text('OK'),
),
],
),
),
icon: const Icon(
Icons.arrow_drop_down,
),
)
],
),
FlowyText.regular(
userProfile.email,
fontSize: 12,
color: theme.colorScheme.onSurface,
overflow: TextOverflow.ellipsis,
)
],
),
),
const Spacer(),
IconButton(
icon: const Icon(Icons.more_horiz_outlined),
onPressed: () {},
),
onPressed: () {
context.push(MobileHomeSettingPage.routeName);
},
icon: const FlowySvg(
FlowySvgs.m_setting_m,
),
)
],
),
);

View File

@ -1,16 +1,26 @@
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),
// TODO(yijing): replace by real data later
class MockRecentFile {
MockRecentFile({
required this.title,
});
final String title;
final String icon = '🐼';
final image = Image.asset(
'assets/images/app_flowy_abstract_cover_1.jpg',
fit: BoxFit.cover,
);
}
final recentFilesList = <MockRecentFile>[
MockRecentFile(title: 'Work out plan'),
MockRecentFile(title: 'Travel plan'),
MockRecentFile(title: 'Meeting notes'),
MockRecentFile(title: 'Recipes'),
MockRecentFile(title: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
];
class MobileHomePageRecentFilesWidget extends StatelessWidget {
@ -20,7 +30,7 @@ class MobileHomePageRecentFilesWidget extends StatelessWidget {
Widget build(BuildContext context) {
// TODO: implement the details later.
return SizedBox(
height: 160,
height: 168,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -33,20 +43,80 @@ class MobileHomePageRecentFilesWidget extends StatelessWidget {
),
Expanded(
child: ListView.separated(
separatorBuilder: (context, index) => const HSpace(20),
separatorBuilder: (context, index) => const HSpace(8),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
vertical: 8,
),
scrollDirection: Axis.horizontal,
itemCount: rainbowColors.length,
itemCount: recentFilesList.length,
itemBuilder: (context, index) {
return Container(
alignment: Alignment.center,
width: 144,
width: 120,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: rainbowColors[index],
color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.5),
),
),
child: Stack(
children: [
Align(
alignment: Alignment.topCenter,
child: SizedBox(
height: 60,
width: double.infinity,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
),
child: recentFilesList[index].image,
),
),
),
Align(
alignment: Alignment.centerLeft,
child: Container(
height: 32,
width: 32,
margin: const EdgeInsets.only(left: 8),
child: Text(
recentFilesList[index].icon,
style: const TextStyle(fontSize: 32),
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 32,
width: double.infinity,
margin: const EdgeInsets.only(
left: 8,
right: 8,
bottom: 8,
),
child: Text(
recentFilesList[index].title,
softWrap: true,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: Theme.of(context)
.colorScheme
.onBackground,
),
maxLines: 2,
),
),
),
],
),
);
},

View File

@ -0,0 +1,57 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
class MobileHomeSettingPage extends StatefulWidget {
const MobileHomeSettingPage({
super.key,
});
static const routeName = '/MobileHomeSettingPage';
@override
State<MobileHomeSettingPage> createState() => _MobileHomeSettingPageState();
}
class _MobileHomeSettingPageState extends State<MobileHomeSettingPage> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: getIt<AuthService>().getUser(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator.adaptive());
}
final userProfile = snapshot.data?.fold((error) => null, (userProfile) {
return userProfile;
});
return Scaffold(
appBar: AppBar(
title: Text(LocaleKeys.settings_title.tr()),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
PersonalInfoSettingGroup(
userProfile: userProfile,
),
// TODO(yijing): implement this along with Notification Page
const NotificationsSettingGroup(),
const AppearanceSettingGroup(),
const SupportSettingGroup(),
const AboutSettingGroup(),
],
),
),
),
);
}),
);
}
}

View File

@ -0,0 +1,185 @@
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:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart';
import 'package:appflowy/plugins/trash/application/prelude.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class MobileHomeTrashPage extends StatelessWidget {
const MobileHomeTrashPage({super.key});
static const routeName = "/MobileHomeTrashPage";
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<TrashBloc>()..add(const TrashEvent.initial()),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(
title: Text(LocaleKeys.trash_text.tr()),
elevation: 0,
actions: [
IconButton(
splashRadius: 20,
icon: const Icon(Icons.more_horiz),
onPressed: () {
showFlowyMobileBottomSheet(
context,
title: LocaleKeys.trash_mobile_actions.tr(),
builder: (_) => Row(
children: [
Expanded(
child: BottomSheetActionWidget(
svg: FlowySvgs.m_restore_m,
text: LocaleKeys.trash_restoreAll.tr(),
onTap: () {
context
..read<TrashBloc>()
.add(const TrashEvent.restoreAll())
..pop();
},
),
),
Expanded(
child: BottomSheetActionWidget(
svg: FlowySvgs.m_delete_m,
text: LocaleKeys.trash_deleteAll.tr(),
onTap: () {
context
..read<TrashBloc>()
.add(const TrashEvent.deleteAll())
..pop();
},
),
)
],
),
);
},
),
],
),
body: BlocBuilder<TrashBloc, TrashState>(
builder: (_, state) {
if (state.objects.isEmpty) {
return const _TrashEmptyPage();
}
return _DeletedFilesListView(state);
},
),
);
},
),
);
}
}
class _DeletedFilesListView extends StatelessWidget {
const _DeletedFilesListView(
this.state,
);
final TrashState state;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ListView.builder(
itemBuilder: (context, index) {
final object = state.objects[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
// TODO(Yijing): implement file type after TrashPB has file type
leading: FlowySvg(
FlowySvgs.documents_s,
size: const Size.square(24),
color: theme.colorScheme.onSurface,
),
title: Text(
object.name,
style: theme.textTheme.labelMedium
?.copyWith(color: theme.colorScheme.onBackground),
),
horizontalTitleGap: 0,
// TODO(yiing): needs improve by container/surface theme color
tileColor: theme.colorScheme.onSurface.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// TODO(yijing): extract icon button
IconButton(
splashRadius: 20,
icon: FlowySvg(
FlowySvgs.m_restore_m,
size: const Size.square(24),
color: theme.colorScheme.onSurface,
),
onPressed: () {
context
.read<TrashBloc>()
.add(TrashEvent.putback(object.id));
},
),
IconButton(
splashRadius: 20,
icon: FlowySvg(
FlowySvgs.m_delete_m,
size: const Size.square(24),
color: theme.colorScheme.onSurface,
),
onPressed: () {
context.read<TrashBloc>().add(TrashEvent.delete(object));
},
)
],
),
),
);
},
itemCount: state.objects.length,
);
}
}
class _TrashEmptyPage extends StatelessWidget {
const _TrashEmptyPage();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'🗑️',
style: TextStyle(fontSize: 40),
),
const SizedBox(height: 8),
Text(
LocaleKeys.trash_mobile_empty.tr(),
style: theme.textTheme.labelLarge,
),
const SizedBox(height: 4),
Text(
LocaleKeys.trash_mobile_emptyDescription.tr(),
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.hintColor,
),
),
],
),
);
}
}

View File

@ -23,10 +23,6 @@ class MobileBottomNavigationBar extends StatelessWidget {
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(
@ -46,13 +42,6 @@ class MobileBottomNavigationBar extends StatelessWidget {
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),

View File

@ -3,3 +3,4 @@ export 'editor/mobile_editor_screen.dart';
export 'home/home.dart';
export 'mobile_bottom_navigation_bar.dart';
export 'root_placeholder_page.dart';
export 'setting/setting.dart';

View File

@ -0,0 +1,3 @@
export 'about_setting_group.dart';
export 'privacy_policy_page.dart';
export 'user_agreement_page.dart';

View File

@ -0,0 +1,41 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../widgets/widgets.dart';
import 'about.dart';
class AboutSettingGroup extends StatelessWidget {
const AboutSettingGroup({
super.key,
});
@override
Widget build(BuildContext context) {
return MobileSettingGroup(
groupTitle: LocaleKeys.settings_mobile_about.tr(),
settingItemList: [
MobileSettingItem(
name: LocaleKeys.settings_mobile_privacyPolicy.tr(),
trailing: const Icon(
Icons.chevron_right,
),
onTap: () {
context.push(PrivacyPolicyPage.routeName);
},
),
MobileSettingItem(
name: LocaleKeys.settings_mobile_userAgreement.tr(),
trailing: const Icon(
Icons.chevron_right,
),
onTap: () {
context.push(UserAgreementPage.routeName);
},
),
],
showDivider: false,
);
}
}

View File

@ -0,0 +1,22 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
class PrivacyPolicyPage extends StatelessWidget {
const PrivacyPolicyPage({super.key});
static const routeName = '/PrivacyPolicyPage';
@override
Widget build(BuildContext context) {
// TODO(yijing): implement page
return Scaffold(
appBar: AppBar(
title: Text(LocaleKeys.settings_mobile_privacyPolicy.tr()),
),
body: const Center(
child: Text('🪜 Under construction'),
),
);
}
}

View File

@ -0,0 +1,22 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
class UserAgreementPage extends StatelessWidget {
const UserAgreementPage({super.key});
static const routeName = '/UserAgreementPage';
@override
Widget build(BuildContext context) {
// TODO(yijing): implement page
return Scaffold(
appBar: AppBar(
title: Text(LocaleKeys.settings_mobile_userAgreement.tr()),
),
body: const Center(
child: Text('🪜 Under construction'),
),
);
}
}

View File

@ -0,0 +1,110 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart';
import 'package:appflowy/util/theme_mode_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'setting.dart';
class AppearanceSettingGroup extends StatefulWidget {
const AppearanceSettingGroup({
super.key,
});
@override
State<AppearanceSettingGroup> createState() => _AppearanceSettingGroupState();
}
class _AppearanceSettingGroupState extends State<AppearanceSettingGroup> {
@override
Widget build(BuildContext context) {
return BlocSelector<AppearanceSettingsCubit, AppearanceSettingsState,
ThemeMode>(
selector: (state) {
return state.themeMode;
},
builder: (context, themeMode) {
final theme = Theme.of(context);
return MobileSettingGroup(
groupTitle: LocaleKeys.settings_menu_appearance.tr(),
settingItemList: [
MobileSettingItem(
name: LocaleKeys.settings_appearance_themeMode_label.tr(),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
themeMode.labelText,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface,
),
),
const Icon(Icons.chevron_right)
],
),
onTap: () {
showFlowyMobileBottomSheet(
context,
title: LocaleKeys.settings_appearance_themeMode_label.tr(),
builder: (_) {
return Column(
children: [
_ThemeModeRadioListTile(
title: LocaleKeys.settings_appearance_themeMode_system
.tr(),
value: ThemeMode.system,
),
_ThemeModeRadioListTile(
title: LocaleKeys.settings_appearance_themeMode_light
.tr(),
value: ThemeMode.light,
),
_ThemeModeRadioListTile(
title: LocaleKeys.settings_appearance_themeMode_dark
.tr(),
value: ThemeMode.dark,
),
],
);
},
);
},
),
],
);
},
);
}
}
class _ThemeModeRadioListTile extends StatelessWidget {
const _ThemeModeRadioListTile({
required this.title,
required this.value,
});
final String title;
final ThemeMode value;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return RadioListTile<ThemeMode>(
dense: true,
contentPadding: const EdgeInsets.fromLTRB(0, 0, 4, 0),
controlAffinity: ListTileControlAffinity.trailing,
title: Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface,
),
),
groupValue: context.read<AppearanceSettingsCubit>().state.themeMode,
value: value,
onChanged: (selectedThemeMode) {
if (selectedThemeMode == null) return;
context.read<AppearanceSettingsCubit>().setThemeMode(selectedThemeMode);
},
);
}
}

View File

@ -0,0 +1,42 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'widgets/widgets.dart';
class NotificationsSettingGroup extends StatefulWidget {
const NotificationsSettingGroup({
super.key,
});
@override
State<NotificationsSettingGroup> createState() =>
_NotificationsSettingGroupState();
}
class _NotificationsSettingGroupState extends State<NotificationsSettingGroup> {
// TODO(yijing):remove this after notification page is implemented
bool isPushNotificationOn = false;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return MobileSettingGroup(
groupTitle: LocaleKeys.notificationHub_title.tr(),
settingItemList: [
MobileSettingItem(
name: LocaleKeys.settings_mobile_pushNotifications.tr(),
trailing: Switch.adaptive(
activeColor: theme.colorScheme.primary,
value: isPushNotificationOn,
onChanged: (bool value) {
setState(() {
isPushNotificationOn = value;
});
},
),
),
],
);
}
}

View File

@ -0,0 +1,108 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class EditUsernameBottomSheet extends StatefulWidget {
const EditUsernameBottomSheet(
this.context, {
this.userName,
required this.onSubmitted,
super.key,
});
final BuildContext context;
final String? userName;
final void Function(String) onSubmitted;
@override
State<EditUsernameBottomSheet> createState() =>
_EditUsernameBottomSheetState();
}
class _EditUsernameBottomSheetState extends State<EditUsernameBottomSheet> {
late TextEditingController _textFieldController;
final _formKey = GlobalKey<FormState>();
@override
void initState() {
super.initState();
_textFieldController = TextEditingController(text: widget.userName);
}
@override
void dispose() {
_textFieldController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
void submitUserName() {
if (_formKey.currentState!.validate()) {
final value = _textFieldController.text;
widget.onSubmitted.call(value);
widget.context.pop();
}
}
return Padding(
padding: EdgeInsets.only(
top: 16,
right: 16,
left: 16,
bottom: MediaQuery.of(context).viewInsets.bottom + 32,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
LocaleKeys.settings_mobile_username.tr(),
style: theme.textTheme.labelSmall,
),
IconButton(
icon: Icon(
Icons.close,
color: theme.hintColor,
),
onPressed: () {
widget.context.pop();
},
)
],
),
const SizedBox(
height: 16,
),
Form(
key: _formKey,
child: TextFormField(
controller: _textFieldController,
keyboardType: TextInputType.text,
validator: (value) {
if (value == null || value.isEmpty) {
return LocaleKeys.settings_mobile_usernameEmptyError.tr();
}
return null;
},
onEditingComplete: submitUserName,
),
),
const SizedBox(
height: 16,
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: submitUserName,
child: Text(LocaleKeys.button_update.tr()),
),
),
],
),
);
}
}

View File

@ -0,0 +1,2 @@
export 'edit_username_bottom_sheet.dart';
export 'personal_info_setting_group.dart';

View File

@ -0,0 +1,72 @@
import 'package:appflowy/env/env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/user/prelude.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../widgets/widgets.dart';
import 'personal_info.dart';
class PersonalInfoSettingGroup extends StatelessWidget {
const PersonalInfoSettingGroup({
super.key,
required this.userProfile,
});
final UserProfilePB? userProfile;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return BlocProvider<SettingsUserViewBloc>(
create: (context) => getIt<SettingsUserViewBloc>(
param1: userProfile,
)..add(const SettingsUserEvent.initial()),
child: BlocSelector<SettingsUserViewBloc, SettingsUserState, String>(
selector: (state) => state.userProfile.name,
builder: (context, userName) {
return MobileSettingGroup(
groupTitle: LocaleKeys.settings_mobile_personalInfo.tr(),
settingItemList: [
MobileSettingItem(
name: userName,
subtitle: isCloudEnabled && userProfile != null
? Text(
userProfile!.email,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface,
),
)
: null,
trailing: const Icon(Icons.chevron_right),
onTap: () {
showModalBottomSheet<void>(
context: context,
// avoid bottom sheet overflow from resizing when keyboard appears
isScrollControlled: true,
builder: (_) {
return EditUsernameBottomSheet(
context,
userName: userName,
onSubmitted: (value) {
context.read<SettingsUserViewBloc>().add(
SettingsUserEvent.updateUserName(
value,
),
);
},
);
},
);
},
)
],
);
},
),
);
}
}

View File

@ -0,0 +1,6 @@
export 'personal_info/personal_info.dart';
export 'notifications_setting_group.dart';
export 'appearance_setting_group.dart';
export 'support_setting_group.dart';
export 'about/about.dart';
export 'widgets/widgets.dart';

View File

@ -0,0 +1,45 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'widgets/widgets.dart';
class SupportSettingGroup extends StatelessWidget {
const SupportSettingGroup({
super.key,
});
@override
Widget build(BuildContext context) {
return MobileSettingGroup(
groupTitle: LocaleKeys.settings_mobile_support.tr(),
settingItemList: [
// 'Help Center'
MobileSettingItem(
name: LocaleKeys.settings_mobile_joinDiscord.tr(),
trailing: const Icon(
Icons.chevron_right,
),
onTap: () => safeLaunchUrl('https://discord.gg/JucBXeU2FE'),
),
MobileSettingItem(
name: LocaleKeys.workspace_errorActions_reportIssue.tr(),
trailing: const Icon(
Icons.chevron_right,
),
onTap: () {
// TODO(yijing): get app version before release
const String version = 'Beta';
final String os = Platform.operatingSystem;
safeLaunchUrl(
'https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&projects=&template=bug_report.yaml&title=[Bug]%20Mobile:%20&version=$version&os=$os',
);
},
),
],
);
}
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'mobile_setting_item_widget.dart';
class MobileSettingGroup extends StatelessWidget {
const MobileSettingGroup({
required this.groupTitle,
required this.settingItemList,
this.showDivider = true,
super.key,
});
final String groupTitle;
final List<MobileSettingItem> settingItemList;
final bool showDivider;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8,
),
Text(
groupTitle,
style: theme.textTheme.labelSmall,
),
const SizedBox(
height: 12,
),
...settingItemList,
showDivider ? const Divider() : const SizedBox.shrink(),
],
);
}
}

View File

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
class MobileSettingItem extends StatelessWidget {
const MobileSettingItem({
super.key,
required this.name,
this.subtitle,
required this.trailing,
this.onTap,
});
final String name;
final Widget? subtitle;
final Widget trailing;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: ListTile(
title: Text(
name,
style: theme.textTheme.labelMedium,
),
subtitle: subtitle,
trailing: trailing,
onTap: onTap,
visualDensity: VisualDensity.compact,
),
);
}
}

View File

@ -0,0 +1,2 @@
export 'mobile_setting_group_widget.dart';
export 'mobile_setting_item_widget.dart';

View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
Future<T?> showFlowyMobileBottomSheet<T>(
BuildContext context, {
required String title,
required Widget Function(BuildContext) builder,
}) async {
return showModalBottomSheet(
context: context,
builder: (context) => Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_BottomSheetTitle(title),
const SizedBox(
height: 16,
),
builder(context),
],
),
),
);
}
class _BottomSheetTitle extends StatelessWidget {
const _BottomSheetTitle(this.title);
final String title;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: theme.textTheme.labelSmall,
),
IconButton(
icon: Icon(
Icons.close,
color: theme.hintColor,
),
onPressed: () {
context.pop();
},
)
],
);
}
}

View File

@ -438,11 +438,11 @@ class DeleteImageAlertDialog extends StatelessWidget {
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text(LocaleKeys.button_Cancel).tr(),
child: const Text(LocaleKeys.button_cancel).tr(),
),
TextButton(
onPressed: onSubmit,
child: const Text(LocaleKeys.button_OK).tr(),
child: const Text(LocaleKeys.button_ok).tr(),
),
],
);

View File

@ -142,7 +142,7 @@ class _MathInputTextFieldState extends State<MathInputTextField> {
),
const HSpace(4.0),
FlowyButton(
text: FlowyText(LocaleKeys.button_Done.tr()),
text: FlowyText(LocaleKeys.button_done.tr()),
useIntrinsicWidth: true,
onTap: () => widget.onSubmit(textEditingController.text),
),

View File

@ -188,11 +188,11 @@ class _MathEquationBlockComponentWidgetState
),
actions: [
SecondaryTextButton(
LocaleKeys.button_Cancel.tr(),
LocaleKeys.button_cancel.tr(),
onPressed: () => dismiss(context),
),
PrimaryTextButton(
LocaleKeys.button_Done.tr(),
LocaleKeys.button_done.tr(),
onPressed: () => updateMathEquation(controller.text, context),
),
],

View File

@ -500,7 +500,7 @@ class AutoCompletionInputFooter extends StatelessWidget {
),
const Space(10, 0),
SecondaryTextButton(
LocaleKeys.button_Cancel.tr(),
LocaleKeys.button_cancel.tr(),
onPressed: onExit,
),
Expanded(

View File

@ -20,7 +20,7 @@ class DiscardDialog extends StatelessWidget {
return NavigatorOkCancelDialog(
message: LocaleKeys.document_plugins_discardResponse.tr(),
okTitle: LocaleKeys.button_discard.tr(),
cancelTitle: LocaleKeys.button_Cancel.tr(),
cancelTitle: LocaleKeys.button_cancel.tr(),
onOkPressed: onConfirm,
onCancelPressed: onCancel,
);

View File

@ -326,7 +326,7 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
TextSpan(
children: [
TextSpan(
text: LocaleKeys.button_Cancel.tr(),
text: LocaleKeys.button_cancel.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
],

View File

@ -30,11 +30,23 @@ GoRouter generateRouter(Widget child) {
if (!PlatformExtension.isMobile) _desktopHomeScreenRoute(),
// Mobile only
if (PlatformExtension.isMobile) ...[
// settings
_mobileHomeSettingPageRoute(),
_mobileSettingPrivacyPolicyPageRoute(),
_mobileSettingUserAgreementPageRoute(),
// view page
_mobileEditorScreenRoute(),
_mobileGridScreenRoute(),
_mobileBoardScreenRoute(),
_mobileCalendarScreenRoute(),
// home
// MobileHomeSettingPage is outside the bottom navigation bar, thus it is not in the StatefulShellRoute.
_mobileHomeScreenWithNavigationBarRoute(),
// trash
_mobileHomeTrashPageRoute(),
],
// Desktop and Mobile
@ -92,7 +104,6 @@ StatefulShellRoute _mobileHomeScreenWithNavigationBarRoute() {
),
],
),
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
@ -103,32 +114,6 @@ StatefulShellRoute _mobileHomeScreenWithNavigationBarRoute() {
),
],
),
// 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(
@ -175,6 +160,46 @@ StatefulShellRoute _mobileHomeScreenWithNavigationBarRoute() {
);
}
GoRoute _mobileHomeSettingPageRoute() {
return GoRoute(
parentNavigatorKey: AppGlobals.rootNavKey,
path: MobileHomeSettingPage.routeName,
pageBuilder: (context, state) {
return const MaterialPage(child: MobileHomeSettingPage());
},
);
}
GoRoute _mobileSettingPrivacyPolicyPageRoute() {
return GoRoute(
parentNavigatorKey: AppGlobals.rootNavKey,
path: PrivacyPolicyPage.routeName,
pageBuilder: (context, state) {
return const MaterialPage(child: PrivacyPolicyPage());
},
);
}
GoRoute _mobileSettingUserAgreementPageRoute() {
return GoRoute(
parentNavigatorKey: AppGlobals.rootNavKey,
path: UserAgreementPage.routeName,
pageBuilder: (context, state) {
return const MaterialPage(child: UserAgreementPage());
},
);
}
GoRoute _mobileHomeTrashPageRoute() {
return GoRoute(
parentNavigatorKey: AppGlobals.rootNavKey,
path: MobileHomeTrashPage.routeName,
pageBuilder: (context, state) {
return const MaterialPage(child: MobileHomeTrashPage());
},
);
}
GoRoute _desktopHomeScreenRoute() {
return GoRoute(
path: DesktopHomeScreen.routeName,

View File

@ -58,7 +58,9 @@ class MobileSignInScreen extends StatelessWidget {
// TODO(yijing): confirm the subtitle before release app
Text(
'You are in charge of your data and customizations.',
style: style.textTheme.bodyMedium,
style: style.textTheme.bodyMedium?.copyWith(
color: style.colorScheme.onSecondary,
),
textAlign: TextAlign.center,
),
const Spacer(
@ -74,7 +76,9 @@ class MobileSignInScreen extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
LocaleKeys.signIn_or.tr(),
style: style.textTheme.bodyMedium,
style: style.textTheme.bodyMedium?.copyWith(
color: style.colorScheme.onSecondary,
),
),
),
const Expanded(child: Divider()),

View File

@ -101,7 +101,10 @@ class _ThirdPartySignInButton extends StatelessWidget {
label: Container(
padding: const EdgeInsets.only(left: 4),
alignment: Alignment.centerLeft,
child: Text(labelText),
child: Text(
labelText,
style: Theme.of(context).textTheme.titleSmall,
),
),
onPressed: onPressed,
),

View File

@ -0,0 +1,18 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
extension LabelTextPhrasing on ThemeMode {
String get labelText {
switch (this) {
case (ThemeMode.light):
return LocaleKeys.settings_appearance_themeMode_light.tr();
case (ThemeMode.dark):
return LocaleKeys.settings_appearance_themeMode_dark.tr();
case (ThemeMode.system):
return LocaleKeys.settings_appearance_themeMode_system.tr();
default:
return "";
}
}
}

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:appflowy/mobile/application/mobile_theme_data.dart';
import 'package:appflowy/user/application/user_settings_service.dart';
import 'package:appflowy/util/platform_extension.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart';
@ -404,204 +405,27 @@ 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.
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,
),
),
],
final mobileThemeData = getMobileThemeData(
brightness,
theme,
fontFamily,
monospaceFontFamily,
);
return mobileThemeData;
}
// Due to Desktop version has multiple themes, it relies on the current theme to build the ThemeData
final desktopThemeData = ThemeData(
brightness: brightness,
dialogBackgroundColor: theme.surface,
textTheme: _getTextTheme(fontFamily: fontFamily, fontColor: theme.text),
textTheme: getTextTheme(fontFamily: fontFamily, fontColor: theme.text),
textSelectionTheme: TextSelectionThemeData(
cursorColor: theme.main2,
selectionHandleColor: theme.main2,
),
iconTheme: IconThemeData(color: theme.icon),
tooltipTheme: TooltipThemeData(
textStyle: _getFontStyle(
textStyle: getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s11,
fontWeight: FontWeight.w400,
@ -664,18 +488,18 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
toggleButtonBGColor: theme.toggleButtonBGColor,
calendarWeekendBGColor: theme.calendarWeekendBGColor,
gridRowCountColor: theme.gridRowCountColor,
code: _getFontStyle(
code: getFontStyle(
fontFamily: monospaceFontFamily,
fontColor: theme.shader3,
),
callout: _getFontStyle(
callout: getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s11,
fontColor: theme.shader3,
),
calloutBGColor: theme.hoverBG3,
tableCellBGColor: theme.surface,
caption: _getFontStyle(
caption: getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s11,
fontWeight: FontWeight.w400,
@ -686,90 +510,90 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
);
return desktopThemeData;
}
}
TextStyle _getFontStyle({
required String fontFamily,
double? fontSize,
FontWeight? fontWeight,
Color? fontColor,
double? letterSpacing,
double? lineHeight,
}) {
try {
return GoogleFonts.getFont(
fontFamily,
fontSize: fontSize ?? FontSizes.s12,
color: fontColor,
fontWeight: fontWeight ?? FontWeight.w500,
letterSpacing: (fontSize ?? FontSizes.s12) * (letterSpacing ?? 0.005),
height: lineHeight,
);
} catch (e) {
return TextStyle(
fontFamily: fontFamily,
fontSize: fontSize ?? FontSizes.s12,
color: fontColor,
fontWeight: fontWeight ?? FontWeight.w500,
fontFamilyFallback: const ["Noto Color Emoji"],
letterSpacing: (fontSize ?? FontSizes.s12) * (letterSpacing ?? 0.005),
height: lineHeight,
);
}
}
TextTheme _getTextTheme({
required String fontFamily,
required Color fontColor,
}) {
return TextTheme(
displayLarge: _getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s32,
fontColor: fontColor,
fontWeight: FontWeight.w600,
lineHeight: 42.0,
), // h2
displayMedium: _getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s24,
fontColor: fontColor,
fontWeight: FontWeight.w600,
lineHeight: 34.0,
), // h3
displaySmall: _getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s20,
fontColor: fontColor,
fontWeight: FontWeight.w600,
lineHeight: 28.0,
), // h4
titleLarge: _getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s18,
fontColor: fontColor,
fontWeight: FontWeight.w600,
), // title
titleMedium: _getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s16,
fontColor: fontColor,
fontWeight: FontWeight.w600,
), // heading
titleSmall: _getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s14,
fontColor: fontColor,
fontWeight: FontWeight.w600,
), // subheading
bodyMedium: _getFontStyle(
fontFamily: fontFamily,
fontColor: fontColor,
), // body-regular
bodySmall: _getFontStyle(
fontFamily: fontFamily,
fontColor: fontColor,
fontWeight: FontWeight.w400,
), // body-thin
TextStyle getFontStyle({
required String fontFamily,
double? fontSize,
FontWeight? fontWeight,
Color? fontColor,
double? letterSpacing,
double? lineHeight,
}) {
try {
return GoogleFonts.getFont(
fontFamily,
fontSize: fontSize ?? FontSizes.s12,
color: fontColor,
fontWeight: fontWeight ?? FontWeight.w500,
letterSpacing: (fontSize ?? FontSizes.s12) * (letterSpacing ?? 0.005),
height: lineHeight,
);
} catch (e) {
return TextStyle(
fontFamily: fontFamily,
fontSize: fontSize ?? FontSizes.s12,
color: fontColor,
fontWeight: fontWeight ?? FontWeight.w500,
fontFamilyFallback: const ["Noto Color Emoji"],
letterSpacing: (fontSize ?? FontSizes.s12) * (letterSpacing ?? 0.005),
height: lineHeight,
);
}
}
TextTheme getTextTheme({
required String fontFamily,
required Color fontColor,
}) {
return TextTheme(
displayLarge: getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s32,
fontColor: fontColor,
fontWeight: FontWeight.w600,
lineHeight: 42.0,
), // h2
displayMedium: getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s24,
fontColor: fontColor,
fontWeight: FontWeight.w600,
lineHeight: 34.0,
), // h3
displaySmall: getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s20,
fontColor: fontColor,
fontWeight: FontWeight.w600,
lineHeight: 28.0,
), // h4
titleLarge: getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s18,
fontColor: fontColor,
fontWeight: FontWeight.w600,
), // title
titleMedium: getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s16,
fontColor: fontColor,
fontWeight: FontWeight.w600,
), // heading
titleSmall: getFontStyle(
fontFamily: fontFamily,
fontSize: FontSizes.s14,
fontColor: fontColor,
fontWeight: FontWeight.w600,
), // subheading
bodyMedium: getFontStyle(
fontFamily: fontFamily,
fontColor: fontColor,
), // body-regular
bodySmall: getFontStyle(
fontFamily: fontFamily,
fontColor: fontColor,
fontWeight: FontWeight.w400,
), // body-thin
);
}

View File

@ -52,7 +52,7 @@ void showSnackBarMessage(
action: !showCancel
? null
: SnackBarAction(
label: LocaleKeys.button_Cancel.tr(),
label: LocaleKeys.button_cancel.tr(),
textColor: Theme.of(context).colorScheme.onSurface,
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/util/theme_mode_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
@ -23,7 +24,7 @@ class BrightnessSetting extends StatelessWidget {
onResetRequested: context.read<AppearanceSettingsCubit>().resetThemeMode,
trailing: [
ThemeValueDropDown(
currentValue: _themeModeLabelText(currentThemeMode),
currentValue: currentThemeMode.labelText,
popupBuilder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -47,7 +48,7 @@ class BrightnessSetting extends StatelessWidget {
return SizedBox(
height: 32,
child: FlowyButton(
text: FlowyText.medium(_themeModeLabelText(themeMode)),
text: FlowyText.medium(themeMode.labelText),
rightIcon: currentThemeMode == themeMode
? const FlowySvg(
FlowySvgs.check_s,
@ -62,17 +63,4 @@ class BrightnessSetting extends StatelessWidget {
),
);
}
String _themeModeLabelText(ThemeMode themeMode) {
switch (themeMode) {
case (ThemeMode.light):
return LocaleKeys.settings_appearance_themeMode_light.tr();
case (ThemeMode.dark):
return LocaleKeys.settings_appearance_themeMode_dark.tr();
case (ThemeMode.system):
return LocaleKeys.settings_appearance_themeMode_system.tr();
default:
return "";
}
}
}

View File

@ -91,14 +91,14 @@ class _FileExporterWidgetState extends State<FileExporterWidget> {
children: [
const Spacer(),
FlowyTextButton(
LocaleKeys.button_Cancel.tr(),
LocaleKeys.button_cancel.tr(),
onPressed: () {
Navigator.of(context).pop();
},
),
const HSpace(8),
FlowyTextButton(
LocaleKeys.button_OK.tr(),
LocaleKeys.button_ok.tr(),
onPressed: () async {
await getIt<FilePickerService>()
.getDirectoryPath()

View File

@ -36,7 +36,7 @@ class ThemeConfirmDeleteDialog extends StatelessWidget {
width: ThemeUploadWidget.buttonSize.width,
child: FlowyButton(
text: FlowyText.semibold(
LocaleKeys.button_OK.tr(),
LocaleKeys.button_ok.tr(),
fontSize: ThemeUploadWidget.buttonFontSize,
),
onTap: () => onConfirm(context),
@ -46,7 +46,7 @@ class ThemeConfirmDeleteDialog extends StatelessWidget {
width: ThemeUploadWidget.buttonSize.width,
child: FlowyButton(
text: FlowyText.semibold(
LocaleKeys.button_Cancel.tr(),
LocaleKeys.button_cancel.tr(),
fontSize: ThemeUploadWidget.buttonFontSize,
),
onTap: () => onCancel(context),

View File

@ -245,14 +245,14 @@ class OkCancelButton extends StatelessWidget {
children: <Widget>[
if (onCancelPressed != null)
SecondaryTextButton(
cancelTitle ?? LocaleKeys.button_Cancel.tr(),
cancelTitle ?? LocaleKeys.button_cancel.tr(),
onPressed: onCancelPressed,
mode: mode,
),
HSpace(Insets.m),
if (onOkPressed != null)
PrimaryTextButton(
okTitle ?? LocaleKeys.button_OK.tr(),
okTitle ?? LocaleKeys.button_ok.tr(),
onPressed: onOkPressed,
mode: mode,
),

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.9949 18.3967L2.2759 13.7097C1.8859 13.3187 1.8859 12.6627 2.2759 12.2717L6.9949 7.58471L8.4009 8.99072L5.43189 11.9907L16.9639 11.9597C18.0879 11.9597 18.9949 11.0777 18.9949 9.99072V4.99072C18.9949 4.43872 19.4429 3.99072 19.9949 3.99072C20.5469 3.99072 20.9949 4.43872 20.9949 4.99072V9.99072C20.9949 12.1947 19.1779 13.9597 16.9639 13.9597L5.43189 13.9907L8.4009 16.9907L6.9949 18.3967Z" fill="#2F3030"/>
</svg>

After

Width:  |  Height:  |  Size: 523 B

View File

@ -57,7 +57,7 @@
"notFoundError": "Workspace not found",
"failedToLoad": "Something went wrong! Failed to load the workspace. Try to close any open instance of AppFlowy and try again.",
"errorActions": {
"reportIssue": "Report issue",
"reportIssue": "Report an issue",
"reachOut": "Reach out on Discord"
}
},
@ -116,6 +116,11 @@
"confirmRestoreAll": {
"title": "Are you sure to restore all pages in Trash?",
"caption": "This action cannot be undone."
},
"mobile": {
"actions": "Trash Actions",
"empty": "Trash Bin is Empty",
"emptyDescription": "You don't have any deleted file"
}
},
"deletePagePrompt": {
@ -194,9 +199,9 @@
"editContact": "Edit Contact"
},
"button": {
"OK": "OK",
"Done": "Done",
"Cancel": "Cancel",
"ok": "OK",
"done": "Done",
"cancel": "Cancel",
"signIn": "Sign In",
"signOut": "Sign Out",
"complete": "Complete",
@ -212,8 +217,8 @@
"edit": "Edit",
"delete": "Delete",
"duplicate": "Duplicate",
"done": "Done",
"putback": "Put Back",
"update": "Update",
"share": "Share",
"removeFromFavorites": "Remove from favorites",
"addToFavorites": "Add to favorites",
@ -378,6 +383,17 @@
"resetToDefault": "Reset to default keybindings",
"couldNotLoadErrorMsg": "Could not load shortcuts, Try again",
"couldNotSaveErrorMsg": "Could not save shortcuts, Try again"
},
"mobile": {
"personalInfo": "Personal Information",
"username": "Username",
"usernameEmptyError": "Username cannot be empty",
"about": "About",
"pushNotifications": "Push Notifications",
"support": "Support",
"joinDiscord": "Join us in Discord",
"privacyPolicy": "Privacy Policy",
"userAgreement": "User Agreement"
}
},
"grid": {