Merge branch 'main' into main

This commit is contained in:
Gustavo Sanchez 2022-02-05 16:35:25 -04:00 committed by GitHub
commit e1f6e483e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 733 additions and 210 deletions

12
.github/workflows/commitlint.yml vendored Normal file
View File

@ -0,0 +1,12 @@
name: Lint Commit Messages
on: [pull_request, push]
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v4

3
.gitignore vendored
View File

@ -15,3 +15,6 @@ Cargo.lock
.idea/
**/temp/**
.ruby-version
package-lock.json
yarn.lock
node_modules

4
.husky/commit-msg Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit

32
Makefile.toml Normal file
View File

@ -0,0 +1,32 @@
[tasks.install-commitlint]
mac_alias = "install-commitlint-macos"
windows_alias = "install-commitlint-windows"
linux_alias = "install-commitlint-linux"
[tasks.install-commitlint-macos]
script = [
"""
brew install npm
yarn install
yarn husky install
""",
]
script_runner = "@shell"
[tasks.install-commitlint-windows]
script = [
"""
echo "WIP"
""",
]
script_runner = "@duckscript"
[tasks.install-commitlint-linux]
script = [
"""
sudo apt install nodejs
yarn install
yarn husky install
""",
]
script_runner = "@duckscript"

20
commitlint.config.js Normal file
View File

@ -0,0 +1,20 @@
// module.exports = {extends: ['@commitlint/config-conventional']}
module.exports = {
rules: {
'type-enum': [2, 'always', ['feat', 'refactor', 'style', 'fix', 'ci']],
'body-leading-blank': [1, 'always'],
'body-max-line-length': [2, 'always', 100],
'footer-leading-blank': [1, 'always'],
'footer-max-line-length': [2, 'always', 100],
'header-max-length': [2, 'always', 100],
'subject-case': [
2,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'type-case': [2, 'always', 'lower-case'],
'body-case': [2, 'never', []]
},
};

View File

@ -0,0 +1,146 @@
{
"appName": "AppFlowy",
"defaultUsername": "Я",
"welcomeText": "Добро пожаловать в @:appName",
"githubStarText": "Поставить звезду на GitHub",
"subscribeNewsletterText": "Подписаться на рассылку",
"letsGoButtonText": "Начнём",
"title": "Заголовок",
"signUp": {
"buttonText": "Зарегистрироваться",
"title": "Регистрация в @:appName",
"getStartedText": "Начать",
"emptyPasswordError": "Пароль не может быть пустым",
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
"unmatchedPasswordError": "Пароли не совпадают",
"alreadyHaveAnAccount": "Уже есть аккаунт?",
"emailHint": "Электронная почта",
"passwordHint": "Пароль",
"repeatPasswordHint": "Повторите пароль"
},
"signIn": {
"loginTitle": "Войти в @:appName",
"loginButtonText": "Войти",
"buttonText": "Авторизация",
"forgotPassword": "Забыли пароль?",
"emailHint": "Электронная почта",
"passwordHint": "Пароль",
"dontHaveAnAccount": "Нет аккаунта?",
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
"unmatchedPasswordError": "Пароли не совпадают"
},
"workspace": {
"create": "Создать рабочее пространство",
"hint": "рабочее пространство",
"notFoundError": "Нет такого рабочего пространства"
},
"shareAction": {
"buttonText": "Поделиться",
"workInProgress": "В разработке",
"markdown": "Markdown",
"copyLink": "Скопировать ссылку"
},
"disclosureAction": {
"rename": "Переименовать",
"delete": "Удалить",
"duplicate": "Дублировать"
},
"blankPageTitle": "Пустая страница",
"newPageText": "Новая страница",
"trash": {
"text": "Корзина",
"restoreAll": "Восстановить всё",
"deleteAll": "Очистить",
"pageHeader": {
"fileName": "Имя",
"lastModified": "Последнее изменение",
"created": "Создан"
}
},
"deletePagePrompt": {
"text": "Эта страница в Корзине",
"restore": "Восстановить страницу",
"deletePermanent": "Удалить навсегда"
},
"dialogCreatePageNameHint": "Имя",
"questionBubble": {
"whatsNew": "Что нового?",
"help": "Помощь",
"debug": {
"name": "Отладочная информация",
"success": "Скопировано в буфер обмена!",
"fail": "Не получилось скопировать"
}
},
"menuAppHeader": {
"addPageTooltip": "Быстро добавить новую страницу",
"defaultNewPageName": "Без заголовка",
"renameDialog": "Переименовать"
},
"toolbar": {
"undo": "Отменить",
"redo": "Повторить",
"bold": "Жирный",
"italic": "Курсив",
"underline": "Подчёркнутый",
"strike": "Зачёркнутый",
"numList": "Нумерованный список",
"bulletList": "Маркированный список",
"checkList": "Список To-Do",
"inlineCode": "Код",
"quote": "Цитата",
"header": "Заголовок",
"highlight": "Выделение"
},
"tooltip": {
"lightMode": "Переключиться в светлую тему",
"darkMode": "Переключиться в тёмную тему"
},
"contactsPage": {
"title": "Контакты",
"whatsHappening": "Что происходит на этой неделе?",
"addContact": "Новый контакт",
"editContact": "Редактировать"
},
"button": {
"OK": "OK",
"Cancel": "Отмена",
"signIn": "Войти",
"signOut": "Выйти",
"complete": "Завершить",
"save": "Сохранить"
},
"label": {
"welcome": "Добро пожаловать!",
"firstName": "Имя",
"middleName": "Отчество",
"lastName": "Фамилия",
"stepX": "Этап {X}"
},
"oAuth": {
"err": {
"failedTitle": "Ошибка подключения к аккаунту.",
"failedMsg": "Убедитесь, что вы завершили вход в своём браузере."
},
"google": {
"title": "Вход через Google",
"instruction1": "Чтобы импортировать ваши Google Контакты, вам нужно будет авторизовать приложение через браузер.",
"instruction2": "Скопируйте этот код в буфер обмена (нажав кнопку или выделив текст):",
"instruction3": "Пройдите по ссылке и введите этот код:",
"instruction4": "Нажмите на кнопку, когда завершите вход:"
}
},
"settings": {
"title": "Настройки",
"menu": {
"appearance": "Внешнией вид",
"language": "Язык",
"open": "Открыть настройки"
},
"appearance": {
"lightLabel": "Светлая тема",
"darkLabel": "Тёмная тема"
}
}
}

View File

@ -2,7 +2,6 @@ import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/user/infrastructure/repos/user_setting_repo.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/language.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@ -29,10 +28,17 @@ class AppWidgetTask extends LaunchTask {
() {
runApp(
EasyLocalization(
supportedLocales: const [Locale('en'), Locale('zh', 'CN'), Locale('it', 'IT'), Locale('fr', 'CA'), Locale('es', 'VE')],
path: 'assets/translations',
fallbackLocale: const Locale('en'),
child: app),
supportedLocales: const [
Locale('en'),
Locale('zh', 'CN'),
Locale('it', 'IT'),
Locale('fr', 'CA'),
Locale('es', 'VE'),
],
path: 'assets/translations',
fallbackLocale: const Locale('en'),
child: app,
),
);
},
blocObserver: ApplicationBlocObserver(),
@ -63,14 +69,14 @@ class ApplicationWidget extends StatelessWidget {
AppTheme theme = context.select<AppearanceSettingModel, AppTheme>(
(value) => value.theme,
);
AppLanguage language = context.select<AppearanceSettingModel, AppLanguage>(
(value) => value.language,
Locale locale = context.select<AppearanceSettingModel, Locale>(
(value) => value.locale,
);
return MultiProvider(
providers: [
Provider.value(value: theme),
Provider.value(value: language),
Provider.value(value: locale),
],
builder: (context, _) {
return MaterialApp(
@ -79,7 +85,7 @@ class ApplicationWidget extends StatelessWidget {
theme: theme.themeData,
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: localeFromLanguageName(language),
locale: locale,
navigatorKey: AppGlobals.rootNavKey,
home: child,
);

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/user/infrastructure/repos/user_setting_repo.dart';
import 'package:equatable/equatable.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra/language.dart';
import 'package:flowy_log/flowy_log.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_setting.pb.dart';
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
@ -10,15 +10,15 @@ import 'package:async/async.dart';
class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
AppearanceSettings setting;
AppTheme _theme;
AppLanguage _language;
Locale _locale;
CancelableOperation? _saveOperation;
AppearanceSettingModel(this.setting)
: _theme = AppTheme.fromName(name: setting.theme),
_language = languageFromString(setting.language);
_locale = Locale(setting.locale.languageCode, setting.locale.countryCode);
AppTheme get theme => _theme;
AppLanguage get language => _language;
Locale get locale => _locale;
Future<void> save() async {
_saveOperation?.cancel;
@ -45,13 +45,18 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
}
}
void setLanguage(BuildContext context, AppLanguage language) {
String languageString = stringFromLanguage(language);
void setLocale(BuildContext context, Locale newLocale) {
if (_locale != newLocale) {
if (!context.supportedLocales.contains(newLocale)) {
Log.error("Unsupported locale: $newLocale");
newLocale = const Locale('en');
Log.debug("Fallback to locale: $newLocale");
}
if (setting.language != languageString) {
context.setLocale(localeFromLanguageName(language));
_language = language;
setting.language = languageString;
context.setLocale(newLocale);
_locale = newLocale;
setting.locale.languageCode = _locale.languageCode;
setting.locale.countryCode = _locale.countryCode ?? "";
notifyListeners();
save();
}
@ -62,8 +67,7 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
setting.resetAsDefault = false;
save();
final language = languageFromLocale(context.deviceLocale);
setLanguage(context, language);
setLocale(context, context.deviceLocale);
}
}
}

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
@ -52,6 +53,8 @@ class FlowyNavigation extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return ChangeNotifierProxyProvider<HomeStackNotifier, NavigationNotifier>(
create: (_) {
final notifier = Provider.of<HomeStackNotifier>(context, listen: false);
@ -65,7 +68,7 @@ class FlowyNavigation extends StatelessWidget {
child: Row(children: [
Selector<NavigationNotifier, PublishNotifier<bool>>(
selector: (context, notifier) => notifier.collapasedNotifier,
builder: (ctx, collapsedNotifier, child) => _renderCollapse(ctx, collapsedNotifier)),
builder: (ctx, collapsedNotifier, child) => _renderCollapse(ctx, collapsedNotifier, theme)),
Selector<NavigationNotifier, List<NavigationItem>>(
selector: (context, notifier) => notifier.navigationItems,
builder: (ctx, items, child) => Expanded(
@ -80,7 +83,7 @@ class FlowyNavigation extends StatelessWidget {
);
}
Widget _renderCollapse(BuildContext context, PublishNotifier<bool> collapsedNotifier) {
Widget _renderCollapse(BuildContext context, PublishNotifier<bool> collapsedNotifier, AppTheme theme) {
return ChangeNotifierProvider.value(
value: collapsedNotifier,
child: Consumer(
@ -94,7 +97,7 @@ class FlowyNavigation extends StatelessWidget {
notifier.value = false;
},
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
icon: svg("home/hide_menu"),
icon: svg("home/hide_menu", color: theme.iconColor),
),
);
} else {

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:flutter/foundation.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flowy_infra/language.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -30,18 +30,18 @@ class LanguageSelectorDropdown extends StatefulWidget {
class _LanguageSelectorDropdownState extends State<LanguageSelectorDropdown> {
@override
Widget build(BuildContext context) {
return DropdownButton<AppLanguage>(
value: context.read<AppearanceSettingModel>().language,
return DropdownButton<Locale>(
value: context.read<AppearanceSettingModel>().locale,
onChanged: (val) {
setState(() {
context.read<AppearanceSettingModel>().setLanguage(context, val!);
context.read<AppearanceSettingModel>().setLocale(context, val!);
});
},
autofocus: true,
items: AppLanguage.values.map((language) {
return DropdownMenuItem<AppLanguage>(
value: language,
child: Text(describeEnum(language)),
items: EasyLocalization.of(context)!.supportedLocales.map((locale) {
return DropdownMenuItem<Locale>(
value: locale,
child: Text(languageFromLocale(locale)),
);
}).toList(),
);

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/application/doc/doc_bloc.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
@ -7,6 +8,7 @@ import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'styles.dart';
import 'widget/banner.dart';
@ -119,8 +121,11 @@ class _DocPageState extends State<DocPage> {
}
Widget _renderToolbar(quill.QuillController controller) {
return EditorToolbar.basic(
controller: controller,
return ChangeNotifierProvider.value(
value: Provider.of<AppearanceSettingModel>(context, listen: true),
child: EditorToolbar.basic(
controller: controller,
),
);
}

View File

@ -7,7 +7,6 @@ import 'package:app_flowy/workspace/infrastructure/repos/view_repo.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/language.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -175,8 +174,8 @@ class DocumentShareButton extends StatelessWidget {
builder: (context, state) {
return ChangeNotifierProvider.value(
value: Provider.of<AppearanceSettingModel>(context, listen: true),
child: Selector<AppearanceSettingModel, AppLanguage>(
selector: (ctx, notifier) => notifier.language,
child: Selector<AppearanceSettingModel, Locale>(
selector: (ctx, notifier) => notifier.locale,
builder: (ctx, _, child) => ConstrainedBox(
constraints: const BoxConstraints.expand(
height: 30,

View File

@ -26,7 +26,7 @@ class MenuAppHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final theme = context.read<AppTheme>();
return SizedBox(
height: MenuAppSizes.headerHeight,
child: Row(

View File

@ -1,3 +1,4 @@
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/header/header.dart';
import 'package:expandable/expandable.dart';
@ -79,7 +80,10 @@ class _MenuAppState extends State<MenuApp> {
iconPadding: EdgeInsets.zero,
hasIcon: false,
),
header: MenuAppHeader(widget.app),
header: ChangeNotifierProvider.value(
value: Provider.of<AppearanceSettingModel>(context, listen: true),
child: MenuAppHeader(widget.app),
),
expanded: _renderViewSection(notifier),
collapsed: const SizedBox(),
),

View File

@ -5,7 +5,6 @@ import 'package:app_flowy/workspace/presentation/stack_page/trash/trash_page.dar
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/language.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
@ -43,8 +42,8 @@ class MenuTrash extends StatelessWidget {
const HSpace(6),
ChangeNotifierProvider.value(
value: Provider.of<AppearanceSettingModel>(context, listen: true),
child: Selector<AppearanceSettingModel, AppLanguage>(
selector: (ctx, notifier) => notifier.language,
child: Selector<AppearanceSettingModel, Locale>(
selector: (ctx, notifier) => notifier.locale,
builder: (ctx, _, child) => FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12),
),
),

View File

@ -1,60 +1,27 @@
import 'package:flutter/material.dart';
enum AppLanguage {
english,
chinese,
italian,
french,
}
String stringFromLanguage(AppLanguage language) {
switch (language) {
case AppLanguage.english:
return "en";
case AppLanguage.chinese:
return "ch";
case AppLanguage.italian:
return "it";
case AppLanguage.french:
return "fr";
}
}
AppLanguage languageFromString(String name) {
AppLanguage language = AppLanguage.english;
if (name == "ch") {
language = AppLanguage.chinese;
} else if (name == "it") {
language = AppLanguage.italian;
} else if (name == "fr") {
language = AppLanguage.french;
}
return language;
}
Locale localeFromLanguageName(AppLanguage language) {
switch (language) {
case AppLanguage.english:
return const Locale('en');
case AppLanguage.chinese:
return const Locale('zh', 'CN');
case AppLanguage.italian:
return const Locale('it', 'IT');
case AppLanguage.french:
return const Locale('fr', 'CA');
}
}
AppLanguage languageFromLocale(Locale locale) {
String languageFromLocale(Locale locale) {
switch (locale.languageCode) {
// Most often used languages
case "en":
return "English";
case "zh":
return AppLanguage.chinese;
case "it":
return AppLanguage.italian;
return "简体中文";
// Then in alphabetical order
case "de":
return "Deutsch";
case "es":
return "Español";
case "fr":
return AppLanguage.french;
return "Français";
case "it":
return "Italiano";
case "ru":
return "русский";
// If not found then the language code will be displayed
default:
return AppLanguage.english;
return locale.languageCode;
}
}

View File

@ -30,16 +30,6 @@ class FlowyIconButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget child = icon;
// if (onPressed == null) {
// child = ColorFiltered(
// colorFilter: ColorFilter.mode(
// Colors.grey,
// BlendMode.saturation,
// ),
// child: child,
// );
// }
final size = Size(width, height ?? width);
assert(size.width > iconPadding.horizontal);

View File

@ -75,7 +75,7 @@ class UserPreferences extends $pb.GeneratedMessage {
class AppearanceSettings extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'AppearanceSettings', createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'theme')
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'language')
..aOM<LocaleSettings>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'locale', subBuilder: LocaleSettings.create)
..aOB(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'resetAsDefault')
..hasRequiredFields = false
;
@ -83,15 +83,15 @@ class AppearanceSettings extends $pb.GeneratedMessage {
AppearanceSettings._() : super();
factory AppearanceSettings({
$core.String? theme,
$core.String? language,
LocaleSettings? locale,
$core.bool? resetAsDefault,
}) {
final _result = create();
if (theme != null) {
_result.theme = theme;
}
if (language != null) {
_result.language = language;
if (locale != null) {
_result.locale = locale;
}
if (resetAsDefault != null) {
_result.resetAsDefault = resetAsDefault;
@ -129,13 +129,15 @@ class AppearanceSettings extends $pb.GeneratedMessage {
void clearTheme() => clearField(1);
@$pb.TagNumber(2)
$core.String get language => $_getSZ(1);
LocaleSettings get locale => $_getN(1);
@$pb.TagNumber(2)
set language($core.String v) { $_setString(1, v); }
set locale(LocaleSettings v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasLanguage() => $_has(1);
$core.bool hasLocale() => $_has(1);
@$pb.TagNumber(2)
void clearLanguage() => clearField(2);
void clearLocale() => clearField(2);
@$pb.TagNumber(2)
LocaleSettings ensureLocale() => $_ensure(1);
@$pb.TagNumber(3)
$core.bool get resetAsDefault => $_getBF(2);
@ -147,3 +149,64 @@ class AppearanceSettings extends $pb.GeneratedMessage {
void clearResetAsDefault() => clearField(3);
}
class LocaleSettings extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'LocaleSettings', createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'languageCode')
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'countryCode')
..hasRequiredFields = false
;
LocaleSettings._() : super();
factory LocaleSettings({
$core.String? languageCode,
$core.String? countryCode,
}) {
final _result = create();
if (languageCode != null) {
_result.languageCode = languageCode;
}
if (countryCode != null) {
_result.countryCode = countryCode;
}
return _result;
}
factory LocaleSettings.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory LocaleSettings.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
LocaleSettings clone() => LocaleSettings()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
LocaleSettings copyWith(void Function(LocaleSettings) updates) => super.copyWith((message) => updates(message as LocaleSettings)) as LocaleSettings; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static LocaleSettings create() => LocaleSettings._();
LocaleSettings createEmptyInstance() => create();
static $pb.PbList<LocaleSettings> createRepeated() => $pb.PbList<LocaleSettings>();
@$core.pragma('dart2js:noInline')
static LocaleSettings getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<LocaleSettings>(create);
static LocaleSettings? _defaultInstance;
@$pb.TagNumber(1)
$core.String get languageCode => $_getSZ(0);
@$pb.TagNumber(1)
set languageCode($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasLanguageCode() => $_has(0);
@$pb.TagNumber(1)
void clearLanguageCode() => clearField(1);
@$pb.TagNumber(2)
$core.String get countryCode => $_getSZ(1);
@$pb.TagNumber(2)
set countryCode($core.String v) { $_setString(1, v); }
@$pb.TagNumber(2)
$core.bool hasCountryCode() => $_has(1);
@$pb.TagNumber(2)
void clearCountryCode() => clearField(2);
}

View File

@ -24,10 +24,21 @@ const AppearanceSettings$json = const {
'1': 'AppearanceSettings',
'2': const [
const {'1': 'theme', '3': 1, '4': 1, '5': 9, '10': 'theme'},
const {'1': 'language', '3': 2, '4': 1, '5': 9, '10': 'language'},
const {'1': 'locale', '3': 2, '4': 1, '5': 11, '6': '.LocaleSettings', '10': 'locale'},
const {'1': 'reset_as_default', '3': 3, '4': 1, '5': 8, '10': 'resetAsDefault'},
],
};
/// Descriptor for `AppearanceSettings`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List appearanceSettingsDescriptor = $convert.base64Decode('ChJBcHBlYXJhbmNlU2V0dGluZ3MSFAoFdGhlbWUYASABKAlSBXRoZW1lEhoKCGxhbmd1YWdlGAIgASgJUghsYW5ndWFnZRIoChByZXNldF9hc19kZWZhdWx0GAMgASgIUg5yZXNldEFzRGVmYXVsdA==');
final $typed_data.Uint8List appearanceSettingsDescriptor = $convert.base64Decode('ChJBcHBlYXJhbmNlU2V0dGluZ3MSFAoFdGhlbWUYASABKAlSBXRoZW1lEicKBmxvY2FsZRgCIAEoCzIPLkxvY2FsZVNldHRpbmdzUgZsb2NhbGUSKAoQcmVzZXRfYXNfZGVmYXVsdBgDIAEoCFIOcmVzZXRBc0RlZmF1bHQ=');
@$core.Deprecated('Use localeSettingsDescriptor instead')
const LocaleSettings$json = const {
'1': 'LocaleSettings',
'2': const [
const {'1': 'language_code', '3': 1, '4': 1, '5': 9, '10': 'languageCode'},
const {'1': 'country_code', '3': 2, '4': 1, '5': 9, '10': 'countryCode'},
],
};
/// Descriptor for `LocaleSettings`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List localeSettingsDescriptor = $convert.base64Decode('Cg5Mb2NhbGVTZXR0aW5ncxIjCg1sYW5ndWFnZV9jb2RlGAEgASgJUgxsYW5ndWFnZUNvZGUSIQoMY291bnRyeV9jb2RlGAIgASgJUgtjb3VudHJ5Q29kZQ==');

View File

@ -1,8 +1,7 @@
use crate::{errors::FlowyError, services::UserSession};
use flowy_database::kv::KV;
use flowy_user_data_model::entities::{
AppearanceSettings, UpdateUserParams, UpdateUserRequest, UserProfile, APPEARANCE_DEFAULT_LANGUAGE,
APPEARANCE_DEFAULT_THEME,
AppearanceSettings, UpdateUserParams, UpdateUserRequest, UserProfile, APPEARANCE_DEFAULT_THEME,
};
use lib_dispatch::prelude::*;
use std::{convert::TryInto, sync::Arc};
@ -50,10 +49,6 @@ pub async fn set_appearance_setting(data: Data<AppearanceSettings>) -> Result<()
setting.theme = APPEARANCE_DEFAULT_THEME.to_string();
}
if setting.language.is_empty() {
setting.theme = APPEARANCE_DEFAULT_LANGUAGE.to_string();
}
let s = serde_json::to_string(&setting)?;
KV::set_str(APPEARANCE_SETTING_CACHE_KEY, s);
Ok(())
@ -64,7 +59,13 @@ pub async fn get_appearance_setting() -> DataResult<AppearanceSettings, FlowyErr
match KV::get_str(APPEARANCE_SETTING_CACHE_KEY) {
None => data_result(AppearanceSettings::default()),
Some(s) => {
let setting: AppearanceSettings = serde_json::from_str(&s)?;
let setting = match serde_json::from_str(&s) {
Ok(setting) => setting,
Err(e) => {
tracing::error!("Deserialize AppearanceSettings failed: {:?}, fallback to default", e);
AppearanceSettings::default()
}
};
data_result(setting)
}
}

View File

@ -35,4 +35,4 @@ cargo install --force cargo-make && \
cargo install --force duckscript_cli && \
cargo make flowy_dev && \
cargo make -p production-linux-x86 appflowy-linux
CMD ["appflowy/frontend/app_flowy/product/0.0.2/linux/Release/AppFlowy/app_flowy"]
CMD ["appflowy/frontend/app_flowy/product/0.0.3/linux/Release/AppFlowy/app_flowy"]

7
package.json Normal file
View File

@ -0,0 +1,7 @@
{
"devDependencies": {
"@commitlint/cli": "16.1.0",
"@commitlint/config-conventional": "16.0.0",
"husky": "7.0.4"
}
}

View File

@ -18,48 +18,22 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
"String" => TypeCategory::Str,
"FFIRequest"
| "FFIResponse"
| "FlowyError"
| "SubscribeObject"
| "FlowyError"
| "NetworkState"
| "UserToken"
| "UserProfile"
| "UpdateUserRequest"
| "UpdateUserParams"
| "SignInRequest"
| "SignInParams"
| "SignInResponse"
| "SignUpRequest"
| "SignUpParams"
| "SignUpResponse"
| "UserToken"
| "UserProfile"
| "UpdateUserRequest"
| "UpdateUserParams"
| "UserPreferences"
| "AppearanceSettings"
| "ClientRevisionWSData"
| "ServerRevisionWSData"
| "NewDocumentUser"
| "FolderInfo"
| "Revision"
| "RepeatedRevision"
| "RevId"
| "RevisionRange"
| "CreateDocParams"
| "DocumentInfo"
| "ResetDocumentParams"
| "DocumentDelta"
| "NewDocUser"
| "DocumentId"
| "WSError"
| "WebSocketRawMessage"
| "Workspace"
| "RepeatedWorkspace"
| "CreateWorkspaceRequest"
| "CreateWorkspaceParams"
| "QueryWorkspaceRequest"
| "WorkspaceId"
| "CurrentWorkspaceSetting"
| "UpdateWorkspaceRequest"
| "UpdateWorkspaceParams"
| "ExportRequest"
| "ExportData"
| "LocaleSettings"
| "App"
| "RepeatedApp"
| "CreateAppRequest"
@ -69,10 +43,8 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
| "AppId"
| "UpdateAppRequest"
| "UpdateAppParams"
| "Trash"
| "RepeatedTrash"
| "RepeatedTrashId"
| "TrashId"
| "ExportRequest"
| "ExportData"
| "View"
| "RepeatedView"
| "CreateViewRequest"
@ -82,23 +54,52 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
| "RepeatedViewId"
| "UpdateViewRequest"
| "UpdateViewParams"
| "Trash"
| "RepeatedTrash"
| "RepeatedTrashId"
| "TrashId"
| "Workspace"
| "RepeatedWorkspace"
| "CreateWorkspaceRequest"
| "CreateWorkspaceParams"
| "QueryWorkspaceRequest"
| "WorkspaceId"
| "CurrentWorkspaceSetting"
| "UpdateWorkspaceRequest"
| "UpdateWorkspaceParams"
| "ClientRevisionWSData"
| "ServerRevisionWSData"
| "NewDocumentUser"
| "CreateDocParams"
| "DocumentInfo"
| "ResetDocumentParams"
| "DocumentDelta"
| "NewDocUser"
| "DocumentId"
| "Revision"
| "RepeatedRevision"
| "RevId"
| "RevisionRange"
| "FolderInfo"
| "WSError"
| "WebSocketRawMessage"
=> TypeCategory::Protobuf,
"FFIStatusCode"
| "FolderEvent"
| "FolderNotification"
| "NetworkEvent"
| "NetworkType"
| "UserEvent"
| "UserNotification"
| "NetworkEvent"
| "NetworkType"
| "ExportType"
| "ViewType"
| "TrashType"
| "ClientRevisionWSDataType"
| "ServerRevisionWSDataType"
| "RevisionState"
| "RevType"
| "ErrorCode"
| "WSChannel"
| "ExportType"
| "TrashType"
| "ViewType"
=> TypeCategory::Enum,
"Option" => TypeCategory::Opt,

View File

@ -16,26 +16,44 @@ pub struct AppearanceSettings {
pub theme: String,
#[pb(index = 2)]
pub language: String,
#[serde(default)]
pub locale: LocaleSettings,
#[pb(index = 3)]
#[serde(default = "reset_default_value")]
pub reset_as_default: bool,
}
#[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)]
pub struct LocaleSettings {
#[pb(index = 1)]
pub language_code: String,
#[pb(index = 2)]
pub country_code: String,
}
impl std::default::Default for LocaleSettings {
fn default() -> Self {
Self {
language_code: "en".to_owned(),
country_code: "".to_owned(),
}
}
}
fn reset_default_value() -> bool {
APPEARANCE_RESET_AS_DEFAULT
}
pub const APPEARANCE_DEFAULT_THEME: &str = "light";
pub const APPEARANCE_DEFAULT_LANGUAGE: &str = "en";
pub const APPEARANCE_RESET_AS_DEFAULT: bool = true;
impl std::default::Default for AppearanceSettings {
fn default() -> Self {
AppearanceSettings {
theme: APPEARANCE_DEFAULT_THEME.to_owned(),
language: APPEARANCE_DEFAULT_LANGUAGE.to_owned(),
locale: LocaleSettings::default(),
reset_as_default: APPEARANCE_RESET_AS_DEFAULT,
}
}

View File

@ -243,7 +243,7 @@ impl ::protobuf::reflect::ProtobufValue for UserPreferences {
pub struct AppearanceSettings {
// message fields
pub theme: ::std::string::String,
pub language: ::std::string::String,
pub locale: ::protobuf::SingularPtrField<LocaleSettings>,
pub reset_as_default: bool,
// special fields
pub unknown_fields: ::protobuf::UnknownFields,
@ -287,30 +287,37 @@ impl AppearanceSettings {
::std::mem::replace(&mut self.theme, ::std::string::String::new())
}
// string language = 2;
// .LocaleSettings locale = 2;
pub fn get_language(&self) -> &str {
&self.language
pub fn get_locale(&self) -> &LocaleSettings {
self.locale.as_ref().unwrap_or_else(|| <LocaleSettings as ::protobuf::Message>::default_instance())
}
pub fn clear_language(&mut self) {
self.language.clear();
pub fn clear_locale(&mut self) {
self.locale.clear();
}
pub fn has_locale(&self) -> bool {
self.locale.is_some()
}
// Param is passed by value, moved
pub fn set_language(&mut self, v: ::std::string::String) {
self.language = v;
pub fn set_locale(&mut self, v: LocaleSettings) {
self.locale = ::protobuf::SingularPtrField::some(v);
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_language(&mut self) -> &mut ::std::string::String {
&mut self.language
pub fn mut_locale(&mut self) -> &mut LocaleSettings {
if self.locale.is_none() {
self.locale.set_default();
}
self.locale.as_mut().unwrap()
}
// Take field
pub fn take_language(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.language, ::std::string::String::new())
pub fn take_locale(&mut self) -> LocaleSettings {
self.locale.take().unwrap_or_else(|| LocaleSettings::new())
}
// bool reset_as_default = 3;
@ -331,6 +338,11 @@ impl AppearanceSettings {
impl ::protobuf::Message for AppearanceSettings {
fn is_initialized(&self) -> bool {
for v in &self.locale {
if !v.is_initialized() {
return false;
}
};
true
}
@ -342,7 +354,7 @@ impl ::protobuf::Message for AppearanceSettings {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.theme)?;
},
2 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.language)?;
::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.locale)?;
},
3 => {
if wire_type != ::protobuf::wire_format::WireTypeVarint {
@ -366,8 +378,9 @@ impl ::protobuf::Message for AppearanceSettings {
if !self.theme.is_empty() {
my_size += ::protobuf::rt::string_size(1, &self.theme);
}
if !self.language.is_empty() {
my_size += ::protobuf::rt::string_size(2, &self.language);
if let Some(ref v) = self.locale.as_ref() {
let len = v.compute_size();
my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
}
if self.reset_as_default != false {
my_size += 2;
@ -381,8 +394,10 @@ impl ::protobuf::Message for AppearanceSettings {
if !self.theme.is_empty() {
os.write_string(1, &self.theme)?;
}
if !self.language.is_empty() {
os.write_string(2, &self.language)?;
if let Some(ref v) = self.locale.as_ref() {
os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?;
os.write_raw_varint32(v.get_cached_size())?;
v.write_to_with_cached_sizes(os)?;
}
if self.reset_as_default != false {
os.write_bool(3, self.reset_as_default)?;
@ -430,10 +445,10 @@ impl ::protobuf::Message for AppearanceSettings {
|m: &AppearanceSettings| { &m.theme },
|m: &mut AppearanceSettings| { &mut m.theme },
));
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"language",
|m: &AppearanceSettings| { &m.language },
|m: &mut AppearanceSettings| { &mut m.language },
fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<LocaleSettings>>(
"locale",
|m: &AppearanceSettings| { &m.locale },
|m: &mut AppearanceSettings| { &mut m.locale },
));
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
"reset_as_default",
@ -457,7 +472,7 @@ impl ::protobuf::Message for AppearanceSettings {
impl ::protobuf::Clear for AppearanceSettings {
fn clear(&mut self) {
self.theme.clear();
self.language.clear();
self.locale.clear();
self.reset_as_default = false;
self.unknown_fields.clear();
}
@ -475,30 +490,239 @@ impl ::protobuf::reflect::ProtobufValue for AppearanceSettings {
}
}
#[derive(PartialEq,Clone,Default)]
pub struct LocaleSettings {
// message fields
pub language_code: ::std::string::String,
pub country_code: ::std::string::String,
// special fields
pub unknown_fields: ::protobuf::UnknownFields,
pub cached_size: ::protobuf::CachedSize,
}
impl<'a> ::std::default::Default for &'a LocaleSettings {
fn default() -> &'a LocaleSettings {
<LocaleSettings as ::protobuf::Message>::default_instance()
}
}
impl LocaleSettings {
pub fn new() -> LocaleSettings {
::std::default::Default::default()
}
// string language_code = 1;
pub fn get_language_code(&self) -> &str {
&self.language_code
}
pub fn clear_language_code(&mut self) {
self.language_code.clear();
}
// Param is passed by value, moved
pub fn set_language_code(&mut self, v: ::std::string::String) {
self.language_code = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_language_code(&mut self) -> &mut ::std::string::String {
&mut self.language_code
}
// Take field
pub fn take_language_code(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.language_code, ::std::string::String::new())
}
// string country_code = 2;
pub fn get_country_code(&self) -> &str {
&self.country_code
}
pub fn clear_country_code(&mut self) {
self.country_code.clear();
}
// Param is passed by value, moved
pub fn set_country_code(&mut self, v: ::std::string::String) {
self.country_code = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_country_code(&mut self) -> &mut ::std::string::String {
&mut self.country_code
}
// Take field
pub fn take_country_code(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.country_code, ::std::string::String::new())
}
}
impl ::protobuf::Message for LocaleSettings {
fn is_initialized(&self) -> bool {
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
while !is.eof()? {
let (field_number, wire_type) = is.read_tag_unpack()?;
match field_number {
1 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.language_code)?;
},
2 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.country_code)?;
},
_ => {
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
},
};
}
::std::result::Result::Ok(())
}
// Compute sizes of nested messages
#[allow(unused_variables)]
fn compute_size(&self) -> u32 {
let mut my_size = 0;
if !self.language_code.is_empty() {
my_size += ::protobuf::rt::string_size(1, &self.language_code);
}
if !self.country_code.is_empty() {
my_size += ::protobuf::rt::string_size(2, &self.country_code);
}
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
self.cached_size.set(my_size);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
if !self.language_code.is_empty() {
os.write_string(1, &self.language_code)?;
}
if !self.country_code.is_empty() {
os.write_string(2, &self.country_code)?;
}
os.write_unknown_fields(self.get_unknown_fields())?;
::std::result::Result::Ok(())
}
fn get_cached_size(&self) -> u32 {
self.cached_size.get()
}
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
&self.unknown_fields
}
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
&mut self.unknown_fields
}
fn as_any(&self) -> &dyn (::std::any::Any) {
self as &dyn (::std::any::Any)
}
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
self as &mut dyn (::std::any::Any)
}
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
self
}
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
Self::descriptor_static()
}
fn new() -> LocaleSettings {
LocaleSettings::new()
}
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
descriptor.get(|| {
let mut fields = ::std::vec::Vec::new();
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"language_code",
|m: &LocaleSettings| { &m.language_code },
|m: &mut LocaleSettings| { &mut m.language_code },
));
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"country_code",
|m: &LocaleSettings| { &m.country_code },
|m: &mut LocaleSettings| { &mut m.country_code },
));
::protobuf::reflect::MessageDescriptor::new_pb_name::<LocaleSettings>(
"LocaleSettings",
fields,
file_descriptor_proto()
)
})
}
fn default_instance() -> &'static LocaleSettings {
static instance: ::protobuf::rt::LazyV2<LocaleSettings> = ::protobuf::rt::LazyV2::INIT;
instance.get(LocaleSettings::new)
}
}
impl ::protobuf::Clear for LocaleSettings {
fn clear(&mut self) {
self.language_code.clear();
self.country_code.clear();
self.unknown_fields.clear();
}
}
impl ::std::fmt::Debug for LocaleSettings {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for LocaleSettings {
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
::protobuf::reflect::ReflectValueRef::Message(self)
}
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\x12user_setting.proto\"n\n\x0fUserPreferences\x12\x17\n\x07user_id\
\x18\x01\x20\x01(\tR\x06userId\x12B\n\x12appearance_setting\x18\x02\x20\
\x01(\x0b2\x13.AppearanceSettingsR\x11appearanceSetting\"p\n\x12Appearan\
ceSettings\x12\x14\n\x05theme\x18\x01\x20\x01(\tR\x05theme\x12\x1a\n\x08\
language\x18\x02\x20\x01(\tR\x08language\x12(\n\x10reset_as_default\x18\
\x03\x20\x01(\x08R\x0eresetAsDefaultJ\xd5\x02\n\x06\x12\x04\0\0\n\x01\n\
\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\
\n\x03\x04\0\x01\x12\x03\x02\x08\x17\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\
\x04\x17\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\
\x02\0\x01\x12\x03\x03\x0b\x12\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\
\x15\x16\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04.\n\x0c\n\x05\x04\0\
\x02\x01\x06\x12\x03\x04\x04\x16\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\
\x04\x17)\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04,-\n\n\n\x02\x04\x01\
\x12\x04\x06\0\n\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\x08\x1a\n\x0b\n\
\x04\x04\x01\x02\0\x12\x03\x07\x04\x15\n\x0c\n\x05\x04\x01\x02\0\x05\x12\
\x03\x07\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07\x0b\x10\n\x0c\n\
\x05\x04\x01\x02\0\x03\x12\x03\x07\x13\x14\n\x0b\n\x04\x04\x01\x02\x01\
\x12\x03\x08\x04\x18\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x08\x04\n\n\
\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x08\x0b\x13\n\x0c\n\x05\x04\x01\
\x02\x01\x03\x12\x03\x08\x16\x17\n\x0b\n\x04\x04\x01\x02\x02\x12\x03\t\
\x04\x1e\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\t\x04\x08\n\x0c\n\x05\
\x04\x01\x02\x02\x01\x12\x03\t\t\x19\n\x0c\n\x05\x04\x01\x02\x02\x03\x12\
\x03\t\x1c\x1db\x06proto3\
\x01(\x0b2\x13.AppearanceSettingsR\x11appearanceSetting\"}\n\x12Appearan\
ceSettings\x12\x14\n\x05theme\x18\x01\x20\x01(\tR\x05theme\x12'\n\x06loc\
ale\x18\x02\x20\x01(\x0b2\x0f.LocaleSettingsR\x06locale\x12(\n\x10reset_\
as_default\x18\x03\x20\x01(\x08R\x0eresetAsDefault\"X\n\x0eLocaleSetting\
s\x12#\n\rlanguage_code\x18\x01\x20\x01(\tR\x0clanguageCode\x12!\n\x0cco\
untry_code\x18\x02\x20\x01(\tR\x0bcountryCodeJ\xdb\x03\n\x06\x12\x04\0\0\
\x0e\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\
\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x17\n\x0b\n\x04\x04\0\x02\0\
\x12\x03\x03\x04\x17\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\
\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x12\n\x0c\n\x05\x04\0\x02\0\x03\
\x12\x03\x03\x15\x16\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04.\n\x0c\n\
\x05\x04\0\x02\x01\x06\x12\x03\x04\x04\x16\n\x0c\n\x05\x04\0\x02\x01\x01\
\x12\x03\x04\x17)\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04,-\n\n\n\x02\
\x04\x01\x12\x04\x06\0\n\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\x08\x1a\n\
\x0b\n\x04\x04\x01\x02\0\x12\x03\x07\x04\x15\n\x0c\n\x05\x04\x01\x02\0\
\x05\x12\x03\x07\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07\x0b\x10\
\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x07\x13\x14\n\x0b\n\x04\x04\x01\
\x02\x01\x12\x03\x08\x04\x1e\n\x0c\n\x05\x04\x01\x02\x01\x06\x12\x03\x08\
\x04\x12\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x08\x13\x19\n\x0c\n\x05\
\x04\x01\x02\x01\x03\x12\x03\x08\x1c\x1d\n\x0b\n\x04\x04\x01\x02\x02\x12\
\x03\t\x04\x1e\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\t\x04\x08\n\x0c\n\
\x05\x04\x01\x02\x02\x01\x12\x03\t\t\x19\n\x0c\n\x05\x04\x01\x02\x02\x03\
\x12\x03\t\x1c\x1d\n\n\n\x02\x04\x02\x12\x04\x0b\0\x0e\x01\n\n\n\x03\x04\
\x02\x01\x12\x03\x0b\x08\x16\n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0c\x04\
\x1d\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x0c\x04\n\n\x0c\n\x05\x04\x02\
\x02\0\x01\x12\x03\x0c\x0b\x18\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0c\
\x1b\x1c\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\r\x04\x1c\n\x0c\n\x05\x04\
\x02\x02\x01\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\
\r\x0b\x17\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\r\x1a\x1bb\x06proto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -6,6 +6,10 @@ message UserPreferences {
}
message AppearanceSettings {
string theme = 1;
string language = 2;
LocaleSettings locale = 2;
bool reset_as_default = 3;
}
message LocaleSettings {
string language_code = 1;
string country_code = 2;
}