mirror of
https://github.com/localsend/localsend.git
synced 2026-06-23 04:10:07 +00:00
199 lines
7.6 KiB
Dart
199 lines
7.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:localsend_app/gen/strings.g.dart';
|
|
import 'package:localsend_app/model/persistence/color_mode.dart';
|
|
import 'package:localsend_app/provider/device_info_provider.dart';
|
|
import 'package:localsend_app/util/native/platform_check.dart';
|
|
import 'package:localsend_app/util/ui/dynamic_colors.dart';
|
|
import 'package:refena_flutter/refena_flutter.dart';
|
|
import 'package:yaru/yaru.dart' as yaru;
|
|
|
|
final _borderRadius = BorderRadius.circular(5);
|
|
|
|
/// On desktop, we need to add additional padding to achieve the same visual appearance as on mobile
|
|
double get desktopPaddingFix => checkPlatformIsDesktop() ? 8 : 0;
|
|
|
|
ThemeData getTheme(ColorMode colorMode, Brightness brightness, DynamicColors? dynamicColors) {
|
|
if (colorMode == ColorMode.yaru) {
|
|
return _getYaruTheme(brightness);
|
|
}
|
|
|
|
final colorScheme = _determineColorScheme(colorMode, brightness, dynamicColors);
|
|
|
|
final lightInputBorder = OutlineInputBorder(
|
|
borderSide: BorderSide(color: colorScheme.secondaryContainer),
|
|
borderRadius: _borderRadius,
|
|
);
|
|
|
|
final darkInputBorder = OutlineInputBorder(
|
|
borderSide: BorderSide(color: colorScheme.secondaryContainer),
|
|
borderRadius: _borderRadius,
|
|
);
|
|
|
|
// https://github.com/localsend/localsend/issues/52
|
|
final String? fontFamily;
|
|
if (checkPlatform([TargetPlatform.windows])) {
|
|
fontFamily = switch (LocaleSettings.currentLocale) {
|
|
AppLocale.ja => 'Yu Gothic UI',
|
|
AppLocale.ko => 'Malgun Gothic',
|
|
AppLocale.zhCn => 'Microsoft YaHei UI',
|
|
AppLocale.zhHk || AppLocale.zhTw => 'Microsoft JhengHei UI',
|
|
_ => 'Segoe UI Variable Display',
|
|
};
|
|
} else if (checkPlatform([TargetPlatform.linux])) {
|
|
fontFamily = switch (LocaleSettings.currentLocale) {
|
|
AppLocale.ja => 'Noto Sans CJK JP',
|
|
AppLocale.ko => 'Noto Sans CJK KR',
|
|
AppLocale.zhCn => 'Noto Sans CJK SC',
|
|
AppLocale.zhHk || AppLocale.zhTw => 'Noto Sans CJK TC',
|
|
_ => 'Noto Sans',
|
|
};
|
|
} else {
|
|
fontFamily = null;
|
|
}
|
|
|
|
return ThemeData(
|
|
colorScheme: colorScheme,
|
|
useMaterial3: true,
|
|
navigationBarTheme: colorScheme.brightness == Brightness.dark
|
|
? NavigationBarThemeData(
|
|
iconTheme: WidgetStateProperty.all(const IconThemeData(color: Colors.white)),
|
|
)
|
|
: null,
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: colorScheme.secondaryContainer,
|
|
border: colorScheme.brightness == Brightness.light ? lightInputBorder : darkInputBorder,
|
|
focusedBorder: colorScheme.brightness == Brightness.light ? lightInputBorder : darkInputBorder,
|
|
enabledBorder: colorScheme.brightness == Brightness.light ? lightInputBorder : darkInputBorder,
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
|
),
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
foregroundColor: colorScheme.brightness == Brightness.dark ? Colors.white : null,
|
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8 + desktopPaddingFix),
|
|
),
|
|
),
|
|
textButtonTheme: TextButtonThemeData(
|
|
style: TextButton.styleFrom(
|
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8 + desktopPaddingFix),
|
|
),
|
|
),
|
|
fontFamily: fontFamily,
|
|
);
|
|
}
|
|
|
|
Future<void> updateSystemOverlayStyle(BuildContext context) async {
|
|
final brightness = Theme.of(context).brightness;
|
|
await updateSystemOverlayStyleWithBrightness(brightness);
|
|
}
|
|
|
|
Future<void> updateSystemOverlayStyleWithBrightness(Brightness brightness) async {
|
|
if (checkPlatform([TargetPlatform.android])) {
|
|
// See https://github.com/flutter/flutter/issues/90098
|
|
final darkMode = brightness == Brightness.dark;
|
|
final androidSdkInt = RefenaScope.defaultRef.read(deviceInfoProvider).androidSdkInt ?? 0;
|
|
final bool edgeToEdge = androidSdkInt >= 29;
|
|
|
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); // ignore: unawaited_futures
|
|
|
|
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
|
statusBarColor: Colors.transparent,
|
|
statusBarIconBrightness: brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
|
systemNavigationBarColor: edgeToEdge ? Colors.transparent : (darkMode ? Colors.black : Colors.white),
|
|
systemNavigationBarContrastEnforced: false,
|
|
systemNavigationBarIconBrightness: darkMode ? Brightness.light : Brightness.dark,
|
|
));
|
|
} else {
|
|
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
|
statusBarBrightness: brightness, // iOS
|
|
statusBarColor: Colors.transparent, // Not relevant to this issue
|
|
));
|
|
}
|
|
}
|
|
|
|
extension ThemeDataExt on ThemeData {
|
|
/// This is the actual [cardColor] being used.
|
|
Color get cardColorWithElevation {
|
|
return ElevationOverlay.applySurfaceTint(cardColor, colorScheme.surfaceTint, 1);
|
|
}
|
|
}
|
|
|
|
extension ColorSchemeExt on ColorScheme {
|
|
Color get warning {
|
|
return Colors.orange;
|
|
}
|
|
|
|
Color? get secondaryContainerIfDark {
|
|
return brightness == Brightness.dark ? secondaryContainer : null;
|
|
}
|
|
|
|
Color? get onSecondaryContainerIfDark {
|
|
return brightness == Brightness.dark ? onSecondaryContainer : null;
|
|
}
|
|
}
|
|
|
|
extension InputDecorationThemeExt on InputDecorationTheme {
|
|
BorderRadius get borderRadius => _borderRadius;
|
|
}
|
|
|
|
ColorScheme _determineColorScheme(ColorMode mode, Brightness brightness, DynamicColors? dynamicColors) {
|
|
final defaultColorScheme = ColorScheme.fromSeed(
|
|
seedColor: Colors.teal,
|
|
brightness: brightness,
|
|
);
|
|
|
|
final colorScheme = switch (mode) {
|
|
ColorMode.system => brightness == Brightness.light ? dynamicColors?.light : dynamicColors?.dark,
|
|
ColorMode.localsend => null,
|
|
ColorMode.oled => (dynamicColors?.dark ?? defaultColorScheme).copyWith(
|
|
surface: Colors.black,
|
|
),
|
|
ColorMode.yaru => throw 'Should reach here',
|
|
};
|
|
|
|
return colorScheme ?? defaultColorScheme;
|
|
}
|
|
|
|
ThemeData _getYaruTheme(Brightness brightness) {
|
|
final baseTheme = brightness == Brightness.light ? yaru.yaruLight : yaru.yaruDark;
|
|
final colorScheme = baseTheme.colorScheme;
|
|
|
|
final lightInputBorder = OutlineInputBorder(
|
|
borderSide: BorderSide(color: colorScheme.secondaryContainer),
|
|
borderRadius: _borderRadius,
|
|
);
|
|
|
|
final darkInputBorder = OutlineInputBorder(
|
|
borderSide: BorderSide(color: colorScheme.secondaryContainer),
|
|
borderRadius: _borderRadius,
|
|
);
|
|
|
|
return baseTheme.copyWith(
|
|
navigationBarTheme: colorScheme.brightness == Brightness.dark
|
|
? NavigationBarThemeData(
|
|
iconTheme: WidgetStateProperty.all(const IconThemeData(color: Colors.white)),
|
|
)
|
|
: null,
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: colorScheme.secondaryContainer,
|
|
border: colorScheme.brightness == Brightness.light ? lightInputBorder : darkInputBorder,
|
|
focusedBorder: colorScheme.brightness == Brightness.light ? lightInputBorder : darkInputBorder,
|
|
enabledBorder: colorScheme.brightness == Brightness.light ? lightInputBorder : darkInputBorder,
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
|
),
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
foregroundColor: colorScheme.brightness == Brightness.dark ? Colors.white : null,
|
|
padding: checkPlatformIsDesktop() ? const EdgeInsets.all(16) : const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
),
|
|
),
|
|
textButtonTheme: TextButtonThemeData(
|
|
style: TextButton.styleFrom(
|
|
padding: checkPlatformIsDesktop() ? const EdgeInsets.all(16) : const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
),
|
|
),
|
|
);
|
|
}
|