diff --git a/lib/data/models/app_feature.dart b/lib/data/models/app_feature.dart new file mode 100644 index 0000000..ef1e73e --- /dev/null +++ b/lib/data/models/app_feature.dart @@ -0,0 +1,26 @@ +enum AppFeature { + cameraMetering, + ndFilters, + theming, + spotMetering, + histogram, + listOfFilms, + equipmentProfiles, + timer, + mainScreenCustomization; + + String get name { + return toString().replaceAll(runtimeType.toString(), '').replaceAll('.', ''); + } + + bool get isFree { + switch (this) { + case AppFeature.cameraMetering: + case AppFeature.ndFilters: + case AppFeature.theming: + return true; + default: + return false; + } + } +} diff --git a/lib/screens/pro_features/screen_pro_features.dart b/lib/screens/pro_features/screen_pro_features.dart new file mode 100644 index 0000000..8abfe3a --- /dev/null +++ b/lib/screens/pro_features/screen_pro_features.dart @@ -0,0 +1,231 @@ +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/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 { + 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(), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: Dimens.grid16)), + ], + ), + ), + 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)), + ), + ), + ], + ), + ); + } +} + +class _FeaturesHeader extends StatelessWidget { + const _FeaturesHeader(); + + @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'), + ), + ), + ), + _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), + ], + ); + } +} + +class _FeatureItem extends StatelessWidget { + final AppFeature feature; + + const _FeatureItem({ + required this.feature, + }); + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: Dimens.grid48), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Dimens.paddingM, + vertical: Dimens.paddingS, + ), + child: Text( + feature.name, + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ), + ), + Opacity( + opacity: feature.isFree ? 1 : 0, + child: const _CheckBox(highlight: false), + ), + _FeatureHighlight( + highlight: true, + roundedBottom: feature == AppFeature.values.last, + child: const _CheckBox(highlight: true), + ), + const SizedBox(width: Dimens.grid16), + ], + ), + ), + ); + } +} + +class _FeatureHighlight extends StatelessWidget { + static double? _freeWidth; + static double? _proWidth; + + final bool highlight; + final bool roundedTop; + final bool roundedBottom; + final Widget child; + + const _FeatureHighlight({ + this.highlight = false, + this.roundedTop = false, + this.roundedBottom = false, + required this.child, + }); + + @override + Widget build(BuildContext context) { + _FeatureHighlight._freeWidth ??= textSize( + 'Free', + Theme.of(context).textTheme.bodyMedium, + MediaQuery.sizeOf(context).width, + ).width; + _FeatureHighlight._proWidth ??= textSize( + 'Pro', + Theme.of(context).textTheme.bodyMedium, + MediaQuery.sizeOf(context).width, + ).width; + return Container( + constraints: + BoxConstraints(minWidth: (highlight ? _FeatureHighlight._proWidth : _FeatureHighlight._freeWidth) ?? 0.0), + decoration: BoxDecoration( + color: highlight ? Theme.of(context).colorScheme.secondaryContainer : null, + borderRadius: roundedTop + ? const BorderRadius.only( + topLeft: Radius.circular(Dimens.borderRadiusM), + topRight: Radius.circular(Dimens.borderRadiusM), + ) + : roundedBottom + ? const BorderRadius.only( + bottomLeft: Radius.circular(Dimens.borderRadiusM), + bottomRight: Radius.circular(Dimens.borderRadiusM), + ) + : null, + ), + child: child, + ); + } +} + +class _CheckBox extends StatelessWidget { + final bool highlight; + + const _CheckBox({required this.highlight}); + + @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, + ), + ); + } +} diff --git a/lib/utils/text_height.dart b/lib/utils/text_height.dart index f5a0fe1..6c3afa4 100644 --- a/lib/utils/text_height.dart +++ b/lib/utils/text_height.dart @@ -17,6 +17,13 @@ double textHeight( String text, TextStyle? style, double maxWidth, +) => + textSize(text, style, maxWidth).height; + +Size textSize( + String text, + TextStyle? style, + double maxWidth, ) { final TextPainter titlePainter = TextPainter( text: TextSpan( @@ -25,5 +32,5 @@ double textHeight( ), textDirection: TextDirection.ltr, )..layout(maxWidth: maxWidth); - return titlePainter.height; + return titlePainter.size; }