mirror of
https://github.com/ImranR98/Obtainium.git
synced 2026-07-31 09:26:48 -04:00
- Replace the 'Group by category' toggle with a 'Group by' control (None/Category/Source); sources are listed alphabetically. Migrates the old groupByCategory setting. - Give grouped entries small gaps so they read as distinct positional tiles, and make the multi-select/pinned highlight follow each tile's positional (or card) corners instead of the default radius. - Make the multi-select 'more' FAB extended to match the Add FAB. - Expose FAB shadow via floatingActionButtonTheme elevation in theme.dart.
154 lines
5.8 KiB
Dart
154 lines
5.8 KiB
Dart
// Builds the Material 3 Expressive theme for the app.
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// Builds the app-wide Material 3 Expressive [ThemeData] for a given
|
|
/// [colorScheme]. Expressive character lives here (large rounded shapes,
|
|
/// emphasized motion, updated M3 component looks) so it propagates to every
|
|
/// screen without per-widget styling.
|
|
ThemeData buildObtainiumTheme(ColorScheme colorScheme, String fontFamily) {
|
|
final cardShape = RoundedSuperellipseBorder(
|
|
borderRadius: BorderRadius.circular(24),
|
|
);
|
|
const buttonShape = StadiumBorder();
|
|
final dialogShape = RoundedSuperellipseBorder(
|
|
borderRadius: BorderRadius.circular(28),
|
|
);
|
|
final fieldShape = RoundedSuperellipseBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
);
|
|
|
|
const pillButtonStyle = ButtonStyle(
|
|
shape: WidgetStatePropertyAll(buttonShape),
|
|
minimumSize: WidgetStatePropertyAll(Size(0, 48)),
|
|
);
|
|
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
colorScheme: colorScheme,
|
|
fontFamily: fontFamily,
|
|
pageTransitionsTheme: const PageTransitionsTheme(
|
|
builders: <TargetPlatform, PageTransitionsBuilder>{
|
|
TargetPlatform.android: FadeForwardsPageTransitionsBuilder(),
|
|
TargetPlatform.iOS: FadeForwardsPageTransitionsBuilder(),
|
|
TargetPlatform.linux: FadeForwardsPageTransitionsBuilder(),
|
|
},
|
|
),
|
|
cardTheme: CardThemeData(
|
|
clipBehavior: Clip.antiAlias,
|
|
elevation: 0,
|
|
color: colorScheme.surfaceContainerLow,
|
|
shape: cardShape,
|
|
margin: EdgeInsets.zero,
|
|
),
|
|
dialogTheme: DialogThemeData(shape: dialogShape),
|
|
snackBarTheme: SnackBarThemeData(
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(16)),
|
|
),
|
|
bottomSheetTheme: const BottomSheetThemeData(
|
|
shape: RoundedSuperellipseBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
|
|
),
|
|
),
|
|
appBarTheme: const AppBarThemeData(centerTitle: false),
|
|
expansionTileTheme: ExpansionTileThemeData(
|
|
shape: cardShape,
|
|
collapsedShape: cardShape,
|
|
),
|
|
listTileTheme: ListTileThemeData(shape: fieldShape),
|
|
chipTheme: const ChipThemeData(shape: StadiumBorder()),
|
|
searchBarTheme: const SearchBarThemeData(
|
|
elevation: WidgetStatePropertyAll(0),
|
|
),
|
|
filledButtonTheme: const FilledButtonThemeData(style: pillButtonStyle),
|
|
elevatedButtonTheme: const ElevatedButtonThemeData(style: pillButtonStyle),
|
|
outlinedButtonTheme: const OutlinedButtonThemeData(style: pillButtonStyle),
|
|
textButtonTheme: const TextButtonThemeData(
|
|
style: ButtonStyle(shape: WidgetStatePropertyAll(buttonShape)),
|
|
),
|
|
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
|
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(20)),
|
|
// FAB shadow depth. Tweak these to try different values (M3 default is 6).
|
|
// `elevation` is the resting shadow; the others apply on interaction.
|
|
elevation: 4,
|
|
focusElevation: 6,
|
|
hoverElevation: 8,
|
|
highlightElevation: 6,
|
|
),
|
|
inputDecorationTheme: InputDecorationThemeData(
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(28)),
|
|
),
|
|
dropdownMenuTheme: DropdownMenuThemeData(
|
|
inputDecorationTheme: InputDecorationThemeData(
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 16,
|
|
),
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(28)),
|
|
),
|
|
menuStyle: MenuStyle(
|
|
shape: WidgetStatePropertyAll(
|
|
RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(16)),
|
|
),
|
|
),
|
|
),
|
|
sliderTheme: SliderThemeData(
|
|
// `year2023: false` opts these components into the updated Material 3
|
|
// appearance. The flag is deprecated by Flutter and will be removed once
|
|
// the new look becomes the framework default, at which point these lines
|
|
// (and the `ignore` directives) can simply be deleted with no visual
|
|
// change. There is no replacement API to migrate to in the meantime.
|
|
// Tracking: https://github.com/flutter/flutter/issues/162186
|
|
// ignore: deprecated_member_use
|
|
year2023: false,
|
|
activeTrackColor: colorScheme.primary,
|
|
inactiveTrackColor: colorScheme.surfaceContainerHighest,
|
|
thumbColor: colorScheme.primary,
|
|
overlayColor: colorScheme.primary.withValues(alpha: 0.12),
|
|
),
|
|
progressIndicatorTheme: const ProgressIndicatorThemeData(
|
|
// See the note on `sliderTheme.year2023` above; no replacement API exists
|
|
// and this can be removed once the M3 look is the framework default.
|
|
// Tracking: https://github.com/flutter/flutter/issues/162186
|
|
// ignore: deprecated_member_use
|
|
year2023: false,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Corner radius for the outer corners of a connected tile run.
|
|
const double connectedTileBigRadius = 24;
|
|
|
|
/// Corner radius for the inner (joined) corners of a connected tile run.
|
|
const double connectedTileSmallRadius = 6;
|
|
|
|
BorderRadius positionalTileRadius({
|
|
required bool isFirst,
|
|
required bool isLast,
|
|
}) {
|
|
return BorderRadius.vertical(
|
|
top: Radius.circular(
|
|
isFirst ? connectedTileBigRadius : connectedTileSmallRadius,
|
|
),
|
|
bottom: Radius.circular(
|
|
isLast ? connectedTileBigRadius : connectedTileSmallRadius,
|
|
),
|
|
);
|
|
}
|
|
|
|
RoundedSuperellipseBorder positionalTileShape({
|
|
required bool isFirst,
|
|
required bool isLast,
|
|
}) => RoundedSuperellipseBorder(
|
|
borderRadius: positionalTileRadius(isFirst: isFirst, isLast: isLast),
|
|
);
|
|
|
|
abstract final class ExpressiveMotion {
|
|
static const Curve emphasized = Curves.easeInOutCubicEmphasized;
|
|
|
|
static const Duration short = Durations.short4;
|
|
static const Duration medium = Durations.medium2;
|
|
}
|