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 updateSystemOverlayStyle(BuildContext context) async { final brightness = Theme.of(context).brightness; await updateSystemOverlayStyleWithBrightness(brightness); } Future 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 InputDecorationThemeData { 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, ); InputDecorationThemeData; 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), ), ), ); }