123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- import 'dart:async';
- import 'package:appflowy/user/application/user_settings_service.dart';
- import 'package:appflowy_backend/log.dart';
- import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
- import 'package:easy_localization/easy_localization.dart';
- import 'package:flowy_infra/size.dart';
- import 'package:flowy_infra/theme.dart';
- import 'package:flowy_infra/theme_extension.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter_bloc/flutter_bloc.dart';
- import 'package:freezed_annotation/freezed_annotation.dart';
- part 'appearance.freezed.dart';
- const _white = Color(0xFFFFFFFF);
- /// [AppearanceSettingsCubit] is used to modify the appearance of AppFlowy.
- /// It includes the [AppTheme], [ThemeMode], [TextStyles] and [Locale].
- class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
- final AppearanceSettingsPB _setting;
- AppearanceSettingsCubit(AppearanceSettingsPB setting)
- : _setting = setting,
- super(AppearanceSettingsState.initial(
- setting.theme,
- setting.themeMode,
- setting.font,
- setting.monospaceFont,
- setting.locale,
- setting.isMenuCollapsed,
- setting.menuOffset,
- ));
- /// Update selected theme in the user's settings and emit an updated state
- /// with the AppTheme named [themeName].
- void setTheme(String themeName) {
- _setting.theme = themeName;
- _saveAppearanceSettings();
- emit(state.copyWith(appTheme: AppTheme.fromName(themeName)));
- }
- /// Update the theme mode in the user's settings and emit an updated state.
- void setThemeMode(ThemeMode themeMode) {
- _setting.themeMode = _themeModeToPB(themeMode);
- _saveAppearanceSettings();
- emit(state.copyWith(themeMode: themeMode));
- }
- /// Updates the current locale and notify the listeners the locale was
- /// changed. Fallback to [en] locale if [newLocale] is not supported.
- void setLocale(BuildContext context, Locale newLocale) {
- if (!context.supportedLocales.contains(newLocale)) {
- Log.warn("Unsupported locale: $newLocale, Fallback to locale: en");
- newLocale = const Locale('en');
- }
- if (state.locale != newLocale) {
- context.setLocale(newLocale);
- _setting.locale.languageCode = newLocale.languageCode;
- _setting.locale.countryCode = newLocale.countryCode ?? "";
- _saveAppearanceSettings();
- emit(state.copyWith(locale: newLocale));
- }
- }
- // Saves the menus current visibility
- void saveIsMenuCollapsed(bool collapsed) {
- _setting.isMenuCollapsed = collapsed;
- _saveAppearanceSettings();
- }
- // Saves the current resize offset of the menu
- void saveMenuOffset(double offset) {
- _setting.menuOffset = offset;
- _saveAppearanceSettings();
- }
- /// Saves key/value setting to disk.
- /// Removes the key if the passed in value is null
- void setKeyValue(String key, String? value) {
- if (key.isEmpty) {
- Log.warn("The key should not be empty");
- return;
- }
- if (value == null) {
- _setting.settingKeyValue.remove(key);
- }
- if (_setting.settingKeyValue[key] != value) {
- if (value == null) {
- _setting.settingKeyValue.remove(key);
- } else {
- _setting.settingKeyValue[key] = value;
- }
- }
- _saveAppearanceSettings();
- }
- String? getValue(String key) {
- if (key.isEmpty) {
- Log.warn("The key should not be empty");
- return null;
- }
- return _setting.settingKeyValue[key];
- }
- /// Called when the application launches.
- /// Uses the device locale when the application is opened for the first time.
- void readLocaleWhenAppLaunch(BuildContext context) {
- if (_setting.resetToDefault) {
- _setting.resetToDefault = false;
- _saveAppearanceSettings();
- setLocale(context, context.deviceLocale);
- return;
- }
- setLocale(context, state.locale);
- }
- Future<void> _saveAppearanceSettings() async {
- UserSettingsBackendService().setAppearanceSetting(_setting).then((result) {
- result.fold(
- (l) => null,
- (error) => Log.error(error),
- );
- });
- }
- }
- ThemeMode _themeModeFromPB(ThemeModePB themeModePB) {
- switch (themeModePB) {
- case ThemeModePB.Light:
- return ThemeMode.light;
- case ThemeModePB.Dark:
- return ThemeMode.dark;
- case ThemeModePB.System:
- default:
- return ThemeMode.system;
- }
- }
- ThemeModePB _themeModeToPB(ThemeMode themeMode) {
- switch (themeMode) {
- case ThemeMode.light:
- return ThemeModePB.Light;
- case ThemeMode.dark:
- return ThemeModePB.Dark;
- case ThemeMode.system:
- default:
- return ThemeModePB.System;
- }
- }
- @freezed
- class AppearanceSettingsState with _$AppearanceSettingsState {
- const AppearanceSettingsState._();
- const factory AppearanceSettingsState({
- required AppTheme appTheme,
- required ThemeMode themeMode,
- required String font,
- required String monospaceFont,
- required Locale locale,
- required bool isMenuCollapsed,
- required double menuOffset,
- }) = _AppearanceSettingsState;
- factory AppearanceSettingsState.initial(
- String themeName,
- ThemeModePB themeModePB,
- String font,
- String monospaceFont,
- LocaleSettingsPB localePB,
- bool isMenuCollapsed,
- double menuOffset,
- ) {
- return AppearanceSettingsState(
- appTheme: AppTheme.fromName(themeName),
- font: font,
- monospaceFont: monospaceFont,
- themeMode: _themeModeFromPB(themeModePB),
- locale: Locale(localePB.languageCode, localePB.countryCode),
- isMenuCollapsed: isMenuCollapsed,
- menuOffset: menuOffset,
- );
- }
- ThemeData get lightTheme => _getThemeData(Brightness.light);
- ThemeData get darkTheme => _getThemeData(Brightness.dark);
- ThemeData _getThemeData(Brightness brightness) {
- // Poppins and SF Mono are not well supported in some languages, so use the
- // built-in font for the following languages.
- final useBuiltInFontLanguages = [
- const Locale('zh', 'CN'),
- const Locale('zh', 'TW'),
- ];
- String fontFamily = font;
- String monospaceFontFamily = monospaceFont;
- if (useBuiltInFontLanguages.contains(locale)) {
- fontFamily = '';
- monospaceFontFamily = '';
- }
- final theme = brightness == Brightness.light
- ? appTheme.lightTheme
- : appTheme.darkTheme;
- return ThemeData(
- brightness: brightness,
- textTheme: _getTextTheme(fontFamily: fontFamily, fontColor: theme.text),
- textSelectionTheme: TextSelectionThemeData(
- cursorColor: theme.main2,
- selectionHandleColor: theme.main2,
- ),
- iconTheme: IconThemeData(color: theme.icon),
- tooltipTheme: TooltipThemeData(
- textStyle: _getFontStyle(
- fontFamily: fontFamily,
- fontSize: FontSizes.s11,
- fontWeight: FontWeight.w400,
- fontColor: theme.surface,
- ),
- ),
- scaffoldBackgroundColor: theme.surface,
- scrollbarTheme: ScrollbarThemeData(
- thumbColor: MaterialStateProperty.all(theme.shader3),
- thickness: MaterialStateProperty.resolveWith((states) {
- const Set<MaterialState> interactiveStates = <MaterialState>{
- MaterialState.pressed,
- MaterialState.hovered,
- MaterialState.dragged,
- };
- if (states.any(interactiveStates.contains)) {
- return 5.0;
- }
- return 3.0;
- }),
- crossAxisMargin: 0.0,
- mainAxisMargin: 0.0,
- radius: Corners.s10Radius,
- ),
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- canvasColor: theme.shader6,
- dividerColor: theme.divider,
- hintColor: theme.hint,
- //action item hover color
- hoverColor: theme.hoverBG2,
- disabledColor: theme.shader4,
- highlightColor: theme.main1,
- indicatorColor: theme.main1,
- toggleableActiveColor: theme.main1,
- cardColor: theme.input,
- colorScheme: ColorScheme(
- brightness: brightness,
- primary: theme.primary,
- onPrimary: theme.onPrimary,
- primaryContainer: theme.main2,
- onPrimaryContainer: _white,
- // page title hover color
- secondary: theme.hoverBG1,
- onSecondary: theme.shader1,
- // setting value hover color
- secondaryContainer: theme.selector,
- onSecondaryContainer: theme.topbarBg,
- tertiary: theme.shader7,
- tertiaryContainer: theme.questionBubbleBG,
- background: theme.surface,
- onBackground: theme.text,
- surface: theme.surface,
- // text&icon color when it is hovered
- onSurface: theme.hoverFG,
- onError: theme.shader7,
- error: theme.red,
- outline: theme.shader4,
- surfaceVariant: theme.sidebarBg,
- shadow: theme.shadow,
- ),
- 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,
- code: _getFontStyle(
- fontFamily: monospaceFontFamily,
- fontColor: theme.shader3,
- ),
- callout: _getFontStyle(
- fontFamily: fontFamily,
- fontSize: FontSizes.s11,
- fontColor: theme.shader3,
- ),
- caption: _getFontStyle(
- fontFamily: fontFamily,
- fontSize: FontSizes.s11,
- fontWeight: FontWeight.w400,
- fontColor: theme.hint,
- ),
- )
- ],
- );
- }
- TextStyle _getFontStyle({
- String? fontFamily,
- double? fontSize,
- FontWeight? fontWeight,
- Color? fontColor,
- double? letterSpacing,
- double? lineHeight,
- }) =>
- 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
- );
- }
- }
|