First steps to move to Material You
parent
8b5bf24f3a
commit
b4145dc5c8
|
@ -13,7 +13,6 @@ import 'package:timezone/data/latest.dart' as tz;
|
|||
|
||||
import 'config/bloc_config.dart';
|
||||
import 'config/bloc_observer.dart';
|
||||
import 'config/brand_theme.dart';
|
||||
import 'config/get_it_config.dart';
|
||||
import 'config/localization.dart';
|
||||
import 'logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
|
@ -45,7 +44,8 @@ void main() async {
|
|||
);
|
||||
|
||||
BlocOverrides.runZoned(
|
||||
() => runApp(Localization(child: MyApp(
|
||||
() => runApp(Localization(
|
||||
child: MyApp(
|
||||
lightThemeData: lightThemeData,
|
||||
darkThemeData: darkThemeData,
|
||||
))),
|
||||
|
@ -81,7 +81,8 @@ class MyApp extends StatelessWidget {
|
|||
title: 'SelfPrivacy',
|
||||
theme: lightThemeData,
|
||||
darkTheme: darkThemeData,
|
||||
themeMode: appSettings.isDarkModeOn ? ThemeMode.dark : ThemeMode.light,
|
||||
themeMode:
|
||||
appSettings.isDarkModeOn ? ThemeMode.dark : ThemeMode.light,
|
||||
home: appSettings.isOnbordingShowing
|
||||
? OnboardingPage(nextPage: InitializingPage())
|
||||
: RootPage(),
|
||||
|
|
|
@ -28,13 +28,18 @@ abstract class AppThemeFactory {
|
|||
|
||||
if (Platform.isLinux) {
|
||||
GtkThemeData themeData = await GtkThemeData.initialize();
|
||||
final isGtkDark =
|
||||
Color(themeData.theme_base_color).computeLuminance() < 0.5;
|
||||
final isInverseNeeded = isGtkDark != isDark;
|
||||
gtkColorsScheme = ColorScheme.fromSeed(
|
||||
seedColor: Color(themeData.theme_selected_bg_color),
|
||||
brightness: Color(themeData.theme_base_color).computeLuminance() > 0.5
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
background: Color(themeData.theme_bg_color),
|
||||
surface: Color(themeData.theme_base_color),
|
||||
brightness: brightness,
|
||||
background: isInverseNeeded
|
||||
? Color(themeData.theme_base_color)
|
||||
: Color(themeData.theme_bg_color),
|
||||
surface: isInverseNeeded
|
||||
? Color(themeData.theme_bg_color)
|
||||
: Color(themeData.theme_base_color),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -46,7 +51,8 @@ abstract class AppThemeFactory {
|
|||
brightness: brightness,
|
||||
);
|
||||
|
||||
final colorScheme = dynamicColorsScheme ?? gtkColorsScheme ?? fallbackColorScheme;
|
||||
final colorScheme =
|
||||
dynamicColorsScheme ?? gtkColorsScheme ?? fallbackColorScheme;
|
||||
|
||||
final appTypography = Typography.material2021();
|
||||
|
||||
|
@ -55,6 +61,7 @@ abstract class AppThemeFactory {
|
|||
brightness: colorScheme.brightness,
|
||||
typography: appTypography,
|
||||
useMaterial3: true,
|
||||
scaffoldBackgroundColor: colorScheme.background,
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
backgroundColor: colorScheme.primary,
|
||||
|
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
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';
|
||||
|
||||
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');
|
||||
return _RisedButton(
|
||||
key: key,
|
||||
title: text,
|
||||
onPressed: onPressed,
|
||||
child: child,
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 48,
|
||||
minWidth: double.infinity,
|
||||
),
|
||||
child: FilledButton(
|
||||
key: key,
|
||||
title: text,
|
||||
onPressed: onPressed,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -26,10 +32,12 @@ class BrandButton {
|
|||
required VoidCallback onPressed,
|
||||
required String title,
|
||||
}) =>
|
||||
_TextButton(
|
||||
key: key,
|
||||
title: title,
|
||||
onPressed: onPressed,
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 48,
|
||||
minWidth: double.infinity,
|
||||
),
|
||||
child: TextButton(onPressed: onPressed, child: Text(title)),
|
||||
);
|
||||
|
||||
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 {
|
||||
const _IconTextButton({Key? key, this.onPressed, this.title, this.icon})
|
||||
: super(key: key);
|
||||
|
|
|
@ -37,20 +37,27 @@ class BrandHeroScreen extends StatelessWidget {
|
|||
padding: EdgeInsets.all(16.0),
|
||||
children: <Widget>[
|
||||
if (heroIcon != null)
|
||||
Icon(
|
||||
heroIcon,
|
||||
size: 48.0,
|
||||
Container(
|
||||
child: Icon(
|
||||
heroIcon,
|
||||
size: 48.0,
|
||||
),
|
||||
alignment: Alignment.bottomLeft,
|
||||
),
|
||||
SizedBox(height: 16.0),
|
||||
SizedBox(height: 8.0),
|
||||
if (heroTitle != null)
|
||||
Text(heroTitle!,
|
||||
style: Theme.of(context).textTheme.headline2,
|
||||
textAlign: TextAlign.center),
|
||||
Text(
|
||||
heroTitle!,
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
color: Colors.black,
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
SizedBox(height: 8.0),
|
||||
if (heroSubtitle != null)
|
||||
Text(heroSubtitle!,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center),
|
||||
textAlign: TextAlign.start),
|
||||
SizedBox(height: 16.0),
|
||||
...children,
|
||||
],
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||
|
||||
final _kBottomTabBarHeight = 51;
|
||||
|
||||
class BrandTabBar extends StatefulWidget {
|
||||
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.services'.tr(), BrandIcons.box, 1),
|
||||
_getIconButton('basis.users'.tr(), BrandIcons.users, 2),
|
||||
_getIconButton('basis.more'.tr(), BrandIcons.menu, 3),
|
||||
_getIconButton('basis.more'.tr(), Icons.menu_rounded, 3),
|
||||
],
|
||||
onDestinationSelected: (index) {
|
||||
widget.controller!.animateTo(index);
|
||||
},
|
||||
selectedIndex: currentIndex ?? 0,
|
||||
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
_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(
|
||||
icon: Icon(iconData),
|
||||
label: label,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.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/pages/more/more.dart';
|
||||
import 'package:selfprivacy/ui/pages/providers/providers.dart';
|
||||
import 'package:selfprivacy/ui/pages/services/services.dart';
|
||||
import 'package:selfprivacy/ui/pages/users/users.dart';
|
||||
|
||||
import '../components/pre_styled_buttons/flashFab.dart';
|
||||
|
||||
class RootPage extends StatefulWidget {
|
||||
const RootPage({Key? key}) : super(key: key);
|
||||
|
||||
|
@ -53,6 +53,7 @@ class _RootPageState extends State<RootPage>
|
|||
bottomNavigationBar: BrandTabBar(
|
||||
controller: tabController,
|
||||
),
|
||||
floatingActionButton: BrandFab(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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/forms/factories/field_cubit_factory.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';
|
||||
|
||||
class RecoveryDomain extends StatelessWidget {
|
||||
|
@ -29,18 +29,17 @@ class RecoveryDomain extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
BrandButton.rised(
|
||||
FilledButton(
|
||||
title: "more.continue".tr(),
|
||||
onPressed: formCubitState.isSubmitting
|
||||
? null
|
||||
: () => context.read<RecoveryDomainFormCubit>().trySubmit(),
|
||||
text: "more.continue".tr(),
|
||||
),
|
||||
)
|
||||
],
|
||||
heroTitle: "recovering.recovery_main_header".tr(),
|
||||
heroSubtitle: "recovering.domain_recovery_description".tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
heroIcon: Icons.link,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue