Compare commits

...

3 commits

Author SHA1 Message Date
Vadim
822ba8c7f8 log list tile tap instead of dialog 2023-10-30 17:46:27 +01:00
Vadim
690badccd2 dim paid features list tiles 2023-10-30 10:31:51 +01:00
Vadim
6d1be75070 added firebase_analytics 2023-10-30 10:25:22 +01:00
9 changed files with 104 additions and 27 deletions

View file

@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/data/analytics/analytics.dart';
import 'package:lightmeter/data/analytics/api/analytics_firebase.dart';
import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/caffeine_service.dart';
import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/haptics_service.dart';
import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart';
@ -36,6 +38,7 @@ class ApplicationWrapper extends StatelessWidget {
final userPreferencesService = UserPreferencesService(snapshot.data![0] as SharedPreferences); final userPreferencesService = UserPreferencesService(snapshot.data![0] as SharedPreferences);
final hasLightSensor = snapshot.data![1] as bool; final hasLightSensor = snapshot.data![1] as bool;
return ServicesProvider( return ServicesProvider(
analytics: const LightmeterAnalytics(api: LightmeterAnalyticsFirebase()),
caffeineService: const CaffeineService(), caffeineService: const CaffeineService(),
environment: env.copyWith(hasLightSensor: hasLightSensor), environment: env.copyWith(hasLightSensor: hasLightSensor),
hapticsService: const HapticsService(), hapticsService: const HapticsService(),

View file

@ -0,0 +1,34 @@
import 'dart:async';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart';
import 'package:lightmeter/data/analytics/entity/analytics_event.dart';
class LightmeterAnalytics {
final ILightmeterAnalyticsApi _api;
const LightmeterAnalytics({required ILightmeterAnalyticsApi api}) : _api = api;
Future<void> logEvent(
LightmeterAnalyticsEvent event, {
Map<String, dynamic>? parameters,
}) async {
if (kDebugMode) {
log('<LightmeterAnalytics> logEvent: ${event.name} / $parameters');
return;
}
return _api.logEvent(
event: event,
parameters: parameters,
);
}
Future<void> logUnlockProFeatures(String listTileTitle) async {
return logEvent(
LightmeterAnalyticsEvent.unlockProFeatures,
parameters: {"listTileTitle": listTileTitle},
);
}
}

View file

@ -0,0 +1,8 @@
import 'package:lightmeter/data/analytics/entity/analytics_event.dart';
abstract class ILightmeterAnalyticsApi {
Future<void> logEvent({
required LightmeterAnalyticsEvent event,
Map<String, dynamic>? parameters,
});
}

View file

@ -0,0 +1,26 @@
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart';
import 'package:lightmeter/data/analytics/entity/analytics_event.dart';
class LightmeterAnalyticsFirebase implements ILightmeterAnalyticsApi {
const LightmeterAnalyticsFirebase();
@override
Future<void> logEvent({
required LightmeterAnalyticsEvent event,
Map<String, dynamic>? parameters,
}) async {
try {
await FirebaseAnalytics.instance.logEvent(
name: event.name,
parameters: parameters,
);
} on FirebaseException catch (e) {
debugPrint('Firebase Analytics Exception: $e');
} catch (e) {
debugPrint(e.toString());
}
}
}

View file

@ -0,0 +1,3 @@
enum LightmeterAnalyticsEvent {
unlockProFeatures,
}

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/data/analytics/analytics.dart';
import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/caffeine_service.dart';
import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/haptics_service.dart';
import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart';
@ -10,6 +11,7 @@ import 'package:lightmeter/environment.dart';
// coverage:ignore-start // coverage:ignore-start
class ServicesProvider extends InheritedWidget { class ServicesProvider extends InheritedWidget {
final LightmeterAnalytics analytics;
final CaffeineService caffeineService; final CaffeineService caffeineService;
final Environment environment; final Environment environment;
final HapticsService hapticsService; final HapticsService hapticsService;
@ -20,6 +22,7 @@ class ServicesProvider extends InheritedWidget {
final VolumeEventsService volumeEventsService; final VolumeEventsService volumeEventsService;
const ServicesProvider({ const ServicesProvider({
required this.analytics,
required this.caffeineService, required this.caffeineService,
required this.environment, required this.environment,
required this.hapticsService, required this.hapticsService,

View file

@ -2,25 +2,35 @@ import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/feature.dart'; import 'package:lightmeter/data/models/feature.dart';
import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/providers/remote_config_provider.dart'; import 'package:lightmeter/providers/remote_config_provider.dart';
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart'; import 'package:lightmeter/providers/services_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/settings/components/utils/show_buy_pro_dialog.dart'; import 'package:lightmeter/screens/settings/components/utils/show_buy_pro_dialog.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
class BuyProListTile extends StatelessWidget { class BuyProListTile extends StatelessWidget {
const BuyProListTile({super.key}); const BuyProListTile({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IAPListTile( final unlockFeaturesEnabled = RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText);
final status = IAPProducts.productOf(context, IAPProductType.paidFeatures)?.status;
final isPending = status == IAPProductStatus.purchased || status == null;
return ListTile(
leading: const Icon(Icons.star), leading: const Icon(Icons.star),
title: Text( title: Text(unlockFeaturesEnabled ? S.of(context).unlockProFeatures : S.of(context).buyLightmeterPro),
RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText)
? S.of(context).unlockProFeatures
: S.of(context).buyLightmeterPro,
),
onTap: () { onTap: () {
showBuyProDialog(context); showBuyProDialog(context);
ServicesProvider.of(context)
.analytics
.logUnlockProFeatures(unlockFeaturesEnabled ? 'Unlock Pro features' : 'Buy Lightmeter Pro');
}, },
showPendingTrailing: true, trailing: isPending
? const SizedBox(
height: Dimens.grid24,
width: Dimens.grid24,
child: CircularProgressIndicator(),
)
: null,
); );
} }
} }

View file

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/res/dimens.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'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
/// Depends on the product status and replaces [onTap] with purchase callback /// Depends on the product status and replaces [onTap] with purchase callback
@ -23,24 +22,14 @@ class IAPListTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final status = IAPProducts.productOf(context, IAPProductType.paidFeatures)?.status; final isPurchased = IAPProducts.isPurchased(context, IAPProductType.paidFeatures);
final isPending = status == IAPProductStatus.purchased || status == null; return Opacity(
return ListTile( opacity: isPurchased ? Dimens.enabledOpacity : Dimens.disabledOpacity,
child: ListTile(
leading: leading, leading: leading,
title: title, title: title,
onTap: switch (status) { onTap: isPurchased ? onTap : null,
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

@ -13,6 +13,7 @@ dependencies:
clipboard: 0.1.3 clipboard: 0.1.3
dynamic_color: 1.6.6 dynamic_color: 1.6.6
exif: 3.1.4 exif: 3.1.4
firebase_analytics: 10.6.2
firebase_core: 2.20.0 firebase_core: 2.20.0
firebase_crashlytics: 3.4.2 firebase_crashlytics: 3.4.2
firebase_remote_config: 4.3.2 firebase_remote_config: 4.3.2