From 3a9baf42d674fd0ecf136c392b9df9c34e1c07a6 Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:53:48 +0200 Subject: [PATCH] finalized `ProFeaturesScreen` layout --- lib/l10n/intl_en.arb | 10 +- .../lightmeter_pro/widget_lightmeter_pro.dart | 2 +- .../pro_features/screen_pro_features.dart | 199 +++++++++--------- .../buy_pro/widget_list_tile_buy_pro.dart | 2 +- ...idget_settings_section_lightmeter_pro.dart | 2 +- .../widget_dialog_pro_features.dart | 8 +- ..._settings_section_lightmeter_pro_test.dart | 4 +- 7 files changed, 118 insertions(+), 109 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 0c3ad42..6ef9401 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -103,11 +103,13 @@ } } }, - "proFeatures": "Pro features", - "unlockProFeatures": "Unlock Pro features", - "unlockProFeaturesDescription": "Unlock professional features:\n \u2022 Equipment profiles containing filters for aperture, shutter speed, and more\n \u2022 List of films with compensation for what's known as reciprocity failure\n \u2022 Spot metering & Histogram\n \u2022 And more!\n\nBy unlocking Pro features you support the development and make it possible to add new features to the app.", "unlock": "Unlock", - "proFeaturesSupportText": "By purchasing you support the development and make it possible to add new features to the app.", + "proFeaturesTitle": "Lightmeter Pro", + "featuresFree": "Free", + "featuresPro": "Pro", + "proFeaturesPromoText": "Lightmeter Pro delivers everything you need to get the best shots!", + "proFeaturesWhatsIncluded": "What's included?", + "proFeaturesSupportText": "By purchasing Lightmeter Pro you support the development and make it possible to add new features to the app.", "unlockFor": "Unlock for {price}", "@unlockFor": { "price": { diff --git a/lib/screens/metering/components/shared/readings_container/components/lightmeter_pro/widget_lightmeter_pro.dart b/lib/screens/metering/components/shared/readings_container/components/lightmeter_pro/widget_lightmeter_pro.dart index 9d5218a..b5fe77d 100644 --- a/lib/screens/metering/components/shared/readings_container/components/lightmeter_pro/widget_lightmeter_pro.dart +++ b/lib/screens/metering/components/shared/readings_container/components/lightmeter_pro/widget_lightmeter_pro.dart @@ -15,7 +15,7 @@ class LightmeterProAnimatedDialog extends StatelessWidget { textColor: Theme.of(context).colorScheme.onErrorContainer, values: [ ReadingValue( - label: S.of(context).proFeatures, + label: S.of(context).proFeaturesTitle, value: S.of(context).unlock, ), ], diff --git a/lib/screens/pro_features/screen_pro_features.dart b/lib/screens/pro_features/screen_pro_features.dart index 8abfe3a..c44ba00 100644 --- a/lib/screens/pro_features/screen_pro_features.dart +++ b/lib/screens/pro_features/screen_pro_features.dart @@ -1,69 +1,90 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/app_feature.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/services_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/theme.dart'; -import 'package:lightmeter/screens/settings/components/about/widget_settings_section_about.dart'; -import 'package:lightmeter/screens/settings/components/general/widget_settings_section_general.dart'; -import 'package:lightmeter/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart'; -import 'package:lightmeter/screens/settings/components/metering/widget_settings_section_metering.dart'; -import 'package:lightmeter/screens/settings/components/theme/widget_settings_section_theme.dart'; -import 'package:lightmeter/screens/settings/flow_settings.dart'; import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; -import 'package:lightmeter/utils/context_utils.dart'; import 'package:lightmeter/utils/text_height.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; -class ProFeaturesScreen extends StatefulWidget { +class ProFeaturesScreen extends StatelessWidget { const ProFeaturesScreen({super.key}); - @override - State createState() => _ProFeaturesScreenState(); -} - -class _ProFeaturesScreenState extends State { @override Widget build(BuildContext context) { - return ScaffoldMessenger( - child: Column( - children: [ - Expanded( - child: SliverScreen( - title: S.of(context).proFeatures, - slivers: [ - const SliverToBoxAdapter(child: _FeaturesHeader()), - SliverList.separated( - itemCount: AppFeature.values.length, - itemBuilder: (context, index) { - return _FeatureItem(feature: AppFeature.values[index]); - }, - separatorBuilder: (_, __) => const Padding( - padding: EdgeInsets.symmetric(horizontal: Dimens.paddingM), - child: Divider(), + return Column( + children: [ + Expanded( + child: SliverScreen( + title: S.of(context).proFeaturesTitle, + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Dimens.paddingM, + vertical: Dimens.paddingS, + ), + child: Text( + S.of(context).proFeaturesPromoText, + style: Theme.of(context).textTheme.bodyLarge, ), ), - const SliverToBoxAdapter(child: SizedBox(height: Dimens.grid16)), - ], - ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB( + Dimens.paddingM, + Dimens.paddingM, + Dimens.paddingM, + 0, + ), + child: Text( + S.of(context).proFeaturesWhatsIncluded, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + ), + const SliverToBoxAdapter(child: _FeaturesHeader()), + SliverList.separated( + itemCount: AppFeature.values.length, + itemBuilder: (context, index) { + return _FeatureItem(feature: AppFeature.values[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), + ), + ), + ], ), - Container( - color: Theme.of(context).colorScheme.surfaceElevated1, - width: MediaQuery.sizeOf(context).width, - padding: EdgeInsets.fromLTRB( - Dimens.paddingM, - Dimens.paddingM, - Dimens.paddingM, - MediaQuery.paddingOf(context).bottom, - ), - child: FilledButton( - onPressed: () {}, - child: Text(S.of(context).unlockFor(IAPProducts.productOf(context, IAPProductType.paidFeatures)!.price)), - ), + ), + Container( + color: Theme.of(context).colorScheme.surfaceElevated1, + width: MediaQuery.sizeOf(context).width, + padding: EdgeInsets.fromLTRB( + Dimens.paddingM, + Dimens.paddingM, + Dimens.paddingM, + Dimens.paddingM + MediaQuery.paddingOf(context).bottom, ), - ], - ), + child: FilledButton( + onPressed: () { + ServicesProvider.maybeOf(context) + ?.analytics + .setCustomKey('iap_product_type', IAPProductType.paidFeatures.storeId); + IAPProductsProvider.maybeOf(context)?.buy(IAPProductType.paidFeatures); + }, + child: Text(S.of(context).unlockFor(IAPProducts.productOf(context, IAPProductType.paidFeatures)!.price)), + ), + ), + ], ); } } @@ -73,41 +94,25 @@ class _FeaturesHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - children: [ - const Spacer(), - _FeatureHighlight( - child: Center( - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: Dimens.paddingM, - vertical: Dimens.paddingS, - ), - child: Text('Free'), + return Padding( + padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), + child: Row( + children: [ + const Spacer(), + _FeatureHighlight(child: Text(S.of(context).featuresFree)), + _FeatureHighlight( + roundedTop: true, + highlight: true, + child: Text( + S.of(context).featuresPro, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Theme.of(context).colorScheme.onSecondaryContainer), ), ), - ), - _FeatureHighlight( - roundedTop: true, - highlight: true, - child: Center( - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: Dimens.paddingM, - vertical: Dimens.paddingS, - ), - child: Text( - 'Pro', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Theme.of(context).colorScheme.onSecondaryContainer), - ), - ), - ), - ), - const SizedBox(width: Dimens.grid16), - ], + ], + ), ); } } @@ -145,7 +150,9 @@ class _FeatureItem extends StatelessWidget { ), Opacity( opacity: feature.isFree ? 1 : 0, - child: const _CheckBox(highlight: false), + child: const _FeatureHighlight( + child: _CheckBox(highlight: false), + ), ), _FeatureHighlight( highlight: true, @@ -179,18 +186,24 @@ class _FeatureHighlight extends StatelessWidget { @override Widget build(BuildContext context) { _FeatureHighlight._freeWidth ??= textSize( - 'Free', + S.of(context).featuresFree, Theme.of(context).textTheme.bodyMedium, MediaQuery.sizeOf(context).width, ).width; _FeatureHighlight._proWidth ??= textSize( - 'Pro', + S.of(context).featuresPro, Theme.of(context).textTheme.bodyMedium, MediaQuery.sizeOf(context).width, ).width; return Container( - constraints: - BoxConstraints(minWidth: (highlight ? _FeatureHighlight._proWidth : _FeatureHighlight._freeWidth) ?? 0.0), + constraints: BoxConstraints( + minWidth: + ((highlight ? _FeatureHighlight._proWidth : _FeatureHighlight._freeWidth) ?? 0.0) + Dimens.paddingM * 2, + ), + padding: const EdgeInsets.symmetric( + horizontal: Dimens.paddingM, + vertical: Dimens.paddingS, + ), decoration: BoxDecoration( color: highlight ? Theme.of(context).colorScheme.secondaryContainer : null, borderRadius: roundedTop @@ -217,15 +230,9 @@ class _CheckBox extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: Dimens.paddingM, - vertical: Dimens.paddingS, - ), - child: Icon( - Icons.check_outlined, - color: highlight ? Theme.of(context).colorScheme.onSecondaryContainer : null, - ), + return Icon( + Icons.check_outlined, + color: highlight ? Theme.of(context).colorScheme.onSecondaryContainer : null, ); } } diff --git a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart index cc026a9..f0cbfcb 100644 --- a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart +++ b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart @@ -13,7 +13,7 @@ class BuyProListTile extends StatelessWidget { final isPending = status == IAPProductStatus.purchased || status == null; return ListTile( leading: const Icon(Icons.star_outlined), - title: Text(S.of(context).unlockProFeatures), + title: Text(S.of(context).proFeaturesTitle), onTap: !isPending ? () { showDialog( diff --git a/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart b/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart index 57d5d14..872f759 100644 --- a/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart +++ b/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart @@ -9,7 +9,7 @@ class LightmeterProSettingsSection extends StatelessWidget { @override Widget build(BuildContext context) { return SettingsSection( - title: S.of(context).proFeatures, + title: S.of(context).proFeaturesTitle, children: const [BuyProListTile()], ); } diff --git a/lib/screens/shared/pro_features_dialog/widget_dialog_pro_features.dart b/lib/screens/shared/pro_features_dialog/widget_dialog_pro_features.dart index 5683301..15442cd 100644 --- a/lib/screens/shared/pro_features_dialog/widget_dialog_pro_features.dart +++ b/lib/screens/shared/pro_features_dialog/widget_dialog_pro_features.dart @@ -12,10 +12,10 @@ class ProFeaturesDialog extends StatelessWidget { double height(BuildContext context) => TransparentDialog.height( context, - title: S.of(context).proFeatures, + title: S.of(context).proFeaturesTitle, contextHeight: dialogTextHeight( context, - S.of(context).unlockProFeaturesDescription, + S.of(context).proFeaturesPromoText, Theme.of(context).textTheme.bodyMedium, Dimens.paddingL * 2, ), @@ -25,14 +25,14 @@ class ProFeaturesDialog extends StatelessWidget { Widget build(BuildContext context) { return TransparentDialog( icon: Icons.star_outlined, - title: S.of(context).proFeatures, + title: S.of(context).proFeaturesTitle, scrollableContent: false, content: Flexible( child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), child: Text( - S.of(context).unlockProFeaturesDescription, + S.of(context).proFeaturesPromoText, style: Theme.of(context).textTheme.bodyMedium, ), ), diff --git a/test/screens/settings/components/widget_settings_section_lightmeter_pro_test.dart b/test/screens/settings/components/widget_settings_section_lightmeter_pro_test.dart index b4d2733..52d017f 100644 --- a/test/screens/settings/components/widget_settings_section_lightmeter_pro_test.dart +++ b/test/screens/settings/components/widget_settings_section_lightmeter_pro_test.dart @@ -34,7 +34,7 @@ void main() { await tester.tap(find.byType(BuyProListTile)); await tester.pumpAndSettle(); expect(find.byType(TransparentDialog), findsOneWidget); - expect(find.text(S.current.proFeatures), findsNWidgets(2)); + expect(find.text(S.current.proFeaturesTitle), findsNWidgets(2)); expect(find.text(S.current.cancel), findsOneWidget); expect(find.text(S.current.unlockFor(_price)), findsOneWidget); @@ -51,7 +51,7 @@ void main() { await tester.tap(find.byType(BuyProListTile)); await tester.pumpAndSettle(); expect(find.byType(TransparentDialog), findsOneWidget); - expect(find.text(S.current.proFeatures), findsNWidgets(2)); + expect(find.text(S.current.proFeaturesTitle), findsNWidgets(2)); expect(find.text(S.current.cancel), findsOneWidget); expect(find.text(S.current.unlockFor(_price)), findsOneWidget);