First steps to move to Material You

pull/90/head
Inex Code 2022-05-17 01:41:00 +03:00
parent 8b5bf24f3a
commit b4145dc5c8
9 changed files with 177 additions and 117 deletions

View File

@ -13,7 +13,6 @@ import 'package:timezone/data/latest.dart' as tz;
import 'config/bloc_config.dart'; import 'config/bloc_config.dart';
import 'config/bloc_observer.dart'; import 'config/bloc_observer.dart';
import 'config/brand_theme.dart';
import 'config/get_it_config.dart'; import 'config/get_it_config.dart';
import 'config/localization.dart'; import 'config/localization.dart';
import 'logic/cubit/app_settings/app_settings_cubit.dart'; import 'logic/cubit/app_settings/app_settings_cubit.dart';
@ -45,7 +44,8 @@ void main() async {
); );
BlocOverrides.runZoned( BlocOverrides.runZoned(
() => runApp(Localization(child: MyApp( () => runApp(Localization(
child: MyApp(
lightThemeData: lightThemeData, lightThemeData: lightThemeData,
darkThemeData: darkThemeData, darkThemeData: darkThemeData,
))), ))),
@ -81,7 +81,8 @@ class MyApp extends StatelessWidget {
title: 'SelfPrivacy', title: 'SelfPrivacy',
theme: lightThemeData, theme: lightThemeData,
darkTheme: darkThemeData, darkTheme: darkThemeData,
themeMode: appSettings.isDarkModeOn ? ThemeMode.dark : ThemeMode.light, themeMode:
appSettings.isDarkModeOn ? ThemeMode.dark : ThemeMode.light,
home: appSettings.isOnbordingShowing home: appSettings.isOnbordingShowing
? OnboardingPage(nextPage: InitializingPage()) ? OnboardingPage(nextPage: InitializingPage())
: RootPage(), : RootPage(),

View File

@ -28,13 +28,18 @@ abstract class AppThemeFactory {
if (Platform.isLinux) { if (Platform.isLinux) {
GtkThemeData themeData = await GtkThemeData.initialize(); GtkThemeData themeData = await GtkThemeData.initialize();
final isGtkDark =
Color(themeData.theme_base_color).computeLuminance() < 0.5;
final isInverseNeeded = isGtkDark != isDark;
gtkColorsScheme = ColorScheme.fromSeed( gtkColorsScheme = ColorScheme.fromSeed(
seedColor: Color(themeData.theme_selected_bg_color), seedColor: Color(themeData.theme_selected_bg_color),
brightness: Color(themeData.theme_base_color).computeLuminance() > 0.5 brightness: brightness,
? Brightness.light background: isInverseNeeded
: Brightness.dark, ? Color(themeData.theme_base_color)
background: Color(themeData.theme_bg_color), : Color(themeData.theme_bg_color),
surface: Color(themeData.theme_base_color), surface: isInverseNeeded
? Color(themeData.theme_bg_color)
: Color(themeData.theme_base_color),
); );
} }
@ -46,7 +51,8 @@ abstract class AppThemeFactory {
brightness: brightness, brightness: brightness,
); );
final colorScheme = dynamicColorsScheme ?? gtkColorsScheme ?? fallbackColorScheme; final colorScheme =
dynamicColorsScheme ?? gtkColorsScheme ?? fallbackColorScheme;
final appTypography = Typography.material2021(); final appTypography = Typography.material2021();
@ -55,6 +61,7 @@ abstract class AppThemeFactory {
brightness: colorScheme.brightness, brightness: colorScheme.brightness,
typography: appTypography, typography: appTypography,
useMaterial3: true, useMaterial3: true,
scaffoldBackgroundColor: colorScheme.background,
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
elevation: 0, elevation: 0,
backgroundColor: colorScheme.primary, backgroundColor: colorScheme.primary,

View File

@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
class FilledButton extends StatelessWidget {
const FilledButton({
Key? key,
this.onPressed,
this.title,
this.child,
}) : super(key: key);
final VoidCallback? onPressed;
final String? title;
final Widget? child;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: child ?? Text(title ?? ''),
style: ElevatedButton.styleFrom(
onPrimary: Theme.of(context).colorScheme.onPrimary,
primary: Theme.of(context).colorScheme.primary,
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
);
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
enum BrandButtonTypes { rised, text, iconText } enum BrandButtonTypes { rised, text, iconText }
@ -13,11 +13,17 @@ class BrandButton {
}) { }) {
assert(text == null || child == null, 'required title or child'); assert(text == null || child == null, 'required title or child');
assert(text != null || child != null, 'required title or child'); assert(text != null || child != null, 'required title or child');
return _RisedButton( return ConstrainedBox(
key: key, constraints: BoxConstraints(
title: text, minHeight: 48,
onPressed: onPressed, minWidth: double.infinity,
child: child, ),
child: FilledButton(
key: key,
title: text,
onPressed: onPressed,
child: child,
),
); );
} }
@ -26,10 +32,12 @@ class BrandButton {
required VoidCallback onPressed, required VoidCallback onPressed,
required String title, required String title,
}) => }) =>
_TextButton( ConstrainedBox(
key: key, constraints: BoxConstraints(
title: title, minHeight: 48,
onPressed: onPressed, minWidth: double.infinity,
),
child: TextButton(onPressed: onPressed, child: Text(title)),
); );
static emptyWithIconText({ static emptyWithIconText({
@ -46,78 +54,6 @@ class BrandButton {
); );
} }
class _RisedButton extends StatelessWidget {
const _RisedButton({
Key? key,
this.onPressed,
this.title,
this.child,
}) : super(key: key);
final VoidCallback? onPressed;
final String? title;
final Widget? child;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(24),
child: ColoredBox(
color: onPressed == null
? BrandColors.gray2
: Theme.of(context).primaryColor,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onPressed,
child: Container(
height: 48,
width: double.infinity,
alignment: Alignment.center,
padding: EdgeInsets.all(12),
child: child ?? BrandText.buttonTitleText(title),
),
),
),
),
);
}
}
class _TextButton extends StatelessWidget {
const _TextButton({
Key? key,
this.onPressed,
this.title,
}) : super(key: key);
final VoidCallback? onPressed;
final String? title;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
behavior: HitTestBehavior.opaque,
child: Container(
height: 48,
width: double.infinity,
alignment: Alignment.center,
padding: EdgeInsets.all(12),
child: Text(
title!,
style: TextStyle(
color: BrandColors.blue,
fontSize: 16,
fontWeight: FontWeight.bold,
height: 1.5,
),
),
),
);
}
}
class _IconTextButton extends StatelessWidget { class _IconTextButton extends StatelessWidget {
const _IconTextButton({Key? key, this.onPressed, this.title, this.icon}) const _IconTextButton({Key? key, this.onPressed, this.title, this.icon})
: super(key: key); : super(key: key);

View File

@ -37,20 +37,27 @@ class BrandHeroScreen extends StatelessWidget {
padding: EdgeInsets.all(16.0), padding: EdgeInsets.all(16.0),
children: <Widget>[ children: <Widget>[
if (heroIcon != null) if (heroIcon != null)
Icon( Container(
heroIcon, child: Icon(
size: 48.0, heroIcon,
size: 48.0,
),
alignment: Alignment.bottomLeft,
), ),
SizedBox(height: 16.0), SizedBox(height: 8.0),
if (heroTitle != null) if (heroTitle != null)
Text(heroTitle!, Text(
style: Theme.of(context).textTheme.headline2, heroTitle!,
textAlign: TextAlign.center), style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Colors.black,
),
textAlign: TextAlign.start,
),
SizedBox(height: 8.0), SizedBox(height: 8.0),
if (heroSubtitle != null) if (heroSubtitle != null)
Text(heroSubtitle!, Text(heroSubtitle!,
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center), textAlign: TextAlign.start),
SizedBox(height: 16.0), SizedBox(height: 16.0),
...children, ...children,
], ],

View File

@ -1,10 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
final _kBottomTabBarHeight = 51;
class BrandTabBar extends StatefulWidget { class BrandTabBar extends StatefulWidget {
BrandTabBar({Key? key, this.controller}) : super(key: key); BrandTabBar({Key? key, this.controller}) : super(key: key);
@ -43,24 +40,17 @@ class _BrandTabBarState extends State<BrandTabBar> {
_getIconButton('basis.providers'.tr(), BrandIcons.server, 0), _getIconButton('basis.providers'.tr(), BrandIcons.server, 0),
_getIconButton('basis.services'.tr(), BrandIcons.box, 1), _getIconButton('basis.services'.tr(), BrandIcons.box, 1),
_getIconButton('basis.users'.tr(), BrandIcons.users, 2), _getIconButton('basis.users'.tr(), BrandIcons.users, 2),
_getIconButton('basis.more'.tr(), BrandIcons.menu, 3), _getIconButton('basis.more'.tr(), Icons.menu_rounded, 3),
], ],
onDestinationSelected: (index) { onDestinationSelected: (index) {
widget.controller!.animateTo(index); widget.controller!.animateTo(index);
}, },
selectedIndex: currentIndex ?? 0, selectedIndex: currentIndex ?? 0,
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected, labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
); );
} }
_getIconButton(String label, IconData iconData, int index) { _getIconButton(String label, IconData iconData, int index) {
var activeColor = Theme.of(context).brightness == Brightness.dark
? BrandColors.white
: BrandColors.black;
var isActive = currentIndex == index;
var color = isActive ? activeColor : BrandColors.inactive;
return NavigationDestination( return NavigationDestination(
icon: Icon(iconData), icon: Icon(iconData),
label: label, label: label,

View File

@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ionicons/ionicons.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart';
import 'package:selfprivacy/ui/helpers/modals.dart';
class BrandFab extends StatefulWidget {
BrandFab({Key? key}) : super(key: key);
@override
_BrandFabState createState() => _BrandFabState();
}
class _BrandFabState extends State<BrandFab>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation _colorTween;
@override
void initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 800));
_colorTween = ColorTween(
begin: BrandColors.black,
end: BrandColors.primary,
).animate(_animationController);
super.initState();
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
}
void _afterLayout(_) {
if (Theme.of(context).brightness == Brightness.dark) {
setState(() {
_colorTween = ColorTween(
begin: BrandColors.white,
end: BrandColors.primary,
).animate(_animationController);
});
}
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
bool wasPrevStateIsEmpty = true;
@override
Widget build(BuildContext context) {
return BlocListener<JobsCubit, JobsState>(
listener: (context, state) {
if (wasPrevStateIsEmpty && state is! JobsStateEmpty) {
wasPrevStateIsEmpty = false;
_animationController.forward();
} else if (!wasPrevStateIsEmpty && state is JobsStateEmpty) {
wasPrevStateIsEmpty = true;
_animationController.reverse();
}
},
child: FloatingActionButton(
onPressed: () {
showBrandBottomSheet(
context: context,
builder: (context) => BrandBottomSheet(
isExpended: true,
child: JobsContent(),
),
);
},
child: AnimatedBuilder(
animation: _colorTween,
builder: (context, child) {
var v = _animationController.value;
var icon = v > 0.5 ? Ionicons.flash : Ionicons.flash_outline;
return Transform.scale(
scale: 1 + (v < 0.5 ? v : 1 - v) * 2,
child: Icon(
icon,
color: _colorTween.value,
),
);
}),
),
);
}
}

View File

@ -1,14 +1,14 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:selfprivacy/logic/api_maps/server.dart'; import 'package:selfprivacy/logic/api_maps/server.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart'; import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart';
import 'package:selfprivacy/ui/pages/more/more.dart'; import 'package:selfprivacy/ui/pages/more/more.dart';
import 'package:selfprivacy/ui/pages/providers/providers.dart'; import 'package:selfprivacy/ui/pages/providers/providers.dart';
import 'package:selfprivacy/ui/pages/services/services.dart'; import 'package:selfprivacy/ui/pages/services/services.dart';
import 'package:selfprivacy/ui/pages/users/users.dart'; import 'package:selfprivacy/ui/pages/users/users.dart';
import '../components/pre_styled_buttons/flashFab.dart';
class RootPage extends StatefulWidget { class RootPage extends StatefulWidget {
const RootPage({Key? key}) : super(key: key); const RootPage({Key? key}) : super(key: key);
@ -53,6 +53,7 @@ class _RootPageState extends State<RootPage>
bottomNavigationBar: BrandTabBar( bottomNavigationBar: BrandTabBar(
controller: tabController, controller: tabController,
), ),
floatingActionButton: BrandFab(),
), ),
); );
} }

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/FilledButton.dart';
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
class RecoveryDomain extends StatelessWidget { class RecoveryDomain extends StatelessWidget {
@ -29,18 +29,17 @@ class RecoveryDomain extends StatelessWidget {
), ),
), ),
SizedBox(height: 16), SizedBox(height: 16),
BrandButton.rised( FilledButton(
title: "more.continue".tr(),
onPressed: formCubitState.isSubmitting onPressed: formCubitState.isSubmitting
? null ? null
: () => context.read<RecoveryDomainFormCubit>().trySubmit(), : () => context.read<RecoveryDomainFormCubit>().trySubmit(),
text: "more.continue".tr(), )
),
], ],
heroTitle: "recovering.recovery_main_header".tr(), heroTitle: "recovering.recovery_main_header".tr(),
heroSubtitle: "recovering.domain_recovery_description".tr(), heroSubtitle: "recovering.domain_recovery_description".tr(),
hasBackButton: true, hasBackButton: true,
hasFlashButton: false, hasFlashButton: false,
heroIcon: Icons.link,
); );
}), }),
); );