ML-117 Improve description of paid features (#119)

* wip

* added `LightmeterProSettingsSection`

* hide Pro section on purchase

* `ElevatedButton` -> `FilledButton`

* moved Pro description to iap/README.md

* intl

* disable only list tiles

* show iap dialog on every iap list tile
This commit is contained in:
Vadim 2023-09-17 22:29:02 +02:00 committed by GitHub
parent cc9f162933
commit 0fbf252d9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 142 additions and 30 deletions

17
iap/README.md Normal file
View file

@ -0,0 +1,17 @@
# Lightmeter Pro
### Equipment profiles
Each equipment profile allows you to select:
- Aperture values and shutter speeds, that your lens and camera have
- ND filters, that fit the chosen lens
- ISO values, that your camera supports
Creating multiple profiles for different cameras and lenses allows you to easily switch between them and always have the relevant readings.
### Films in use
Select the films that you usually use. Selecting one will apply a correction to shutter speeds greater than 1" to compensate for the reciprocity failure.
Each equipment profile allows you to select:\n- Aperture values and shutter speeds, that your lens and camera have\n- ND filters, that fit the chosen lens\n- ISO values, that your camera supports\nCreating multiple profiles for different cameras and lenses allows you to easily switch between them and always have the relevant readings!

View file

@ -92,5 +92,9 @@
"type": "String"
}
}
}
}
},
"buyLightmeterPro": "Buy Lightmeter Pro",
"lightmeterPro": "Lightmeter Pro",
"lightmeterProDescription": "Unlocks extra features, such as equipment profiles containing filters for aperture, shutter speed, and more; and a list of films with compensation for what's known as reciprocity failure.\n\nThe source code of Lightmeter is available on GitHub. You are welcome to compile it yourself. However, if you want to support the development and receive new features and updates, consider purchasing Lightmeter Pro.",
"buy": "Buy"
}

View file

@ -92,5 +92,9 @@
"type": "String"
}
}
}
}
},
"buyLightmeterPro": "Acheter Lightmeter Pro",
"lightmeterPro": "Lightmeter Pro",
"lightmeterProDescription": "Déverrouille des fonctionnalités supplémentaires, telles que des profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec une compensation pour ce que l'on appelle l'échec de réciprocité.\n\nLe code source du Lightmeter est disponible sur GitHub. Vous pouvez le compiler vous-même. Cependant, si vous souhaitez soutenir le développement et recevoir de nouvelles fonctionnalités et mises à jour, envisagez d'acheter Lightmeter Pro.",
"buy": "Acheter"
}

View file

@ -92,5 +92,9 @@
"type": "String"
}
}
}
}
},
"buyLightmeterPro": "Купить Lightmeter Pro",
"lightmeterPro": "Lightmeter Pro",
"lightmeterProDescription": "Даёт доступ к таким функциям как профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений, а также набору пленок с компенсацией эффекта Шварцшильда.\n\nИсходный код Lightmeter доступен на GitHub. Вы можете собрать его самостоятельно. Однако если вы хотите поддержать разработку и получать новые функции и обновления, то приобретите Lightmeter Pro.",
"buy": "Купить"
}

View file

@ -92,5 +92,9 @@
"type": "String"
}
}
}
}
},
"buyLightmeterPro": "Buy Lightmeter Pro",
"lightmeterPro": "Lightmeter Pro",
"lightmeterProDescription": "Unlocks extra features, such as equipment profiles containing filters for aperture, shutter speed, and more; and a list of films with compensation for what's known as reciprocity failure.\n\nThe source code of Lightmeter is available on GitHub. You are welcome to compile it yourself. However, if you want to support the development and receive new features and updates, consider purchasing Lightmeter Pro.",
"buy": "Buy"
}

