Compare commits

...

2 commits

Author SHA1 Message Date
Vadim
2e5a043c39 updated pro features description 2023-11-09 16:31:15 +01:00
Vadim
e5961118b0 Fixed remote config initalization 2023-11-09 16:12:57 +01:00
9 changed files with 86 additions and 19 deletions

View file

@ -30,7 +30,7 @@ class ApplicationWrapper extends StatelessWidget {
future: Future.wait<dynamic>([ future: Future.wait<dynamic>([
SharedPreferences.getInstance(), SharedPreferences.getInstance(),
const LightSensorService(LocalPlatform()).hasSensor(), const LightSensorService(LocalPlatform()).hasSensor(),
const RemoteConfigService().activeAndFetchFeatures(), if (env.buildType != BuildType.dev) const RemoteConfigService().activeAndFetchFeatures(),
]), ]),
builder: (_, snapshot) { builder: (_, snapshot) {
if (snapshot.data != null) { if (snapshot.data != null) {
@ -47,7 +47,8 @@ class ApplicationWrapper extends StatelessWidget {
userPreferencesService: userPreferencesService, userPreferencesService: userPreferencesService,
volumeEventsService: const VolumeEventsService(LocalPlatform()), volumeEventsService: const VolumeEventsService(LocalPlatform()),
child: RemoteConfigProvider( child: RemoteConfigProvider(
remoteConfigService: const RemoteConfigService(), remoteConfigService:
env.buildType != BuildType.dev ? const RemoteConfigService() : const MockRemoteConfigService(),
child: EquipmentProfileProvider( child: EquipmentProfileProvider(
storageService: iapService, storageService: iapService,
child: FilmsProvider( child: FilmsProvider(

View file

@ -1,5 +1,5 @@
enum Feature { unlockProFeaturesText } enum Feature { unlockProFeaturesText }
const featuresDefaultValues = { const featuresDefaultValues = {
Feature.unlockProFeaturesText: false, Feature.unlockProFeaturesText: true,
}; };

View file

@ -7,9 +7,26 @@ import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:lightmeter/data/models/feature.dart'; import 'package:lightmeter/data/models/feature.dart';
class RemoteConfigService { abstract class IRemoteConfigService {
const IRemoteConfigService();
Future<void> activeAndFetchFeatures();
Future<void> fetchConfig();
dynamic getValue(Feature feature);
Map<Feature, dynamic> getAll();
Stream<Set<Feature>> onConfigUpdated();
bool isEnabled(Feature feature);
}
class RemoteConfigService implements IRemoteConfigService {
const RemoteConfigService(); const RemoteConfigService();
@override
Future<void> activeAndFetchFeatures() async { Future<void> activeAndFetchFeatures() async {
final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance; final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance;
const cacheStaleDuration = kDebugMode ? Duration(minutes: 1) : Duration(hours: 12); const cacheStaleDuration = kDebugMode ? Duration(minutes: 1) : Duration(hours: 12);
@ -28,19 +45,22 @@ class RemoteConfigService {
log('Firebase remote config initialized successfully'); log('Firebase remote config initialized successfully');
} on FirebaseException catch (e) { } on FirebaseException catch (e) {
_logError('Firebase exception during Firebase Remote Config initialization: $e'); _logError('Firebase exception during Firebase Remote Config initialization: $e');
} on Exception catch (e) { } catch (e) {
_logError('Error during Firebase Remote Config initialization: $e'); _logError('Error during Firebase Remote Config initialization: $e');
} }
} }
@override
Future<void> fetchConfig() async { Future<void> fetchConfig() async {
// https://github.com/firebase/flutterfire/issues/6196#issuecomment-927751667 // https://github.com/firebase/flutterfire/issues/6196#issuecomment-927751667
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
await FirebaseRemoteConfig.instance.fetch(); await FirebaseRemoteConfig.instance.fetch();
} }
@override
dynamic getValue(Feature feature) => FirebaseRemoteConfig.instance.getValue(feature.name).toValue(feature); dynamic getValue(Feature feature) => FirebaseRemoteConfig.instance.getValue(feature.name).toValue(feature);
@override
Map<Feature, dynamic> getAll() { Map<Feature, dynamic> getAll() {
final Map<Feature, dynamic> result = {}; final Map<Feature, dynamic> result = {};
for (final value in FirebaseRemoteConfig.instance.getAll().entries) { for (final value in FirebaseRemoteConfig.instance.getAll().entries) {
@ -54,6 +74,7 @@ class RemoteConfigService {
return result; return result;
} }
@override
Stream<Set<Feature>> onConfigUpdated() => FirebaseRemoteConfig.instance.onConfigUpdated.asyncMap( Stream<Set<Feature>> onConfigUpdated() => FirebaseRemoteConfig.instance.onConfigUpdated.asyncMap(
(event) async { (event) async {
await FirebaseRemoteConfig.instance.activate(); await FirebaseRemoteConfig.instance.activate();
@ -69,6 +90,7 @@ class RemoteConfigService {
}, },
); );
@override
bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name); bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name);
void _logError(dynamic throwable, {StackTrace? stackTrace}) { void _logError(dynamic throwable, {StackTrace? stackTrace}) {
@ -76,6 +98,29 @@ class RemoteConfigService {
} }
} }
class MockRemoteConfigService implements IRemoteConfigService {
const MockRemoteConfigService();
@override
Future<void> activeAndFetchFeatures() async {}
@override
Future<void> fetchConfig() async {}
@override
Map<Feature, dynamic> getAll() => featuresDefaultValues;
@override
dynamic getValue(Feature feature) => featuresDefaultValues[feature];
@override
// ignore: cast_nullable_to_non_nullable
bool isEnabled(Feature feature) => featuresDefaultValues[feature] as bool;
@override
Stream<Set<Feature>> onConfigUpdated() => const Stream.empty();
}
extension on RemoteConfigValue { extension on RemoteConfigValue {
dynamic toValue(Feature feature) { dynamic toValue(Feature feature) {
switch (feature) { switch (feature) {

View file

@ -98,11 +98,11 @@
}, },
"lightmeterPro": "Lightmeter Pro", "lightmeterPro": "Lightmeter Pro",
"buyLightmeterPro": "Buy Lightmeter Pro", "buyLightmeterPro": "Buy 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.", "lightmeterProDescription": "Unlocks extra 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\n \u2022 Histogram\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", "buy": "Buy",
"proFeatures": "Pro features", "proFeatures": "Pro features",
"unlockProFeatures": "Unlock Pro features", "unlockProFeatures": "Unlock Pro features",
"unlockProFeaturesDescription": "Unlock professional 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\nBy unlocking Pro features you support the development and make it possible to add new features to the app.", "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\n \u2022 Histogram\n\nBy unlocking Pro features you support the development and make it possible to add new features to the app.",
"unlock": "Unlock", "unlock": "Unlock",
"tooltipAdd": "Add", "tooltipAdd": "Add",
"tooltipClose": "Close", "tooltipClose": "Close",

View file

@ -98,11 +98,11 @@
}, },
"buyLightmeterPro": "Acheter Lightmeter Pro", "buyLightmeterPro": "Acheter Lightmeter Pro",
"lightmeterPro": "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.", "lightmeterProDescription": "Débloque des fonctionnalités supplémentaires:\n \u2022 Profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore\n \u2022 Liste de films avec une compensation pour ce que l'on appelle l'échec de réciprocité\n \u2022 Mesure spot\n \u2022 Histogramme\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", "buy": "Acheter",
"proFeatures": "Fonctionnalités professionnelles", "proFeatures": "Fonctionnalités professionnelles",
"unlockProFeatures": "Déverrouiller les fonctionnalités professionnelles", "unlockProFeatures": "Déverrouiller les fonctionnalités professionnelles",
"unlockProFeaturesDescription": "Déverrouillez des fonctions professionnelles, 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 compensation pour ce que l'on appelle l'échec de réciprocité.\n\nEn débloquant les fonctionnalités Pro, vous soutenez le développement et permettez d'ajouter de nouvelles fonctionnalités à l'application.", "unlockProFeaturesDescription": "Déverrouillez des fonctions professionnelles:\n \u2022 Profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec compensation pour ce que l'on appelle l'échec de réciprocité\n \u2022 Mesure spot\n \u2022 Histogramme\n\nEn débloquant les fonctionnalités Pro, vous soutenez le développement et permettez d'ajouter de nouvelles fonctionnalités à l'application.",
"unlock": "Déverrouiller", "unlock": "Déverrouiller",
"tooltipAdd": "Ajouter", "tooltipAdd": "Ajouter",
"tooltipClose": "Fermer", "tooltipClose": "Fermer",

View file

@ -98,11 +98,11 @@
}, },
"buyLightmeterPro": "Купить Lightmeter Pro", "buyLightmeterPro": "Купить Lightmeter Pro",
"lightmeterPro": "Lightmeter Pro", "lightmeterPro": "Lightmeter Pro",
"lightmeterProDescription": "Даёт доступ к таким функциям как профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений, а также набору пленок с компенсацией эффекта Шварцшильда.\n\nИсходный код Lightmeter доступен на GitHub. Вы можете собрать его самостоятельно. Однако если вы хотите поддержать разработку и получать новые функции и обновления, то приобретите Lightmeter Pro.", "lightmeterProDescription": "Даёт доступ к различным функциям:\n \u2022 Профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений\n \u2022 Список пленок с компенсацией эффекта Шварцшильда\n \u2022 Точечный замер\n \u2022 Гистограмма\n\nИсходный код Lightmeter доступен на GitHub. Вы можете собрать его самостоятельно. Однако если вы хотите поддержать разработку и получать новые функции и обновления, то приобретите Lightmeter Pro.",
"buy": "Купить", "buy": "Купить",
"proFeatures": "Профессиональные настройки", "proFeatures": "Профессиональные настройки",
"unlockProFeatures": "Разблокировать профессиональные настройки", "unlockProFeatures": "Разблокировать профессиональные настройки",
"unlockProFeaturesDescription": "Вы можете разблокировать профессиональные настройки, такие как профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений, а также набору пленок с компенсацией эффекта Шварцшильда.\n\nПолучая доступ к профессиональным настройкам, вы поддерживаете разработку и делаете возможным появление новых функций в приложении.", "unlockProFeaturesDescription": "Вы можете разблокировать профессиональные настройки:\n \u2022 Профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений\n \u2022 Список пленок с компенсацией эффекта Шварцшильда\n \u2022 Точечный замер\n \u2022 Гистограмма\n\nПолучая доступ к профессиональным настройкам, вы поддерживаете разработку и делаете возможным появление новых функций в приложении.",
"unlock": "Разблокировать", "unlock": "Разблокировать",
"tooltipAdd": "Добавить", "tooltipAdd": "Добавить",
"tooltipClose": "Закрыть", "tooltipClose": "Закрыть",

View file

@ -98,11 +98,11 @@
}, },
"buyLightmeterPro": "购买 Lightmeter Pro", "buyLightmeterPro": "购买 Lightmeter Pro",
"lightmeterPro": "Lightmeter Pro", "lightmeterPro": "Lightmeter Pro",
"lightmeterProDescription": "购买以解锁额外功能。例如包含光圈、快门速度等参数的配置文件;以及一个胶卷预设列表来提供倒易率失效时的曝光补偿。\n\n您可以在 GitHub 上获取 Lightmeter 的源代码,欢迎自行编译。不过,如果您想支持开发并获得新功能和更新,请考虑购买 Lightmeter Pro。", "lightmeterProDescription": "解锁额外功能:\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶片预设列表,用于在反转率发生故障时提供曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n您可以在 GitHub 上获取 Lightmeter 的源代码,欢迎自行编译。不过,如果您想支持开发并获得新功能和更新,请考虑购买 Lightmeter Pro。",
"buy": "购买", "buy": "购买",
"proFeatures": "专业功能", "proFeatures": "专业功能",
"unlockProFeatures": "解锁专业功能", "unlockProFeatures": "解锁专业功能",
"unlockProFeaturesDescription": "解锁专业功能。例如包含光圈、快门速度等参数的配置文件;以及一个胶卷预设列表来提供倒易率失效时的曝光补偿。\n\n通过解锁专业版功能您可以支持开发工作帮助为应用程序添加新功能。", "unlockProFeaturesDescription": "\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶片预设列表,用于在反转率发生故障时提供曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n通过解锁专业版功能您可以支持开发工作帮助为应用程序添加新功能。",
"unlock": "解锁", "unlock": "解锁",
"tooltipAdd": "添加", "tooltipAdd": "添加",
"tooltipClose": "关闭", "tooltipClose": "关闭",

View file

@ -6,7 +6,7 @@ import 'package:lightmeter/data/models/feature.dart';
import 'package:lightmeter/data/remote_config_service.dart'; import 'package:lightmeter/data/remote_config_service.dart';
class RemoteConfigProvider extends StatefulWidget { class RemoteConfigProvider extends StatefulWidget {
final RemoteConfigService remoteConfigService; final IRemoteConfigService remoteConfigService;
final Widget child; final Widget child;
const RemoteConfigProvider({ const RemoteConfigProvider({

View file

@ -7,6 +7,31 @@ import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
Future<void> showBuyProDialog(BuildContext context) { Future<void> showBuyProDialog(BuildContext context) {
final unlockFeaturesEnabled = RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText); final unlockFeaturesEnabled = RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText);
Widget splitDescription() {
final description =
unlockFeaturesEnabled ? S.of(context).unlockProFeaturesDescription : S.of(context).lightmeterProDescription;
final paragraphs = description.split('\n\n');
final features = paragraphs.first.split('\n \u2022 ').sublist(1);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(paragraphs.first.split('\n \u2022 ').first),
...features.map(
(f) => Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('\u2022 '),
Flexible(child: Text(f)),
],
),
),
Text('\n${paragraphs.last}'),
],
);
}
return showDialog( return showDialog(
context: context, context: context,
builder: (_) => AlertDialog( builder: (_) => AlertDialog(
@ -14,11 +39,7 @@ Future<void> showBuyProDialog(BuildContext context) {
titlePadding: Dimens.dialogIconTitlePadding, titlePadding: Dimens.dialogIconTitlePadding,
title: Text(unlockFeaturesEnabled ? S.of(context).proFeatures : S.of(context).lightmeterPro), title: Text(unlockFeaturesEnabled ? S.of(context).proFeatures : S.of(context).lightmeterPro),
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL),
content: SingleChildScrollView( content: SingleChildScrollView(child: splitDescription()),
child: Text(
unlockFeaturesEnabled ? S.of(context).unlockProFeaturesDescription : S.of(context).lightmeterProDescription,
),
),
actionsPadding: Dimens.dialogActionsPadding, actionsPadding: Dimens.dialogActionsPadding,
actions: [ actions: [
TextButton( TextButton(