From 260af158abb47a0b68244278cd5f554dea37bfda Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Sun, 3 Aug 2025 19:19:38 +0200 Subject: [PATCH] theme --- lib/res/theme.dart | 10 ++ .../widget_offering_lightmeter_pro.dart | 146 +++++++++++------- .../lightmeter_pro/screen_lightmeter_pro.dart | 94 +++++------ .../widget_dialog_animated.dart | 2 +- .../button/widget_button_filled_large.dart | 24 +++ .../shared/sliver_screen/screen_sliver.dart | 3 + 6 files changed, 179 insertions(+), 100 deletions(-) create mode 100644 lib/screens/shared/button/widget_button_filled_large.dart diff --git a/lib/res/theme.dart b/lib/res/theme.dart index 0362dac..7c8454a 100644 --- a/lib/res/theme.dart +++ b/lib/res/theme.dart @@ -64,6 +64,16 @@ ThemeData themeFrom(Color primaryColor, Brightness brightness) { scaffoldBackgroundColor: scheme.surface, ); return theme.copyWith( + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + minimumSize: const Size.square(Dimens.grid56), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(Dimens.borderRadiusM), + ), + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, + ), + ), listTileTheme: ListTileThemeData( style: ListTileStyle.list, iconColor: scheme.onSurface, diff --git a/lib/screens/lightmeter_pro/components/offering/widget_offering_lightmeter_pro.dart b/lib/screens/lightmeter_pro/components/offering/widget_offering_lightmeter_pro.dart index 7725777..f7c9500 100644 --- a/lib/screens/lightmeter_pro/components/offering/widget_offering_lightmeter_pro.dart +++ b/lib/screens/lightmeter_pro/components/offering/widget_offering_lightmeter_pro.dart @@ -1,19 +1,19 @@ import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/theme.dart'; +import 'package:lightmeter/screens/shared/button/widget_button_filled_large.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; -class LightmeterProBottomControls extends StatefulWidget { - const LightmeterProBottomControls({super.key}); +class LightmeterProOffering extends StatefulWidget { + const LightmeterProOffering({super.key}); @override - State createState() => _LightmeterProBottomControlsState(); + State createState() => _LightmeterProOfferingState(); } -class _LightmeterProBottomControlsState extends State { +class _LightmeterProOfferingState extends State { late final Future> productsFuture; bool _isLoading = true; IAPProduct? monthly; @@ -36,10 +36,24 @@ class _LightmeterProBottomControlsState extends State onProductSelected(monthly), ), const SizedBox(height: Dimens.grid8), if (lifetime case final lifetime?) - _OfferingItems( - product: lifetime, + _ProductItem( + title: S.of(context).lifetime, + price: lifetime.price, isSelected: selected == lifetime, onPressed: () => onProductSelected(lifetime), ), @@ -121,45 +133,66 @@ class _Products extends StatelessWidget { } } -class _OfferingItems extends StatelessWidget { - const _OfferingItems({ - required this.product, +class _ProductItem extends StatelessWidget { + const _ProductItem({ + required this.title, + required this.price, required this.isSelected, required this.onPressed, }); - final IAPProduct product; + final String title; + final String price; final bool isSelected; final VoidCallback onPressed; @override Widget build(BuildContext context) { - return Card( - color: Theme.of(context).colorScheme.primaryContainer, - shape: isSelected - ? RoundedRectangleBorder( - borderRadius: BorderRadius.circular(Dimens.borderRadiusL), - side: BorderSide( + return DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(Dimens.borderRadiusM), + + /// TODO: fix color alteration. It is a bit darker here for some reason + /// than reading containers + color: isSelected + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.primaryContainer, + border: isSelected + ? Border.all( color: Theme.of(context).colorScheme.primary, - width: 3, - ), - ) - : null, + width: 2, + ) + : null, + ), child: GestureDetector( + behavior: HitTestBehavior.translucent, onTap: onPressed, child: Padding( - padding: const EdgeInsets.symmetric(vertical: Dimens.paddingS), - child: ListTile( - title: AnimatedDefaultTextStyle( - duration: Dimens.durationM, - style: Theme.of(context).textTheme.bodyLarge!.boldIfSelected(isSelected), - child: Text(product.type.name), - ), - trailing: AnimatedDefaultTextStyle( - duration: Dimens.durationM, - style: Theme.of(context).textTheme.bodyMedium!.boldIfSelected(isSelected), - child: Text(product.price), - ), + /// [Radio] has 12pt paddings around the button. + /// [Dimens.paddingM] - 12pt = 4pt + padding: const EdgeInsets.fromLTRB( + Dimens.grid4, + Dimens.grid4, + Dimens.paddingM, + Dimens.grid4, + ), + child: Row( + children: [ + Radio( + value: isSelected, + groupValue: true, + onChanged: (_) => onPressed(), + ), + _ProductAnimatedText( + title, + isSelected: isSelected, + ), + const Spacer(), + _ProductAnimatedText( + price, + isSelected: isSelected, + ), + ], ), ), ), @@ -167,18 +200,25 @@ class _OfferingItems extends StatelessWidget { } } -class _RestorePurchasesButton extends StatelessWidget { - const _RestorePurchasesButton(); +class _ProductAnimatedText extends StatelessWidget { + const _ProductAnimatedText(this.text, {required this.isSelected}); + + final String text; + final bool isSelected; @override Widget build(BuildContext context) { - return TextButton( - onPressed: () {}, - child: Text(S.of(context).restorePurchases), + return AnimatedDefaultTextStyle( + duration: Dimens.durationM, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w500, + color: + isSelected ? Theme.of(context).colorScheme.onPrimaryContainer : Theme.of(context).colorScheme.onSurface, + ), + child: Text(text), ); } } -extension on TextStyle { - TextStyle boldIfSelected(bool isSelected) => copyWith(fontWeight: isSelected ? FontWeight.w500 : FontWeight.normal); -} +/// rgba(212,227,252,255) #d4e3fc +/// \ No newline at end of file diff --git a/lib/screens/lightmeter_pro/screen_lightmeter_pro.dart b/lib/screens/lightmeter_pro/screen_lightmeter_pro.dart index 6281c05..a69b25f 100644 --- a/lib/screens/lightmeter_pro/screen_lightmeter_pro.dart +++ b/lib/screens/lightmeter_pro/screen_lightmeter_pro.dart @@ -6,6 +6,7 @@ import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/lightmeter_pro/components/offering/widget_offering_lightmeter_pro.dart'; import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; import 'package:lightmeter/utils/text_height.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; class LightmeterProScreen extends StatelessWidget { final features = @@ -15,55 +16,56 @@ class LightmeterProScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - children: [ - Expanded( - child: SliverScreen( - title: Text(S.of(context).proFeaturesTitle), - slivers: [ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(Dimens.paddingM), - child: Text( - S.of(context).proFeaturesPromoText, - style: Theme.of(context).textTheme.bodyLarge, - ), - ), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB( - Dimens.paddingM, - 0, - Dimens.paddingM, - Dimens.paddingS, - ), - child: Text( - S.of(context).proFeaturesWhatsIncluded, - style: Theme.of(context).textTheme.headlineSmall, - ), - ), - ), - const SliverToBoxAdapter(child: _FeaturesHeader()), - SliverList.separated( - itemCount: features.length, - itemBuilder: (_, index) => _FeatureItem(feature: features[index]), - separatorBuilder: (_, __) => const Padding( - padding: EdgeInsets.symmetric(horizontal: Dimens.paddingM), - child: Divider(), - ), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(Dimens.paddingM), - child: Text(S.of(context).proFeaturesSupportText), - ), - ), - ], + return SliverScreen( + title: Text(S.of(context).proFeaturesTitle), + appBarActions: [ + IconButton( + onPressed: IAPProductsProvider.of(context).restorePurchases, + icon: const Icon(Icons.restore), + tooltip: S.of(context).restorePurchases, + ), + ], + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(Dimens.paddingM), + child: Text( + S.of(context).proFeaturesPromoText, + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB( + Dimens.paddingM, + 0, + Dimens.paddingM, + Dimens.paddingS, + ), + child: Text( + S.of(context).proFeaturesWhatsIncluded, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + ), + const SliverToBoxAdapter(child: _FeaturesHeader()), + SliverList.separated( + itemCount: features.length, + itemBuilder: (_, index) => _FeatureItem(feature: features[index]), + separatorBuilder: (_, __) => const Padding( + padding: EdgeInsets.symmetric(horizontal: Dimens.paddingM), + child: Divider(), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(Dimens.paddingM), + child: Text(S.of(context).proFeaturesSupportText), ), ), - const LightmeterProBottomControls(), ], + bottomNavigationBar: const LightmeterProOffering(), ); } } diff --git a/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart b/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart index 2a0f50d..2fb8e30 100644 --- a/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart +++ b/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart @@ -128,7 +128,7 @@ class AnimatedDialogState extends State with SingleTickerProvide @override Widget build(BuildContext context) { - return InkWell( + return GestureDetector( key: _key, onTap: _openDialog, child: Opacity( diff --git a/lib/screens/shared/button/widget_button_filled_large.dart b/lib/screens/shared/button/widget_button_filled_large.dart new file mode 100644 index 0000000..8f7af2b --- /dev/null +++ b/lib/screens/shared/button/widget_button_filled_large.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/generated/l10n.dart'; + +class FilledButtonLarge extends StatelessWidget { + const FilledButtonLarge({ + required this.title, + required this.onPressed, + }); + + final String title; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return FilledButton( + style: Theme.of(context) + .filledButtonTheme + .style! + .copyWith(textStyle: WidgetStatePropertyAll(Theme.of(context).textTheme.titleMedium)), + onPressed: onPressed, + child: Text(S.of(context).continuePurchase), + ); + } +} diff --git a/lib/screens/shared/sliver_screen/screen_sliver.dart b/lib/screens/shared/sliver_screen/screen_sliver.dart index fd96546..cae353d 100644 --- a/lib/screens/shared/sliver_screen/screen_sliver.dart +++ b/lib/screens/shared/sliver_screen/screen_sliver.dart @@ -8,12 +8,14 @@ class SliverScreen extends StatelessWidget { final List appBarActions; final PreferredSizeWidget? bottom; final List slivers; + final Widget? bottomNavigationBar; const SliverScreen({ this.title, this.appBarActions = const [], this.bottom, required this.slivers, + this.bottomNavigationBar, super.key, }); @@ -34,6 +36,7 @@ class SliverScreen extends StatelessWidget { ], ), ), + bottomNavigationBar: bottomNavigationBar, ); } }