View file

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
import 'package:lightmeter/screens/settings/components/utils/show_buy_pro_dialog.dart';
class BuyProListTile extends StatelessWidget {
const BuyProListTile({super.key});
@override
Widget build(BuildContext context) {
return IAPListTile(
leading: const Icon(Icons.star),
title: Text(S.of(context).buyLightmeterPro),
onTap: () {
showBuyProDialog(context);
},
showPendingTrailing: true,
);
}
}

View file

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart';
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
class LightmeterProSettingsSection extends StatelessWidget {
const LightmeterProSettingsSection({super.key});
@override
Widget build(BuildContext context) {
return SettingsSection(
title: S.of(context).lightmeterPro,
children: const [BuyProListTile()],
);
}
}

View file

@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/settings/components/utils/show_buy_pro_dialog.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
/// Depends on the product status and replaces [onTap] with purchase callback
@ -8,26 +10,37 @@ class IAPListTile extends StatelessWidget {
final Icon leading;
final Text title;
final VoidCallback onTap;
final bool showPendingTrailing;
const IAPListTile({
this.product = IAPProductType.paidFeatures,
required this.leading,
required this.title,
required this.onTap,
this.showPendingTrailing = false,
super.key,
});
@override
Widget build(BuildContext context) {
final status = IAPProducts.productOf(context, IAPProductType.paidFeatures)?.status;
final isPending = status == IAPProductStatus.purchased || status == null;
return ListTile(
leading: leading,
title: title,
onTap: switch (IAPProducts.productOf(context, product)?.status) {
IAPProductStatus.purchasable => () => IAPProductsProvider.of(context).buy(product),
onTap: switch (status) {
IAPProductStatus.purchasable => () => showBuyProDialog(context),
IAPProductStatus.pending => null,
IAPProductStatus.purchased => onTap,
null => null,
},
trailing: showPendingTrailing && isPending
? const SizedBox(
height: Dimens.grid24,
width: Dimens.grid24,
child: CircularProgressIndicator(),
)
: null,
);
}
}

View file

@ -4,12 +4,10 @@ import 'package:lightmeter/res/dimens.dart';
class SettingsSection extends StatelessWidget {
final String title;
final List<Widget> children;
final bool enabled;
const SettingsSection({
required this.title,
required this.children,
this.enabled = true,
super.key,
});
@ -25,25 +23,22 @@ class SettingsSection extends StatelessWidget {
child: Card(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
child: Opacity(
opacity: enabled ? Dimens.enabledOpacity : Dimens.disabledOpacity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
child: Text(
title,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(color: Theme.of(context).colorScheme.onSurface),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
child: Text(
title,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(color: Theme.of(context).colorScheme.onSurface),
),
...children,
],
),
),
...children,
],
),
),
),

View file

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
Future<void> showBuyProDialog(BuildContext context) {
return showDialog(
context: context,
builder: (_) => AlertDialog(
icon: const Icon(Icons.star),
titlePadding: Dimens.dialogIconTitlePadding,
title: Text(S.of(context).lightmeterPro),
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL),
content: SingleChildScrollView(child: Text(S.of(context).lightmeterProDescription)),
actionsPadding: Dimens.dialogActionsPadding,
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: Text(S.of(context).cancel),
),
FilledButton(
onPressed: () {
Navigator.of(context).pop();
IAPProductsProvider.of(context).buy(IAPProductType.paidFeatures);
},
child: Text(S.of(context).buy),
),
],
),
);
}

View file

@ -3,10 +3,12 @@ import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/settings/components/about/widget_settings_section_about.dart';
import 'package:lightmeter/screens/settings/components/equipment/widget_settings_section_equipment.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:m3_lightmeter_iap/m3_lightmeter_iap.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key});
@ -43,6 +45,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
SliverList(
delegate: SliverChildListDelegate(
<Widget>[
if (!IAPProducts.isPurchased(context, IAPProductType.paidFeatures))
const LightmeterProSettingsSection(),
const MeteringSettingsSection(),
const EquipmentSettingsSection(),
const GeneralSettingsSection(),