2023-10-31 17:42:25 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:developer';
|
|
|
|
|
|
|
|
import 'package:firebase_core/firebase_core.dart';
|
|
|
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
|
|
|
import 'package:firebase_remote_config/firebase_remote_config.dart';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:lightmeter/data/models/feature.dart';
|
|
|
|
|
2023-11-11 20:05:11 +00:00
|
|
|
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 {
|
2023-10-31 17:42:25 +00:00
|
|
|
const RemoteConfigService();
|
|
|
|
|
2023-11-11 20:05:11 +00:00
|
|
|
@override
|
2023-10-31 17:42:25 +00:00
|
|
|
Future<void> activeAndFetchFeatures() async {
|
|
|
|
final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance;
|
|
|
|
const cacheStaleDuration = kDebugMode ? Duration(minutes: 1) : Duration(hours: 12);
|
|
|
|
|
|
|
|
try {
|
|
|
|
await remoteConfig.setConfigSettings(
|
|
|
|
RemoteConfigSettings(
|
|
|
|
fetchTimeout: const Duration(seconds: 15),
|
|
|
|
minimumFetchInterval: cacheStaleDuration,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
await remoteConfig.setDefaults(featuresDefaultValues.map((key, value) => MapEntry(key.name, value)));
|
|
|
|
await remoteConfig.activate();
|
|
|
|
await remoteConfig.ensureInitialized();
|
|
|
|
|
|
|
|
log('Firebase remote config initialized successfully');
|
|
|
|
} on FirebaseException catch (e) {
|
|
|
|
_logError('Firebase exception during Firebase Remote Config initialization: $e');
|
2023-11-11 20:05:11 +00:00
|
|
|
} catch (e) {
|
2023-10-31 17:42:25 +00:00
|
|
|
_logError('Error during Firebase Remote Config initialization: $e');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-11 20:05:11 +00:00
|
|
|
@override
|
2023-11-07 10:57:36 +00:00
|
|
|
Future<void> fetchConfig() async {
|
|
|
|
// https://github.com/firebase/flutterfire/issues/6196#issuecomment-927751667
|
|
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
|
|
await FirebaseRemoteConfig.instance.fetch();
|
|
|
|
}
|
|
|
|
|
2023-11-11 20:05:11 +00:00
|
|
|
@override
|
2023-10-31 17:42:25 +00:00
|
|
|
dynamic getValue(Feature feature) => FirebaseRemoteConfig.instance.getValue(feature.name).toValue(feature);
|
|
|
|
|
2023-11-11 20:05:11 +00:00
|
|
|
@override
|
2023-10-31 17:42:25 +00:00
|
|
|
Map<Feature, dynamic> getAll() {
|
|
|
|
final Map<Feature, dynamic> result = {};
|
|
|
|
for (final value in FirebaseRemoteConfig.instance.getAll().entries) {
|
|
|
|
try {
|
|
|
|
final feature = Feature.values.firstWhere((f) => f.name == value.key);
|
|
|
|
result[feature] = value.value.toValue(feature);
|
|
|
|
} catch (e) {
|
|
|
|
log(e.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-11-11 20:05:11 +00:00
|
|
|
@override
|
2023-10-31 17:42:25 +00:00
|
|
|
Stream<Set<Feature>> onConfigUpdated() => FirebaseRemoteConfig.instance.onConfigUpdated.asyncMap(
|
|
|
|
(event) async {
|
|
|
|
await FirebaseRemoteConfig.instance.activate();
|
|
|
|
final Set<Feature> updatedFeatures = {};
|
|
|
|
for (final key in event.updatedKeys) {
|
|
|
|
try {
|
|
|
|
updatedFeatures.add(Feature.values.firstWhere((element) => element.name == key));
|
|
|
|
} catch (e) {
|
|
|
|
log(e.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return updatedFeatures;
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-11-11 20:05:11 +00:00
|
|
|
@override
|
2023-10-31 17:42:25 +00:00
|
|
|
bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name);
|
|
|
|
|
|
|
|
void _logError(dynamic throwable, {StackTrace? stackTrace}) {
|
|
|
|
FirebaseCrashlytics.instance.recordError(throwable, stackTrace);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-11 20:05:11 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2023-10-31 17:42:25 +00:00
|
|
|
extension on RemoteConfigValue {
|
|
|
|
dynamic toValue(Feature feature) {
|
|
|
|
switch (feature) {
|
|
|
|
case Feature.unlockProFeaturesText:
|
|
|
|
return asBool();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|