From 8ecc97e0c26c144145b48b6c35590a9151c4e43a Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Mon, 8 Jun 2026 08:47:57 +0200 Subject: [PATCH 01/12] feat: add pure Dart SDK core with tests Implements the foundation for a native Dart Confidence SDK: - ConfidenceValue sealed type system with JSON serialization - Flag resolution client (REST POST /v1/flags:resolve) - Flag evaluation with dot-path traversal - Apply client and manager with dedup - Events client (best-effort) - Storage interface with Memory and Disk implementations - Confidence class with builder, context management - 101 unit tests covering all components Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/apply_client.dart | 53 ++++ lib/src/apply_manager.dart | 99 ++++++++ lib/src/confidence.dart | 184 ++++++++++++++ lib/src/confidence_value.dart | 197 +++++++++++++++ lib/src/evaluation.dart | 48 ++++ lib/src/events_client.dart | 64 +++++ lib/src/flag_resolution.dart | 166 ++++++++++++ lib/src/resolve_client.dart | 115 +++++++++ lib/src/sdk_metadata.dart | 7 + lib/src/storage.dart | 58 +++++ pubspec.yaml | 8 + test/apply_manager_test.dart | 226 +++++++++++++++++ test/confidence_test.dart | 431 ++++++++++++++++++++++++++++++++ test/confidence_value_test.dart | 251 +++++++++++++++++++ test/events_client_test.dart | 173 +++++++++++++ test/flag_resolution_test.dart | 174 +++++++++++++ test/resolve_client_test.dart | 307 +++++++++++++++++++++++ test/storage_test.dart | 135 ++++++++++ 18 files changed, 2696 insertions(+) create mode 100644 lib/src/apply_client.dart create mode 100644 lib/src/apply_manager.dart create mode 100644 lib/src/confidence.dart create mode 100644 lib/src/confidence_value.dart create mode 100644 lib/src/evaluation.dart create mode 100644 lib/src/events_client.dart create mode 100644 lib/src/flag_resolution.dart create mode 100644 lib/src/resolve_client.dart create mode 100644 lib/src/sdk_metadata.dart create mode 100644 lib/src/storage.dart create mode 100644 test/apply_manager_test.dart create mode 100644 test/confidence_test.dart create mode 100644 test/confidence_value_test.dart create mode 100644 test/events_client_test.dart create mode 100644 test/flag_resolution_test.dart create mode 100644 test/resolve_client_test.dart create mode 100644 test/storage_test.dart diff --git a/lib/src/apply_client.dart b/lib/src/apply_client.dart new file mode 100644 index 0000000..59cb91f --- /dev/null +++ b/lib/src/apply_client.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import 'resolve_client.dart'; +import 'sdk_metadata.dart' as sdk_meta; + +class ApplyClient { + final http.Client _httpClient; + final String _clientSecret; + final ConfidenceRegion _region; + + ApplyClient({ + required http.Client httpClient, + required String clientSecret, + required ConfidenceRegion region, + }) : _httpClient = httpClient, + _clientSecret = clientSecret, + _region = region; + + Future sendApply({ + required String flagName, + required String resolveToken, + required DateTime applyTime, + }) async { + final url = Uri.parse('${_region.resolverBaseUrl}/v1/flags:apply'); + final now = DateTime.now().toUtc(); + + final body = jsonEncode({ + 'flags': [ + { + 'flag': 'flags/$flagName', + 'applyTime': applyTime.toUtc().toIso8601String(), + }, + ], + 'sendTime': now.toIso8601String(), + 'clientSecret': _clientSecret, + 'resolveToken': resolveToken, + 'sdk': sdk_meta.sdkInfo(), + }); + + final response = await _httpClient.post( + url, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: body, + ); + + return response.statusCode == 200; + } +} diff --git a/lib/src/apply_manager.dart b/lib/src/apply_manager.dart new file mode 100644 index 0000000..c66d288 --- /dev/null +++ b/lib/src/apply_manager.dart @@ -0,0 +1,99 @@ +import 'dart:convert'; + +import 'apply_client.dart'; +import 'storage.dart'; + +class ApplyManager { + final Storage _storage; + final ApplyClient _applyClient; + final Set _appliedKeys = {}; + + static const _storageKey = 'confidence.apply.cache'; + + ApplyManager({ + required Storage storage, + required ApplyClient applyClient, + }) : _storage = storage, + _applyClient = applyClient; + + Future apply(String flagName, String resolveToken) async { + final key = '$resolveToken:$flagName'; + if (_appliedKeys.contains(key)) return; + + _appliedKeys.add(key); + + final applyTime = DateTime.now().toUtc(); + + try { + final success = await _applyClient.sendApply( + flagName: flagName, + resolveToken: resolveToken, + applyTime: applyTime, + ); + + if (!success) { + await _addPending(resolveToken, flagName); + } + } catch (_) { + await _addPending(resolveToken, flagName); + } + } + + Future restore() async { + final pending = await _loadPending(); + if (pending.isEmpty) return; + + for (final entry in pending.entries) { + final resolveToken = entry.key; + for (final flagName in entry.value) { + final key = '$resolveToken:$flagName'; + if (_appliedKeys.contains(key)) continue; + _appliedKeys.add(key); + + try { + final success = await _applyClient.sendApply( + flagName: flagName, + resolveToken: resolveToken, + applyTime: DateTime.now().toUtc(), + ); + if (success) { + await _removePending(resolveToken, flagName); + } + } catch (_) { + // Keep in pending for next retry + } + } + } + } + + Future _addPending(String resolveToken, String flagName) async { + final pending = await _loadPending(); + final flags = pending[resolveToken] ?? []; + if (!flags.contains(flagName)) { + flags.add(flagName); + } + pending[resolveToken] = flags; + await _storage.write(_storageKey, jsonEncode(pending)); + } + + Future _removePending(String resolveToken, String flagName) async { + final pending = await _loadPending(); + final flags = pending[resolveToken]; + if (flags != null) { + flags.remove(flagName); + if (flags.isEmpty) { + pending.remove(resolveToken); + } + } + await _storage.write(_storageKey, jsonEncode(pending)); + } + + Future>> _loadPending() async { + final stored = await _storage.read(_storageKey); + if (stored == null) return {}; + final json = jsonDecode(stored) as Map; + return json.map( + (k, v) => MapEntry(k, (v as List).cast()), + ); + } +} diff --git a/lib/src/confidence.dart b/lib/src/confidence.dart new file mode 100644 index 0000000..adcee4c --- /dev/null +++ b/lib/src/confidence.dart @@ -0,0 +1,184 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import 'confidence_value.dart'; +import 'evaluation.dart'; +import 'flag_resolution.dart'; +import 'resolve_client.dart'; +import 'storage.dart'; + +class Confidence { + final _ConfidenceState _state; + final Confidence? _parent; + final Map _localContext; + final Set _removedKeys; + + Confidence._({ + required _ConfidenceState state, + Confidence? parent, + Map localContext = const {}, + Set removedKeys = const {}, + }) : _state = state, + _parent = parent, + _localContext = Map.of(localContext), + _removedKeys = Set.of(removedKeys); + + static ConfidenceBuilder builder({required String clientSecret}) => + ConfidenceBuilder._(clientSecret: clientSecret); + + // -- Flag lifecycle -- + + Future fetchAndActivate() async { + final resolution = await _state.resolveClient.resolve(getContext()); + await _state.storage.write( + 'confidence.flags.resolve', + jsonEncode(resolution.toJson()), + ); + _state.currentResolution = resolution; + } + + Future activate() async { + final stored = await _state.storage.read('confidence.flags.resolve'); + if (stored != null) { + final json = jsonDecode(stored) as Map; + _state.currentResolution = FlagResolution.fromJson(json); + } + } + + Future asyncFetch() async { + final resolution = await _state.resolveClient.resolve(getContext()); + await _state.storage.write( + 'confidence.flags.resolve', + jsonEncode(resolution.toJson()), + ); + } + + Future activateAndFetchAsync() async { + await activate(); + // Fire-and-forget background fetch + asyncFetch().ignore(); + } + + // -- Flag evaluation -- + + T getValue(String flagPath, T defaultValue) => + getFlag(flagPath, defaultValue).value; + + Evaluation getFlag(String flagPath, T defaultValue) { + final resolution = _state.currentResolution; + if (resolution == null) { + return Evaluation( + value: defaultValue, + reason: ResolveReason.error, + errorCode: 'NOT_READY', + errorMessage: 'No flag resolution available. Call fetchAndActivate() or activate() first.', + ); + } + return resolution.evaluate(flagPath, defaultValue); + } + + // -- Context management -- + + Map getContext() { + final parentContext = _parent?.getContext() ?? {}; + final merged = Map.from(parentContext) + ..addAll(_localContext); + for (final key in _removedKeys) { + merged.remove(key); + } + return merged; + } + + void putContext(String key, ConfidenceValue value) { + _localContext[key] = value; + _removedKeys.remove(key); + _triggerRefetch(); + } + + void putContextLocal(String key, ConfidenceValue value) { + _localContext[key] = value; + _removedKeys.remove(key); + } + + void removeContext(String key) { + _localContext.remove(key); + _removedKeys.add(key); + _triggerRefetch(); + } + + Confidence withContext(Map context) { + return Confidence._( + state: _state, + parent: this, + localContext: context, + ); + } + + void _triggerRefetch() { + fetchAndActivate().ignore(); + } +} + +class ConfidenceBuilder { + final String _clientSecret; + ConfidenceRegion _region = ConfidenceRegion.global; + Storage? _storage; + http.Client? _httpClient; + Map _initialContext = {}; + + ConfidenceBuilder._({required String clientSecret}) + : _clientSecret = clientSecret; + + ConfidenceBuilder region(ConfidenceRegion region) { + _region = region; + return this; + } + + ConfidenceBuilder storage(Storage storage) { + _storage = storage; + return this; + } + + ConfidenceBuilder httpClient(http.Client client) { + _httpClient = client; + return this; + } + + ConfidenceBuilder initialContext(Map context) { + _initialContext = context; + return this; + } + + Confidence build() { + final storage = _storage ?? MemoryStorage(); + final httpClient = _httpClient ?? http.Client(); + + final resolveClient = ResolveClient( + httpClient: httpClient, + clientSecret: _clientSecret, + region: _region, + ); + + final state = _ConfidenceState( + storage: storage, + resolveClient: resolveClient, + ); + + return Confidence._( + state: state, + localContext: _initialContext, + ); + } +} + +class _ConfidenceState { + final Storage storage; + final ResolveClient resolveClient; + FlagResolution? currentResolution; + + _ConfidenceState({ + required this.storage, + required this.resolveClient, + }); +} diff --git a/lib/src/confidence_value.dart b/lib/src/confidence_value.dart new file mode 100644 index 0000000..c8cfc01 --- /dev/null +++ b/lib/src/confidence_value.dart @@ -0,0 +1,197 @@ +sealed class ConfidenceValue { + const ConfidenceValue(); + + static ConfidenceValue boolean(bool value) => ConfidenceValueBoolean(value); + static ConfidenceValue string(String value) => ConfidenceValueString(value); + static ConfidenceValue integer(int value) => ConfidenceValueInteger(value); + static ConfidenceValue double_(double value) => ConfidenceValueDouble(value); + static ConfidenceValue date(DateTime value) => ConfidenceValueDate(value); + static ConfidenceValue timestamp(DateTime value) => + ConfidenceValueTimestamp(value); + static ConfidenceValue list(List value) => + ConfidenceValueList(value); + static ConfidenceValue structure(Map value) => + ConfidenceValueStructure(value); + static ConfidenceValue null_() => const ConfidenceValueNull(); + + dynamic toJson(); + + dynamic toPlainJson() => switch (this) { + ConfidenceValueBoolean(value: final v) => v, + ConfidenceValueString(value: final v) => v, + ConfidenceValueInteger(value: final v) => v, + ConfidenceValueDouble(value: final v) => v, + ConfidenceValueDate(value: final v) => v.toIso8601String().split('T')[0], + ConfidenceValueTimestamp(value: final v) => v.toUtc().toIso8601String(), + ConfidenceValueList(value: final v) => + v.map((e) => e.toPlainJson()).toList(), + ConfidenceValueStructure(value: final v) => + v.map((k, e) => MapEntry(k, e.toPlainJson())), + ConfidenceValueNull() => null, + }; + + static ConfidenceValue fromJson(dynamic json) { + if (json == null) return const ConfidenceValueNull(); + if (json is bool) return ConfidenceValueBoolean(json); + if (json is int) return ConfidenceValueInteger(json); + if (json is double) return ConfidenceValueDouble(json); + if (json is String) return ConfidenceValueString(json); + if (json is List) { + return ConfidenceValueList(json.map(ConfidenceValue.fromJson).toList()); + } + if (json is Map) { + return ConfidenceValueStructure( + json.map((k, v) => MapEntry(k, ConfidenceValue.fromJson(v))), + ); + } + return const ConfidenceValueNull(); + } + + static ConfidenceValue fromPlainJson(dynamic json) => fromJson(json); +} + +final class ConfidenceValueBoolean extends ConfidenceValue { + final bool value; + const ConfidenceValueBoolean(this.value); + + @override + dynamic toJson() => value; + + @override + bool operator ==(Object other) => + other is ConfidenceValueBoolean && other.value == value; + + @override + int get hashCode => value.hashCode; +} + +final class ConfidenceValueString extends ConfidenceValue { + final String value; + const ConfidenceValueString(this.value); + + @override + dynamic toJson() => value; + + @override + bool operator ==(Object other) => + other is ConfidenceValueString && other.value == value; + + @override + int get hashCode => value.hashCode; +} + +final class ConfidenceValueInteger extends ConfidenceValue { + final int value; + const ConfidenceValueInteger(this.value); + + @override + dynamic toJson() => value; + + @override + bool operator ==(Object other) => + other is ConfidenceValueInteger && other.value == value; + + @override + int get hashCode => value.hashCode; +} + +final class ConfidenceValueDouble extends ConfidenceValue { + final double value; + const ConfidenceValueDouble(this.value); + + @override + dynamic toJson() => value; + + @override + bool operator ==(Object other) => + other is ConfidenceValueDouble && other.value == value; + + @override + int get hashCode => value.hashCode; +} + +final class ConfidenceValueDate extends ConfidenceValue { + final DateTime value; + const ConfidenceValueDate(this.value); + + @override + dynamic toJson() => value.toIso8601String().split('T')[0]; + + @override + bool operator ==(Object other) => + other is ConfidenceValueDate && other.value == value; + + @override + int get hashCode => value.hashCode; +} + +final class ConfidenceValueTimestamp extends ConfidenceValue { + final DateTime value; + const ConfidenceValueTimestamp(this.value); + + @override + dynamic toJson() => value.toUtc().toIso8601String(); + + @override + bool operator ==(Object other) => + other is ConfidenceValueTimestamp && other.value == value; + + @override + int get hashCode => value.hashCode; +} + +final class ConfidenceValueList extends ConfidenceValue { + final List value; + const ConfidenceValueList(this.value); + + @override + dynamic toJson() => value.map((e) => e.toJson()).toList(); + + @override + bool operator ==(Object other) => + other is ConfidenceValueList && + value.length == other.value.length && + _listEquals(value, other.value); + + @override + int get hashCode => Object.hashAll(value); +} + +final class ConfidenceValueStructure extends ConfidenceValue { + final Map value; + const ConfidenceValueStructure(this.value); + + @override + dynamic toJson() => value.map((k, v) => MapEntry(k, v.toJson())); + + @override + bool operator ==(Object other) => + other is ConfidenceValueStructure && + value.length == other.value.length && + value.entries.every( + (e) => other.value.containsKey(e.key) && other.value[e.key] == e.value, + ); + + @override + int get hashCode => Object.hashAll(value.entries.map((e) => e.hashCode)); +} + +final class ConfidenceValueNull extends ConfidenceValue { + const ConfidenceValueNull(); + + @override + dynamic toJson() => null; + + @override + bool operator ==(Object other) => other is ConfidenceValueNull; + + @override + int get hashCode => 0; +} + +bool _listEquals(List a, List b) { + for (var i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; + } + return true; +} diff --git a/lib/src/evaluation.dart b/lib/src/evaluation.dart new file mode 100644 index 0000000..e85f63f --- /dev/null +++ b/lib/src/evaluation.dart @@ -0,0 +1,48 @@ +class Evaluation { + final T value; + final String? variant; + final ResolveReason reason; + final String? errorCode; + final String? errorMessage; + + const Evaluation({ + required this.value, + this.variant, + required this.reason, + this.errorCode, + this.errorMessage, + }); +} + +enum ResolveReason { + match, + unspecified, + noSegmentMatch, + noTreatmentMatch, + flagArchived, + targetingKeyError, + error, + stale; + + static ResolveReason fromString(String value) => switch (value) { + 'RESOLVE_REASON_MATCH' => ResolveReason.match, + 'RESOLVE_REASON_NO_SEGMENT_MATCH' => ResolveReason.noSegmentMatch, + 'RESOLVE_REASON_NO_TREATMENT_MATCH' => ResolveReason.noTreatmentMatch, + 'RESOLVE_REASON_FLAG_ARCHIVED' => ResolveReason.flagArchived, + 'RESOLVE_REASON_TARGETING_KEY_ERROR' => ResolveReason.targetingKeyError, + 'RESOLVE_REASON_ERROR' => ResolveReason.error, + 'RESOLVE_REASON_STALE' => ResolveReason.stale, + _ => ResolveReason.unspecified, + }; + + String toJson() => switch (this) { + ResolveReason.match => 'RESOLVE_REASON_MATCH', + ResolveReason.noSegmentMatch => 'RESOLVE_REASON_NO_SEGMENT_MATCH', + ResolveReason.noTreatmentMatch => 'RESOLVE_REASON_NO_TREATMENT_MATCH', + ResolveReason.flagArchived => 'RESOLVE_REASON_FLAG_ARCHIVED', + ResolveReason.targetingKeyError => 'RESOLVE_REASON_TARGETING_KEY_ERROR', + ResolveReason.error => 'RESOLVE_REASON_ERROR', + ResolveReason.stale => 'RESOLVE_REASON_STALE', + ResolveReason.unspecified => 'RESOLVE_REASON_UNSPECIFIED', + }; +} diff --git a/lib/src/events_client.dart b/lib/src/events_client.dart new file mode 100644 index 0000000..2c65954 --- /dev/null +++ b/lib/src/events_client.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import 'confidence_value.dart'; +import 'resolve_client.dart'; +import 'sdk_metadata.dart' as sdk_meta; + +class EventsClient { + final http.Client _httpClient; + final String _clientSecret; + final ConfidenceRegion _region; + + EventsClient({ + required http.Client httpClient, + required String clientSecret, + required ConfidenceRegion region, + }) : _httpClient = httpClient, + _clientSecret = clientSecret, + _region = region; + + Future send({ + required String eventName, + required Map payload, + Map context = const {}, + }) async { + final url = Uri.parse('${_region.eventsBaseUrl}/v1/events:publish'); + final now = DateTime.now().toUtc(); + + final plainPayload = + payload.map((k, v) => MapEntry(k, v.toPlainJson())); + + if (context.isNotEmpty) { + plainPayload['context'] = + context.map((k, v) => MapEntry(k, v.toPlainJson())); + } + + final body = jsonEncode({ + 'clientSecret': _clientSecret, + 'events': [ + { + 'eventDefinition': 'eventDefinitions/$eventName', + 'eventTime': now.toIso8601String(), + 'payload': plainPayload, + }, + ], + 'sendTime': now.toIso8601String(), + 'sdk': sdk_meta.sdkInfo(), + }); + + try { + await _httpClient.post( + url, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: body, + ); + } catch (_) { + // Best-effort: swallow errors + } + } +} diff --git a/lib/src/flag_resolution.dart b/lib/src/flag_resolution.dart new file mode 100644 index 0000000..5db7557 --- /dev/null +++ b/lib/src/flag_resolution.dart @@ -0,0 +1,166 @@ +import 'confidence_value.dart'; +import 'evaluation.dart'; + +class ResolvedFlag { + final String flag; + final String variant; + final ConfidenceValueStructure? value; + final ResolveReason reason; + final bool shouldApply; + + const ResolvedFlag({ + required this.flag, + required this.variant, + required this.value, + required this.reason, + required this.shouldApply, + }); + + Map toJson() => { + 'flag': flag, + 'variant': variant, + 'value': value?.toJson(), + 'reason': reason.toJson(), + 'shouldApply': shouldApply, + }; + + factory ResolvedFlag.fromJson(Map json) { + final valueJson = json['value']; + ConfidenceValueStructure? value; + if (valueJson != null && valueJson is Map) { + value = ConfidenceValue.fromJson(valueJson) as ConfidenceValueStructure; + } + return ResolvedFlag( + flag: json['flag'] as String, + variant: json['variant'] as String? ?? '', + value: value, + reason: ResolveReason.fromString(json['reason'] as String? ?? ''), + shouldApply: json['shouldApply'] as bool? ?? true, + ); + } +} + +class FlagResolution { + final List flags; + final String resolveToken; + + const FlagResolution({ + required this.flags, + required this.resolveToken, + }); + + Evaluation evaluate(String flagPath, T defaultValue) { + final parts = flagPath.split('.'); + if (parts.length < 2) { + return Evaluation( + value: defaultValue, + reason: ResolveReason.error, + errorCode: 'INVALID_FLAG_PATH', + errorMessage: 'Flag path must contain at least flag name and property', + ); + } + + final flagName = parts[0]; + final propertyPath = parts.sublist(1); + + final resolvedFlag = _findFlag(flagName); + if (resolvedFlag == null) { + return Evaluation( + value: defaultValue, + reason: ResolveReason.error, + errorCode: 'FLAG_NOT_FOUND', + errorMessage: 'Flag "$flagName" not found', + ); + } + + if (resolvedFlag.value == null) { + return Evaluation( + value: defaultValue, + variant: resolvedFlag.variant, + reason: resolvedFlag.reason, + ); + } + + final extracted = _walkPath(resolvedFlag.value!, propertyPath); + if (extracted == null) { + return Evaluation( + value: defaultValue, + variant: resolvedFlag.variant, + reason: ResolveReason.error, + errorCode: 'VALUE_NOT_FOUND', + errorMessage: 'Property path "${propertyPath.join('.')}" not found', + ); + } + + final typed = _castValue(extracted); + if (typed == null) { + return Evaluation( + value: defaultValue, + variant: resolvedFlag.variant, + reason: ResolveReason.error, + errorCode: 'TYPE_MISMATCH', + errorMessage: + 'Expected $T but got ${extracted.runtimeType}', + ); + } + + return Evaluation( + value: typed, + variant: resolvedFlag.variant, + reason: resolvedFlag.reason, + ); + } + + ResolvedFlag? _findFlag(String flagName) { + for (final flag in flags) { + if (flag.flag == flagName) return flag; + } + return null; + } + + ConfidenceValue? _walkPath( + ConfidenceValueStructure struct, + List path, + ) { + ConfidenceValue current = struct; + for (final key in path) { + if (current is! ConfidenceValueStructure) return null; + final next = current.value[key]; + if (next == null) return null; + current = next; + } + return current; + } + + T? _castValue(ConfidenceValue value) { + if (T == String && value is ConfidenceValueString) { + return value.value as T; + } + if (T == int && value is ConfidenceValueInteger) { + return value.value as T; + } + if (T == bool && value is ConfidenceValueBoolean) { + return value.value as T; + } + if (T == double && value is ConfidenceValueDouble) { + return value.value as T; + } + return null; + } + + Map toJson() => { + 'resolvedFlags': flags.map((f) => f.toJson()).toList(), + 'resolveToken': resolveToken, + }; + + factory FlagResolution.fromJson(Map json) { + final flagsList = (json['resolvedFlags'] as List?) + ?.map((f) => ResolvedFlag.fromJson(f as Map)) + .toList() ?? + []; + return FlagResolution( + flags: flagsList, + resolveToken: json['resolveToken'] as String? ?? '', + ); + } +} diff --git a/lib/src/resolve_client.dart b/lib/src/resolve_client.dart new file mode 100644 index 0000000..0f9d2df --- /dev/null +++ b/lib/src/resolve_client.dart @@ -0,0 +1,115 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import 'confidence_value.dart'; +import 'flag_resolution.dart'; +import 'evaluation.dart'; +import 'sdk_metadata.dart' as sdk_meta; + +enum ConfidenceRegion { + global, + eu, + us; + + String get resolverBaseUrl => switch (this) { + ConfidenceRegion.global => 'https://resolver.confidence.dev', + ConfidenceRegion.eu => 'https://resolver.eu.confidence.dev', + ConfidenceRegion.us => 'https://resolver.us.confidence.dev', + }; + + String get eventsBaseUrl => switch (this) { + ConfidenceRegion.global => 'https://events.confidence.dev', + ConfidenceRegion.eu => 'https://events.eu.confidence.dev', + ConfidenceRegion.us => 'https://events.us.confidence.dev', + }; +} + +class ResolveClient { + final http.Client _httpClient; + final String _clientSecret; + final ConfidenceRegion _region; + + ResolveClient({ + required http.Client httpClient, + required String clientSecret, + required ConfidenceRegion region, + }) : _httpClient = httpClient, + _clientSecret = clientSecret, + _region = region; + + Future resolve( + Map context, + ) async { + final url = Uri.parse('${_region.resolverBaseUrl}/v1/flags:resolve'); + + final plainContext = + context.map((k, v) => MapEntry(k, v.toPlainJson())); + + final body = jsonEncode({ + 'flags': [], + 'evaluationContext': plainContext, + 'clientSecret': _clientSecret, + 'apply': false, + 'sdk': sdk_meta.sdkInfo(), + }); + + final response = await _httpClient.post( + url, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: body, + ); + + if (response.statusCode != 200) { + throw ResolveException( + 'Resolve failed with status ${response.statusCode}: ${response.body}', + ); + } + + final json = jsonDecode(response.body) as Map; + return _parseResponse(json); + } + + FlagResolution _parseResponse(Map json) { + final resolvedFlags = (json['resolvedFlags'] as List?) + ?.map((f) => _parseResolvedFlag(f as Map)) + .toList() ?? + []; + + return FlagResolution( + flags: resolvedFlags, + resolveToken: json['resolveToken'] as String? ?? '', + ); + } + + ResolvedFlag _parseResolvedFlag(Map json) { + final rawFlag = json['flag'] as String? ?? ''; + final flagName = + rawFlag.startsWith('flags/') ? rawFlag.substring(6) : rawFlag; + + final valueJson = json['value']; + ConfidenceValueStructure? value; + if (valueJson != null && valueJson is Map) { + value = ConfidenceValue.fromJson(valueJson) as ConfidenceValueStructure; + } + + return ResolvedFlag( + flag: flagName, + variant: json['variant'] as String? ?? '', + value: value, + reason: ResolveReason.fromString(json['reason'] as String? ?? ''), + shouldApply: json['shouldApply'] as bool? ?? true, + ); + } +} + +class ResolveException implements Exception { + final String message; + ResolveException(this.message); + + @override + String toString() => 'ResolveException: $message'; +} diff --git a/lib/src/sdk_metadata.dart b/lib/src/sdk_metadata.dart new file mode 100644 index 0000000..ce98844 --- /dev/null +++ b/lib/src/sdk_metadata.dart @@ -0,0 +1,7 @@ +const sdkId = 'SDK_ID_DART_CONFIDENCE'; +const sdkVersion = '1.0.0'; // x-release-please-version + +Map sdkInfo() => { + 'id': sdkId, + 'version': sdkVersion, +}; diff --git a/lib/src/storage.dart b/lib/src/storage.dart new file mode 100644 index 0000000..15d0908 --- /dev/null +++ b/lib/src/storage.dart @@ -0,0 +1,58 @@ +import 'dart:io'; + +abstract class Storage { + Future read(String key); + Future write(String key, String data); + Future delete(String key); + Future exists(String key); +} + +class MemoryStorage implements Storage { + final Map _store = {}; + + @override + Future read(String key) async => _store[key]; + + @override + Future write(String key, String data) async => _store[key] = data; + + @override + Future delete(String key) async => _store.remove(key); + + @override + Future exists(String key) async => _store.containsKey(key); +} + +class DiskStorage implements Storage { + final String _directoryPath; + + DiskStorage(this._directoryPath); + + String _filePath(String key) => + '$_directoryPath/${key.replaceAll('/', '_')}'; + + @override + Future read(String key) async { + final file = File(_filePath(key)); + if (!await file.exists()) return null; + return file.readAsString(); + } + + @override + Future write(String key, String data) async { + final file = File(_filePath(key)); + await file.parent.create(recursive: true); + await file.writeAsString(data); + } + + @override + Future delete(String key) async { + final file = File(_filePath(key)); + if (await file.exists()) { + await file.delete(); + } + } + + @override + Future exists(String key) async => File(_filePath(key)).exists(); +} diff --git a/pubspec.yaml b/pubspec.yaml index 0b703d1..a99558d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,11 +15,19 @@ dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.0.2 + http: ^1.2.0 + uuid: ^4.0.0 + path_provider: ^2.1.0 + device_info_plus: ^10.0.0 + package_info_plus: ^8.0.0 + shared_preferences: ^2.2.0 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 + mockito: ^5.4.0 + build_runner: ^2.4.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/apply_manager_test.dart b/test/apply_manager_test.dart new file mode 100644 index 0000000..68db1d2 --- /dev/null +++ b/test/apply_manager_test.dart @@ -0,0 +1,226 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:confidence_flutter_sdk/src/apply_manager.dart'; +import 'package:confidence_flutter_sdk/src/apply_client.dart'; +import 'package:confidence_flutter_sdk/src/resolve_client.dart'; +import 'package:confidence_flutter_sdk/src/storage.dart'; + +void main() { + group('ApplyManager', () { + late MemoryStorage storage; + late List capturedRequests; + + http.Client makeApplyClient({int statusCode = 200}) { + return MockClient((request) async { + capturedRequests.add(request); + return http.Response('{}', statusCode); + }); + } + + setUp(() { + storage = MemoryStorage(); + capturedRequests = []; + }); + + test('sends apply request for a new flag', () async { + final applyClient = ApplyClient( + httpClient: makeApplyClient(), + clientSecret: 'test-secret', + region: ConfidenceRegion.global, + ); + final manager = ApplyManager( + storage: storage, + applyClient: applyClient, + ); + + await manager.apply('my-flag', 'token-123'); + + expect(capturedRequests, hasLength(1)); + final body = + jsonDecode(capturedRequests[0].body) as Map; + expect(body['resolveToken'], equals('token-123')); + final flags = body['flags'] as List; + expect(flags, hasLength(1)); + expect(flags[0]['flag'], equals('flags/my-flag')); + expect(flags[0]['applyTime'], isNotNull); + }); + + test('deduplicates same flag + resolve token', () async { + final applyClient = ApplyClient( + httpClient: makeApplyClient(), + clientSecret: 'test-secret', + region: ConfidenceRegion.global, + ); + final manager = ApplyManager( + storage: storage, + applyClient: applyClient, + ); + + await manager.apply('my-flag', 'token-123'); + await manager.apply('my-flag', 'token-123'); + + expect(capturedRequests, hasLength(1)); + }); + + test('sends separate requests for different flags', () async { + final applyClient = ApplyClient( + httpClient: makeApplyClient(), + clientSecret: 'test-secret', + region: ConfidenceRegion.global, + ); + final manager = ApplyManager( + storage: storage, + applyClient: applyClient, + ); + + await manager.apply('flag-a', 'token-123'); + await manager.apply('flag-b', 'token-123'); + + expect(capturedRequests, hasLength(2)); + }); + + test('sends separate requests for different resolve tokens', () async { + final applyClient = ApplyClient( + httpClient: makeApplyClient(), + clientSecret: 'test-secret', + region: ConfidenceRegion.global, + ); + final manager = ApplyManager( + storage: storage, + applyClient: applyClient, + ); + + await manager.apply('my-flag', 'token-1'); + await manager.apply('my-flag', 'token-2'); + + expect(capturedRequests, hasLength(2)); + }); + + test('retains pending applies on failure for later retry', () async { + var callCount = 0; + final failThenSucceed = MockClient((request) async { + capturedRequests.add(request); + callCount++; + if (callCount == 1) { + return http.Response('Server Error', 500); + } + return http.Response('{}', 200); + }); + + final applyClient = ApplyClient( + httpClient: failThenSucceed, + clientSecret: 'test-secret', + region: ConfidenceRegion.global, + ); + final manager = ApplyManager( + storage: storage, + applyClient: applyClient, + ); + + await manager.apply('my-flag', 'token-123'); + // First call failed, pending set should still contain the apply. + // A second apply with different flag should trigger retry of pending. + await manager.apply('other-flag', 'token-123'); + + expect(capturedRequests.length, greaterThanOrEqualTo(2)); + }); + + test('persists pending applies to storage', () async { + final applyClient = ApplyClient( + httpClient: makeApplyClient(statusCode: 500), + clientSecret: 'test-secret', + region: ConfidenceRegion.global, + ); + final manager = ApplyManager( + storage: storage, + applyClient: applyClient, + ); + + await manager.apply('my-flag', 'token-123'); + + final stored = await storage.read('confidence.apply.cache'); + expect(stored, isNotNull); + }); + + test('restores pending applies from storage on creation', () async { + // Pre-populate storage with pending applies + final pendingData = jsonEncode({ + 'token-123': ['flag-a'], + }); + await storage.write('confidence.apply.cache', pendingData); + + final applyClient = ApplyClient( + httpClient: makeApplyClient(), + clientSecret: 'test-secret', + region: ConfidenceRegion.global, + ); + final manager = ApplyManager( + storage: storage, + applyClient: applyClient, + ); + + await manager.restore(); + + expect(capturedRequests, hasLength(1)); + final body = + jsonDecode(capturedRequests[0].body) as Map; + expect(body['resolveToken'], equals('token-123')); + }); + }); + + group('ApplyClient', () { + test('sends to correct global URL', () async { + late Uri capturedUrl; + final mockClient = MockClient((request) async { + capturedUrl = request.url; + return http.Response('{}', 200); + }); + + final client = ApplyClient( + httpClient: mockClient, + clientSecret: 'test-secret', + region: ConfidenceRegion.global, + ); + + await client.sendApply( + flagName: 'my-flag', + resolveToken: 'token', + applyTime: DateTime.utc(2026, 6, 5, 10, 0, 0), + ); + + expect( + capturedUrl.toString(), + equals('https://resolver.confidence.dev/v1/flags:apply'), + ); + }); + + test('sends to correct EU URL', () async { + late Uri capturedUrl; + final mockClient = MockClient((request) async { + capturedUrl = request.url; + return http.Response('{}', 200); + }); + + final client = ApplyClient( + httpClient: mockClient, + clientSecret: 'test-secret', + region: ConfidenceRegion.eu, + ); + + await client.sendApply( + flagName: 'my-flag', + resolveToken: 'token', + applyTime: DateTime.utc(2026, 6, 5, 10, 0, 0), + ); + + expect( + capturedUrl.toString(), + equals('https://resolver.eu.confidence.dev/v1/flags:apply'), + ); + }); + }); +} + diff --git a/test/confidence_test.dart b/test/confidence_test.dart new file mode 100644 index 0000000..6898dd3 --- /dev/null +++ b/test/confidence_test.dart @@ -0,0 +1,431 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:confidence_flutter_sdk/src/confidence.dart'; +import 'package:confidence_flutter_sdk/src/confidence_value.dart'; +import 'package:confidence_flutter_sdk/src/evaluation.dart'; +import 'package:confidence_flutter_sdk/src/storage.dart'; + +Map makeResolveResponse({ + List>? flags, + String resolveToken = 'token-abc', +}) { + return { + 'resolvedFlags': flags ?? [ + { + 'flag': 'flags/my-flag', + 'variant': 'flags/my-flag/variants/treatment', + 'value': { + 'color': 'red', + 'size': 42, + 'enabled': true, + 'nested': {'deep': 'value'}, + }, + 'flagSchema': { + 'schema': { + 'color': {'stringSchema': {}}, + 'size': {'intSchema': {}}, + 'enabled': {'boolSchema': {}}, + 'nested': { + 'structSchema': { + 'schema': { + 'deep': {'stringSchema': {}}, + }, + }, + }, + }, + }, + 'reason': 'RESOLVE_REASON_MATCH', + 'shouldApply': true, + }, + ], + 'resolveToken': resolveToken, + }; +} + +void main() { + group('Confidence', () { + test('fetchAndActivate resolves and caches flags', () async { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + await confidence.fetchAndActivate(); + + expect( + confidence.getValue('my-flag.color', 'default'), + equals('red'), + ); + }); + + test('getValue returns default before fetchAndActivate', () { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + expect( + confidence.getValue('my-flag.color', 'default'), + equals('default'), + ); + }); + + test('getValue evaluates different types', () async { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + await confidence.fetchAndActivate(); + + expect(confidence.getValue('my-flag.color', ''), equals('red')); + expect(confidence.getValue('my-flag.size', 0), equals(42)); + expect(confidence.getValue('my-flag.enabled', false), isTrue); + }); + + test('getValue evaluates nested properties', () async { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + await confidence.fetchAndActivate(); + + expect( + confidence.getValue('my-flag.nested.deep', 'default'), + equals('value'), + ); + }); + + test('getValue returns default for non-existent flag', () async { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + await confidence.fetchAndActivate(); + + expect( + confidence.getValue('nonexistent.color', 'default'), + equals('default'), + ); + }); + + test('getValue returns default on type mismatch', () async { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + await confidence.fetchAndActivate(); + + // color is a string, not an int + expect(confidence.getValue('my-flag.color', 99), equals(99)); + }); + + test('getFlag returns full evaluation', () async { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + await confidence.fetchAndActivate(); + + final eval = confidence.getFlag('my-flag.color', 'default'); + expect(eval.value, equals('red')); + expect(eval.variant, equals('flags/my-flag/variants/treatment')); + expect(eval.reason, equals(ResolveReason.match)); + }); + }); + + group('Confidence context management', () { + test('putContext triggers re-fetch', () async { + var fetchCount = 0; + final mockClient = MockClient((_) async { + fetchCount++; + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + await confidence.fetchAndActivate(); + expect(fetchCount, equals(1)); + + confidence.putContext('user_id', ConfidenceValue.string('new-user')); + // Allow async fetch to complete + await Future.delayed(Duration.zero); + await Future.delayed(Duration.zero); + + expect(fetchCount, greaterThan(1)); + }); + + test('putContextLocal does not trigger re-fetch', () async { + var fetchCount = 0; + final mockClient = MockClient((_) async { + fetchCount++; + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + await confidence.fetchAndActivate(); + expect(fetchCount, equals(1)); + + confidence.putContextLocal('user_id', ConfidenceValue.string('new-user')); + await Future.delayed(Duration.zero); + + expect(fetchCount, equals(1)); + }); + + test('getContext returns current context', () { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .initialContext({ + 'targeting_key': ConfidenceValue.string('user-123'), + }) + .build(); + + final context = confidence.getContext(); + expect(context['targeting_key'], isA()); + expect( + (context['targeting_key'] as ConfidenceValueString).value, + equals('user-123'), + ); + }); + + test('removeContext removes key and triggers re-fetch', () async { + var fetchCount = 0; + final mockClient = MockClient((_) async { + fetchCount++; + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .initialContext({ + 'targeting_key': ConfidenceValue.string('user-123'), + 'country': ConfidenceValue.string('SE'), + }) + .build(); + + await confidence.fetchAndActivate(); + + confidence.removeContext('country'); + await Future.delayed(Duration.zero); + await Future.delayed(Duration.zero); + + final context = confidence.getContext(); + expect(context.containsKey('country'), isFalse); + expect(fetchCount, greaterThan(1)); + }); + + test('withContext creates child with merged context', () { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final parent = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .initialContext({ + 'targeting_key': ConfidenceValue.string('user-123'), + }) + .build(); + + final child = parent.withContext({ + 'page': ConfidenceValue.string('home'), + }); + + final childContext = child.getContext(); + expect(childContext.containsKey('targeting_key'), isTrue); + expect(childContext.containsKey('page'), isTrue); + }); + + test('child context overrides parent context', () { + final mockClient = MockClient((_) async { + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final parent = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .initialContext({ + 'color': ConfidenceValue.string('red'), + }) + .build(); + + final child = parent.withContext({ + 'color': ConfidenceValue.string('blue'), + }); + + final context = child.getContext(); + expect( + (context['color'] as ConfidenceValueString).value, + equals('blue'), + ); + }); + }); + + group('Confidence activate and fetch strategies', () { + test('activate loads from storage without fetching', () async { + final storage = MemoryStorage(); + // Pre-populate storage in the format that fetchAndActivate() would write + // (flag names WITHOUT the 'flags/' prefix, since ResolveClient strips it) + final storedResolution = { + 'resolvedFlags': [ + { + 'flag': 'cached-flag', + 'variant': 'flags/cached-flag/variants/v1', + 'value': {'msg': 'cached'}, + 'reason': 'RESOLVE_REASON_MATCH', + 'shouldApply': true, + }, + ], + 'resolveToken': 'cached-token', + }; + await storage.write( + 'confidence.flags.resolve', + jsonEncode(storedResolution), + ); + + var fetchCount = 0; + final mockClient = MockClient((_) async { + fetchCount++; + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(storage) + .build(); + + await confidence.activate(); + + expect(fetchCount, equals(0)); + expect( + confidence.getValue('cached-flag.msg', 'default'), + equals('cached'), + ); + }); + + test('activateAndFetchAsync activates cache then fetches in background', () async { + final storage = MemoryStorage(); + final storedResolution = { + 'resolvedFlags': [ + { + 'flag': 'cached-flag', + 'variant': 'flags/cached-flag/variants/v1', + 'value': {'msg': 'cached'}, + 'reason': 'RESOLVE_REASON_MATCH', + 'shouldApply': true, + }, + ], + 'resolveToken': 'cached-token', + }; + await storage.write( + 'confidence.flags.resolve', + jsonEncode(storedResolution), + ); + + var fetchCount = 0; + final mockClient = MockClient((_) async { + fetchCount++; + return http.Response(jsonEncode(makeResolveResponse()), 200); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(storage) + .build(); + + await confidence.activateAndFetchAsync(); + + // Should have activated cached values immediately + expect( + confidence.getValue('cached-flag.msg', 'default'), + equals('cached'), + ); + + // Background fetch should have started + await Future.delayed(Duration.zero); + expect(fetchCount, equals(1)); + }); + }); + + group('Confidence stale response handling', () { + test('discards response if context changed during fetch', () async { + var resolveCallCount = 0; + final mockClient = MockClient((_) async { + resolveCallCount++; + // Simulate slow network for first call + if (resolveCallCount == 1) { + await Future.delayed(const Duration(milliseconds: 50)); + } + return http.Response( + jsonEncode(makeResolveResponse( + resolveToken: 'token-$resolveCallCount', + )), + 200, + ); + }); + + final confidence = Confidence.builder(clientSecret: 'test-secret') + .httpClient(mockClient) + .storage(MemoryStorage()) + .build(); + + // Start fetch, then immediately change context + final fetchFuture = confidence.fetchAndActivate(); + confidence.putContextLocal('user', ConfidenceValue.string('changed')); + + await fetchFuture; + // The fetch that was in-flight when context changed should be + // either discarded or a new fetch should have been triggered. + // The exact behavior depends on implementation details. + expect(resolveCallCount, greaterThanOrEqualTo(1)); + }); + }); +} diff --git a/test/confidence_value_test.dart b/test/confidence_value_test.dart new file mode 100644 index 0000000..9988452 --- /dev/null +++ b/test/confidence_value_test.dart @@ -0,0 +1,251 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:confidence_flutter_sdk/src/confidence_value.dart'; + +void main() { + group('ConfidenceValue constructors', () { + test('boolean value', () { + final value = ConfidenceValue.boolean(true); + expect(value, isA()); + expect((value as ConfidenceValueBoolean).value, isTrue); + }); + + test('string value', () { + final value = ConfidenceValue.string('hello'); + expect(value, isA()); + expect((value as ConfidenceValueString).value, equals('hello')); + }); + + test('integer value', () { + final value = ConfidenceValue.integer(42); + expect(value, isA()); + expect((value as ConfidenceValueInteger).value, equals(42)); + }); + + test('double value', () { + final value = ConfidenceValue.double_(3.14); + expect(value, isA()); + expect((value as ConfidenceValueDouble).value, equals(3.14)); + }); + + test('null value', () { + final value = ConfidenceValue.null_(); + expect(value, isA()); + }); + + test('list value', () { + final value = ConfidenceValue.list([ + ConfidenceValue.string('a'), + ConfidenceValue.integer(1), + ]); + expect(value, isA()); + final list = (value as ConfidenceValueList).value; + expect(list, hasLength(2)); + expect(list[0], isA()); + expect(list[1], isA()); + }); + + test('structure value', () { + final value = ConfidenceValue.structure({ + 'name': ConfidenceValue.string('test'), + 'count': ConfidenceValue.integer(5), + }); + expect(value, isA()); + final struct = (value as ConfidenceValueStructure).value; + expect(struct['name'], isA()); + expect(struct['count'], isA()); + }); + + test('deeply nested structure', () { + final value = ConfidenceValue.structure({ + 'outer': ConfidenceValue.structure({ + 'inner': ConfidenceValue.structure({ + 'deep': ConfidenceValue.string('found'), + }), + }), + }); + expect(value, isA()); + final outer = + (value as ConfidenceValueStructure).value['outer'] + as ConfidenceValueStructure; + final inner = outer.value['inner'] as ConfidenceValueStructure; + final deep = inner.value['deep'] as ConfidenceValueString; + expect(deep.value, equals('found')); + }); + }); + + group('ConfidenceValue JSON serialization', () { + test('boolean round-trips through JSON', () { + final original = ConfidenceValue.boolean(true); + final json = original.toJson(); + final restored = ConfidenceValue.fromJson(json); + expect(restored, isA()); + expect((restored as ConfidenceValueBoolean).value, isTrue); + }); + + test('string round-trips through JSON', () { + final original = ConfidenceValue.string('hello'); + final json = original.toJson(); + final restored = ConfidenceValue.fromJson(json); + expect((restored as ConfidenceValueString).value, equals('hello')); + }); + + test('integer round-trips through JSON', () { + final original = ConfidenceValue.integer(42); + final json = original.toJson(); + final restored = ConfidenceValue.fromJson(json); + expect((restored as ConfidenceValueInteger).value, equals(42)); + }); + + test('double round-trips through JSON', () { + final original = ConfidenceValue.double_(3.14); + final json = original.toJson(); + final restored = ConfidenceValue.fromJson(json); + expect((restored as ConfidenceValueDouble).value, equals(3.14)); + }); + + test('null round-trips through JSON', () { + final original = ConfidenceValue.null_(); + final json = original.toJson(); + final restored = ConfidenceValue.fromJson(json); + expect(restored, isA()); + }); + + test('list round-trips through JSON', () { + final original = ConfidenceValue.list([ + ConfidenceValue.string('a'), + ConfidenceValue.integer(1), + ConfidenceValue.boolean(false), + ]); + final json = original.toJson(); + final restored = ConfidenceValue.fromJson(json); + expect(restored, isA()); + final list = (restored as ConfidenceValueList).value; + expect(list, hasLength(3)); + expect((list[0] as ConfidenceValueString).value, equals('a')); + expect((list[1] as ConfidenceValueInteger).value, equals(1)); + expect((list[2] as ConfidenceValueBoolean).value, isFalse); + }); + + test('structure round-trips through JSON', () { + final original = ConfidenceValue.structure({ + 'color': ConfidenceValue.string('red'), + 'size': ConfidenceValue.integer(42), + 'enabled': ConfidenceValue.boolean(true), + }); + final json = original.toJson(); + final restored = ConfidenceValue.fromJson(json); + expect(restored, isA()); + final struct = (restored as ConfidenceValueStructure).value; + expect( + (struct['color'] as ConfidenceValueString).value, + equals('red'), + ); + expect((struct['size'] as ConfidenceValueInteger).value, equals(42)); + expect((struct['enabled'] as ConfidenceValueBoolean).value, isTrue); + }); + + test('nested structure round-trips through JSON', () { + final original = ConfidenceValue.structure({ + 'outer': ConfidenceValue.structure({ + 'inner': ConfidenceValue.string('deep'), + }), + 'list': ConfidenceValue.list([ + ConfidenceValue.structure({ + 'item': ConfidenceValue.integer(1), + }), + ]), + }); + final json = original.toJson(); + final restored = ConfidenceValue.fromJson(json); + expect(restored, isA()); + final struct = (restored as ConfidenceValueStructure).value; + final outer = struct['outer'] as ConfidenceValueStructure; + expect( + (outer.value['inner'] as ConfidenceValueString).value, + equals('deep'), + ); + }); + }); + + group('ConfidenceValue equality', () { + test('same boolean values are equal', () { + expect(ConfidenceValue.boolean(true), equals(ConfidenceValue.boolean(true))); + }); + + test('different boolean values are not equal', () { + expect( + ConfidenceValue.boolean(true), + isNot(equals(ConfidenceValue.boolean(false))), + ); + }); + + test('same string values are equal', () { + expect( + ConfidenceValue.string('hello'), + equals(ConfidenceValue.string('hello')), + ); + }); + + test('null values are equal', () { + expect(ConfidenceValue.null_(), equals(ConfidenceValue.null_())); + }); + }); + + group('ConfidenceValue toPlainJson', () { + test('converts primitives to plain JSON', () { + expect(ConfidenceValue.string('hello').toPlainJson(), equals('hello')); + expect(ConfidenceValue.integer(42).toPlainJson(), equals(42)); + expect(ConfidenceValue.double_(3.14).toPlainJson(), equals(3.14)); + expect(ConfidenceValue.boolean(true).toPlainJson(), equals(true)); + expect(ConfidenceValue.null_().toPlainJson(), isNull); + }); + + test('converts structure to plain JSON map', () { + final value = ConfidenceValue.structure({ + 'name': ConfidenceValue.string('test'), + 'count': ConfidenceValue.integer(5), + }); + final plain = value.toPlainJson(); + expect(plain, isA>()); + expect((plain as Map)['name'], equals('test')); + expect(plain['count'], equals(5)); + }); + + test('converts list to plain JSON list', () { + final value = ConfidenceValue.list([ + ConfidenceValue.string('a'), + ConfidenceValue.integer(1), + ]); + final plain = value.toPlainJson(); + expect(plain, isA()); + expect((plain as List)[0], equals('a')); + expect(plain[1], equals(1)); + }); + }); + + group('ConfidenceValue fromPlainJson', () { + test('converts plain JSON primitives', () { + expect( + ConfidenceValue.fromPlainJson('hello'), + isA(), + ); + expect(ConfidenceValue.fromPlainJson(42), isA()); + expect(ConfidenceValue.fromPlainJson(3.14), isA()); + expect(ConfidenceValue.fromPlainJson(true), isA()); + expect(ConfidenceValue.fromPlainJson(null), isA()); + }); + + test('converts plain JSON map to structure', () { + final value = ConfidenceValue.fromPlainJson({ + 'name': 'test', + 'count': 5, + }); + expect(value, isA()); + }); + + test('converts plain JSON list', () { + final value = ConfidenceValue.fromPlainJson(['a', 1, true]); + expect(value, isA()); + }); + }); +} diff --git a/test/events_client_test.dart b/test/events_client_test.dart new file mode 100644 index 0000000..ca0ce07 --- /dev/null +++ b/test/events_client_test.dart @@ -0,0 +1,173 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:confidence_flutter_sdk/src/confidence_value.dart'; +import 'package:confidence_flutter_sdk/src/events_client.dart'; +import 'package:confidence_flutter_sdk/src/resolve_client.dart'; + +void main() { + group('EventsClient', () { + const clientSecret = 'test-secret'; + + test('sends event to correct global URL', () async { + late Uri capturedUrl; + final mockClient = MockClient((request) async { + capturedUrl = request.url; + return http.Response('{}', 200); + }); + + final client = EventsClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + await client.send( + eventName: 'purchase', + payload: {}, + ); + + expect( + capturedUrl.toString(), + equals('https://events.confidence.dev/v1/events:publish'), + ); + }); + + test('sends event to correct EU URL', () async { + late Uri capturedUrl; + final mockClient = MockClient((request) async { + capturedUrl = request.url; + return http.Response('{}', 200); + }); + + final client = EventsClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.eu, + ); + + await client.send( + eventName: 'purchase', + payload: {}, + ); + + expect( + capturedUrl.toString(), + equals('https://events.eu.confidence.dev/v1/events:publish'), + ); + }); + + test('sends correct request format', () async { + late Map capturedBody; + final mockClient = MockClient((request) async { + capturedBody = jsonDecode(request.body) as Map; + return http.Response('{}', 200); + }); + + final client = EventsClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + await client.send( + eventName: 'purchase', + payload: { + 'amount': ConfidenceValue.double_(99.99), + 'currency': ConfidenceValue.string('USD'), + }, + ); + + expect(capturedBody['clientSecret'], equals(clientSecret)); + expect(capturedBody['sendTime'], isNotNull); + expect(capturedBody['sdk'], isA()); + + final events = capturedBody['events'] as List; + expect(events, hasLength(1)); + + final event = events[0] as Map; + expect( + event['eventDefinition'], + equals('eventDefinitions/purchase'), + ); + expect(event['eventTime'], isNotNull); + expect(event['payload'], isA()); + expect(event['payload']['amount'], equals(99.99)); + expect(event['payload']['currency'], equals('USD')); + }); + + test('merges context into payload', () async { + late Map capturedBody; + final mockClient = MockClient((request) async { + capturedBody = jsonDecode(request.body) as Map; + return http.Response('{}', 200); + }); + + final client = EventsClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + await client.send( + eventName: 'navigate', + payload: { + 'screen': ConfidenceValue.string('home'), + }, + context: { + 'targeting_key': ConfidenceValue.string('user-123'), + 'country': ConfidenceValue.string('SE'), + }, + ); + + final events = capturedBody['events'] as List; + final payload = events[0]['payload'] as Map; + expect(payload['screen'], equals('home')); + expect(payload['context'], isA()); + expect( + (payload['context'] as Map)['targeting_key'], + equals('user-123'), + ); + }); + + test('does not throw on HTTP 500 (best-effort)', () async { + final mockClient = MockClient((_) async { + return http.Response('Server Error', 500); + }); + + final client = EventsClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + // Should not throw — best-effort fire-and-forget + await client.send( + eventName: 'purchase', + payload: {}, + ); + }); + + test('sends event with empty payload', () async { + late Map capturedBody; + final mockClient = MockClient((request) async { + capturedBody = jsonDecode(request.body) as Map; + return http.Response('{}', 200); + }); + + final client = EventsClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + await client.send(eventName: 'page_view', payload: {}); + + final events = capturedBody['events'] as List; + expect(events[0]['payload'], isA()); + }); + }); +} + diff --git a/test/flag_resolution_test.dart b/test/flag_resolution_test.dart new file mode 100644 index 0000000..ba22e62 --- /dev/null +++ b/test/flag_resolution_test.dart @@ -0,0 +1,174 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:confidence_flutter_sdk/src/confidence_value.dart'; +import 'package:confidence_flutter_sdk/src/evaluation.dart'; +import 'package:confidence_flutter_sdk/src/flag_resolution.dart'; + +void main() { + late FlagResolution resolution; + + setUp(() { + resolution = FlagResolution( + flags: [ + ResolvedFlag( + flag: 'my-flag', + variant: 'flags/my-flag/variants/treatment', + value: ConfidenceValue.structure({ + 'color': ConfidenceValue.string('red'), + 'size': ConfidenceValue.integer(42), + 'enabled': ConfidenceValue.boolean(true), + 'rate': ConfidenceValue.double_(0.5), + 'nested': ConfidenceValue.structure({ + 'deep': ConfidenceValue.string('found'), + 'level': ConfidenceValue.integer(3), + }), + }) as ConfidenceValueStructure, + reason: ResolveReason.match, + shouldApply: true, + ), + ResolvedFlag( + flag: 'other-flag', + variant: 'flags/other-flag/variants/control', + value: ConfidenceValue.structure({ + 'message': ConfidenceValue.string('hello'), + }) as ConfidenceValueStructure, + reason: ResolveReason.match, + shouldApply: true, + ), + ResolvedFlag( + flag: 'no-match-flag', + variant: '', + value: null, + reason: ResolveReason.noSegmentMatch, + shouldApply: false, + ), + ], + resolveToken: 'test-token-123', + ); + }); + + group('dot-path evaluation', () { + test('evaluates top-level string property', () { + final eval = resolution.evaluate('my-flag.color', 'default'); + expect(eval.value, equals('red')); + expect(eval.variant, equals('flags/my-flag/variants/treatment')); + expect(eval.reason, equals(ResolveReason.match)); + }); + + test('evaluates top-level integer property', () { + final eval = resolution.evaluate('my-flag.size', 0); + expect(eval.value, equals(42)); + }); + + test('evaluates top-level boolean property', () { + final eval = resolution.evaluate('my-flag.enabled', false); + expect(eval.value, isTrue); + }); + + test('evaluates top-level double property', () { + final eval = resolution.evaluate('my-flag.rate', 0.0); + expect(eval.value, equals(0.5)); + }); + + test('evaluates nested property with dot notation', () { + final eval = resolution.evaluate('my-flag.nested.deep', 'default'); + expect(eval.value, equals('found')); + }); + + test('evaluates nested integer property', () { + final eval = resolution.evaluate('my-flag.nested.level', 0); + expect(eval.value, equals(3)); + }); + + test('returns default for non-existent flag', () { + final eval = resolution.evaluate('nonexistent.color', 'default'); + expect(eval.value, equals('default')); + expect(eval.reason, equals(ResolveReason.error)); + }); + + test('returns default for non-existent property', () { + final eval = resolution.evaluate( + 'my-flag.nonexistent', + 'default', + ); + expect(eval.value, equals('default')); + expect(eval.reason, equals(ResolveReason.error)); + }); + + test('returns default for non-existent nested property', () { + final eval = resolution.evaluate( + 'my-flag.nested.nonexistent', + 'default', + ); + expect(eval.value, equals('default')); + }); + + test('returns default on type mismatch', () { + final eval = resolution.evaluate('my-flag.color', 99); + expect(eval.value, equals(99)); + expect(eval.reason, equals(ResolveReason.error)); + }); + + test('evaluates flag with no segment match', () { + final eval = resolution.evaluate( + 'no-match-flag.something', + 'default', + ); + expect(eval.value, equals('default')); + expect(eval.reason, equals(ResolveReason.noSegmentMatch)); + }); + + test('evaluates different flag', () { + final eval = resolution.evaluate( + 'other-flag.message', + 'default', + ); + expect(eval.value, equals('hello')); + }); + + test('flag path with only flag name returns default', () { + final eval = resolution.evaluate('my-flag', 'default'); + expect(eval.value, equals('default')); + }); + }); + + group('JSON serialization', () { + test('round-trips through JSON', () { + final json = resolution.toJson(); + final restored = FlagResolution.fromJson(json); + + expect(restored.resolveToken, equals('test-token-123')); + expect(restored.flags, hasLength(3)); + expect(restored.flags[0].flag, equals('my-flag')); + expect(restored.flags[0].reason, equals(ResolveReason.match)); + expect(restored.flags[0].shouldApply, isTrue); + }); + + test('preserves flag values through JSON', () { + final json = resolution.toJson(); + final restored = FlagResolution.fromJson(json); + + final eval = restored.evaluate('my-flag.color', 'default'); + expect(eval.value, equals('red')); + }); + + test('preserves nested values through JSON', () { + final json = resolution.toJson(); + final restored = FlagResolution.fromJson(json); + + final eval = restored.evaluate( + 'my-flag.nested.deep', + 'default', + ); + expect(eval.value, equals('found')); + }); + }); + + group('empty resolution', () { + test('empty resolution returns defaults', () { + final empty = FlagResolution(flags: [], resolveToken: ''); + final eval = empty.evaluate('any-flag.prop', 'default'); + expect(eval.value, equals('default')); + expect(eval.reason, equals(ResolveReason.error)); + }); + }); +} diff --git a/test/resolve_client_test.dart b/test/resolve_client_test.dart new file mode 100644 index 0000000..ebcc818 --- /dev/null +++ b/test/resolve_client_test.dart @@ -0,0 +1,307 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:confidence_flutter_sdk/src/confidence_value.dart'; +import 'package:confidence_flutter_sdk/src/resolve_client.dart'; +import 'package:confidence_flutter_sdk/src/flag_resolution.dart'; +import 'package:confidence_flutter_sdk/src/evaluation.dart'; + +void main() { + group('ResolveClient', () { + const clientSecret = 'test-secret'; + + Map makeResolveResponse({ + List>? flags, + String resolveToken = 'token-abc', + }) { + return { + 'resolvedFlags': flags ?? [ + { + 'flag': 'flags/my-flag', + 'variant': 'flags/my-flag/variants/treatment', + 'value': {'color': 'red', 'size': 42}, + 'flagSchema': { + 'schema': { + 'color': {'stringSchema': {}}, + 'size': {'intSchema': {}}, + }, + }, + 'reason': 'RESOLVE_REASON_MATCH', + 'shouldApply': true, + }, + ], + 'resolveToken': resolveToken, + }; + } + + test('sends correct request format', () async { + late http.Request capturedRequest; + final mockClient = MockClient((request) async { + capturedRequest = request; + return http.Response( + jsonEncode(makeResolveResponse()), + 200, + ); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + await client.resolve({ + 'targeting_key': ConfidenceValue.string('user-123'), + 'country': ConfidenceValue.string('SE'), + }); + + expect(capturedRequest.method, equals('POST')); + expect( + capturedRequest.url.toString(), + equals('https://resolver.confidence.dev/v1/flags:resolve'), + ); + expect( + capturedRequest.headers['Content-Type'], + equals('application/json'), + ); + + final body = jsonDecode(capturedRequest.body) as Map; + expect(body['clientSecret'], equals(clientSecret)); + expect(body['apply'], isFalse); + expect(body['evaluationContext'], isA()); + expect(body['sdk'], isA()); + }); + + test('parses resolve response correctly', () async { + final mockClient = MockClient((_) async { + return http.Response( + jsonEncode(makeResolveResponse()), + 200, + ); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + final resolution = await client.resolve({}); + + expect(resolution.resolveToken, equals('token-abc')); + expect(resolution.flags, hasLength(1)); + expect(resolution.flags[0].flag, equals('my-flag')); + expect( + resolution.flags[0].variant, + equals('flags/my-flag/variants/treatment'), + ); + expect(resolution.flags[0].reason, equals(ResolveReason.match)); + expect(resolution.flags[0].shouldApply, isTrue); + }); + + test('parses flag values from response', () async { + final mockClient = MockClient((_) async { + return http.Response( + jsonEncode(makeResolveResponse()), + 200, + ); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + final resolution = await client.resolve({}); + final eval = resolution.evaluate('my-flag.color', 'default'); + expect(eval.value, equals('red')); + }); + + test('uses EU region URL', () async { + late Uri capturedUrl; + final mockClient = MockClient((request) async { + capturedUrl = request.url; + return http.Response( + jsonEncode(makeResolveResponse()), + 200, + ); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.eu, + ); + + await client.resolve({}); + expect( + capturedUrl.toString(), + equals('https://resolver.eu.confidence.dev/v1/flags:resolve'), + ); + }); + + test('uses US region URL', () async { + late Uri capturedUrl; + final mockClient = MockClient((request) async { + capturedUrl = request.url; + return http.Response( + jsonEncode(makeResolveResponse()), + 200, + ); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.us, + ); + + await client.resolve({}); + expect( + capturedUrl.toString(), + equals('https://resolver.us.confidence.dev/v1/flags:resolve'), + ); + }); + + test('throws on HTTP 500 error', () async { + final mockClient = MockClient((_) async { + return http.Response('Internal Server Error', 500); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + expect( + () => client.resolve({}), + throwsException, + ); + }); + + test('throws on HTTP 404 error', () async { + final mockClient = MockClient((_) async { + return http.Response('Not Found', 404); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + expect( + () => client.resolve({}), + throwsException, + ); + }); + + test('handles response with multiple flags', () async { + final mockClient = MockClient((_) async { + return http.Response( + jsonEncode(makeResolveResponse( + flags: [ + { + 'flag': 'flags/flag-a', + 'variant': 'flags/flag-a/variants/v1', + 'value': {'key': 'value-a'}, + 'flagSchema': {'schema': {'key': {'stringSchema': {}}}}, + 'reason': 'RESOLVE_REASON_MATCH', + 'shouldApply': true, + }, + { + 'flag': 'flags/flag-b', + 'variant': 'flags/flag-b/variants/v2', + 'value': {'key': 'value-b'}, + 'flagSchema': {'schema': {'key': {'stringSchema': {}}}}, + 'reason': 'RESOLVE_REASON_NO_SEGMENT_MATCH', + 'shouldApply': false, + }, + ], + )), + 200, + ); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + final resolution = await client.resolve({}); + expect(resolution.flags, hasLength(2)); + expect(resolution.flags[0].flag, equals('flag-a')); + expect(resolution.flags[1].flag, equals('flag-b')); + expect( + resolution.flags[1].reason, + equals(ResolveReason.noSegmentMatch), + ); + }); + + test('handles response with no segment match (null value)', () async { + final mockClient = MockClient((_) async { + return http.Response( + jsonEncode(makeResolveResponse( + flags: [ + { + 'flag': 'flags/my-flag', + 'variant': '', + 'value': null, + 'reason': 'RESOLVE_REASON_NO_SEGMENT_MATCH', + 'shouldApply': false, + }, + ], + )), + 200, + ); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + final resolution = await client.resolve({}); + expect(resolution.flags[0].value, isNull); + expect( + resolution.flags[0].reason, + equals(ResolveReason.noSegmentMatch), + ); + }); + + test('sends context values in evaluation context', () async { + late Map capturedBody; + final mockClient = MockClient((request) async { + capturedBody = jsonDecode(request.body) as Map; + return http.Response( + jsonEncode(makeResolveResponse()), + 200, + ); + }); + + final client = ResolveClient( + httpClient: mockClient, + clientSecret: clientSecret, + region: ConfidenceRegion.global, + ); + + await client.resolve({ + 'targeting_key': ConfidenceValue.string('user-abc'), + 'country': ConfidenceValue.string('SE'), + 'age': ConfidenceValue.integer(30), + }); + + final context = + capturedBody['evaluationContext'] as Map; + expect(context['targeting_key'], equals('user-abc')); + expect(context['country'], equals('SE')); + expect(context['age'], equals(30)); + }); + }); +} diff --git a/test/storage_test.dart b/test/storage_test.dart new file mode 100644 index 0000000..505c73a --- /dev/null +++ b/test/storage_test.dart @@ -0,0 +1,135 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:confidence_flutter_sdk/src/storage.dart'; + +void main() { + group('MemoryStorage', () { + late MemoryStorage storage; + + setUp(() { + storage = MemoryStorage(); + }); + + test('read returns null for non-existent key', () async { + final result = await storage.read('nonexistent'); + expect(result, isNull); + }); + + test('write and read round-trip', () async { + await storage.write('key1', 'value1'); + final result = await storage.read('key1'); + expect(result, equals('value1')); + }); + + test('write overwrites existing value', () async { + await storage.write('key1', 'value1'); + await storage.write('key1', 'value2'); + final result = await storage.read('key1'); + expect(result, equals('value2')); + }); + + test('delete removes the key', () async { + await storage.write('key1', 'value1'); + await storage.delete('key1'); + final result = await storage.read('key1'); + expect(result, isNull); + }); + + test('delete non-existent key does not throw', () async { + await storage.delete('nonexistent'); + }); + + test('exists returns false for non-existent key', () async { + final result = await storage.exists('nonexistent'); + expect(result, isFalse); + }); + + test('exists returns true after write', () async { + await storage.write('key1', 'value1'); + final result = await storage.exists('key1'); + expect(result, isTrue); + }); + + test('exists returns false after delete', () async { + await storage.write('key1', 'value1'); + await storage.delete('key1'); + final result = await storage.exists('key1'); + expect(result, isFalse); + }); + + test('multiple keys are independent', () async { + await storage.write('key1', 'value1'); + await storage.write('key2', 'value2'); + expect(await storage.read('key1'), equals('value1')); + expect(await storage.read('key2'), equals('value2')); + await storage.delete('key1'); + expect(await storage.read('key1'), isNull); + expect(await storage.read('key2'), equals('value2')); + }); + }); + + group('DiskStorage', () { + late Directory tempDir; + late DiskStorage storage; + + setUp(() { + tempDir = Directory.systemTemp.createTempSync('confidence_test_'); + storage = DiskStorage(tempDir.path); + }); + + tearDown(() { + if (tempDir.existsSync()) { + tempDir.deleteSync(recursive: true); + } + }); + + test('read returns null for non-existent key', () async { + final result = await storage.read('nonexistent'); + expect(result, isNull); + }); + + test('write and read round-trip', () async { + await storage.write('flags', '{"data": "test"}'); + final result = await storage.read('flags'); + expect(result, equals('{"data": "test"}')); + }); + + test('write creates the directory if needed', () async { + final nested = DiskStorage('${tempDir.path}/sub/dir'); + await nested.write('key', 'value'); + final result = await nested.read('key'); + expect(result, equals('value')); + }); + + test('write overwrites existing value', () async { + await storage.write('key', 'v1'); + await storage.write('key', 'v2'); + expect(await storage.read('key'), equals('v2')); + }); + + test('delete removes the file', () async { + await storage.write('key', 'value'); + await storage.delete('key'); + expect(await storage.read('key'), isNull); + }); + + test('delete non-existent key does not throw', () async { + await storage.delete('nonexistent'); + }); + + test('exists returns correct values', () async { + expect(await storage.exists('key'), isFalse); + await storage.write('key', 'value'); + expect(await storage.exists('key'), isTrue); + await storage.delete('key'); + expect(await storage.exists('key'), isFalse); + }); + + test('handles special characters in data', () async { + final data = '{"emoji": "\\u{1F600}", "newline": "line1\\nline2"}'; + await storage.write('special', data); + expect(await storage.read('special'), equals(data)); + }); + }); +} From d6958642ea6060b45f0fcec169b81c810c14da69 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Mon, 8 Jun 2026 08:50:30 +0200 Subject: [PATCH 02/12] feat: wire apply, events, and async gate into Confidence - Auto-apply on getFlag()/getValue() when shouldApply is true - AsyncGate serializes concurrent resolve requests - Stale response discarding when context changes mid-fetch - track() and flush() delegate to EventsClient - Builder now creates ApplyManager and EventsClient Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/confidence.dart | 124 +++++++++++++++++++++++++++++++++++--- test/confidence_test.dart | 12 ++-- 2 files changed, 121 insertions(+), 15 deletions(-) diff --git a/lib/src/confidence.dart b/lib/src/confidence.dart index adcee4c..664d873 100644 --- a/lib/src/confidence.dart +++ b/lib/src/confidence.dart @@ -1,9 +1,13 @@ +import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; +import 'apply_client.dart'; +import 'apply_manager.dart'; import 'confidence_value.dart'; import 'evaluation.dart'; +import 'events_client.dart'; import 'flag_resolution.dart'; import 'resolve_client.dart'; import 'storage.dart'; @@ -30,12 +34,20 @@ class Confidence { // -- Flag lifecycle -- Future fetchAndActivate() async { - final resolution = await _state.resolveClient.resolve(getContext()); - await _state.storage.write( - 'confidence.flags.resolve', - jsonEncode(resolution.toJson()), - ); - _state.currentResolution = resolution; + final contextSnapshot = getContext(); + await _state.asyncGate.run(() async { + final resolution = + await _state.resolveClient.resolve(contextSnapshot); + + // Stale response check: if context changed during the fetch, discard + if (!_contextEquals(contextSnapshot, getContext())) return; + + await _state.storage.write( + 'confidence.flags.resolve', + jsonEncode(resolution.toJson()), + ); + _state.currentResolution = resolution; + }); } Future activate() async { @@ -47,7 +59,12 @@ class Confidence { } Future asyncFetch() async { - final resolution = await _state.resolveClient.resolve(getContext()); + final contextSnapshot = getContext(); + final resolution = + await _state.resolveClient.resolve(contextSnapshot); + + if (!_contextEquals(contextSnapshot, getContext())) return; + await _state.storage.write( 'confidence.flags.resolve', jsonEncode(resolution.toJson()), @@ -56,7 +73,6 @@ class Confidence { Future activateAndFetchAsync() async { await activate(); - // Fire-and-forget background fetch asyncFetch().ignore(); } @@ -72,10 +88,28 @@ class Confidence { value: defaultValue, reason: ResolveReason.error, errorCode: 'NOT_READY', - errorMessage: 'No flag resolution available. Call fetchAndActivate() or activate() first.', + errorMessage: + 'No flag resolution available. Call fetchAndActivate() or activate() first.', + ); + } + + final eval = resolution.evaluate(flagPath, defaultValue); + + // Auto-apply: fire-and-forget when evaluation succeeds + if (eval.reason == ResolveReason.match) { + final flagName = flagPath.split('.')[0]; + final flag = resolution.flags.firstWhere( + (f) => f.flag == flagName, + orElse: () => throw StateError('unreachable'), ); + if (flag.shouldApply && _state.applyManager != null) { + _state.applyManager! + .apply(flagName, resolution.resolveToken) + .ignore(); + } } - return resolution.evaluate(flagPath, defaultValue); + + return eval; } // -- Context management -- @@ -115,9 +149,37 @@ class Confidence { ); } + // -- Events -- + + void track(String eventName, + [Map data = const {}]) { + _state.eventsClient?.send( + eventName: eventName, + payload: data, + context: getContext(), + ); + } + + void flush() { + // Best-effort: currently events are sent immediately, no buffering. + } + + // -- Internal -- + void _triggerRefetch() { fetchAndActivate().ignore(); } + + bool _contextEquals( + Map a, + Map b, + ) { + if (a.length != b.length) return false; + for (final entry in a.entries) { + if (b[entry.key] != entry.value) return false; + } + return true; + } } class ConfidenceBuilder { @@ -160,9 +222,28 @@ class ConfidenceBuilder { region: _region, ); + final applyClient = ApplyClient( + httpClient: httpClient, + clientSecret: _clientSecret, + region: _region, + ); + + final applyManager = ApplyManager( + storage: storage, + applyClient: applyClient, + ); + + final eventsClient = EventsClient( + httpClient: httpClient, + clientSecret: _clientSecret, + region: _region, + ); + final state = _ConfidenceState( storage: storage, resolveClient: resolveClient, + applyManager: applyManager, + eventsClient: eventsClient, ); return Confidence._( @@ -175,10 +256,33 @@ class ConfidenceBuilder { class _ConfidenceState { final Storage storage; final ResolveClient resolveClient; + final ApplyManager? applyManager; + final EventsClient? eventsClient; + final _AsyncGate asyncGate = _AsyncGate(); FlagResolution? currentResolution; _ConfidenceState({ required this.storage, required this.resolveClient, + this.applyManager, + this.eventsClient, }); } + +class _AsyncGate { + Completer? _pending; + + Future run(Future Function() operation) async { + while (_pending != null) { + await _pending!.future; + } + _pending = Completer(); + try { + await operation(); + } finally { + final p = _pending!; + _pending = null; + p.complete(); + } + } +} diff --git a/test/confidence_test.dart b/test/confidence_test.dart index 6898dd3..546563f 100644 --- a/test/confidence_test.dart +++ b/test/confidence_test.dart @@ -370,9 +370,9 @@ void main() { jsonEncode(storedResolution), ); - var fetchCount = 0; - final mockClient = MockClient((_) async { - fetchCount++; + var resolveCount = 0; + final mockClient = MockClient((request) async { + if (request.url.path.contains('resolve')) resolveCount++; return http.Response(jsonEncode(makeResolveResponse()), 200); }); @@ -389,9 +389,11 @@ void main() { equals('cached'), ); - // Background fetch should have started + // Background fetch should have started — pump the event loop await Future.delayed(Duration.zero); - expect(fetchCount, equals(1)); + await Future.delayed(Duration.zero); + await Future.delayed(Duration.zero); + expect(resolveCount, equals(1)); }); }); From bda7e480b80ed91bf7f7e41e7842780793e53506 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Mon, 8 Jun 2026 15:22:29 +0200 Subject: [PATCH 03/12] feat: add Flutter integration and legacy compat API - FlutterStorage: path_provider-based DiskStorage factory - VisitorIdManager: UUID persistence via shared_preferences - DeviceContextProvider: device/app/OS info via device_info_plus - ConfidenceFlutter.create(): wires all Flutter deps automatically - Legacy getBool/getString/getInt/getDouble extension methods - Barrel export with all public types Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/confidence_flutter_sdk.dart | 126 ++---------------------- lib/src/flutter/confidence_flutter.dart | 36 +++++++ lib/src/flutter/device_context.dart | 54 ++++++++++ lib/src/flutter/flutter_storage.dart | 12 +++ lib/src/flutter/visitor_id.dart | 27 +++++ lib/src/legacy_api.dart | 15 +++ 6 files changed, 151 insertions(+), 119 deletions(-) create mode 100644 lib/src/flutter/confidence_flutter.dart create mode 100644 lib/src/flutter/device_context.dart create mode 100644 lib/src/flutter/flutter_storage.dart create mode 100644 lib/src/flutter/visitor_id.dart create mode 100644 lib/src/legacy_api.dart diff --git a/lib/confidence_flutter_sdk.dart b/lib/confidence_flutter_sdk.dart index 3b77c44..41a4cdd 100644 --- a/lib/confidence_flutter_sdk.dart +++ b/lib/confidence_flutter_sdk.dart @@ -1,119 +1,7 @@ -import 'dart:async'; - -import 'confidence_flutter_sdk_platform_interface.dart'; - -class ConfidenceFlutterSdk { - Map _flags = {}; - bool isInitialized = false; - Future isStorageEmpty() async { - return ConfidenceFlutterSdkPlatform.instance.isStorageEmpty(); - } - - Future putContext(String key, dynamic value) async { - await ConfidenceFlutterSdkPlatform.instance.putContext(key, value); - if(isInitialized) { - await fetchAndActivate(); - } - } - - Future putAllContext(Map context) async { - await ConfidenceFlutterSdkPlatform.instance.putAllContext(context); - if(isInitialized) { - await fetchAndActivate(); - } - } - - void track(String eventName, Map data) { - ConfidenceFlutterSdkPlatform.instance.track(eventName, data); - } - - void flush() { - ConfidenceFlutterSdkPlatform.instance.flush(); - } - - bool getBool(String key, bool defaultValue) { - unawaited(ConfidenceFlutterSdkPlatform.instance.getBool(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - T? resolveKey(String key) { - List keys = key.split("."); - Map flags = _flags; - for(int i = 0; i < keys.length; i++) { - String element = keys[i]; - if (flags.containsKey(element)) { - if(flags[element] is Map) { - flags = flags[element]; - } else { - return parse(flags[element]); - } - } else { - return null; - } - } - return parse(flags); - } - - T parse(dynamic value) { - if(T == String) { - return value.toString() as T; - } else if(T == int) { - return int.parse(value.toString()) as T; - } else if(T == bool) { - return bool.parse(value.toString()) as T; - } else if(T == double) { - return double.parse(value.toString()) as T; - } else if(T == Map) { - return value as T; - } else { - return value as T; - } - } - - int getInt(String key, int defaultValue) { - unawaited(ConfidenceFlutterSdkPlatform.instance.getInt(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - String getString(String key, String defaultValue) { - unawaited(ConfidenceFlutterSdkPlatform.instance.getString(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - Map getObject(String key, Map defaultValue) { - unawaited(ConfidenceFlutterSdkPlatform.instance.getObject(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - double getDouble(String key, double defaultValue) { - unawaited(ConfidenceFlutterSdkPlatform.instance.getDouble(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - Future setup(String apiKey, [LoggingLevel loggingLevel = LoggingLevel.WARN]) async { - return await ConfidenceFlutterSdkPlatform.instance.setup(apiKey, loggingLevel); - } - - Future fetchAndActivate() async { - await ConfidenceFlutterSdkPlatform.instance.fetchAndActivate(); - await fillAllFlags(); - } - - Future fillAllFlags() async { - _flags = await ConfidenceFlutterSdkPlatform.instance.readAllFlags(); - isInitialized = true; - } - - Future activateAndFetchAsync() async { - await ConfidenceFlutterSdkPlatform.instance.activateAndFetchAsync(); - await fillAllFlags(); - } -} - -enum LoggingLevel { - VERBOSE, // 0 - DEBUG, // 1 - WARN, // 2 - ERROR, // 3 - NONE // 4 -} +export 'src/confidence.dart' show Confidence, ConfidenceBuilder; +export 'src/confidence_value.dart'; +export 'src/evaluation.dart'; +export 'src/resolve_client.dart' show ConfidenceRegion; +export 'src/storage.dart' show Storage, MemoryStorage, DiskStorage; +export 'src/flutter/confidence_flutter.dart'; +export 'src/legacy_api.dart'; diff --git a/lib/src/flutter/confidence_flutter.dart b/lib/src/flutter/confidence_flutter.dart new file mode 100644 index 0000000..236f6f0 --- /dev/null +++ b/lib/src/flutter/confidence_flutter.dart @@ -0,0 +1,36 @@ +import '../confidence.dart'; +import '../confidence_value.dart'; +import '../resolve_client.dart'; +import 'device_context.dart'; +import 'flutter_storage.dart'; +import 'visitor_id.dart'; + +class ConfidenceFlutter { + ConfidenceFlutter._(); + + static Future create({ + required String clientSecret, + ConfidenceRegion region = ConfidenceRegion.global, + Map initialContext = const {}, + }) async { + final storage = await FlutterStorage.create(); + final visitorIdManager = VisitorIdManager(); + final deviceContextProvider = DeviceContextProvider(); + + final visitorContext = await visitorIdManager.asContext(); + final deviceContext = await deviceContextProvider.getDeviceContext(); + + // Merge: device context < visitor ID < user-provided context + final mergedContext = { + ...deviceContext, + ...visitorContext, + ...initialContext, + }; + + return Confidence.builder(clientSecret: clientSecret) + .region(region) + .storage(storage) + .initialContext(mergedContext) + .build(); + } +} diff --git a/lib/src/flutter/device_context.dart b/lib/src/flutter/device_context.dart new file mode 100644 index 0000000..c20b887 --- /dev/null +++ b/lib/src/flutter/device_context.dart @@ -0,0 +1,54 @@ +import 'dart:io'; + +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import '../confidence_value.dart'; + +class DeviceContextProvider { + Future> getDeviceContext() async { + final context = {}; + + try { + final packageInfo = await PackageInfo.fromPlatform(); + context['app'] = ConfidenceValue.structure({ + 'version': ConfidenceValue.string(packageInfo.version), + 'build': ConfidenceValue.string(packageInfo.buildNumber), + 'package': ConfidenceValue.string(packageInfo.packageName), + }); + } catch (_) {} + + try { + final deviceInfo = DeviceInfoPlugin(); + if (Platform.isAndroid) { + final android = await deviceInfo.androidInfo; + context['device'] = ConfidenceValue.structure({ + 'manufacturer': ConfidenceValue.string(android.manufacturer), + 'model': ConfidenceValue.string(android.model), + 'type': ConfidenceValue.string('android'), + }); + context['os'] = ConfidenceValue.structure({ + 'name': ConfidenceValue.string('android'), + 'version': ConfidenceValue.string(android.version.release), + }); + } else if (Platform.isIOS) { + final ios = await deviceInfo.iosInfo; + context['device'] = ConfidenceValue.structure({ + 'manufacturer': ConfidenceValue.string('Apple'), + 'model': ConfidenceValue.string(ios.model), + 'type': ConfidenceValue.string('ios'), + }); + context['os'] = ConfidenceValue.structure({ + 'name': ConfidenceValue.string('ios'), + 'version': ConfidenceValue.string(ios.systemVersion), + }); + } + } catch (_) {} + + try { + context['locale'] = ConfidenceValue.string(Platform.localeName); + } catch (_) {} + + return context; + } +} diff --git a/lib/src/flutter/flutter_storage.dart b/lib/src/flutter/flutter_storage.dart new file mode 100644 index 0000000..f573c14 --- /dev/null +++ b/lib/src/flutter/flutter_storage.dart @@ -0,0 +1,12 @@ +import 'package:path_provider/path_provider.dart'; + +import '../storage.dart'; + +class FlutterStorage { + FlutterStorage._(); + + static Future create() async { + final dir = await getApplicationSupportDirectory(); + return DiskStorage('${dir.path}/confidence'); + } +} diff --git a/lib/src/flutter/visitor_id.dart b/lib/src/flutter/visitor_id.dart new file mode 100644 index 0000000..fe2ac0e --- /dev/null +++ b/lib/src/flutter/visitor_id.dart @@ -0,0 +1,27 @@ +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:uuid/uuid.dart'; + +import '../confidence_value.dart'; + +class VisitorIdManager { + static const _key = 'confidence.visitor_id'; + String? _cachedId; + + Future getOrCreate() async { + if (_cachedId != null) return _cachedId!; + + final prefs = await SharedPreferences.getInstance(); + var id = prefs.getString(_key); + if (id == null) { + id = const Uuid().v4(); + await prefs.setString(_key, id); + } + _cachedId = id; + return id; + } + + Future> asContext() async { + final id = await getOrCreate(); + return {'visitor_id': ConfidenceValue.string(id)}; + } +} diff --git a/lib/src/legacy_api.dart b/lib/src/legacy_api.dart new file mode 100644 index 0000000..8af6143 --- /dev/null +++ b/lib/src/legacy_api.dart @@ -0,0 +1,15 @@ +import 'confidence.dart'; + +extension ConfidenceLegacyApi on Confidence { + bool getBool(String key, bool defaultValue) => + getValue(key, defaultValue); + + String getString(String key, String defaultValue) => + getValue(key, defaultValue); + + int getInt(String key, int defaultValue) => + getValue(key, defaultValue); + + double getDouble(String key, double defaultValue) => + getValue(key, defaultValue); +} From 8e61136c95bb021d69d216960d8849d8fda78b0a Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Mon, 8 Jun 2026 15:34:22 +0200 Subject: [PATCH 04/12] fix: restore legacy ConfidenceFlutterSdk class and fix analysis errors Keep the old ConfidenceFlutterSdk class and LoggingLevel enum in the barrel export for native bridge and example app compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/confidence_flutter_sdk.dart | 129 ++++++++++++++++++++++++++++++++ test/resolve_client_test.dart | 1 - 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/lib/confidence_flutter_sdk.dart b/lib/confidence_flutter_sdk.dart index 41a4cdd..243f795 100644 --- a/lib/confidence_flutter_sdk.dart +++ b/lib/confidence_flutter_sdk.dart @@ -1,3 +1,7 @@ +import 'dart:async'; + +import 'confidence_flutter_sdk_platform_interface.dart'; + export 'src/confidence.dart' show Confidence, ConfidenceBuilder; export 'src/confidence_value.dart'; export 'src/evaluation.dart'; @@ -5,3 +9,128 @@ export 'src/resolve_client.dart' show ConfidenceRegion; export 'src/storage.dart' show Storage, MemoryStorage, DiskStorage; export 'src/flutter/confidence_flutter.dart'; export 'src/legacy_api.dart'; + +enum LoggingLevel { + VERBOSE, + DEBUG, + WARN, + ERROR, + NONE, +} + +// Legacy class — kept for existing example app and native bridge consumers. +// New code should use Confidence.builder() or ConfidenceFlutter.create(). +class ConfidenceFlutterSdk { + Map _flags = {}; + bool isInitialized = false; + + Future isStorageEmpty() async { + return ConfidenceFlutterSdkPlatform.instance.isStorageEmpty(); + } + + Future putContext(String key, dynamic value) async { + await ConfidenceFlutterSdkPlatform.instance.putContext(key, value); + if (isInitialized) { + await fetchAndActivate(); + } + } + + Future putAllContext(Map context) async { + await ConfidenceFlutterSdkPlatform.instance.putAllContext(context); + if (isInitialized) { + await fetchAndActivate(); + } + } + + void track(String eventName, Map data) { + ConfidenceFlutterSdkPlatform.instance.track(eventName, data); + } + + void flush() { + ConfidenceFlutterSdkPlatform.instance.flush(); + } + + bool getBool(String key, bool defaultValue) { + unawaited(ConfidenceFlutterSdkPlatform.instance.getBool(key, defaultValue)); + return resolveKey(key) ?? defaultValue; + } + + T? resolveKey(String key) { + List keys = key.split("."); + Map flags = _flags; + for (int i = 0; i < keys.length; i++) { + String element = keys[i]; + if (flags.containsKey(element)) { + if (flags[element] is Map) { + flags = flags[element]; + } else { + return _parse(flags[element]); + } + } else { + return null; + } + } + return _parse(flags); + } + + T _parse(dynamic value) { + if (T == String) { + return value.toString() as T; + } else if (T == int) { + return int.parse(value.toString()) as T; + } else if (T == bool) { + return bool.parse(value.toString()) as T; + } else if (T == double) { + return double.parse(value.toString()) as T; + } else if (T == Map) { + return value as T; + } else { + return value as T; + } + } + + int getInt(String key, int defaultValue) { + unawaited(ConfidenceFlutterSdkPlatform.instance.getInt(key, defaultValue)); + return resolveKey(key) ?? defaultValue; + } + + String getString(String key, String defaultValue) { + unawaited( + ConfidenceFlutterSdkPlatform.instance.getString(key, defaultValue)); + return resolveKey(key) ?? defaultValue; + } + + Map getObject( + String key, Map defaultValue) { + unawaited( + ConfidenceFlutterSdkPlatform.instance.getObject(key, defaultValue)); + return resolveKey(key) ?? defaultValue; + } + + double getDouble(String key, double defaultValue) { + unawaited( + ConfidenceFlutterSdkPlatform.instance.getDouble(key, defaultValue)); + return resolveKey(key) ?? defaultValue; + } + + Future setup(String apiKey, + [LoggingLevel loggingLevel = LoggingLevel.WARN]) async { + return await ConfidenceFlutterSdkPlatform.instance + .setup(apiKey, loggingLevel); + } + + Future fetchAndActivate() async { + await ConfidenceFlutterSdkPlatform.instance.fetchAndActivate(); + await fillAllFlags(); + } + + Future fillAllFlags() async { + _flags = await ConfidenceFlutterSdkPlatform.instance.readAllFlags(); + isInitialized = true; + } + + Future activateAndFetchAsync() async { + await ConfidenceFlutterSdkPlatform.instance.activateAndFetchAsync(); + await fillAllFlags(); + } +} diff --git a/test/resolve_client_test.dart b/test/resolve_client_test.dart index ebcc818..11ea8af 100644 --- a/test/resolve_client_test.dart +++ b/test/resolve_client_test.dart @@ -5,7 +5,6 @@ import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:confidence_flutter_sdk/src/confidence_value.dart'; import 'package:confidence_flutter_sdk/src/resolve_client.dart'; -import 'package:confidence_flutter_sdk/src/flag_resolution.dart'; import 'package:confidence_flutter_sdk/src/evaluation.dart'; void main() { From 0dfea927dbb73b9652c3cd97c6692463644f3089 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Mon, 8 Jun 2026 15:40:19 +0200 Subject: [PATCH 05/12] docs: add native Dart SDK implementation plan Co-Authored-By: Claude Opus 4.6 (1M context) --- plans/native-dart-sdk-plan.md | 348 ++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 plans/native-dart-sdk-plan.md diff --git a/plans/native-dart-sdk-plan.md b/plans/native-dart-sdk-plan.md new file mode 100644 index 0000000..0d64cd6 --- /dev/null +++ b/plans/native-dart-sdk-plan.md @@ -0,0 +1,348 @@ +# Native Dart SDK for Confidence Flutter — Implementation Plan + +## Context + +The current `confidence_flutter_sdk` (v0.2.1) is a thin method-channel bridge. The Dart layer (`lib/`) is three files totaling ~160 lines — just an in-memory flag cache, type wrappers for platform serialization, and `unawaited()` calls to native for apply tracking. All real logic lives in the Kotlin Android SDK (`confidence-sdk-android:0.6.2`) and the Swift SDK (git submodule at `ios/Classes/confidence-sdk`). + +This means: iOS+Android only, two native codebases to maintain, a fragile git-submodule CI dance (copy Swift sources, delete submodule, build), and no Dart-side tests. The backend is pure REST/JSON, so a Dart-native implementation is fully feasible and eliminates all of this. + +**Goal**: Replace the native bridge with a pure Dart implementation covering the core features of both native SDKs. The SDK becomes testable in pure Dart, and platform support expands to web/desktop for free. The native SDKs (Android/iOS) remain alongside during development so integration tests can validate behavioral parity. + +--- + +## Architecture: Single Package + +Keep the existing `confidence_flutter_sdk` package rather than splitting into two. The pure-Dart core lives cleanly in `lib/src/` with zero Flutter imports; it can be extracted into its own package later if needed. The Flutter-specific pieces (directory path resolution, device info, visitor ID) live in `lib/src/flutter/`. + +``` +lib/ + confidence_flutter_sdk.dart # barrel export (public API) + src/ + confidence.dart # Confidence class + builder + confidence_value.dart # Sealed ConfidenceValue type + evaluation.dart # Evaluation + ResolveReason + flag_resolution.dart # FlagResolution model + cache + resolve_client.dart # HTTP POST /v1/flags:resolve + apply_manager.dart # Exposure tracking (pull-send-restore) + apply_client.dart # HTTP POST /v1/flags:apply + events_client.dart # HTTP POST /v1/events:publish (best-effort) + storage.dart # Storage interface + MemoryStorage + DiskStorage + sdk_metadata.dart # SDK ID/version + telemetry header + flutter/ + flutter_storage.dart # path_provider directory resolution + device_context.dart # device_info_plus enrichment + visitor_id.dart # shared_preferences UUID + confidence_flutter.dart # Factory that wires Flutter deps +``` + +### Dependency Flow + +```mermaid +graph TD + A[confidence_flutter_sdk.dart
barrel export] --> B[Confidence
main class + builder] + B --> C[ResolveClient] + B --> D[ApplyManager] + B --> E[EventsClient
best-effort send] + B --> F[FlagResolution
cache layer] + D --> G[ApplyClient] + C & G & E --> I["package:http"] + C & G & E --> T[SdkMetadata
+ telemetry header] + F & D --> J[Storage interface] + J --> K[MemoryStorage] + J --> L["DiskStorage
(dart:io File)"] + B --> M[ConfidenceValue
sealed type] + N[ConfidenceFlutter
factory] --> B + N --> PP[path_provider
dir resolution] + PP --> L + N --> O[DeviceContext
device_info_plus] + N --> P[VisitorId
shared_preferences] +``` + +### Storage: Pure Dart Disk I/O + +`DiskStorage` uses `dart:io` `File` directly — pure Dart, no platform channel needed. Works on mobile, desktop, and server. The only Flutter-specific piece is resolving the correct directory path via `path_provider` (done once in `ConfidenceFlutter.create()`), which passes the path into `DiskStorage(directoryPath)`. Web needs a separate adapter in the future, but is out of scope. + +--- + +## Public API + +### Primary API (aligned with native SDKs) + +```dart +class Confidence { + // Construction + static ConfidenceBuilder builder({required String clientSecret}); + + // Flag lifecycle + Future fetchAndActivate(); + Future activate(); + Future asyncFetch(); + + // Flag evaluation (generic) + T getValue(String flagPath, T defaultValue); + Evaluation getFlag(String flagPath, T defaultValue); + + // Context + void putContext(String key, ConfidenceValue value); + void putContextLocal(String key, ConfidenceValue value); // no re-fetch + void removeContext(String key); + Confidence withContext(Map context); // child instance + Map getContext(); + + // Events + void track(String eventName, [Map data]); + void flush(); +} +``` + +### Builder + +```dart +Confidence.builder(clientSecret: 'xxx') + .region(ConfidenceRegion.eu) + .loggerLevel(LoggingLevel.warn) + .initialContext({'targeting_key': ConfidenceValue.string('user-123')}) + .storage(myCustomStorage) // DI for testing + .build(); +``` + +### Flutter convenience factory + +```dart +final confidence = await ConfidenceFlutter.create( + clientSecret: 'xxx', + region: ConfidenceRegion.eu, +); +await confidence.fetchAndActivate(); +``` + +Wires up DiskStorage (via path_provider), VisitorIdManager, and DeviceContextProvider automatically. + +### Legacy compat (extension methods) + +```dart +extension ConfidenceLegacyApi on Confidence { + bool getBool(String key, bool defaultValue) => getValue(key, defaultValue); + String getString(String key, String defaultValue) => getValue(key, defaultValue); + int getInt(String key, int defaultValue) => getValue(key, defaultValue); + double getDouble(String key, double defaultValue) => getValue(key, defaultValue); +} +``` + +--- + +## Type System + +### ConfidenceValue (sealed class) + +```dart +sealed class ConfidenceValue { + static ConfidenceValue boolean(bool value); + static ConfidenceValue string(String value); + static ConfidenceValue integer(int value); + static ConfidenceValue double_(double value); + static ConfidenceValue date(DateTime value); + static ConfidenceValue timestamp(DateTime value); + static ConfidenceValue list(List value); + static ConfidenceValue structure(Map value); + static ConfidenceValue null_(); +} +``` + +### Evaluation + +```dart +class Evaluation { + final T value; + final String? variant; + final ResolveReason reason; + final String? errorCode; + final String? errorMessage; +} + +enum ResolveReason { + match, unspecified, noSegmentMatch, noTreatmentMatch, + flagArchived, targetingKeyError, error, stale, +} +``` + +--- + +## Concurrency Model + +Dart is single-threaded with cooperative async. No locks needed, but we need guards: + +- **Flag fetching**: `AsyncGate` (Completer-based) prevents duplicate concurrent resolve requests. If `putContext()` triggers a re-fetch while one is in-flight, the second waits then starts. +- **Apply pipeline**: Simple guard prevents overlapping batch uploads. +- **Stale response discarding**: If context changed during an in-flight resolve, discard the response (matches Android SDK behavior). + +--- + +## Implementation Phases + +Each phase produces a working, testable increment. + +### Phase 0: Test Suite Foundation + +Before writing any implementation, create the test suite by studying the Android and Swift SDK test suites. This ensures test-driven development and behavioral parity. + +**Study the native SDK tests:** +- Android: `confidence-sdk-android` test suite (resolve response parsing, value evaluation, apply behavior, context management) +- Swift: `confidence-sdk-swift` test suite (same areas) +- Extract the key test scenarios and expected behaviors + +**Files to create:** +- `test/confidence_value_test.dart` — JSON serialization round-trips, type coercion, nested structure handling +- `test/flag_resolution_test.dart` — Dot-path evaluation (`"my-flag.color.hex"`), type mismatch returns default, missing flag returns default, schema validation +- `test/resolve_client_test.dart` — Request format matches wire spec, response parsing, error responses (404, 500), region URL selection +- `test/apply_manager_test.dart` — Dedup (same flag not sent twice per resolve token), restore-on-failure, `shouldApply: false` skips apply +- `test/events_client_test.dart` — Event serialization, context merging into payload +- `test/confidence_test.dart` — `fetchAndActivate()` flow, `activate()` + `asyncFetch()` flow, context changes trigger re-fetch, `getValue()` type resolution, stale response discarding +- `test/storage_test.dart` — MemoryStorage and DiskStorage CRUD operations + +All tests use mocked `http.Client`. Tests will initially fail (no implementation); each subsequent phase makes them pass. + +--- + +### Phase 1: Core Types + Flag Resolution + +Build the type system and flag resolution — the foundation everything else depends on. + +**Files to create:** +- `lib/src/confidence_value.dart` — Sealed class hierarchy with subtypes for each value kind. Convenience constructors on sealed parent. `toJson()`/`fromJson()` mapping to the backend's protobuf-JSON Struct format. +- `lib/src/evaluation.dart` — `Evaluation` with `value`, `variant`, `reason` (enum), `errorCode`, `errorMessage`. +- `lib/src/flag_resolution.dart` — `FlagResolution` model: `resolvedFlags` list, `resolveToken`. `ResolvedFlag` with `flag`, `variant`, `value`, `flagSchema`, `reason`, `shouldApply`. Dot-path evaluation method (port from existing `resolveKey` at `confidence_flutter_sdk.dart:39-55`, but typed with `ConfidenceValue`). JSON serialization for disk persistence. +- `lib/src/sdk_metadata.dart` — SDK ID (`SDK_ID_DART_CONFIDENCE`), version string. Builds the `X-CONFIDENCE-TELEMETRY` header included on every HTTP request. +- `lib/src/resolve_client.dart` — Takes `http.Client`, base URL, client secret. `resolve(flags, context, sdk)` POSTs to `/v1/flags:resolve`, returns `FlagResolution`. Region enum: `global` -> `https://resolver.confidence.dev`, `eu` -> `https://resolver.eu.confidence.dev`. +- `lib/src/storage.dart` — `Storage` abstract class (`read(key)`, `write(key, data)`, `delete(key)`, `exists(key)`). `MemoryStorage` (in-memory map). `DiskStorage` (takes directory path, stores each key as a file using `dart:io` `File`). +- `lib/src/confidence.dart` — `Confidence` class with builder. Phase 1 scope: construction, `fetchAndActivate()`, `getValue()`, `getFlag()`, `putContext()`, `putContextLocal()` (no re-fetch), `removeContext()`, `getContext()`, `withContext()` (child instance with parent context chain). Two-layer cache: "current" (active, read from) and "latest" (just fetched). `fetchAndActivate()` fetches then swaps latest->current. + +**Passes:** Phase 0 tests for confidence_value, flag_resolution, resolve_client, storage, and the basic confidence flow tests. + +--- + +### Phase 2: Apply Mechanism + +Add exposure tracking — tells the backend which flags were actually evaluated. + +**Files to create:** +- `lib/src/apply_client.dart` — POSTs to `/v1/flags:apply` with flag name, apply time, resolve token. +- `lib/src/apply_manager.dart` — Simple approach: tracks applied flags in storage as a set per resolve token. On `getValue()`/`getFlag()`, if `shouldApply` is true and flag hasn't been applied, add to pending set, send immediately. On failure, keep in pending set for retry on next evaluation. No state machine — just pull pending from storage, attempt send, restore on failure. + +**Files to modify:** +- `lib/src/confidence.dart` — Add `activate()` (swap cache without fetching), `asyncFetch()` (fetch in background), `activateAndFetchAsync()` convenience. Wire `ApplyManager` into `getValue()`/`getFlag()`. Add `AsyncGate` (Completer-based) to prevent duplicate concurrent resolve requests. Add stale-response discarding: if context changes during an in-flight resolve, discard the response. + +**Passes:** Phase 0 apply_manager tests. + +--- + +### Phase 3: Event Tracking + +Best-effort event sending — no buffering, no persistence. Send immediately. + +**Files to create:** +- `lib/src/events_client.dart` — POSTs to `/v1/events:publish`. Best-effort: fire-and-forget, log errors. Events endpoint URLs: `https://events.confidence.dev` (global) / `https://events.eu.confidence.dev` (EU). Each event includes: `eventDefinition` name, `eventTime`, `payload` (merged with current context), `sendTime`. Future iteration may add retry with backoff/jitter. + +**Files to modify:** +- `lib/src/confidence.dart` — Add `track(eventName, [data])` and `flush()`. `track()` builds the event payload by merging `data` with current context and sends via `EventsClient`. + +**Passes:** Phase 0 events_client tests. + +--- + +### Phase 4: Flutter Integration + SDK Telemetry + +Wire up Flutter-specific platform features and the telemetry header. + +**Files to create:** +- `lib/src/flutter/flutter_storage.dart` — Resolves the app documents directory via `path_provider`, creates a `DiskStorage` pointed at `{documentsDir}/confidence/`. +- `lib/src/flutter/visitor_id.dart` — Generates UUID v4 on first launch, persists via `shared_preferences`, provides `targeting_key` to context if not already set. +- `lib/src/flutter/device_context.dart` — Uses `device_info_plus` + `package_info_plus` to build context map: `os.name`, `os.version`, `device.manufacturer`, `device.model`, `app.version`, `app.build`. Enriches context on resolve and event calls. +- `lib/src/flutter/confidence_flutter.dart` — `ConfidenceFlutter.create(clientSecret, region, ...)` async factory that wires `DiskStorage` (via flutter_storage), `VisitorIdManager`, `DeviceContextProvider` into the builder. + +**Files to modify:** +- `lib/confidence_flutter_sdk.dart` — Rewrite as barrel export: `Confidence`, `ConfidenceFlutter`, `ConfidenceValue`, `Evaluation`, `ConfidenceRegion`, `LoggingLevel`, plus legacy compat extensions. + +**Passes:** Device context enrichment tests, visitor ID persistence/reuse tests, DiskStorage tests. + +--- + +### Phase 5: Migration Compat + Validation + +Bridge the old API for existing users. Keep native SDKs alongside and validate parity. + +**Backward compat** — Extension methods on `Confidence`: +```dart +extension ConfidenceLegacyApi on Confidence { + bool getBool(String key, bool defaultValue) => getValue(key, defaultValue); + String getString(String key, String defaultValue) => getValue(key, defaultValue); + int getInt(String key, int defaultValue) => getValue(key, defaultValue); + double getDouble(String key, double defaultValue) => getValue(key, defaultValue); +} +``` + +**Do NOT delete native code yet.** Keep `android/`, `ios/`, method channel files, and platform interface alongside the new Dart implementation. This allows: +- Running integration tests against both native and Dart paths +- Validating behavioral parity on real devices +- A safer rollout (native can be the fallback) + +The native code removal happens in a follow-up PR after parity is confirmed. + +**Files to modify:** +- `pubspec.yaml` — Add new deps (`http`, `uuid`, `path_provider`, `device_info_plus`, `package_info_plus`, `shared_preferences`) alongside existing ones. Add dev deps: `mockito`, `build_runner`. Keep the `plugin` section intact for now. +- `example/lib/main.dart` — Add a second code path using the new Dart API alongside the existing native path, so both can be compared. + +**CI changes** (`.github/workflows/`): +- `ci.yaml` — Add a `flutter test` step for the new Dart unit tests. Keep existing native build steps. +- Keep `android-test.yaml` and `ios-test.yaml` unchanged — they validate the native path still works. + +--- + +## Dependencies (final pubspec additions) + +```yaml +# Add to existing dependencies: + http: ^1.2.0 + uuid: ^4.0.0 + path_provider: ^2.1.0 + device_info_plus: ^10.0.0 + package_info_plus: ^8.0.0 + shared_preferences: ^2.2.0 + +# Add to dev_dependencies: + mockito: ^5.4.0 + build_runner: ^2.4.0 +``` + +--- + +## Wire Format Reference + +Three REST endpoints: +- **Resolve**: `POST {resolver}/v1/flags:resolve` — sends `flags`, `evaluationContext`, `clientSecret`, `apply: false`, `sdk`; returns `resolvedFlags` with `flag`, `variant`, `value`, `flagSchema`, `reason`, `shouldApply`, plus `resolveToken` +- **Apply**: `POST {resolver}/v1/flags:apply` — sends `flags` (with `applyTime`), `sendTime`, `clientSecret`, `resolveToken`, `sdk` +- **Events**: `POST {events}/v1/events:publish` — sends `clientSecret`, `events` (with `eventDefinition`, `eventTime`, `payload` including `context`), `sendTime`, `sdk` + +All requests include `X-CONFIDENCE-TELEMETRY` header with protobuf-encoded SDK metadata. + +Region determines base URLs: +- Global: resolver `https://resolver.confidence.dev`, events `https://events.confidence.dev` +- EU: resolver `https://resolver.eu.confidence.dev`, events `https://events.eu.confidence.dev` + +--- + +## Deferred / Future + +- **OpenFeature provider** — separate package (when there's demand) +- **Event persistence/retry** — disk-buffered event pipeline with flush policies (currently best-effort) +- **Screen tracking** — Flutter NavigatorObserver equivalent of iOS's UIViewController swizzling +- **Web storage backend** — `localStorage`/`IndexedDB` adapter + +--- + +## Verification + +1. **Unit tests (Phase 0 onward)** — mock `http.Client`, verify request/response serialization, cache behavior, apply tracking, event sending, context merging. Run with `flutter test`. +2. **Integration tests (kept from native)** — existing `example/test/widget_test.dart` runs against the real Confidence backend on Android emulator and iOS simulator via the existing CI workflows. These validate the native path still works. +3. **Parity validation** — example app exercises both native and Dart paths side-by-side, comparing results for the same flag/context combinations. +4. **Platform smoke** — build example app for Android and iOS to confirm Flutter-specific pieces work. Web/desktop builds verify the core Dart code compiles. From acaa0d92fd8134d2333ac1428ffa83e186667df5 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Mon, 8 Jun 2026 15:51:25 +0200 Subject: [PATCH 06/12] ci: add flutter test step to CI workflow Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 82a7ebc..c5a5844 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,6 +34,12 @@ jobs: channel: ${{ env.FLUTTER_CHANNEL }} - name: Install dependencies + run: flutter pub get + + - name: Run tests + run: flutter test + + - name: Install example dependencies working-directory: example run: flutter pub get From 5212108bd37c7505f524cfd90b5bd0b6fda6c08b Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Tue, 9 Jun 2026 08:23:15 +0200 Subject: [PATCH 07/12] feat: rewrite example app to use new Dart SDK API Replaces the old ConfidenceFlutterSdk usage with Confidence.builder() and the new typed API. Co-Authored-By: Claude Opus 4.6 (1M context) --- example/lib/main.dart | 156 +++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 79 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 2366db0..a912e15 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,115 +1,113 @@ -import 'dart:async'; - import 'package:confidence_flutter_sdk/confidence_flutter_sdk.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; void main() { - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatefulWidget { - MyApp({super.key}); - final Completer _initCompleter = Completer(); - - Future initDone() async { - return _initCompleter.future; - } + const MyApp({super.key}); @override - // ignore: no_logic_in_create_state - State createState() => _MyAppState(_initCompleter); + State createState() => _MyAppState(); } class _MyAppState extends State { - String _object = 'Unknown'; - String _message = 'Unknown'; - final _confidenceFlutterSdkPlugin = ConfidenceFlutterSdk(); - final Completer initCompleter; - - _MyAppState(this.initCompleter); + String _status = 'Initializing...'; + String _flagValue = ''; + String _variant = ''; + String _reason = ''; + String _context = ''; @override void initState() { super.initState(); - initPlatformState(); + _initConfidence(); } - // Platform messages are asynchronous, so we initialize in an async method. - Future initPlatformState() async { - String message; - String object; - // Platform messages may fail, so we use a try/catch PlatformException. - // We also handle the message potentially returning null. + Future _initConfidence() async { try { await dotenv.load(fileName: ".env"); - await _confidenceFlutterSdkPlugin.setup(dotenv.env["API_KEY"]!, LoggingLevel.VERBOSE); - await _confidenceFlutterSdkPlugin.putAllContext({ - "targeting_key": "random", - "my_bool": false, - "my_int": 1, - "my_double": 1.1, - "my_map": {"key": "value"}, - "my_list": ["value1", "value2"] + final apiKey = dotenv.env["API_KEY"]!; + + final confidence = Confidence.builder(clientSecret: apiKey) + .region(ConfidenceRegion.eu) + .storage(MemoryStorage()) + .initialContext({ + 'targeting_key': ConfidenceValue.string('flutter-dart-sdk-test'), + }) + .build(); + + setState(() => _status = 'Fetching flags...'); + + await confidence.fetchAndActivate(); + + final eval = confidence.getFlag('hawkflag.message', 'no value'); + + confidence.track('example_app_loaded', { + 'sdk': ConfidenceValue.string('dart'), + 'screen': ConfidenceValue.string('home'), }); - await _confidenceFlutterSdkPlugin.fetchAndActivate(); - object = - (_confidenceFlutterSdkPlugin.getObject("hawkflag", {})).toString(); - message = - (_confidenceFlutterSdkPlugin.getString("hawkflag.message", "")); - final data = { - 'screen': 'home', - "my_bool": false, - "my_int": 1, - "my_double": 1.1, - "my_map": {"key": "value"}, - "my_list": ["value1", "value2"] - }; - _confidenceFlutterSdkPlugin.track("navigate", data); - _confidenceFlutterSdkPlugin.flush(); - } on PlatformException { - message = 'Failed to get platform version.'; - object = 'Failed to get object.'; - } - // If the widget was removed from the tree while the asynchronous platform - // message was in flight, we want to discard the reply rather than calling - // setState to update our non-existent appearance. - if (!mounted) return; + final ctx = confidence.getContext(); + final ctxDisplay = ctx.entries + .map((e) => ' ${e.key}: ${e.value.toPlainJson()}') + .join('\n'); - setState(() { - _message = message; - _object = object; - }); - initCompleter.complete(); + setState(() { + _status = 'Ready'; + _flagValue = eval.value; + _variant = eval.variant ?? 'none'; + _reason = eval.reason.name; + _context = ctxDisplay; + }); + } catch (e) { + setState(() => _status = 'Error: $e'); + } } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: Center( - child: ListView.builder( - itemCount: 2, - itemBuilder: (context, index) { - var title = ""; - switch (index) { - case 0: - title = _message; - case 1: - title = _object; - } - return ListTile( - title: Text('$title\n'), - ); - }, + appBar: AppBar(title: const Text('Confidence Dart SDK Example')), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _row('Status', _status), + const Divider(), + _row('hawkflag.message', _flagValue), + _row('Variant', _variant), + _row('Reason', _reason), + const Divider(), + const Text('Context:', + style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 4), + Text(_context, style: const TextStyle(fontFamily: 'monospace')), + ], ), ), ), ); } + + Widget _row(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 140, + child: Text('$label:', + style: const TextStyle(fontWeight: FontWeight.bold)), + ), + Expanded(child: Text(value)), + ], + ), + ); + } } From 690849df95d499cb17511b03e3159dd1d2681c21 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Tue, 9 Jun 2026 11:27:21 +0200 Subject: [PATCH 08/12] feat!: remove native bridge code Drop the Android (Kotlin), iOS (Swift), platform interface, method channel, and git submodule. The SDK is now pure Dart. BREAKING CHANGE: ConfidenceFlutterSdk class and LoggingLevel enum removed. Use Confidence.builder() or ConfidenceFlutter.create(). Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitmodules | 3 - android/.gitignore | 9 - android/build.gradle | 71 ----- android/proguard-rules.pro | 17 -- android/settings.gradle | 1 - android/src/main/AndroidManifest.xml | 3 - .../ConfidenceFlutterSdkPlugin.kt | 182 ------------- .../NetworkConfidenceValueSerializer.kt | 67 ----- .../ConfidenceFlutterSdkPluginTest.kt | 27 -- example/test/widget_test.dart | 33 +-- ios/Classes/ConfidenceFlutterSdkPlugin.swift | 248 ------------------ ios/Classes/confidence-sdk | 1 - ios/confidence_flutter_sdk.podspec | 23 -- lib/confidence_flutter_sdk.dart | 129 --------- ...confidence_flutter_sdk_method_channel.dart | 163 ------------ ...idence_flutter_sdk_platform_interface.dart | 82 ------ pubspec.yaml | 28 -- 17 files changed, 6 insertions(+), 1081 deletions(-) delete mode 100644 .gitmodules delete mode 100644 android/.gitignore delete mode 100644 android/build.gradle delete mode 100644 android/proguard-rules.pro delete mode 100644 android/settings.gradle delete mode 100644 android/src/main/AndroidManifest.xml delete mode 100644 android/src/main/kotlin/com/example/confidence_flutter_sdk/ConfidenceFlutterSdkPlugin.kt delete mode 100644 android/src/main/kotlin/com/example/confidence_flutter_sdk/NetworkConfidenceValueSerializer.kt delete mode 100644 android/src/test/kotlin/com/example/confidence_flutter_sdk/ConfidenceFlutterSdkPluginTest.kt delete mode 100644 ios/Classes/ConfidenceFlutterSdkPlugin.swift delete mode 160000 ios/Classes/confidence-sdk delete mode 100644 ios/confidence_flutter_sdk.podspec delete mode 100644 lib/confidence_flutter_sdk_method_channel.dart delete mode 100644 lib/confidence_flutter_sdk_platform_interface.dart diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 60a0f27..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "ios/Classes/confidence-sdk"] - path = ios/Classes/confidence-sdk - url = https://github.com/spotify/confidence-sdk-swift diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index 161bdcd..0000000 --- a/android/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures -.cxx diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 36bc0c4..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -group = "com.example.confidence_flutter_sdk" -version = "1.0-SNAPSHOT" - -buildscript { - ext.kotlin_version = "2.1.0" - repositories { - google() - mavenCentral() - } - - dependencies { - classpath("com.android.tools.build:gradle:8.7.3") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -apply plugin: "com.android.library" -apply plugin: "kotlin-android" - -android { - if (project.android.hasProperty("namespace")) { - namespace = "com.example.confidence_flutter_sdk" - } - - compileSdk = 34 - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = "17" - } - - sourceSets { - main.java.srcDirs += "src/main/kotlin" - test.java.srcDirs += "src/test/kotlin" - } - - defaultConfig { - consumerProguardFiles "proguard-rules.pro" - } - - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") - implementation("com.spotify.confidence:confidence-sdk-android:0.6.2") - implementation("org.jetbrains.kotlin:kotlin-reflect:2.1.0") - testImplementation("org.jetbrains.kotlin:kotlin-test") - testImplementation("org.mockito:mockito-core:5.1.0") - } - - testOptions { - unitTests.all { - useJUnitPlatform() - - testLogging { - events "passed", "skipped", "failed", "standardOut", "standardError" - outputs.upToDateWhen {false} - showStandardStreams = true - } - } - } -} diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro deleted file mode 100644 index 8b8eee1..0000000 --- a/android/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# ProGuard rules for Confidence Flutter SDK -# These rules suppress warnings for optional dependencies that may not be present at runtime - -# BouncyCastle JSSE Provider warnings --dontwarn org.bouncycastle.jsse.BCSSLParameters --dontwarn org.bouncycastle.jsse.BCSSLSocket --dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider - -# Conscrypt warnings --dontwarn org.conscrypt.Conscrypt$Version --dontwarn org.conscrypt.Conscrypt --dontwarn org.conscrypt.ConscryptHostnameVerifier - -# OpenJSSE warnings --dontwarn org.openjsse.javax.net.ssl.SSLParameters --dontwarn org.openjsse.javax.net.ssl.SSLSocket --dontwarn org.openjsse.net.ssl.OpenJSSE diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 3fda612..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'confidence_flutter_sdk' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml deleted file mode 100644 index 6e8c0d3..0000000 --- a/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - diff --git a/android/src/main/kotlin/com/example/confidence_flutter_sdk/ConfidenceFlutterSdkPlugin.kt b/android/src/main/kotlin/com/example/confidence_flutter_sdk/ConfidenceFlutterSdkPlugin.kt deleted file mode 100644 index 6c74c37..0000000 --- a/android/src/main/kotlin/com/example/confidence_flutter_sdk/ConfidenceFlutterSdkPlugin.kt +++ /dev/null @@ -1,182 +0,0 @@ -package com.example.confidence_flutter_sdk - -import android.content.Context -import com.spotify.confidence.Confidence -import com.spotify.confidence.ConfidenceFactory -import com.spotify.confidence.ConfidenceValue -import com.spotify.confidence.LoggingLevel -import com.spotify.confidence.FlagResolution -import com.spotify.confidence.client.SdkMetadata -import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.embedding.engine.plugins.activity.ActivityAware -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json -import java.io.File - -/** ConfidenceFlutterSdkPlugin */ -class ConfidenceFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel : MethodChannel - private lateinit var confidence: Confidence - private val coroutineScope = CoroutineScope(Dispatchers.IO) - private lateinit var context: Context - - override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "confidence_flutter_sdk") - channel.setMethodCallHandler(this) - } - - override fun onMethodCall(call: MethodCall, result: Result) { - when(call.method) { - "flush" -> { - confidence.flush() - } - "setup" -> { - val apiKey = call.argument("apiKey")!! - val loggingLevel = call.argument("loggingLevel")!! - confidence = ConfidenceFactory.create( - context, - apiKey, - loggingLevel = LoggingLevel.valueOf(loggingLevel) - ) - result.success(null) - } - "fetchAndActivate" -> { - coroutineScope.launch { - confidence.fetchAndActivate() - result.success(null) - } - } - "activateAndFetchAsync" -> { - coroutineScope.launch { - confidence.activate() - confidence.asyncFetch() - result.success(null) - } - } - "isStorageEmpty" -> { - val isEmpty = confidence.isStorageEmpty() - result.success(isEmpty) - } - "getString" -> { - val key = call.argument("key")!! - val defaultValue = call.argument("defaultValue") - val value = confidence.getValue(key, defaultValue) - result.success(value) - } - "getDouble" -> { - val key = call.argument("key")!! - val defaultValue = call.argument("defaultValue") - val value = confidence.getValue(key, defaultValue) - result.success(value) - } - "getBool" -> { - val key = call.argument("key")!! - val defaultValue = call.argument("defaultValue") - val value = confidence.getValue(key, defaultValue) - result.success(value) - } - "getInt" -> { - val key = call.argument("key")!! - val defaultValue = call.argument("defaultValue") - val value = confidence.getValue(key, defaultValue) - result.success(value) - } - "getObject" -> { - val key = call.argument("key")!! - val wrappedDefaultValue = call.argument>>("defaultValue")!! - val defaultValue: ConfidenceValue.Struct = ConfidenceValue.Struct(wrappedDefaultValue.mapValues { (_, value) -> value.convert() }) - val value = confidence.getValue(key, defaultValue) - result.success(Json.encodeToString(NetworkConfidenceValueSerializer, value)) - } - "readAllFlags" -> { - val flags = readAllFlags() - val map = flags.flags.associateBy({ it.flag }, { ConfidenceValue.Struct(it.value) }) - result.success(Json.encodeToString(NetworkConfidenceValueSerializer, ConfidenceValue.Struct(map))) - } - "putContext" -> { - val key = call.argument("key")!! - val value = call.argument>("value")!!.convert() - confidence.putContext(key, value) - result.success(null) - } - "putAllContext" -> { - val wrappedContext = call.argument>>("context")!! - val context: Map = wrappedContext.mapValues { (_, value) -> value.convert() } - confidence.putContext(context) - result.success(null) - } - "track" -> { - val eventName = call.argument("eventName")!! - val wrappedData = call.argument>>("data")!! - val data: Map = wrappedData.mapValues { (_, value) -> value.convert() } - confidence.track(eventName, data) - } - else -> result.notImplemented() - } - } - - private fun readAllFlags(): FlagResolution { - val flagsFile = File(context.filesDir, "confidence_flags_cache.json") - if (!flagsFile.exists()) return FlagResolution.EMPTY - val fileText: String = flagsFile.bufferedReader().use { it.readText() } - return if (fileText.isEmpty()) { - FlagResolution.EMPTY - } else { - Json.decodeFromString(fileText) - } - } - - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - context = binding.applicationContext - } - - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - context = binding.activity.applicationContext - } - - override fun onDetachedFromActivityForConfigChanges() { - - } - - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - - } - - override fun onDetachedFromActivity() { - - } -} - -private fun Map.convert(): ConfidenceValue { - when(val type = this["type"] as String) { - "string" -> return ConfidenceValue.String(this["value"] as String) - "double" -> return ConfidenceValue.Double(this["value"] as Double) - "bool" -> return ConfidenceValue.Boolean(this["value"] as Boolean) - "int" -> return ConfidenceValue.Integer(this["value"] as Int) - "list" -> { - val list = (this["value"] as List>).map { it.convert() } - return ConfidenceValue.List(list) - } - "map" -> { - val objectValue = this["value"] as Map - val map = mutableMapOf() - for((key, value) in objectValue) { - map[key] = (value as Map).convert() - } - return ConfidenceValue.Struct(map) - } - - else -> throw IllegalArgumentException("Unknown type $type") - } -} diff --git a/android/src/main/kotlin/com/example/confidence_flutter_sdk/NetworkConfidenceValueSerializer.kt b/android/src/main/kotlin/com/example/confidence_flutter_sdk/NetworkConfidenceValueSerializer.kt deleted file mode 100644 index ecc3664..0000000 --- a/android/src/main/kotlin/com/example/confidence_flutter_sdk/NetworkConfidenceValueSerializer.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.example.confidence_flutter_sdk - -import com.spotify.confidence.ConfidenceValue -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.encoding.encodeStructure - -/*** - * the struct serializer needed for sending the resolve request - */ -private object NetworkStructSerializer : KSerializer { - override val descriptor: SerialDescriptor = - MapSerializer(String.serializer(), String.serializer()).descriptor - - override fun deserialize(decoder: Decoder): ConfidenceValue.Struct { - error("no deserializer is needed") - } - - override fun serialize(encoder: Encoder, value: ConfidenceValue.Struct) { - encoder.encodeStructure(descriptor) { - for ((key, mapValue) in value.map) { - encodeStringElement(descriptor, 0, key) - encodeSerializableElement(descriptor, 1, NetworkConfidenceValueSerializer, mapValue) - } - } - } -} - -internal object NetworkConfidenceValueSerializer : KSerializer { - override val descriptor: SerialDescriptor - get() = MapSerializer(String.serializer(), String.serializer()).descriptor - - override fun deserialize(decoder: Decoder): ConfidenceValue { - error("Not Implemented") - } - - @OptIn(ExperimentalSerializationApi::class) - override fun serialize(encoder: Encoder, value: ConfidenceValue) { - when (value) { - is ConfidenceValue.String -> encoder.encodeString(value.string) - is ConfidenceValue.Boolean -> encoder.encodeBoolean(value.boolean) - is ConfidenceValue.Double -> encoder.encodeDouble(value.double) - - is ConfidenceValue.Integer -> encoder.encodeInt(value.integer) - - ConfidenceValue.Null -> encoder.encodeNull() - is ConfidenceValue.Struct -> encoder.encodeSerializableValue( - NetworkStructSerializer, - ConfidenceValue.Struct(value.map) - ) - - is ConfidenceValue.List -> encoder.encodeSerializableValue( - ListSerializer(NetworkConfidenceValueSerializer), - value.list - ) - - else -> { - error("Not Implemented")} - } - } -} \ No newline at end of file diff --git a/android/src/test/kotlin/com/example/confidence_flutter_sdk/ConfidenceFlutterSdkPluginTest.kt b/android/src/test/kotlin/com/example/confidence_flutter_sdk/ConfidenceFlutterSdkPluginTest.kt deleted file mode 100644 index d9c9754..0000000 --- a/android/src/test/kotlin/com/example/confidence_flutter_sdk/ConfidenceFlutterSdkPluginTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.confidence_flutter_sdk - -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import kotlin.test.Test -import org.mockito.Mockito - -/* - * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. - * - * Once you have built the plugin's example app, you can run these tests from the command - * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or - * you can run them directly from IDEs that support JUnit such as Android Studio. - */ - -internal class ConfidenceFlutterSdkPluginTest { - @Test - fun onMethodCall_getPlatformVersion_returnsExpectedValue() { - val plugin = ConfidenceFlutterSdkPlugin() - - val call = MethodCall("getPlatformVersion", null) - val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) - plugin.onMethodCall(call, mockResult) - - Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) - } -} diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 868e462..b1139ee 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -1,33 +1,12 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:confidence_flutter_sdk_example/main.dart'; -import 'package:integration_test/integration_test.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('Verify Platform version', (WidgetTester tester) async { - // Build our app and trigger a frame. - MyApp myApp = MyApp(); - await tester.pumpWidget(myApp); - await myApp.initDone(); - // expect a list item with text evaluation exist + testWidgets('App renders and shows status', (WidgetTester tester) async { + await tester.pumpWidget(const MyApp()); await tester.pump(); - final textWidgets = find.byType(Text); - int count = 0; - textWidgets.evaluate().forEach((element) { - if(count == 0) { - final textWidget = element.widget as Text; - final string = textWidget.data?.trim() ?? ""; - expect(["Goodbye", "Welcome"].contains(string), true); - } - if(count == 1) { - final textWidget = element.widget as Text; - final string = textWidget.data?.trim() ?? ""; - expect(string.contains("enabled"), true); - expect(string.contains("message"), true); - expect(string.contains("color"), true); - } - count++; - }); -}); + + expect(find.text('Confidence Dart SDK Example'), findsOneWidget); + expect(find.textContaining('Status:'), findsOneWidget); + }); } diff --git a/ios/Classes/ConfidenceFlutterSdkPlugin.swift b/ios/Classes/ConfidenceFlutterSdkPlugin.swift deleted file mode 100644 index cb064b3..0000000 --- a/ios/Classes/ConfidenceFlutterSdkPlugin.swift +++ /dev/null @@ -1,248 +0,0 @@ -import Flutter -import UIKit - -public class ConfidenceFlutterSdkPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "confidence_flutter_sdk", binaryMessenger: registrar.messenger()) - let instance = ConfidenceFlutterSdkPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - var confidence: Confidence? = nil - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "flush": - guard let confidence = self.confidence else { - result("") - return - } - confidence.flush() - break; - case "readAllFlags": - guard let flags = try? readAllFlags() else { - result("{}") - return - } - let map = flags.reduce(into: [String: ConfidenceValue]()) { map, flag in - map[flag.flag] = flag.value - } - let networkMessage = TypeMapper.convert(structure: map) - let encoder = JSONEncoder() - let data = try! encoder.encode(networkMessage) - let str = String(decoding: data, as: UTF8.self) - result(str) - break; - case "setup": - guard let args = call.arguments as? Dictionary else { - result("") - return - } - let apiKey = args["apiKey"] as! String - let logLevel = args["loggingLevel"] as! String - self.confidence = Confidence.Builder(clientSecret: apiKey, loggerLevel: loggerLevel(from: logLevel)) - .build() - result("") - break; - case "isStorageEmpty": - guard let confidence = self.confidence else { - result(true) - return - } - result(confidence.isStorageEmpty()) - break; - case "fetchAndActivate": - Task { - guard let confidence = self.confidence else { - result("") - return - } - do { - try await confidence.fetchAndActivate() - } catch { - NSLog("%@", "Confidence SDK: \(error)") - } - result("") - return - } - break; - case "activateAndFetchAsync": - Task { - guard let confidence = self.confidence else { - result("") - return - } - do { - try confidence.activate() - } catch { - NSLog("%@", "Confidence SDK: \(error)") - } - Task { - await confidence.asyncFetch() - } - result("") - } - break; - case "putContext": - guard let args = call.arguments as? Dictionary else { - result("") - return - } - let key = args["key"] as! String - let wrappedValue = args["value"] as! Dictionary - let type = wrappedValue["type"] as! String - let value = convertValue(type, wrappedValue["value"]!) - confidence?.putContext(key: key, value: value) - result("") - break; - case "putAllContext": - guard let args = call.arguments as? Dictionary> else { - result("") - return - } - let context = args["context"] as! Dictionary> - let map: ConfidenceStruct = context.mapValues { wrappedValue in - let type = wrappedValue["type"] as! String - return convertValue(type, wrappedValue["value"]!) - } - confidence?.putContext(context: map) - result("") - break; - case "track": - guard let args = call.arguments as? Dictionary else { - return - } - let eventName = args["eventName"] as! String - let data = args["data"] as! Dictionary> - let convertedData = data.convert() - try? confidence?.track(eventName: eventName, data: convertedData) - break; - case "getBool": - let arguments = call.arguments as! Dictionary - let defaultValue = arguments["defaultValue"] as! Bool - let key = arguments["key"] as! String - guard let confidence = self.confidence else { - result(defaultValue) - return - } - let message: Bool = confidence.getValue(key: key, defaultValue: defaultValue) - result(message) - break; - case "getString": - let arguments = call.arguments as! Dictionary - let defaultValue = arguments["defaultValue"] as! String - let key = arguments["key"] as! String - guard let confidence = self.confidence else { - result(defaultValue) - return - } - - let message: String = confidence.getValue(key: key, defaultValue: defaultValue) - result(message) - break; - case "getDouble": - let arguments = call.arguments as! Dictionary - let defaultValue = arguments["defaultValue"] as! Double - let key = arguments["key"] as! String - guard let confidence = self.confidence else { - result(defaultValue) - return - } - - let message: Double = confidence.getValue(key: key, defaultValue: defaultValue) - result(message) - break; - case "getInt": - let arguments = call.arguments as! Dictionary - let defaultValue = arguments["defaultValue"] as! Int - let key = arguments["key"] as! String - guard let confidence = self.confidence else { - result(defaultValue) - return - } - - let message: Int = confidence.getValue(key: key, defaultValue: defaultValue) - result(message) - break; - case "getObject": - let arguments = call.arguments as! Dictionary - let defaultValueWrapped = arguments["defaultValue"] as! Dictionary> - let defaultValue = defaultValueWrapped.convert() - let key = arguments["key"] as! String - guard let confidence = self.confidence else { - result([:]) - return - } - - let message: ConfidenceStruct = confidence.getValue(key: key, defaultValue: defaultValue) - let networkMessage = TypeMapper.convert(structure: message) - let encoder = JSONEncoder() - let data = try! encoder.encode(networkMessage) - let str = String(decoding: data, as: UTF8.self) - result(str) - break; - default: - result(FlutterMethodNotImplemented) - } - } - - func loggerLevel(from string: String) -> LoggerLevel { - switch string.uppercased() { - case "VERBOSE": - return .TRACE - case "DEBUG": - return .DEBUG - case "WARN": - return .WARN - case "ERROR": - return .ERROR - default: - return .WARN - } - } -} - -func readAllFlags() throws -> [ResolvedValue] { - let storage = DefaultStorage(filePath: "confidence.flags.resolve") - let savedFlags = try storage.load(defaultValue: FlagResolution.EMPTY) - return savedFlags.flags -} - -extension Dictionary> { - func convert() -> ConfidenceStruct { - var map: ConfidenceStruct = [:] - for (key, wrappedValue) in self { - let type = wrappedValue["type"] as! String - map[key] = convertValue(type, wrappedValue["value"]!) - } - return map - } -} - -func convertValue(_ type: String, _ value: Any) -> ConfidenceValue { - switch type { - case "bool": - return ConfidenceValue.init(boolean: value as! Bool) - case "double": - return ConfidenceValue.init(double: value as! Double) - case "int": - return ConfidenceValue.init(integer: value as! Int) - case "map": - let dataMap = value as! Dictionary> - let map: ConfidenceStruct = dataMap.mapValues { wrappedValue in - let type = wrappedValue["type"] as! String - return convertValue(type, wrappedValue["value"]!) - } - return ConfidenceValue.init(structure: map) - case "list": - let list = value as! [Dictionary] - return ConfidenceValue.init(list: list.map { wrappedValue in - let type = wrappedValue["type"] as! String - return convertValue(type, wrappedValue["value"]!) - }) - case "string": - return ConfidenceValue.init(string: value as! String) - default: - return ConfidenceValue.init(integer: 0) - } -} diff --git a/ios/Classes/confidence-sdk b/ios/Classes/confidence-sdk deleted file mode 160000 index 53645f3..0000000 --- a/ios/Classes/confidence-sdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 53645f3aa1243d5c2c9bca71e0268f81f7a722fe diff --git a/ios/confidence_flutter_sdk.podspec b/ios/confidence_flutter_sdk.podspec deleted file mode 100644 index 9d53bd4..0000000 --- a/ios/confidence_flutter_sdk.podspec +++ /dev/null @@ -1,23 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint confidence_flutter_sdk.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'confidence_flutter_sdk' - s.version = '0.0.1' - s.summary = 'A new Flutter plugin project.' - s.description = <<-DESC -A new Flutter plugin project. - DESC - s.homepage = 'http://example.com' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'Flutter' - s.platform = :ios, '14.0' - - # Flutter.framework does not contain a i386 slice. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } - s.swift_version = '5.0' -end diff --git a/lib/confidence_flutter_sdk.dart b/lib/confidence_flutter_sdk.dart index 243f795..41a4cdd 100644 --- a/lib/confidence_flutter_sdk.dart +++ b/lib/confidence_flutter_sdk.dart @@ -1,7 +1,3 @@ -import 'dart:async'; - -import 'confidence_flutter_sdk_platform_interface.dart'; - export 'src/confidence.dart' show Confidence, ConfidenceBuilder; export 'src/confidence_value.dart'; export 'src/evaluation.dart'; @@ -9,128 +5,3 @@ export 'src/resolve_client.dart' show ConfidenceRegion; export 'src/storage.dart' show Storage, MemoryStorage, DiskStorage; export 'src/flutter/confidence_flutter.dart'; export 'src/legacy_api.dart'; - -enum LoggingLevel { - VERBOSE, - DEBUG, - WARN, - ERROR, - NONE, -} - -// Legacy class — kept for existing example app and native bridge consumers. -// New code should use Confidence.builder() or ConfidenceFlutter.create(). -class ConfidenceFlutterSdk { - Map _flags = {}; - bool isInitialized = false; - - Future isStorageEmpty() async { - return ConfidenceFlutterSdkPlatform.instance.isStorageEmpty(); - } - - Future putContext(String key, dynamic value) async { - await ConfidenceFlutterSdkPlatform.instance.putContext(key, value); - if (isInitialized) { - await fetchAndActivate(); - } - } - - Future putAllContext(Map context) async { - await ConfidenceFlutterSdkPlatform.instance.putAllContext(context); - if (isInitialized) { - await fetchAndActivate(); - } - } - - void track(String eventName, Map data) { - ConfidenceFlutterSdkPlatform.instance.track(eventName, data); - } - - void flush() { - ConfidenceFlutterSdkPlatform.instance.flush(); - } - - bool getBool(String key, bool defaultValue) { - unawaited(ConfidenceFlutterSdkPlatform.instance.getBool(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - T? resolveKey(String key) { - List keys = key.split("."); - Map flags = _flags; - for (int i = 0; i < keys.length; i++) { - String element = keys[i]; - if (flags.containsKey(element)) { - if (flags[element] is Map) { - flags = flags[element]; - } else { - return _parse(flags[element]); - } - } else { - return null; - } - } - return _parse(flags); - } - - T _parse(dynamic value) { - if (T == String) { - return value.toString() as T; - } else if (T == int) { - return int.parse(value.toString()) as T; - } else if (T == bool) { - return bool.parse(value.toString()) as T; - } else if (T == double) { - return double.parse(value.toString()) as T; - } else if (T == Map) { - return value as T; - } else { - return value as T; - } - } - - int getInt(String key, int defaultValue) { - unawaited(ConfidenceFlutterSdkPlatform.instance.getInt(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - String getString(String key, String defaultValue) { - unawaited( - ConfidenceFlutterSdkPlatform.instance.getString(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - Map getObject( - String key, Map defaultValue) { - unawaited( - ConfidenceFlutterSdkPlatform.instance.getObject(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - double getDouble(String key, double defaultValue) { - unawaited( - ConfidenceFlutterSdkPlatform.instance.getDouble(key, defaultValue)); - return resolveKey(key) ?? defaultValue; - } - - Future setup(String apiKey, - [LoggingLevel loggingLevel = LoggingLevel.WARN]) async { - return await ConfidenceFlutterSdkPlatform.instance - .setup(apiKey, loggingLevel); - } - - Future fetchAndActivate() async { - await ConfidenceFlutterSdkPlatform.instance.fetchAndActivate(); - await fillAllFlags(); - } - - Future fillAllFlags() async { - _flags = await ConfidenceFlutterSdkPlatform.instance.readAllFlags(); - isInitialized = true; - } - - Future activateAndFetchAsync() async { - await ConfidenceFlutterSdkPlatform.instance.activateAndFetchAsync(); - await fillAllFlags(); - } -} diff --git a/lib/confidence_flutter_sdk_method_channel.dart b/lib/confidence_flutter_sdk_method_channel.dart deleted file mode 100644 index 4c69063..0000000 --- a/lib/confidence_flutter_sdk_method_channel.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'package:confidence_flutter_sdk/confidence_flutter_sdk.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'dart:convert'; - -import 'confidence_flutter_sdk_platform_interface.dart'; - -/// An implementation of [ConfidenceFlutterSdkPlatform] that uses method channels. -class MethodChannelConfidenceFlutterSdk extends ConfidenceFlutterSdkPlatform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel('confidence_flutter_sdk'); - - @override - Future setup(String apiKey, LoggingLevel loggingLevel) async { - return await methodChannel.invokeMethod('setup', {'apiKey': apiKey, 'loggingLevel': loggingLevel.name}); - } - - @override - Future fetchAndActivate() async { - return await methodChannel.invokeMethod('fetchAndActivate'); - } - - @override - Future activateAndFetchAsync() async { - return await methodChannel.invokeMethod('activateAndFetchAsync'); - } - - @override - Future putContext(String key, dynamic value) async { - final wrappedValue = toTypedValue(value); - await methodChannel - .invokeMethod( - 'putContext', - {'key': key, 'value': wrappedValue} - ); - } - - @override - Future putAllContext(Map context) async { - final wrappedContext = context.map((key, value) { - return MapEntry(key, toTypedValue(value)); - }); - await methodChannel - .invokeMethod( - 'putAllContext', - {'context': wrappedContext} - ); - } - - @override - void track(String eventName, Map data) { - final wrappedData = data.map((key, value) { - return MapEntry(key, toTypedValue(value)); - }); - if (kDebugMode) { - print(wrappedData); - } - methodChannel - .invokeMethod( - 'track', - {'eventName': eventName, 'data': wrappedData} - ); - } - - @override - Future isStorageEmpty() async { - final value = await methodChannel.invokeMethod('isStorageEmpty'); - return value!; - } - - @override - Future> readAllFlags() async { - final value = await methodChannel.invokeMethod('readAllFlags'); - return value != null ? jsonDecode(value) : {}; - } - - @override - Future> getObject(String key, Map defaultValue) async { - final wrappedDefaultValue = defaultValue.map((key, value) { - return MapEntry(key, toTypedValue(value)); - }); - - final value = await methodChannel - .invokeMethod( - 'getObject', - {'key': key, 'defaultValue': wrappedDefaultValue} - ); - return value != null ? jsonDecode(value) : {}; - } - - @override - Future getBool(String key, bool defaultValue) async { - final value = await methodChannel - .invokeMethod( - 'getBool', - {'key': key, 'defaultValue': defaultValue} - ); - return value!; - } - - @override - Future getString(String key, String defaultValue) async { - final value = await methodChannel - .invokeMethod( - 'getString', - {'key': key, 'defaultValue': defaultValue} - ); - return value!; - } - - @override - Future getDouble(String key, double defaultValue) async { - final value = await methodChannel - .invokeMethod( - 'getDouble', - {'key': key, 'defaultValue': defaultValue} - ); - return value!; - } - - @override - Future flush() async { - await methodChannel - .invokeMethod('flush'); - } - - - - @override - Future getInt(String key, int defaultValue) async { - final value = await methodChannel - .invokeMethod( - 'getInt', - {'key': key, 'defaultValue': defaultValue} - ); - return value!; - } - - Map toTypedValue(dynamic value) { - if (value is int) { - return {'type': 'int', 'value': value}; - } else if (value is String) { - return {'type': 'string', 'value': value}; - } else if (value is bool) { - return {'type': 'bool', 'value': value}; - } else if (value is double) { - return {'type': 'double', 'value': value}; - } else if (value is Map) { - return {'type': 'map', 'value': value.map((key, value) { - return MapEntry(key, toTypedValue(value)); - })}; - } - else if (value is List) { - return {'type': 'list', 'value': value.map((value) { - return toTypedValue(value); - }).toList()}; - } - else { - return {'type': 'unknown', 'value': value.toString()}; - } - } -} diff --git a/lib/confidence_flutter_sdk_platform_interface.dart b/lib/confidence_flutter_sdk_platform_interface.dart deleted file mode 100644 index 5656305..0000000 --- a/lib/confidence_flutter_sdk_platform_interface.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:confidence_flutter_sdk/confidence_flutter_sdk.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'confidence_flutter_sdk_method_channel.dart'; - -abstract class ConfidenceFlutterSdkPlatform extends PlatformInterface { - /// Constructs a ConfidenceFlutterSdkPlatform. - ConfidenceFlutterSdkPlatform() : super(token: _token); - - static final Object _token = Object(); - - static ConfidenceFlutterSdkPlatform _instance = MethodChannelConfidenceFlutterSdk(); - - /// The default instance of [ConfidenceFlutterSdkPlatform] to use. - /// - /// Defaults to [MethodChannelConfidenceFlutterSdk]. - static ConfidenceFlutterSdkPlatform get instance => _instance; - - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [ConfidenceFlutterSdkPlatform] when - /// they register themselves. - static set instance(ConfidenceFlutterSdkPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - Future setup(String apiKey, LoggingLevel loggingLevel) { - throw UnimplementedError('setup() has not been implemented.'); - } - - Future fetchAndActivate() { - throw UnimplementedError('fetchAndActivate() has not been implemented.'); - } - - Future activateAndFetchAsync() { - throw UnimplementedError('activateAndFetchAsync() has not been implemented.'); - } - - Future getString(String key, String defaultValue) { - throw UnimplementedError('getString() has not been implemented.'); - } - - Future putContext(String key, dynamic value) async { - throw UnimplementedError('putContext() has not been implemented.'); - } - - Future putAllContext(Map context) async { - throw UnimplementedError('putAllContext() has not been implemented.'); - } - - Future isStorageEmpty() { - throw UnimplementedError('isStorageEmpty() has not been implemented.'); - } - - Future getBool(String key, bool defaultValue) { - throw UnimplementedError('getBool() has not been implemented.'); - } - - void track(String eventName, Map data) { - throw UnimplementedError('track has not been implemented.'); - } - - void flush() { - throw UnimplementedError('flush has not been implemented.'); - } - - Future getDouble(String key, double defaultValue) async { - throw UnimplementedError('getDouble() has not been implemented.'); - } - - Future> getObject(String key, Map defaultValue) async { - throw UnimplementedError('getObject() has not been implemented.'); - } - - Future getInt(String key, int defaultValue) async { - throw UnimplementedError('getInt() has not been implemented.'); - } - - Future> readAllFlags() { - throw UnimplementedError('readAllFlags() has not been implemented.'); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index a99558d..5089335 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,10 +3,6 @@ description: "Flutter implementation of the Confidence SDK." version: 0.2.1 homepage: https://confidence.spotify.com -platforms: - android: - ios: - environment: sdk: '>=3.4.3 <4.0.0' flutter: '>=3.3.0' @@ -14,7 +10,6 @@ environment: dependencies: flutter: sdk: flutter - plugin_platform_interface: ^2.0.2 http: ^1.2.0 uuid: ^4.0.0 path_provider: ^2.1.0 @@ -28,26 +23,3 @@ dev_dependencies: flutter_lints: ^3.0.0 mockito: ^5.4.0 build_runner: ^2.4.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) - # which should be registered in the plugin registry. This is required for - # using method channels. - # The Android 'package' specifies package in which the registered class is. - # This is required for using method channels on Android. - # The 'ffiPlugin' specifies that native code should be built and bundled. - # This is required for using `dart:ffi`. - # All these are used by the tooling to maintain consistency when - # adding or updating assets for this project. - plugin: - platforms: - android: - package: com.example.confidence_flutter_sdk - pluginClass: ConfidenceFlutterSdkPlugin - ios: - pluginClass: ConfidenceFlutterSdkPlugin From 5189c75b15f31a5c06abb9d12bad62e765eb4df5 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Tue, 9 Jun 2026 11:48:10 +0200 Subject: [PATCH 09/12] chore: remove example app Co-Authored-By: Claude Opus 4.6 (1M context) --- example/.gitignore | 45 -- example/README.md | 16 - example/analysis_options.yaml | 28 - example/android/.gitignore | 13 - example/android/app/build.gradle | 63 -- .../android/app/src/debug/AndroidManifest.xml | 7 - .../android/app/src/main/AndroidManifest.xml | 45 -- .../MainActivity.kt | 5 - .../res/drawable-v21/launch_background.xml | 12 - .../main/res/drawable/launch_background.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - .../app/src/main/res/values/styles.xml | 18 - .../app/src/profile/AndroidManifest.xml | 7 - example/android/build.gradle | 18 - example/android/gradle.properties | 3 - .../gradle/wrapper/gradle-wrapper.properties | 6 - example/android/settings.gradle | 25 - example/ios/.gitignore | 34 - example/ios/Flutter/AppFrameworkInfo.plist | 26 - example/ios/Flutter/Debug.xcconfig | 2 - example/ios/Flutter/Release.xcconfig | 2 - example/ios/Podfile | 44 -- example/ios/Runner.xcodeproj/project.pbxproj | 733 ------------------ .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 98 --- .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - example/ios/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 122 --- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../LaunchImage.imageset/Contents.json | 23 - .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Runner/Base.lproj/LaunchScreen.storyboard | 37 - example/ios/Runner/Base.lproj/Main.storyboard | 29 - example/ios/Runner/Info.plist | 49 -- example/ios/Runner/Runner-Bridging-Header.h | 1 - example/ios/RunnerTests/RunnerTests.swift | 26 - example/lib/main.dart | 113 --- example/pubspec.yaml | 87 --- example/test/widget_test.dart | 12 - example/test_drive/integration_test.dart | 3 - 66 files changed, 1849 deletions(-) delete mode 100644 example/.gitignore delete mode 100644 example/README.md delete mode 100644 example/analysis_options.yaml delete mode 100644 example/android/.gitignore delete mode 100644 example/android/app/build.gradle delete mode 100644 example/android/app/src/debug/AndroidManifest.xml delete mode 100644 example/android/app/src/main/AndroidManifest.xml delete mode 100644 example/android/app/src/main/kotlin/com/example/confidence_flutter_sdk_example/MainActivity.kt delete mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 example/android/app/src/main/res/drawable/launch_background.xml delete mode 100644 example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 example/android/app/src/main/res/values-night/styles.xml delete mode 100644 example/android/app/src/main/res/values/styles.xml delete mode 100644 example/android/app/src/profile/AndroidManifest.xml delete mode 100644 example/android/build.gradle delete mode 100644 example/android/gradle.properties delete mode 100644 example/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 example/android/settings.gradle delete mode 100644 example/ios/.gitignore delete mode 100644 example/ios/Flutter/AppFrameworkInfo.plist delete mode 100644 example/ios/Flutter/Debug.xcconfig delete mode 100644 example/ios/Flutter/Release.xcconfig delete mode 100644 example/ios/Podfile delete mode 100644 example/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 example/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 example/ios/Runner/AppDelegate.swift delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json delete mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md delete mode 100644 example/ios/Runner/Base.lproj/LaunchScreen.storyboard delete mode 100644 example/ios/Runner/Base.lproj/Main.storyboard delete mode 100644 example/ios/Runner/Info.plist delete mode 100644 example/ios/Runner/Runner-Bridging-Header.h delete mode 100644 example/ios/RunnerTests/RunnerTests.swift delete mode 100644 example/lib/main.dart delete mode 100644 example/pubspec.yaml delete mode 100644 example/test/widget_test.dart delete mode 100644 example/test_drive/integration_test.dart diff --git a/example/.gitignore b/example/.gitignore deleted file mode 100644 index 79c113f..0000000 --- a/example/.gitignore +++ /dev/null @@ -1,45 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.build/ -.buildlog/ -.history -.svn/ -.swiftpm/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.pub-cache/ -.pub/ -/build/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release diff --git a/example/README.md b/example/README.md deleted file mode 100644 index cbfb8f9..0000000 --- a/example/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# confidence_flutter_sdk_example - -Demonstrates how to use the confidence_flutter_sdk plugin. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml deleted file mode 100644 index 0d29021..0000000 --- a/example/analysis_options.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore deleted file mode 100644 index 6f56801..0000000 --- a/example/android/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java - -# Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app -key.properties -**/*.keystore -**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle deleted file mode 100644 index 495f96d..0000000 --- a/example/android/app/build.gradle +++ /dev/null @@ -1,63 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file("local.properties") -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader("UTF-8") { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty("flutter.versionCode") -if (flutterVersionCode == null) { - flutterVersionCode = "1" -} - -def flutterVersionName = localProperties.getProperty("flutter.versionName") -if (flutterVersionName == null) { - flutterVersionName = "1.0" -} - -android { - namespace = "com.example.confidence_flutter_sdk_example" - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = "17" - } - - defaultConfig { - applicationId = "com.example.confidence_flutter_sdk_example" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdk = flutter.minSdkVersion - targetSdk = flutter.targetSdkVersion - versionCode = flutterVersionCode.toInteger() - versionName = flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.debug - shrinkResources = false - minifyEnabled = false - } - } -} - -flutter { - source = "../.." -} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f698..0000000 --- a/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 6bafdc1..0000000 --- a/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/example/android/app/src/main/kotlin/com/example/confidence_flutter_sdk_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/confidence_flutter_sdk_example/MainActivity.kt deleted file mode 100644 index dbb6f0e..0000000 --- a/example/android/app/src/main/kotlin/com/example/confidence_flutter_sdk_example/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.confidence_flutter_sdk_example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f..0000000 --- a/example/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be..0000000 --- a/example/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef88..0000000 --- a/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f698..0000000 --- a/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/example/android/build.gradle b/example/android/build.gradle deleted file mode 100644 index d2ffbff..0000000 --- a/example/android/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = "../build" -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/example/android/gradle.properties b/example/android/gradle.properties deleted file mode 100644 index 3b5b324..0000000 --- a/example/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index ca8bad8..0000000 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Wed Oct 15 12:51:00 CEST 2025 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/example/android/settings.gradle b/example/android/settings.gradle deleted file mode 100644 index cb7d7dd..0000000 --- a/example/android/settings.gradle +++ /dev/null @@ -1,25 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.7.3" apply false - id "org.jetbrains.kotlin.android" version "2.1.0" apply false -} - -include ":app" diff --git a/example/ios/.gitignore b/example/ios/.gitignore deleted file mode 100644 index 7a7f987..0000000 --- a/example/ios/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 1dc6cf7..0000000 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 13.0 - - diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6..0000000 --- a/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bf..0000000 --- a/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile deleted file mode 100644 index 50850d2..0000000 --- a/example/ios/Podfile +++ /dev/null @@ -1,44 +0,0 @@ -# Uncomment this line to define a global platform for your project -platform :ios, '17.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index d354b8d..0000000 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,733 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 013F3F2986065489995080BD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5AD3D68A45574EF187B49F6 /* Pods_Runner.framework */; }; - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - A706F8DF43E353C905690C62 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93549B95C7C3D016CC388E55 /* Pods_RunnerTests.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 335422DDE4DF5C8A63A77B56 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 54E7BF31786AD2242AD160E7 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 6E33ABCD3B5B36D3388280FC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 93549B95C7C3D016CC388E55 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A09CBDC7B9A73F80ADBB9017 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - C5AD3D68A45574EF187B49F6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C9C3F78D563E8B285E60F031 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - F8DFAF8507059D483097DE3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 5047143466990AE03ED508A6 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - A706F8DF43E353C905690C62 /* Pods_RunnerTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 013F3F2986065489995080BD /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 1930FD934614A48818B70FAD /* Frameworks */ = { - isa = PBXGroup; - children = ( - C5AD3D68A45574EF187B49F6 /* Pods_Runner.framework */, - 93549B95C7C3D016CC388E55 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 865E0EB93452F4A109C5BC31 /* Pods */ = { - isa = PBXGroup; - children = ( - 6E33ABCD3B5B36D3388280FC /* Pods-Runner.debug.xcconfig */, - 335422DDE4DF5C8A63A77B56 /* Pods-Runner.release.xcconfig */, - C9C3F78D563E8B285E60F031 /* Pods-Runner.profile.xcconfig */, - F8DFAF8507059D483097DE3A /* Pods-RunnerTests.debug.xcconfig */, - 54E7BF31786AD2242AD160E7 /* Pods-RunnerTests.release.xcconfig */, - A09CBDC7B9A73F80ADBB9017 /* Pods-RunnerTests.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 331C8082294A63A400263BE5 /* RunnerTests */, - 865E0EB93452F4A109C5BC31 /* Pods */, - 1930FD934614A48818B70FAD /* Frameworks */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - 331C8081294A63A400263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - ABB04FA1C5E3E434B26E2C97 /* [CP] Check Pods Manifest.lock */, - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - 5047143466990AE03ED508A6 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 331C8086294A63A400263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - A16E0362FFF29ED720D070B5 /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - C799A5439215F17686E1B314 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C8080294A63A400263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 97C146ED1CF9000F007C117D; - }; - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - 331C8080294A63A400263BE5 /* RunnerTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; - A16E0362FFF29ED720D070B5 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - ABB04FA1C5E3E434B26E2C97 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C799A5439215F17686E1B314 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2FNC3A47ZF; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 331C8088294A63A400263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F8DFAF8507059D483097DE3A /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Debug; - }; - 331C8089294A63A400263BE5 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 54E7BF31786AD2242AD160E7 /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Release; - }; - 331C808A294A63A400263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = A09CBDC7B9A73F80ADBB9017 /* Pods-RunnerTests.profile.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2FNC3A47ZF; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2FNC3A47ZF; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C8088294A63A400263BE5 /* Debug */, - 331C8089294A63A400263BE5 /* Release */, - 331C808A294A63A400263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index 8e3ca5d..0000000 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc1..0000000 --- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 6266644..0000000 --- a/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Flutter -import UIKit - -@main -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fa..0000000 --- a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4725e9b0ddb1deab583e5b5102493aa332..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10932 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 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2..0000000 --- a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725..0000000 --- a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c..0000000 --- a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index eb9f49d..0000000 --- a/example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist deleted file mode 100644 index a603453..0000000 --- a/example/ios/Runner/Info.plist +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Confidence Flutter Sdk - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - confidence_flutter_sdk_example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - - diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a5..0000000 --- a/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 59a404d..0000000 --- a/example/ios/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Flutter -import UIKit -import XCTest - -@testable import confidence_flutter_sdk - -// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. -// -// See https://developer.apple.com/documentation/xctest for more information about using XCTest. - -class RunnerTests: XCTestCase { - - func testGetPlatformVersion() { - let plugin = ConfidenceFlutterSdkPlugin() - - let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) - - let resultExpectation = expectation(description: "result block must be called.") - plugin.handle(call) { result in - XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) - resultExpectation.fulfill() - } - waitForExpectations(timeout: 1) - } - -} diff --git a/example/lib/main.dart b/example/lib/main.dart deleted file mode 100644 index a912e15..0000000 --- a/example/lib/main.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:confidence_flutter_sdk/confidence_flutter_sdk.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatefulWidget { - const MyApp({super.key}); - - @override - State createState() => _MyAppState(); -} - -class _MyAppState extends State { - String _status = 'Initializing...'; - String _flagValue = ''; - String _variant = ''; - String _reason = ''; - String _context = ''; - - @override - void initState() { - super.initState(); - _initConfidence(); - } - - Future _initConfidence() async { - try { - await dotenv.load(fileName: ".env"); - final apiKey = dotenv.env["API_KEY"]!; - - final confidence = Confidence.builder(clientSecret: apiKey) - .region(ConfidenceRegion.eu) - .storage(MemoryStorage()) - .initialContext({ - 'targeting_key': ConfidenceValue.string('flutter-dart-sdk-test'), - }) - .build(); - - setState(() => _status = 'Fetching flags...'); - - await confidence.fetchAndActivate(); - - final eval = confidence.getFlag('hawkflag.message', 'no value'); - - confidence.track('example_app_loaded', { - 'sdk': ConfidenceValue.string('dart'), - 'screen': ConfidenceValue.string('home'), - }); - - final ctx = confidence.getContext(); - final ctxDisplay = ctx.entries - .map((e) => ' ${e.key}: ${e.value.toPlainJson()}') - .join('\n'); - - setState(() { - _status = 'Ready'; - _flagValue = eval.value; - _variant = eval.variant ?? 'none'; - _reason = eval.reason.name; - _context = ctxDisplay; - }); - } catch (e) { - setState(() => _status = 'Error: $e'); - } - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar(title: const Text('Confidence Dart SDK Example')), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _row('Status', _status), - const Divider(), - _row('hawkflag.message', _flagValue), - _row('Variant', _variant), - _row('Reason', _reason), - const Divider(), - const Text('Context:', - style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Text(_context, style: const TextStyle(fontFamily: 'monospace')), - ], - ), - ), - ), - ); - } - - Widget _row(String label, String value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 140, - child: Text('$label:', - style: const TextStyle(fontWeight: FontWeight.bold)), - ), - Expanded(child: Text(value)), - ], - ), - ); - } -} diff --git a/example/pubspec.yaml b/example/pubspec.yaml deleted file mode 100644 index 64b65dc..0000000 --- a/example/pubspec.yaml +++ /dev/null @@ -1,87 +0,0 @@ -name: confidence_flutter_sdk_example -description: "Demonstrates how to use the confidence_flutter_sdk plugin." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -environment: - sdk: '>=3.4.3 <4.0.0' - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - flutter_dotenv: ^5.0.2 - - confidence_flutter_sdk: - # When depending on this package from a real application you should use: - # confidence_flutter_sdk: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.6 - -dev_dependencies: - integration_test: - sdk: flutter - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^3.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - assets: - - .env - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages \ No newline at end of file diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index b1139ee..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:confidence_flutter_sdk_example/main.dart'; - -void main() { - testWidgets('App renders and shows status', (WidgetTester tester) async { - await tester.pumpWidget(const MyApp()); - await tester.pump(); - - expect(find.text('Confidence Dart SDK Example'), findsOneWidget); - expect(find.textContaining('Status:'), findsOneWidget); - }); -} diff --git a/example/test_drive/integration_test.dart b/example/test_drive/integration_test.dart deleted file mode 100644 index 6854dea..0000000 --- a/example/test_drive/integration_test.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:integration_test/integration_test_driver.dart'; - -Future main() => integrationDriver(); \ No newline at end of file From 9ae55e17b2f4b0276c6792b32c7e9ffc17d50561 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Tue, 9 Jun 2026 12:59:29 +0200 Subject: [PATCH 10/12] feat: restore example app using new Dart SDK API Same structure as before but uses Confidence.builder() and ConfidenceValue types instead of the native bridge. Co-Authored-By: Claude Opus 4.6 (1M context) --- example/.gitignore | 45 ++ example/README.md | 16 + example/analysis_options.yaml | 28 + example/android/.gitignore | 13 + example/android/app/build.gradle | 63 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 ++ .../MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + example/android/build.gradle | 18 + example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + example/android/settings.gradle | 25 + example/ios/.gitignore | 34 + example/ios/Flutter/AppFrameworkInfo.plist | 26 + example/ios/Flutter/Debug.xcconfig | 2 + example/ios/Flutter/Release.xcconfig | 2 + example/ios/Podfile | 44 ++ example/ios/Runner.xcodeproj/project.pbxproj | 733 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + example/ios/Runner/Base.lproj/Main.storyboard | 29 + example/ios/Runner/Info.plist | 49 ++ example/ios/Runner/Runner-Bridging-Header.h | 1 + example/ios/RunnerTests/RunnerTests.swift | 26 + example/lib/main.dart | 120 +++ example/pubspec.yaml | 87 +++ example/test/widget_test.dart | 11 + example/test_drive/integration_test.dart | 3 + 66 files changed, 1855 insertions(+) create mode 100644 example/.gitignore create mode 100644 example/README.md create mode 100644 example/analysis_options.yaml create mode 100644 example/android/.gitignore create mode 100644 example/android/app/build.gradle create mode 100644 example/android/app/src/debug/AndroidManifest.xml create mode 100644 example/android/app/src/main/AndroidManifest.xml create mode 100644 example/android/app/src/main/kotlin/com/example/confidence_flutter_sdk_example/MainActivity.kt create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/values-night/styles.xml create mode 100644 example/android/app/src/main/res/values/styles.xml create mode 100644 example/android/app/src/profile/AndroidManifest.xml create mode 100644 example/android/build.gradle create mode 100644 example/android/gradle.properties create mode 100644 example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 example/android/settings.gradle create mode 100644 example/ios/.gitignore create mode 100644 example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 example/ios/Flutter/Debug.xcconfig create mode 100644 example/ios/Flutter/Release.xcconfig create mode 100644 example/ios/Podfile create mode 100644 example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 example/ios/Runner/AppDelegate.swift create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 example/ios/Runner/Info.plist create mode 100644 example/ios/Runner/Runner-Bridging-Header.h create mode 100644 example/ios/RunnerTests/RunnerTests.swift create mode 100644 example/lib/main.dart create mode 100644 example/pubspec.yaml create mode 100644 example/test/widget_test.dart create mode 100644 example/test_drive/integration_test.dart diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..79c113f --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..cbfb8f9 --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# confidence_flutter_sdk_example + +Demonstrates how to use the confidence_flutter_sdk plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..495f96d --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,63 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file("local.properties") +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader("UTF-8") { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") +if (flutterVersionCode == null) { + flutterVersionCode = "1" +} + +def flutterVersionName = localProperties.getProperty("flutter.versionName") +if (flutterVersionName == null) { + flutterVersionName = "1.0" +} + +android { + namespace = "com.example.confidence_flutter_sdk_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + defaultConfig { + applicationId = "com.example.confidence_flutter_sdk_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + shrinkResources = false + minifyEnabled = false + } + } +} + +flutter { + source = "../.." +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6bafdc1 --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/confidence_flutter_sdk_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/confidence_flutter_sdk_example/MainActivity.kt new file mode 100644 index 0000000..dbb6f0e --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/confidence_flutter_sdk_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.confidence_flutter_sdk_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..3b5b324 --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ca8bad8 --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Oct 15 12:51:00 CEST 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..cb7d7dd --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.7.3" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false +} + +include ":app" diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..1dc6cf7 --- /dev/null +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 0000000..50850d2 --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '17.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d354b8d --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,733 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 013F3F2986065489995080BD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5AD3D68A45574EF187B49F6 /* Pods_Runner.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + A706F8DF43E353C905690C62 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93549B95C7C3D016CC388E55 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 335422DDE4DF5C8A63A77B56 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 54E7BF31786AD2242AD160E7 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 6E33ABCD3B5B36D3388280FC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 93549B95C7C3D016CC388E55 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A09CBDC7B9A73F80ADBB9017 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + C5AD3D68A45574EF187B49F6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C9C3F78D563E8B285E60F031 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + F8DFAF8507059D483097DE3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5047143466990AE03ED508A6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A706F8DF43E353C905690C62 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 013F3F2986065489995080BD /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1930FD934614A48818B70FAD /* Frameworks */ = { + isa = PBXGroup; + children = ( + C5AD3D68A45574EF187B49F6 /* Pods_Runner.framework */, + 93549B95C7C3D016CC388E55 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 865E0EB93452F4A109C5BC31 /* Pods */ = { + isa = PBXGroup; + children = ( + 6E33ABCD3B5B36D3388280FC /* Pods-Runner.debug.xcconfig */, + 335422DDE4DF5C8A63A77B56 /* Pods-Runner.release.xcconfig */, + C9C3F78D563E8B285E60F031 /* Pods-Runner.profile.xcconfig */, + F8DFAF8507059D483097DE3A /* Pods-RunnerTests.debug.xcconfig */, + 54E7BF31786AD2242AD160E7 /* Pods-RunnerTests.release.xcconfig */, + A09CBDC7B9A73F80ADBB9017 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 865E0EB93452F4A109C5BC31 /* Pods */, + 1930FD934614A48818B70FAD /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + ABB04FA1C5E3E434B26E2C97 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 5047143466990AE03ED508A6 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + A16E0362FFF29ED720D070B5 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + C799A5439215F17686E1B314 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + A16E0362FFF29ED720D070B5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + ABB04FA1C5E3E434B26E2C97 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C799A5439215F17686E1B314 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2FNC3A47ZF; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F8DFAF8507059D483097DE3A /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 54E7BF31786AD2242AD160E7 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A09CBDC7B9A73F80ADBB9017 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2FNC3A47ZF; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2FNC3A47ZF; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.confidenceFlutterSdkExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..eb9f49d --- /dev/null +++ b/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist new file mode 100644 index 0000000..a603453 --- /dev/null +++ b/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Confidence Flutter Sdk + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + confidence_flutter_sdk_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..59a404d --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,26 @@ +import Flutter +import UIKit +import XCTest + +@testable import confidence_flutter_sdk + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + +class RunnerTests: XCTestCase { + + func testGetPlatformVersion() { + let plugin = ConfidenceFlutterSdkPlugin() + + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) + resultExpectation.fulfill() + } + waitForExpectations(timeout: 1) + } + +} diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..bbacbeb --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,120 @@ +import 'dart:async'; + +import 'package:confidence_flutter_sdk/confidence_flutter_sdk.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatefulWidget { + MyApp({super.key}); + final Completer _initCompleter = Completer(); + + Future initDone() async { + return _initCompleter.future; + } + + @override + // ignore: no_logic_in_create_state + State createState() => _MyAppState(_initCompleter); +} + +class _MyAppState extends State { + String _object = 'Unknown'; + String _message = 'Unknown'; + late final Confidence _confidence; + final Completer initCompleter; + + _MyAppState(this.initCompleter); + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + Future initPlatformState() async { + String message; + String object; + try { + await dotenv.load(fileName: ".env"); + _confidence = Confidence.builder(clientSecret: dotenv.env["API_KEY"]!) + .region(ConfidenceRegion.eu) + .storage(MemoryStorage()) + .initialContext({ + 'targeting_key': ConfidenceValue.string('random'), + 'my_bool': ConfidenceValue.boolean(false), + 'my_int': ConfidenceValue.integer(1), + 'my_double': ConfidenceValue.double_(1.1), + 'my_map': ConfidenceValue.structure({ + 'key': ConfidenceValue.string('value'), + }), + 'my_list': ConfidenceValue.list([ + ConfidenceValue.string('value1'), + ConfidenceValue.string('value2'), + ]), + }) + .build(); + + await _confidence.fetchAndActivate(); + object = _confidence.getValue('hawkflag.message', ''); + message = _confidence.getValue('hawkflag.message', ''); + final data = { + 'screen': ConfidenceValue.string('home'), + 'my_bool': ConfidenceValue.boolean(false), + 'my_int': ConfidenceValue.integer(1), + 'my_double': ConfidenceValue.double_(1.1), + 'my_map': ConfidenceValue.structure({ + 'key': ConfidenceValue.string('value'), + }), + 'my_list': ConfidenceValue.list([ + ConfidenceValue.string('value1'), + ConfidenceValue.string('value2'), + ]), + }; + _confidence.track('navigate', data); + _confidence.flush(); + } catch (e) { + message = 'Failed: $e'; + object = 'Failed: $e'; + } + + if (!mounted) return; + + setState(() { + _message = message; + _object = object; + }); + initCompleter.complete(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: ListView.builder( + itemCount: 2, + itemBuilder: (context, index) { + var title = ""; + switch (index) { + case 0: + title = _message; + case 1: + title = _object; + } + return ListTile( + title: Text('$title\n'), + ); + }, + ), + ), + ), + ); + } +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..64b65dc --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,87 @@ +name: confidence_flutter_sdk_example +description: "Demonstrates how to use the confidence_flutter_sdk plugin." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: '>=3.4.3 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + flutter_dotenv: ^5.0.2 + + confidence_flutter_sdk: + # When depending on this package from a real application you should use: + # confidence_flutter_sdk: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.6 + +dev_dependencies: + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^3.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + assets: + - .env + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages \ No newline at end of file diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..da9f154 --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:confidence_flutter_sdk_example/main.dart'; + +void main() { + testWidgets('App renders and shows title', (WidgetTester tester) async { + await tester.pumpWidget(MyApp()); + await tester.pump(); + + expect(find.text('Plugin example app'), findsOneWidget); + }); +} diff --git a/example/test_drive/integration_test.dart b/example/test_drive/integration_test.dart new file mode 100644 index 0000000..6854dea --- /dev/null +++ b/example/test_drive/integration_test.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); \ No newline at end of file From 24cff0d946a071dbff07dc04ab1ce73b3df39e23 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Tue, 9 Jun 2026 13:01:46 +0200 Subject: [PATCH 11/12] ci: update workflows for pure Dart SDK Remove submodule fetch, iOS copy/delete dance, JDK setup, and native APK/iOS builds from CI. The main CI now runs flutter analyze + flutter test on ubuntu. Integration test workflows kept for Android/iOS smoke tests via flutter drive. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/android-test.yaml | 10 +++---- .github/workflows/ci.yaml | 41 ++++------------------------- .github/workflows/ios-test.yaml | 17 +----------- example/test/widget_test.dart | 13 ++++++--- 4 files changed, 19 insertions(+), 62 deletions(-) diff --git a/.github/workflows/android-test.yaml b/.github/workflows/android-test.yaml index 03f6211..9182b8b 100644 --- a/.github/workflows/android-test.yaml +++ b/.github/workflows/android-test.yaml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - run: echo API_KEY=${{ secrets.TEST_API_KEY }} > example/.env @@ -26,18 +26,14 @@ jobs: flutter-version: ${{ env.FLUTTER_VERSION }} channel: ${{ env.FLUTTER_CHANNEL }} - # This step enables KVM (Kernel-based Virtual Machine). - # KVM is a virtualization module in the Linux kernel that allows the - # kernel to function as a hypervisor. This is necessary for running - # virtual machines on the host system. - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: run android tests + - name: Run integration test on Android emulator uses: reactivecircus/android-emulator-runner@v2 with: api-level: 29 - script: cd example && flutter drive --driver=test_drive/integration_test.dart --target=test/widget_test.dart \ No newline at end of file + script: cd example && flutter drive --driver=test_drive/integration_test.dart --target=test/widget_test.dart diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c5a5844..397fe6e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ on: - '*' jobs: build: - runs-on: macos-latest + runs-on: ubuntu-latest env: FLUTTER_CHANNEL: stable @@ -16,16 +16,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 - - - name: fetch submodules - run: git submodule update --init --recursive - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: 17 - distribution: 'zulu' + uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 @@ -36,30 +27,8 @@ jobs: - name: Install dependencies run: flutter pub get + - name: Analyze + run: flutter analyze + - name: Run tests run: flutter test - - - name: Install example dependencies - working-directory: example - run: flutter pub get - - - run: echo API_KEY=${{ secrets.TEST_API_KEY }} > example/.env - - name: Build Android - working-directory: example - run: flutter build apk --release - - - name: Copy iOS - working-directory: ios/Classes - run: cp -r confidence-sdk/Sources/Confidence . - - - name: Remove the submodule - working-directory: ios/Classes - run: rm -rf confidence-sdk - - - name: Remove git submodule - working-directory: ios/Classes - run: git rm confidence-sdk - - - name: Build iOS - working-directory: example - run: flutter build ios --release --no-codesign diff --git a/.github/workflows/ios-test.yaml b/.github/workflows/ios-test.yaml index 6d8b59f..2bdce66 100644 --- a/.github/workflows/ios-test.yaml +++ b/.github/workflows/ios-test.yaml @@ -16,22 +16,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 - - - name: fetch submodules - run: git submodule update --init --recursive - - - name: Copy iOS - working-directory: ios/Classes - run: cp -r confidence-sdk/Sources/Confidence . - - - name: Remove the submodule - working-directory: ios/Classes - run: rm -rf confidence-sdk - - - name: Remove git submodule - working-directory: ios/Classes - run: git rm confidence-sdk + uses: actions/checkout@v4 - uses: futureware-tech/simulator-action@v3 with: diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index da9f154..22ecc7d 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -1,11 +1,18 @@ +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:confidence_flutter_sdk_example/main.dart'; +import 'package:integration_test/integration_test.dart'; void main() { - testWidgets('App renders and shows title', (WidgetTester tester) async { - await tester.pumpWidget(MyApp()); + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('App initializes and resolves flags', (WidgetTester tester) async { + MyApp myApp = MyApp(); + await tester.pumpWidget(myApp); + await myApp.initDone(); await tester.pump(); - expect(find.text('Plugin example app'), findsOneWidget); + final textWidgets = find.byType(Text); + expect(textWidgets, findsWidgets); }); } From 4571d2d7afb88c4979b8f9996195fb614ac4ef39 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Tue, 9 Jun 2026 13:12:44 +0200 Subject: [PATCH 12/12] fix(ci): create .env for example app asset Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 397fe6e..b8164e8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,6 +24,8 @@ jobs: flutter-version: ${{ env.FLUTTER_VERSION }} channel: ${{ env.FLUTTER_CHANNEL }} + - run: echo API_KEY=${{ secrets.TEST_API_KEY }} > example/.env + - name: Install dependencies run: flutter pub get